using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Timers; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("DataForge")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("DataForge")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.0.3")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.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 DataForge { internal static class LocalizationOverrideManager { internal sealed class LocalizationPayload { public Dictionary All { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public Dictionary> Languages { get; set; } = new Dictionary>(StringComparer.OrdinalIgnoreCase); } private const string DomainName = "localization"; private const string DefaultLanguageFileName = "English.yml"; private const string SyncedPayloadKey = "localization"; private const long ReloadDelayTicks = 10000000L; private static readonly object StateLock = new object(); private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); private static readonly ISerializer Serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).DisableAliases().Build(); private static LocalizationPayload ActivePayload = new LocalizationPayload(); private static CustomSyncedValue? SyncedPayload; private static FileSystemWatcher? Watcher; private static DataForgeFileWatcher.DebouncedAction? ReloadDebouncer; private static string? LastParsedPayload; private static readonly Dictionary> OriginalTranslationsByLanguage = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary> AppliedKeysByLanguage = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); private static string LocalizationDirectory => Path.Combine(ConfigDirectory, "localization"); internal static void Initialize(ConfigSync configSync) { SyncedPayload = new CustomSyncedValue(configSync, "localization", "", 100); SyncedPayload.ValueChanged += OnSyncedPayloadChanged; } internal static void Dispose() { if (SyncedPayload != null) { SyncedPayload.ValueChanged -= OnSyncedPayloadChanged; } Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } internal static void SetupFileWatcher() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } else { EnsureConfigDirectoryAndDefaultOverride(); Watcher?.Dispose(); ReloadDebouncer?.Dispose(); ReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadYamlValues); Watcher = DataForgeFileWatcher.Create(ConfigDirectory, "*.*", includeSubdirectories: true, ReadYamlValues); } } internal static void ReloadFromDiskAndSync() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { ApplySyncedPayload(SyncedPayload?.Value ?? ""); return; } EnsureConfigDirectoryAndDefaultOverride(); LocalizationPayload localizationPayload = LoadPayloadFromDisk(); string text = SerializePayload(localizationPayload); lock (StateLock) { ActivePayload = localizationPayload; LastParsedPayload = text; } PublishPayload(text); ApplyCurrentLocalization(); } internal static void ApplyCurrentLocalization() { Localization instance = Localization.instance; if (instance != null) { ApplyCurrentLocalization(instance, instance.GetSelectedLanguage()); } } internal static void ApplyCurrentLocalization(Localization localization, string? language) { if (localization == null) { return; } string text = NormalizeLanguage(language); Dictionary dictionary; lock (StateLock) { dictionary = BuildTranslationsForLanguage(ActivePayload, text); } RestoreRemovedTranslations(localization, text, dictionary.Keys); foreach (KeyValuePair item in dictionary) { ApplyTranslation(localization, text, item.Key, item.Value); } AppliedKeysByLanguage[text] = new HashSet(dictionary.Keys, StringComparer.OrdinalIgnoreCase); } private static void ReadYamlValues(object sender, FileSystemEventArgs e) { if (ShouldReloadForFileEvent(e)) { ReloadDebouncer?.Schedule(); } } private static void ReloadYamlValues() { try { DataForgePlugin.Log.LogDebug((object)"Reloading localization YAML files..."); ReloadFromDiskAndSync(); DataForgePlugin.Log.LogInfo((object)"Localization YAML reload complete."); } catch (Exception arg) { DataForgePlugin.Log.LogError((object)$"Error reloading localization YAML files: {arg}"); } } private static bool ShouldReloadForFileEvent(FileSystemEventArgs e) { if (!DataForgePlugin.UsesLocalAuthorityFiles) { return false; } if (IsLocalizationFile(e.FullPath)) { return true; } if (e is RenamedEventArgs e2) { return IsLocalizationFile(e2.OldFullPath); } return false; } private static void OnSyncedPayloadChanged() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { string payload = SyncedPayload?.Value ?? ""; DataForgeProfiler.Profile(string.Format("{0}.ApplySyncedPayload chars={1}", "localization", payload.Length), delegate { ApplySyncedPayload(payload); }); } } private static void ApplySyncedPayload(string payload) { if (!string.Equals(LastParsedPayload, payload, StringComparison.Ordinal)) { LocalizationPayload activePayload = DeserializePayload(payload, "synced localization payload"); lock (StateLock) { ActivePayload = activePayload; LastParsedPayload = payload; } } ApplyCurrentLocalization(); } private static void PublishPayload(string payload) { DataForgeSync.PublishPayload(SyncedPayload, "localization", payload); } private static LocalizationPayload LoadPayloadFromDisk() { LocalizationPayload localizationPayload = new LocalizationPayload(); if (!Directory.Exists(LocalizationDirectory)) { return localizationPayload; } foreach (string item in Directory.GetFiles(LocalizationDirectory, "*.yml").Concat(Directory.GetFiles(LocalizationDirectory, "*.yaml")).OrderBy((string path) => path, StringComparer.OrdinalIgnoreCase)) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item); Dictionary dictionary = LoadTranslationMap(item, fileNameWithoutExtension + " localization"); if (dictionary.Count != 0) { if (!localizationPayload.Languages.TryGetValue(fileNameWithoutExtension, out Dictionary value)) { value = new Dictionary(StringComparer.OrdinalIgnoreCase); localizationPayload.Languages[fileNameWithoutExtension] = value; } MergeTranslations(value, dictionary); } } return localizationPayload; } private static Dictionary LoadTranslationMap(string path, string source) { if (!File.Exists(path)) { return new Dictionary(StringComparer.OrdinalIgnoreCase); } string text = File.ReadAllText(path); if (string.IsNullOrWhiteSpace(text)) { return new Dictionary(StringComparer.OrdinalIgnoreCase); } try { return NormalizeTranslationMap(Deserializer.Deserialize>(text), source); } catch (Exception ex) { DataForgePlugin.Log.LogError((object)("Failed to parse " + source + " from '" + path + "': " + ex.Message)); return new Dictionary(StringComparer.OrdinalIgnoreCase); } } private static Dictionary NormalizeTranslationMap(Dictionary? map, string source) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (map == null) { return dictionary; } foreach (KeyValuePair item in map) { string text = NormalizeToken(item.Key); if (text.Length == 0) { DataForgePlugin.Log.LogWarning((object)("Skipping localization entry with an empty token in " + source + ".")); } else { dictionary[text] = item.Value ?? ""; } } return dictionary; } private static string SerializePayload(LocalizationPayload payload) { return Serializer.Serialize(payload); } private static LocalizationPayload DeserializePayload(string payload, string source) { if (string.IsNullOrWhiteSpace(payload)) { return new LocalizationPayload(); } try { return NormalizePayload(Deserializer.Deserialize(payload), source); } catch (Exception ex) { DataForgePlugin.Log.LogError((object)("Failed to parse " + source + ": " + ex.Message)); return new LocalizationPayload(); } } private static LocalizationPayload NormalizePayload(LocalizationPayload? payload, string source) { LocalizationPayload localizationPayload = new LocalizationPayload(); if (payload == null) { return localizationPayload; } MergeTranslations(localizationPayload.All, NormalizeStringTranslationMap(payload.All, source + " common")); foreach (KeyValuePair> language in payload.Languages) { string text = NormalizeLanguage(language.Key); if (text.Length != 0) { localizationPayload.Languages[text] = NormalizeStringTranslationMap(language.Value, source + " " + text); } } return localizationPayload; } private static Dictionary NormalizeStringTranslationMap(Dictionary? map, string source) { return NormalizeTranslationMap(map?.ToDictionary, string, string>((KeyValuePair pair) => pair.Key, (KeyValuePair pair) => pair.Value, StringComparer.OrdinalIgnoreCase), source); } private static Dictionary BuildTranslationsForLanguage(LocalizationPayload payload, string language) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); MergeTranslations(dictionary, payload.All); if (!language.Equals("English", StringComparison.OrdinalIgnoreCase) && payload.Languages.TryGetValue("English", out Dictionary value)) { MergeTranslations(dictionary, value); } if (payload.Languages.TryGetValue(language, out Dictionary value2)) { MergeTranslations(dictionary, value2); } return dictionary; } private static void MergeTranslations(Dictionary target, Dictionary source) { foreach (KeyValuePair item in source) { target[NormalizeToken(item.Key)] = item.Value; } } private static void ApplyTranslation(Localization localization, string language, string token, string text) { token = NormalizeToken(token); if (token.Length != 0) { Dictionary originalTranslations = GetOriginalTranslations(language); if (!originalTranslations.ContainsKey(token)) { originalTranslations[token] = (localization.m_translations.TryGetValue(token, out var value) ? value : null); } localization.m_translations[token] = text; } } private static void RestoreRemovedTranslations(Localization localization, string language, IEnumerable currentTokens) { HashSet current = new HashSet(currentTokens.Select(NormalizeToken), StringComparer.OrdinalIgnoreCase); if (!AppliedKeysByLanguage.TryGetValue(language, out HashSet value)) { return; } Dictionary originalTranslations = GetOriginalTranslations(language); string[] array = value.Where((string token) => !current.Contains(token)).ToArray(); foreach (string key in array) { if (originalTranslations.TryGetValue(key, out var value2)) { if (value2 == null) { localization.m_translations.Remove(key); } else { localization.m_translations[key] = value2; } originalTranslations.Remove(key); } } } private static Dictionary GetOriginalTranslations(string language) { if (!OriginalTranslationsByLanguage.TryGetValue(language, out Dictionary value)) { value = new Dictionary(StringComparer.OrdinalIgnoreCase); OriginalTranslationsByLanguage[language] = value; } return value; } private static void EnsureConfigDirectoryAndDefaultOverride() { Directory.CreateDirectory(ConfigDirectory); Directory.CreateDirectory(LocalizationDirectory); string path = Path.Combine(LocalizationDirectory, "English.yml"); if (!File.Exists(path)) { File.WriteAllText(path, DefaultEnglishLocalizationTemplate()); } } private static string DefaultEnglishLocalizationTemplate() { return string.Join(Environment.NewLine, "# DataForge server-synced localization.", "#", "# Put language files in this folder using Valheim language names:", "# English.yml, Korean.yml, Turkish.yml, German.yml, etc.", "#", "# English.yml is the fallback file. If a client uses another language,", "# DataForge first applies English.yml and then applies that client's language file.", "#", "# To use a localization key, put a token like $df_item_meadhealthtest in an override field.", "# To override text directly, put plain text in the field instead of a $ token.", "#", "# Example localization entry:", "# $df_item_meadhealthtest: \"Test item\"", "# $df_item_meadhealthtest_description: \"A test item cloned from major healing mead.\"", "#", "# Example item override:", "# - item: MeadHealthtest", "# cloneFrom: MeadHealthMajor", "# name: $df_item_meadhealthtest", "# description: Direct text override without localization", ""); } private static bool IsLocalizationFile(string path) { string extension = Path.GetExtension(path); if (!extension.Equals(".yml", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".yaml", StringComparison.OrdinalIgnoreCase)) { return false; } string fullPath = Path.GetFullPath(path); string fullPath2 = Path.GetFullPath(LocalizationDirectory); char directorySeparatorChar = Path.DirectorySeparatorChar; return fullPath.StartsWith(fullPath2 + directorySeparatorChar, StringComparison.OrdinalIgnoreCase); } private static string NormalizeToken(string token) { return (token ?? "").Trim().TrimStart(new char[1] { '$' }).Trim(); } private static string NormalizeLanguage(string? language) { string text = language?.Trim() ?? ""; if (text.Length != 0) { return text; } return "English"; } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] internal static class DataForgeLocalizationSetupLanguagePatch { private static void Postfix(Localization __instance, string language) { LocalizationOverrideManager.ApplyCurrentLocalization(__instance, language); } } [BepInPlugin("sighsorry.DataForge", "DataForge", "1.0.3")] public class DataForgePlugin : BaseUnityPlugin { public enum Toggle { On = 1, Off = 0 } private sealed class ConfigurationManagerAttributes { [UsedImplicitly] public int? Order; [UsedImplicitly] public bool? Browsable; [UsedImplicitly] public string? Category; [UsedImplicitly] public Action? CustomDrawer; } internal const string ModName = "DataForge"; internal const string ModVersion = "1.0.3"; internal const string Author = "sighsorry"; internal const string ModGUID = "sighsorry.DataForge"; private static readonly string ConfigFileName = "sighsorry.DataForge.cfg"; private static readonly string ConfigFileFullPath = Path.Combine(Paths.ConfigPath, ConfigFileName); private static readonly ConfigSync ConfigSync = new ConfigSync("sighsorry.DataForge") { DisplayName = "DataForge", CurrentVersion = "1.0.3", MinimumRequiredVersion = "1.0.3" }; private readonly Harmony _harmony = new Harmony("sighsorry.DataForge"); private readonly object _reloadLock = new object(); private FileSystemWatcher? _watcher; private DataForgeFileWatcher.DebouncedAction? _configReloadDebouncer; private string? _lastConfigFileText; private static bool _sourceOfTruthFileModeReady; internal static string ConnectionError = ""; internal static readonly ManualLogSource Log = Logger.CreateLogSource("DataForge"); private const long ReloadDelayTicks = 10000000L; private const int MaxStoredFireplaceFuelLimit = 9999; private static ConfigEntry _serverConfigLocked = null; private static ConfigEntry _enableItemOverrides = null; private static ConfigEntry _enableRecipeOverrides = null; private static ConfigEntry _enableStatusEffectOverrides = null; private static ConfigEntry _enablePieceOverrides = null; private static ConfigEntry _stackableStackMultiplier = null; private static ConfigEntry _itemWeightMultiplier = null; private static ConfigEntry _showPieceComfortInHammer = null; private static ConfigEntry _highlightStationExtensionsInHammer = null; private static ConfigEntry _ignoreStationExtensionSpacing = null; private static ConfigEntry _maxStoredFireplaceFuel = null; private static ConfigEntry _logStartupTimings = null; internal static bool IsSourceOfTruth => ConfigSync.IsSourceOfTruth; internal static bool UsesLocalAuthorityFiles { get { if (IsSourceOfTruth) { return !IsRemoteServerClient; } return false; } } internal static bool IsRemoteServerClient { get { try { return ZNet.HasServerHost() && ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()); } catch { return false; } } } internal static bool ItemOverridesEnabled => _enableItemOverrides.Value.IsOn(); internal static bool RecipeOverridesEnabled => _enableRecipeOverrides.Value.IsOn(); internal static bool StatusEffectOverridesEnabled => _enableStatusEffectOverrides.Value.IsOn(); internal static bool PieceOverridesEnabled => _enablePieceOverrides.Value.IsOn(); internal static int StackableStackMultiplier => Math.Min(10, Math.Max(1, _stackableStackMultiplier.Value)); internal static float ItemWeightMultiplier => Math.Min(2f, Math.Max(0f, _itemWeightMultiplier.Value)); internal static bool ShowPieceComfortInHammer => _showPieceComfortInHammer.Value.IsOn(); internal static bool HighlightStationExtensionsInHammer => _highlightStationExtensionsInHammer.Value.IsOn(); internal static bool IgnoreStationExtensionSpacing => _ignoreStationExtensionSpacing.Value.IsOn(); internal static int MaxStoredFireplaceFuel => Math.Min(9999, Math.Max(0, _maxStoredFireplaceFuel.Value)); internal static bool LogStartupTimings => _logStartupTimings.Value.IsOn(); public void Awake() { //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Expected O, but got Unknown //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Expected O, but got Unknown bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; _serverConfigLocked = ConfigEntry("1 - General", "Lock Configuration", Toggle.On, "If on, the configuration is locked and can be changed by server admins only.", synchronizedSetting: true, 1000); ConfigSync.AddLockingConfigEntry(_serverConfigLocked); _enableItemOverrides = ConfigEntry("1 - General", "Enable Item Overrides", Toggle.On, "If on, item YAML overrides are applied to matching ObjectDB item prefabs.", synchronizedSetting: true, 900); _enableItemOverrides.SettingChanged += delegate { ItemOverrideManager.ApplyCurrentConfiguration(); }; _enableRecipeOverrides = ConfigEntry("1 - General", "Enable Recipe Overrides", Toggle.On, "If on, recipe YAML overrides are applied to ObjectDB recipes.", synchronizedSetting: true, 800); _enableRecipeOverrides.SettingChanged += delegate { RecipeOverrideManager.ApplyCurrentConfiguration(); }; _enableStatusEffectOverrides = ConfigEntry("1 - General", "Enable Status Effect Overrides", Toggle.On, "If on, status effect YAML overrides are applied to ObjectDB status effects.", synchronizedSetting: true, 700); _enableStatusEffectOverrides.SettingChanged += delegate { StatusEffectOverrideManager.ApplyCurrentConfiguration(); }; _enablePieceOverrides = ConfigEntry("1 - General", "Enable Piece Overrides", Toggle.On, "If on, piece YAML overrides are applied to matching prefabs and loaded pieces.", synchronizedSetting: true, 600); _enablePieceOverrides.SettingChanged += delegate { PieceOverrideManager.ApplyCurrentConfiguration(); }; _stackableStackMultiplier = ConfigEntry("2 - Misc", "Stackable Stack Multiplier", 1, new ConfigDescription("Integer multiplier applied to baseline max stack size for stackable items unless maxStackSize is explicitly set in item YAML. 1 disables this feature.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), Array.Empty()), synchronizedSetting: true, 500); _stackableStackMultiplier.SettingChanged += delegate { ItemOverrideManager.ApplyCurrentConfiguration(); }; _itemWeightMultiplier = ConfigEntry("2 - Misc", "Item Weight Multiplier", 1f, new ConfigDescription("Multiplier applied to baseline item weight for all items unless weight is explicitly set in item YAML. 1 disables this feature; 0 makes affected items weightless.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 2f), Array.Empty()), synchronizedSetting: true, 400); _itemWeightMultiplier.SettingChanged += delegate { ItemOverrideManager.ApplyCurrentConfiguration(); }; _showPieceComfortInHammer = ConfigEntry("2 - Misc", "Show Comfort In Hammer", Toggle.On, "If on, hammer build icons show an orange comfort value badge for pieces with comfort 1 or higher.", synchronizedSetting: false, 300); _showPieceComfortInHammer.SettingChanged += delegate { PieceComfortHudBadges.RefreshVisibleHud(); }; _highlightStationExtensionsInHammer = ConfigEntry("2 - Misc", "Highlight Station Extensions In Hammer", Toggle.On, "If on, hovering a crafting station or station extension in the hammer tab highlights related station/extension pieces in pale cyan. This setting is client-side only.", synchronizedSetting: false, 250); _highlightStationExtensionsInHammer.SettingChanged += delegate { PieceComfortHudBadges.RefreshVisibleHud(); }; _ignoreStationExtensionSpacing = ConfigEntry("2 - Misc", "Ignore Station Extension Spacing", Toggle.On, "If on, station extensions ignore the vanilla spacing check against other station extensions, allowing close or overlapping extension placement. Other placement restrictions remain unchanged.", synchronizedSetting: true, 200); _maxStoredFireplaceFuel = ConfigEntry("2 - Misc", "maxStoredFuel", 100, $"Maximum stored fuel allowed in fireplaces without changing each fireplace's displayed max fuel. 0 disables this feature. Values are clamped to 0-{9999}. If this value is not greater than a fireplace's max fuel, that fireplace uses vanilla behavior.", synchronizedSetting: true, 100); _maxStoredFireplaceFuel.SettingChanged += delegate { ClampMaxStoredFireplaceFuel(); }; ClampMaxStoredFireplaceFuel(); _logStartupTimings = ConfigEntry("2 - Misc", "Log Startup Timings", Toggle.Off, "If on, logs a lobby-to-world connection timeline plus DataForge synced payload parsing and world-data apply timings. Use only while diagnosing connection or loading delays.", synchronizedSetting: false, 50); LocalizationOverrideManager.Initialize(ConfigSync); StatusEffectOverrideManager.Initialize(ConfigSync); ItemOverrideManager.Initialize(ConfigSync); RecipeOverrideManager.Initialize(ConfigSync); PieceOverrideManager.Initialize(ConfigSync); ConfigSync.SourceOfTruthChanged += OnSourceOfTruthChanged; DataForgeConsoleCommands.Register(); Assembly executingAssembly = Assembly.GetExecutingAssembly(); _harmony.PatchAll(executingAssembly); SetupWatcher(); ((BaseUnityPlugin)this).Config.Save(); _lastConfigFileText = ReadFileTextIfExists(ConfigFileFullPath); if (saveOnConfigSet) { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private void OnDestroy() { SaveWithRespectToConfigSet(); _watcher?.Dispose(); _watcher = null; _configReloadDebouncer?.Dispose(); _configReloadDebouncer = null; LocalizationOverrideManager.Dispose(); StatusEffectOverrideManager.Dispose(); ItemOverrideManager.Dispose(); RecipeOverrideManager.Dispose(); PieceOverrideManager.Dispose(); ConfigSync.SourceOfTruthChanged -= OnSourceOfTruthChanged; _harmony.UnpatchSelf(); } private static void OnSourceOfTruthChanged(bool isSourceOfTruth) { if (isSourceOfTruth) { EnsureSourceOfTruthFileMode(); return; } _sourceOfTruthFileModeReady = false; LocalizationOverrideManager.SetupFileWatcher(); StatusEffectOverrideManager.SetupFileWatcher(); ItemOverrideManager.SetupFileWatcher(); RecipeOverrideManager.SetupFileWatcher(); PieceOverrideManager.SetupFileWatcher(); } internal static void EnsureSourceOfTruthFileMode() { if (UsesLocalAuthorityFiles && !_sourceOfTruthFileModeReady) { _sourceOfTruthFileModeReady = true; LocalizationOverrideManager.SetupFileWatcher(); StatusEffectOverrideManager.SetupFileWatcher(); ItemOverrideManager.SetupFileWatcher(); RecipeOverrideManager.SetupFileWatcher(); PieceOverrideManager.SetupFileWatcher(); LocalizationOverrideManager.ReloadFromDiskAndSync(); StatusEffectOverrideManager.ReloadFromDiskAndSync(); ItemOverrideManager.ReloadFromDiskAndSync(); RecipeOverrideManager.ReloadFromDiskAndSync(); PieceOverrideManager.ReloadFromDiskAndSync(); } } private void Update() { VneiPrefabCleanupGuard.TryPatchVneiIndexAll(_harmony); } private static void ClampMaxStoredFireplaceFuel() { int num = Math.Min(9999, Math.Max(0, _maxStoredFireplaceFuel.Value)); if (_maxStoredFireplaceFuel.Value != num) { _maxStoredFireplaceFuel.Value = num; } } private void SetupWatcher() { _watcher?.Dispose(); _configReloadDebouncer?.Dispose(); _configReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadConfigValues); _watcher = DataForgeFileWatcher.Create(Paths.ConfigPath, ConfigFileName, includeSubdirectories: false, ReadConfigValues); } private void ReadConfigValues(object sender, FileSystemEventArgs e) { _configReloadDebouncer?.Schedule(); } private void ReloadConfigValues() { lock (_reloadLock) { if (!File.Exists(ConfigFileFullPath)) { Log.LogWarning((object)"Config file does not exist. Skipping reload."); return; } try { string b = ReadFileTextIfExists(ConfigFileFullPath); if (string.Equals(_lastConfigFileText, b, StringComparison.Ordinal)) { Log.LogDebug((object)"Skipping configuration reload because the config file content did not change."); return; } Log.LogDebug((object)"Reloading configuration..."); SaveWithRespectToConfigSet(reload: true); _lastConfigFileText = ReadFileTextIfExists(ConfigFileFullPath); StatusEffectOverrideManager.ApplyCurrentConfiguration(); ItemOverrideManager.ApplyCurrentConfiguration(); RecipeOverrideManager.ApplyCurrentConfiguration(); PieceOverrideManager.ApplyCurrentConfiguration(); Log.LogInfo((object)"Configuration reload complete."); } catch (Exception arg) { Log.LogError((object)$"Error reloading configuration: {arg}"); } } } private void SaveWithRespectToConfigSet(bool reload = false) { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; if (reload) { ((BaseUnityPlugin)this).Config.Reload(); } else { ((BaseUnityPlugin)this).Config.Save(); } if (saveOnConfigSet) { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private static string? ReadFileTextIfExists(string path) { try { return File.Exists(path) ? File.ReadAllText(path) : null; } catch (IOException) { return null; } catch (UnauthorizedAccessException) { return null; } } private ConfigEntry ConfigEntry(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true, int? order = null) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown object[] array = description.Tags ?? Array.Empty(); if (order.HasValue) { object[] array2 = new object[array.Length + 1]; Array.Copy(array, array2, array.Length); array2[array.Length] = new ConfigurationManagerAttributes { Order = order.Value }; array = array2; } ConfigDescription val = new ConfigDescription(description.Description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), description.AcceptableValues, array); ConfigEntry val2 = ((BaseUnityPlugin)this).Config.Bind(group, name, value, val); ConfigSync.AddConfigEntry(val2).SynchronizedConfig = synchronizedSetting; return val2; } private ConfigEntry ConfigEntry(string group, string name, T value, string description, bool synchronizedSetting = true, int? order = null) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown return ConfigEntry(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty()), synchronizedSetting, order); } } public static class ToggleExtensions { public static bool IsOn(this DataForgePlugin.Toggle value) { return value == DataForgePlugin.Toggle.On; } public static bool IsOff(this DataForgePlugin.Toggle value) { return value == DataForgePlugin.Toggle.Off; } } internal static class DataForgeConsoleCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__WriteFullScaffoldFiles; public static ConsoleOptionsFetcher <1>__GetFullTabOptions; } private const string WriteFullCommandName = "dataforge:full"; private static readonly List FullTabOptions = new List { "item", "recipe", "effect", "piece", "all" }; private static bool _registered; internal static void Register() { //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_0056: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown if (!_registered) { _registered = true; object obj = <>O.<0>__WriteFullScaffoldFiles; if (obj == null) { ConsoleEvent val = WriteFullScaffoldFiles; <>O.<0>__WriteFullScaffoldFiles = val; obj = (object)val; } object obj2 = <>O.<1>__GetFullTabOptions; if (obj2 == null) { ConsoleOptionsFetcher val2 = GetFullTabOptions; <>O.<1>__GetFullTabOptions = val2; obj2 = (object)val2; } new ConsoleCommand("dataforge:full", "Write DataForge full scaffold YAML files with explicit defaults. Usage: dataforge:full [item|recipe|effect|piece|all]", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false); } } private static List GetFullTabOptions() { return FullTabOptions; } private static void WriteFullScaffoldFiles(ConsoleEventArgs args) { if (!TryParseScope(args, out var includeItem, out var includeRecipe, out var includeEffect, out var includePiece)) { return; } if (includeItem) { if (ItemOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path, out string error)) { Terminal context = args.Context; if (context != null) { context.AddString("Wrote item full scaffold to " + path); } } else { Terminal context2 = args.Context; if (context2 != null) { context2.AddString(error); } } } if (includeRecipe) { if (RecipeOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path2, out string error2)) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("Wrote recipe full scaffold to " + path2); } } else { Terminal context4 = args.Context; if (context4 != null) { context4.AddString(error2); } } } if (includeEffect) { if (StatusEffectOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path3, out string error3)) { Terminal context5 = args.Context; if (context5 != null) { context5.AddString("Wrote effect full scaffold to " + path3); } } else { Terminal context6 = args.Context; if (context6 != null) { context6.AddString(error3); } } } if (!includePiece) { return; } if (PieceOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path4, out string error4)) { Terminal context7 = args.Context; if (context7 != null) { context7.AddString("Wrote piece full scaffold to " + path4); } } else { Terminal context8 = args.Context; if (context8 != null) { context8.AddString(error4); } } } private static bool TryParseScope(ConsoleEventArgs args, out bool includeItem, out bool includeRecipe, out bool includeEffect, out bool includePiece) { string text = ((args.Length >= 2) ? (args[1] ?? "").Trim().ToLowerInvariant() : "all"); if (text.Length == 0) { text = "all"; } switch (text) { case "all": includeItem = true; includeRecipe = true; includeEffect = true; includePiece = true; return true; case "items": case "item": includeItem = true; includeRecipe = false; includeEffect = false; includePiece = false; return true; case "recipe": case "recipes": includeItem = false; includeRecipe = true; includeEffect = false; includePiece = false; return true; case "effect": case "effects": includeItem = false; includeRecipe = false; includeEffect = true; includePiece = false; return true; case "piece": case "pieces": includeItem = false; includeRecipe = false; includeEffect = false; includePiece = true; return true; default: { includeItem = false; includeRecipe = false; includeEffect = false; includePiece = false; Terminal context = args.Context; if (context != null) { context.AddString("Syntax: dataforge:full [item|recipe|effect|piece|all]"); } return false; } } } } internal static class DataForgeLogContext { private sealed class PopWhenDisposed : IDisposable { private readonly string? previous; private bool disposed; internal PopWhenDisposed(string? previous) { this.previous = previous; } public void Dispose() { if (!disposed) { CurrentContext = previous; disposed = true; } } } [ThreadStatic] private static string? CurrentContext; internal static IDisposable Push(string? context) { string currentContext = CurrentContext; CurrentContext = (string.IsNullOrWhiteSpace(context) ? currentContext : context); return new PopWhenDisposed(currentContext); } internal static string FormatSource(string source, int entryIndex) { string text = source?.Trim() ?? ""; string text2 = ((text.Length == 0) ? "unknown source" : Path.GetFileName(text)); if (text2.Length == 0) { text2 = text; } return $"{text2}#{entryIndex}"; } internal static void Warning(string message) { DataForgePlugin.Log.LogWarning((object)WithContext(message)); } private static string WithContext(string message) { if (!string.IsNullOrWhiteSpace(CurrentContext)) { return CurrentContext + ": " + message; } return message; } } internal static class DataForgeReferenceSections { private sealed class GroupedEntry { public TSource Entry { get; set; } public string SortKey { get; set; } = ""; public string OwnerName { get; set; } = "Unknown / Untracked"; } internal const string VanillaOwnerName = "Valheim"; internal const string UnknownOwnerName = "Unknown / Untracked"; internal static string SerializeReferenceSections(IEnumerable entries, Func getSortKey, Func getOwnerName, Func getOutput, ISerializer serializer) { List>> list = (from entry in entries.Select(delegate(TSource entry) { string text = (getOwnerName(entry) ?? "").Trim(); return new GroupedEntry { Entry = entry, SortKey = (getSortKey(entry) ?? "").Trim(), OwnerName = ((text.Length > 0) ? text : "Unknown / Untracked") }; }) orderby GetOwnerSortBucket(entry.OwnerName) select entry).ThenBy, string>((GroupedEntry entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase).ThenBy, string>((GroupedEntry entry) => entry.SortKey, StringComparer.OrdinalIgnoreCase).GroupBy, string>((GroupedEntry entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase) .ToList(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; foreach (IGrouping> item in list) { if (flag) { stringBuilder.AppendLine(); } AppendSectionHeaderComment(stringBuilder, item.Key); foreach (GroupedEntry item2 in item) { string value = CollapseScalarBlockListsToInlineLists(serializer.Serialize(new TOutput[1] { getOutput(item2.Entry) }).TrimEnd('\r', '\n')); stringBuilder.AppendLine(value); } flag = true; } if (!flag) { return "[]" + Environment.NewLine; } return stringBuilder.ToString(); } private static void AppendSectionHeaderComment(StringBuilder builder, string ownerName) { builder.Append("# ===== "); builder.Append(string.IsNullOrWhiteSpace(ownerName) ? "Unknown / Untracked" : ownerName.Trim()); builder.AppendLine(" ====="); } private static int GetOwnerSortBucket(string ownerName) { if (string.Equals(ownerName, "Valheim", StringComparison.OrdinalIgnoreCase)) { return 0; } if (!string.Equals(ownerName, "Unknown / Untracked", StringComparison.OrdinalIgnoreCase)) { return 1; } return 2; } private static string CollapseScalarBlockListsToInlineLists(string yaml) { if (string.IsNullOrWhiteSpace(yaml) || yaml.IndexOf("- ", StringComparison.Ordinal) < 0) { return yaml; } string[] array = yaml.Replace("\r\n", "\n").Split(new char[1] { '\n' }); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < array.Length; i++) { if (TryCollapseScalarBlockList(array, ref i, out string collapsedLine)) { stringBuilder.AppendLine(collapsedLine); } else { stringBuilder.AppendLine(array[i]); } } return stringBuilder.ToString().TrimEnd('\r', '\n'); } private static bool TryCollapseScalarBlockList(string[] lines, ref int index, out string collapsedLine) { collapsedLine = ""; string text = lines[index]; int num = text.IndexOf(':'); if (num < 0 || num != text.Length - 1) { return false; } int num2 = index + 1; if (num2 >= lines.Length) { return false; } int firstNonWhitespaceIndex = GetFirstNonWhitespaceIndex(text); int firstNonWhitespaceIndex2 = GetFirstNonWhitespaceIndex(lines[num2]); if (firstNonWhitespaceIndex < 0 || firstNonWhitespaceIndex2 <= firstNonWhitespaceIndex || !lines[num2].TrimStart(Array.Empty()).StartsWith("- ", StringComparison.Ordinal)) { return false; } List list = new List(); int i; for (i = num2; i < lines.Length; i++) { string text2 = lines[i]; if (GetFirstNonWhitespaceIndex(text2) != firstNonWhitespaceIndex2 || !text2.TrimStart(Array.Empty()).StartsWith("- ", StringComparison.Ordinal)) { break; } string text3 = text2.TrimStart(Array.Empty()).Substring(2).Trim(); if (text3.Length == 0 || Enumerable.Contains(text3, ':') || Enumerable.Contains(text3, ',')) { return false; } list.Add(text3); } if (list.Count == 0) { return false; } collapsedLine = text + " [" + string.Join(", ", list) + "]"; index = i - 1; return true; } private static int GetFirstNonWhitespaceIndex(string line) { for (int i = 0; i < line.Length; i++) { if (!char.IsWhiteSpace(line[i])) { return i; } } return -1; } } internal static class DataForgeOwnerResolver { internal static string GetPrefabOwnerName(string? prefabName) { string text = NormalizeName(prefabName); if (text.Length == 0) { return "Unknown / Untracked"; } foreach (string item in EnumerateLookupCandidates(text)) { if (DataForgeVanillaAssetCatalog.IsVanillaPrefab(item)) { return "Valheim"; } } return DataForgeAssetOwnerCatalog.GetOwnerName(text); } internal static string GetAssetOwnerName(string? assetName) { string text = NormalizeName(assetName); if (text.Length == 0) { return "Unknown / Untracked"; } foreach (string item in EnumerateLookupCandidates(text)) { if (DataForgeVanillaAssetCatalog.IsVanillaAsset(item)) { return "Valheim"; } } return DataForgeAssetOwnerCatalog.GetOwnerName(text); } private static IEnumerable EnumerateLookupCandidates(string normalizedName) { HashSet seen = new HashSet(StringComparer.OrdinalIgnoreCase); AddIfNew(normalizedName); AddIfNew(TrimCloneSuffix(normalizedName)); int num = normalizedName.IndexOf(':'); if (num > 0) { AddIfNew(normalizedName.Substring(0, num)); } foreach (string item in seen) { yield return item; } void AddIfNew(string candidate) { string text = NormalizeName(candidate); if (text.Length > 0) { seen.Add(text); } } } private static string NormalizeName(string? name) { return (name ?? "").Replace("(Clone)", "").Trim(); } private static string TrimCloneSuffix(string name) { if (!name.EndsWith("(Clone)", StringComparison.Ordinal)) { return name; } return name.Substring(0, name.Length - "(Clone)".Length).TrimEnd(Array.Empty()); } } internal static class DataForgeVanillaAssetCatalog { private enum CatalogState { Uninitialized, Loaded, Unavailable } private static readonly object Sync = new object(); private static readonly HashSet PrefabNames = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet AssetNames = new HashSet(StringComparer.OrdinalIgnoreCase); private static CatalogState _state; internal static bool IsVanillaPrefab(string prefabName) { EnsureLoaded(); if (_state == CatalogState.Loaded && !string.IsNullOrWhiteSpace(prefabName)) { return PrefabNames.Contains(prefabName); } return false; } internal static bool IsVanillaAsset(string assetName) { EnsureLoaded(); if (_state == CatalogState.Loaded && !string.IsNullOrWhiteSpace(assetName)) { return AssetNames.Contains(assetName); } return false; } private static void EnsureLoaded() { if (_state != CatalogState.Uninitialized) { return; } lock (Sync) { if (_state != CatalogState.Uninitialized) { return; } string text = Path.Combine(Application.dataPath, "StreamingAssets", "SoftRef", "manifest_extended"); if (!File.Exists(text)) { _state = CatalogState.Unavailable; DataForgePlugin.Log.LogWarning((object)("Vanilla asset manifest was not found at '" + text + "'. Reference owner sections may place unmapped entries under 'Unknown / Untracked'.")); return; } foreach (string item in File.ReadLines(text)) { int num = item.IndexOf("path in bundle:", StringComparison.OrdinalIgnoreCase); if (num < 0) { continue; } string text2 = item.Substring(num + "path in bundle:".Length).Trim(); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text2); if (!string.IsNullOrWhiteSpace(fileNameWithoutExtension)) { if (text2.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) { PrefabNames.Add(fileNameWithoutExtension); } else if (text2.EndsWith(".asset", StringComparison.OrdinalIgnoreCase)) { AssetNames.Add(fileNameWithoutExtension); } } } _state = CatalogState.Loaded; DataForgePlugin.Log.LogDebug((object)$"Loaded {PrefabNames.Count} vanilla prefab names and {AssetNames.Count} vanilla asset names from '{text}'."); } } } internal static class DataForgeAssetOwnerCatalog { private sealed class PluginResourceSnapshot { public string OwnerName { get; set; } = ""; public string PluginName { get; set; } = ""; public string PluginGuid { get; set; } = ""; public string AssemblyName { get; set; } = ""; public string[] ResourceNames { get; set; } = Array.Empty(); } private static readonly object Sync = new object(); private static readonly Dictionary AssetOwners = new Dictionary(StringComparer.OrdinalIgnoreCase); private static string _loadedSignature = ""; internal static string GetOwnerName(string assetName) { EnsureMappingsLoaded(); foreach (string item in EnumerateLookupCandidates(assetName)) { if (AssetOwners.TryGetValue(item, out string value) && !string.IsNullOrWhiteSpace(value)) { return value; } } return "Unknown / Untracked"; } private static void EnsureMappingsLoaded() { string text = BuildSignature(); if (string.Equals(text, _loadedSignature, StringComparison.Ordinal)) { return; } lock (Sync) { if (string.Equals(text, _loadedSignature, StringComparison.Ordinal)) { return; } AssetOwners.Clear(); List pluginResources = GetPluginResources(); foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles()) { string text2 = ((Object)allLoadedAssetBundle).name ?? ""; if (text2.Length == 0) { continue; } string value = ResolveOwnerName(text2, pluginResources); if (string.IsNullOrWhiteSpace(value)) { continue; } string[] allAssetNames = allLoadedAssetBundle.GetAllAssetNames(); foreach (string text3 in allAssetNames) { if (text3.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase) || text3.EndsWith(".asset", StringComparison.OrdinalIgnoreCase)) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text3); if (!string.IsNullOrWhiteSpace(fileNameWithoutExtension)) { AssetOwners[fileNameWithoutExtension] = value; } } } } _loadedSignature = text; DataForgePlugin.Log.LogDebug((object)$"Tracked {AssetOwners.Count} mod asset owner mapping(s) for reference sections."); } } private static IEnumerable EnumerateLookupCandidates(string assetName) { string normalizedName = (assetName ?? "").Replace("(Clone)", "").Trim(); if (normalizedName.Length != 0) { yield return normalizedName; int num = normalizedName.IndexOf(':'); if (num > 0) { yield return normalizedName.Substring(0, num); } } } private static List GetPluginResources() { return (from plugin in Chainloader.PluginInfos.Values.Select(delegate(PluginInfo pluginInfo) { string text = (pluginInfo.Metadata.Name ?? "").Trim(); string text2 = (pluginInfo.Metadata.GUID ?? "").Trim(); string assemblyName = ""; string[] resourceNames = Array.Empty(); try { assemblyName = ((object)pluginInfo.Instance)?.GetType().Assembly.GetName().Name ?? ""; resourceNames = ((object)pluginInfo.Instance)?.GetType().Assembly.GetManifestResourceNames() ?? Array.Empty(); } catch { } return new PluginResourceSnapshot { OwnerName = ((text.Length > 0) ? text : text2), PluginName = text, PluginGuid = text2, AssemblyName = assemblyName, ResourceNames = resourceNames }; }) where plugin.OwnerName.Length > 0 select plugin).ToList(); } private static string ResolveOwnerName(string bundleName, List plugins) { PluginResourceSnapshot pluginResourceSnapshot = plugins.FirstOrDefault((PluginResourceSnapshot plugin) => plugin.ResourceNames.Any((string resourceName) => resourceName.EndsWith(bundleName, StringComparison.OrdinalIgnoreCase))); if (pluginResourceSnapshot != null) { return pluginResourceSnapshot.OwnerName; } string normalizedBundleName = NormalizeToken(Path.GetFileNameWithoutExtension(bundleName)); if (normalizedBundleName.Length == 0) { return ""; } return plugins.FirstOrDefault(delegate(PluginResourceSnapshot plugin) { string pluginToken = NormalizeToken(plugin.PluginName); string pluginToken2 = NormalizeToken(plugin.PluginGuid); string pluginToken3 = NormalizeToken(plugin.AssemblyName); return IsTokenMatch(normalizedBundleName, pluginToken) || IsTokenMatch(normalizedBundleName, pluginToken2) || IsTokenMatch(normalizedBundleName, pluginToken3); })?.OwnerName ?? ""; } private static bool IsTokenMatch(string bundleName, string pluginToken) { if (pluginToken.Length > 0) { if (bundleName.IndexOf(pluginToken, StringComparison.OrdinalIgnoreCase) < 0) { return pluginToken.IndexOf(bundleName, StringComparison.OrdinalIgnoreCase) >= 0; } return true; } return false; } private static string NormalizeToken(string value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } StringBuilder stringBuilder = new StringBuilder(); foreach (char c in value) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } private static string BuildSignature() { IEnumerable values = (from bundle in AssetBundle.GetAllLoadedAssetBundles() select ((Object)bundle).name ?? "" into name where name.Length > 0 select name).OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase); IEnumerable values2 = Chainloader.PluginInfos.Values.Select(delegate(PluginInfo pluginInfo) { string text = pluginInfo.Metadata.Name ?? ""; string text2 = pluginInfo.Metadata.GUID ?? ""; string text3 = ""; try { text3 = ((object)pluginInfo.Instance)?.GetType().Assembly.GetName().Name ?? ""; } catch { } return text2 + ":" + text + ":" + text3; }).OrderBy((string token) => token, StringComparer.OrdinalIgnoreCase); return string.Join("|", values) + "||" + string.Join("|", values2); } } internal readonly struct DataForgeItemSortGroup { internal int BigGroupRank { get; } internal int SubGroupRank { get; } internal int DetailRank { get; } internal DataForgeItemSortGroup(int bigGroupRank, int subGroupRank, int detailRank) { BigGroupRank = bigGroupRank; SubGroupRank = subGroupRank; DetailRank = detailRank; } } internal static class DataForgeItemSortClassifier { private const int Melee = 0; private const int Ranged = 1; private const int Magic = 2; private const int Equipment = 3; private const int Food = 4; private const int Consumable = 5; private const int Meadbase = 6; private const int Misc = 7; private const int Unknown = 8; internal static DataForgeItemSortGroup Classify(string? itemName, SharedData? shared) { //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Invalid comparison between Unknown and I4 if (shared == null) { return ClassifyFallback(itemName); } if (MatchElementalMagic(shared)) { return Group(2, 0); } if (MatchBloodMagic(shared)) { return Group(2, 1); } if (MatchBow(itemName, shared)) { return Group(1, 0); } if (MatchArrow(itemName, shared)) { return Group(1, 1); } if (MatchCrossbow(itemName, shared)) { return Group(1, 2); } if (MatchBolt(itemName, shared)) { return Group(1, 3); } if (MatchAmmo(shared)) { return Group(1, 4); } if (MatchBomb(shared)) { return Group(1, 5); } if (MatchSword(itemName, shared)) { return Group(0, 0, SwordDetail(itemName, shared)); } if (MatchAxe(itemName, shared)) { return Group(0, 1, AxeDetail(itemName, shared)); } if (MatchClub(itemName, shared)) { return Group(0, 2, ClubDetail(itemName, shared)); } if (MatchKnife(itemName, shared)) { return Group(0, 3); } if (MatchSpear(itemName, shared)) { return Group(0, 4); } if (MatchPolearm(itemName, shared)) { return Group(0, 5); } if (MatchFists(itemName, shared)) { return Group(0, 6); } if (MatchShield(itemName, shared)) { return Group(0, 7, ShieldDetail(itemName)); } if (MatchPickaxe(itemName, shared)) { return Group(0, 8); } if (MatchTool(itemName, shared)) { return Group(0, 9); } if (MatchHelmet(shared)) { return Group(3, 0); } if (MatchChest(shared)) { return Group(3, 1); } if (MatchLegs(shared)) { return Group(3, 2); } if (MatchCape(shared)) { return Group(3, 3); } if (MatchUtility(shared)) { return Group(3, 4); } if (MatchTrinket(shared)) { return Group(3, 5); } if (MatchFeast(itemName)) { return Group(4, 3); } if (MatchNativeFood(shared, out var subGroupRank)) { return Group(4, subGroupRank); } if (MatchMeadbase(itemName, shared)) { return Group(6, 0); } if (MatchMead(itemName, shared)) { return Group(5, 0); } if (MatchPotion(itemName, shared)) { return Group(5, 1); } if ((int)shared.m_itemType == 13) { return Group(7, 0); } if (shared.m_value > 0) { return Group(7, 1); } return ClassifyFallback(itemName); } private static DataForgeItemSortGroup ClassifyFallback(string? itemName) { if (HasToken(itemName, "staff", "magic", "elemental")) { return Group(2, 0); } if (HasToken(itemName, "bloodmagic")) { return Group(2, 1); } if (HasToken(itemName, "bow") && !HasToken(itemName, "crossbow")) { return Group(1, 0); } if (HasToken(itemName, "arrow")) { return Group(1, 1); } if (HasToken(itemName, "crossbow", "arbalest")) { return Group(1, 2); } if (HasToken(itemName, "bolt")) { return Group(1, 3); } if (HasToken(itemName, "bomb")) { return Group(1, 5); } if (HasToken(itemName, "sword")) { return Group(0, 0, SwordDetail(itemName, null)); } if (HasToken(itemName, "axe", "battleaxe")) { return Group(0, 1, AxeDetail(itemName, null)); } if (HasToken(itemName, "club", "mace", "sledge")) { return Group(0, 2, ClubDetail(itemName, null)); } if (HasToken(itemName, "knife")) { return Group(0, 3); } if (HasToken(itemName, "spear")) { return Group(0, 4); } if (HasToken(itemName, "atgeir", "polearm")) { return Group(0, 5); } if (HasToken(itemName, "fist")) { return Group(0, 6); } if (HasToken(itemName, "shield", "buckler")) { return Group(0, 7, ShieldDetail(itemName)); } if (HasToken(itemName, "pickaxe")) { return Group(0, 8); } if (HasToken(itemName, "hammer", "hoe", "cultivator", "torch", "fishingrod", "tankard")) { return Group(0, 9); } if (HasToken(itemName, "helmet", "hood", "helm")) { return Group(3, 0); } if (HasToken(itemName, "chest", "cuirass", "tunic", "robe")) { return Group(3, 1); } if (HasToken(itemName, "legs", "leg", "pants", "greaves")) { return Group(3, 2); } if (HasToken(itemName, "cape", "shoulder")) { return Group(3, 3); } if (HasToken(itemName, "trinket")) { return Group(3, 5); } if (HasToken(itemName, "feast")) { return Group(4, 3); } if (HasToken(itemName, "meadbase")) { return Group(6, 0); } if (HasToken(itemName, "mead")) { return Group(5, 0); } if (HasToken(itemName, "potion")) { return Group(5, 1); } if (HasToken(itemName, "trophy")) { return Group(7, 0); } return Group(8, 0); } private static bool MatchSword(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)1)) { return HasToken(itemName, shared, "sword"); } return true; } private static bool MatchAxe(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)7)) { return HasToken(itemName, shared, "axe", "battleaxe"); } return true; } private static bool MatchClub(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)3)) { return HasToken(itemName, shared, "club", "mace", "sledge"); } return true; } private static bool MatchKnife(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)2)) { return HasToken(itemName, shared, "knife"); } return true; } private static bool MatchSpear(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)5)) { return HasToken(itemName, shared, "spear"); } return true; } private static bool MatchPolearm(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)4) && !IsItemTypeOrAttach(shared, (ItemType)20)) { return HasToken(itemName, shared, "atgeir", "polearm"); } return true; } private static bool MatchFists(string? itemName, SharedData shared) { if (!IsSkillAttack(shared, (SkillType)11)) { return HasToken(itemName, shared, "fist"); } return true; } private static bool MatchShield(string? itemName, SharedData shared) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if (!IsItemTypeOrAttach(shared, (ItemType)5) && (int)shared.m_skillType != 6) { return HasToken(itemName, shared, "shield", "buckler"); } return true; } private static bool MatchPickaxe(string? itemName, SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 if ((int)shared.m_skillType != 12) { return HasToken(itemName, shared, "pickaxe"); } return true; } private static bool MatchTool(string? itemName, SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 if ((int)shared.m_itemType != 19 && !IsItemTypeOrAttach(shared, (ItemType)15) && (int)shared.m_skillType != 104 && (int)shared.m_skillType != 106) { return HasToken(itemName, shared, "hammer", "hoe", "cultivator", "torch", "fishingrod", "tankard"); } return true; } private static bool MatchBow(string? itemName, SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 if ((int)shared.m_skillType != 14 && (int)shared.m_skillType != 9 && (int)shared.m_skillType != 10) { if ((int)shared.m_itemType != 4 && !IsSkillAttack(shared, (SkillType)8)) { if (HasToken(itemName, shared, "bow")) { return !HasToken(itemName, shared, "crossbow"); } return false; } return true; } return false; } private static bool MatchCrossbow(string? itemName, SharedData shared) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 if (!MatchAmmo(shared) && ((int)shared.m_skillType == 14 || HasToken(itemName, shared, "crossbow", "arbalest"))) { if ((int)shared.m_itemType != 4 && !HasAttackAnimation(shared)) { return HasToken(itemName, shared, "crossbow", "arbalest"); } return true; } return false; } private static bool MatchArrow(string? itemName, SharedData shared) { if (!HasAttackAnimation(shared) && (string.Equals(shared.m_ammoType, "$ammo_arrows", StringComparison.Ordinal) || HasToken(itemName, shared, "arrow"))) { return GetTotalDamage(shared) > 0f; } return false; } private static bool MatchBolt(string? itemName, SharedData shared) { if (!HasAttackAnimation(shared) && (string.Equals(shared.m_ammoType, "$ammo_bolts", StringComparison.Ordinal) || HasToken(itemName, shared, "bolt"))) { return GetTotalDamage(shared) > 0f; } return false; } private static bool MatchAmmo(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 if ((int)shared.m_itemType != 9) { return (int)shared.m_itemType == 23; } return true; } private static bool MatchBomb(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000a: 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_0024: Invalid comparison between Unknown and I4 if ((int)shared.m_itemType == 3 && (int)shared.m_animationState == 0) { Attack attack = shared.m_attack; if (attack != null && (int)attack.m_attackType == 2) { return string.Equals(shared.m_attack?.m_attackAnimation, "throw_bomb", StringComparison.Ordinal); } } return false; } private static bool MatchElementalMagic(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 return (int)shared.m_skillType == 9; } private static bool MatchBloodMagic(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 return (int)shared.m_skillType == 10; } private static bool MatchHelmet(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)6); } private static bool MatchChest(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)7); } private static bool MatchLegs(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)11); } private static bool MatchCape(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)17); } private static bool MatchUtility(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)18); } private static bool MatchTrinket(SharedData shared) { return IsItemTypeOrAttach(shared, (ItemType)24); } private static bool MatchNativeFood(SharedData shared, out int subGroupRank) { SharedData foodSharedData = GetFoodSharedData(shared); subGroupRank = 0; if (!HasFoodCarrier(shared) || (foodSharedData.m_food <= 0f && foodSharedData.m_foodStamina <= 0f && foodSharedData.m_foodEitr <= 0f)) { return false; } if (foodSharedData.m_food >= foodSharedData.m_foodStamina && foodSharedData.m_food >= foodSharedData.m_foodEitr) { subGroupRank = 0; return true; } if (foodSharedData.m_foodStamina >= foodSharedData.m_food && foodSharedData.m_foodStamina >= foodSharedData.m_foodEitr) { subGroupRank = 1; return true; } subGroupRank = 2; return true; } private static bool HasFoodCarrier(SharedData shared) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if ((int)shared.m_itemType != 2) { if ((int)shared.m_itemType == 1) { return (Object)(object)shared.m_appendToolTip != (Object)null; } return false; } return true; } private static SharedData GetFoodSharedData(SharedData shared) { return shared.m_appendToolTip?.m_itemData?.m_shared ?? shared; } private static bool MatchFeast(string? itemName) { if (HasToken(itemName, "feast")) { return HasToken(itemName, "material"); } return false; } private static bool MatchMeadbase(string? itemName, SharedData shared) { return HasToken(itemName, shared, "meadbase"); } private static bool MatchMead(string? itemName, SharedData shared) { if (!MatchMeadbase(itemName, shared)) { return HasToken(itemName, shared, "mead"); } return false; } private static bool MatchPotion(string? itemName, SharedData shared) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Invalid comparison between Unknown and I4 if (!MatchMead(itemName, shared) && !MatchMeadbase(itemName, shared) && !MatchNativeFood(shared, out var _)) { if (!HasToken(itemName, shared, "potion")) { if ((int)shared.m_itemType == 1 || (int)shared.m_itemType == 2) { return (Object)(object)shared.m_consumeStatusEffect != (Object)null; } return false; } return true; } return false; } private static int SwordDetail(string? itemName, SharedData? shared) { if (IsTwoHanded(shared) || HasToken(itemName, "greatsword", "twohandedsword", "2hsword")) { return 1; } return 0; } private static int AxeDetail(string? itemName, SharedData? shared) { if (IsBattleaxe(itemName, shared)) { return 2; } if (!IsTwoHanded(shared) && !HasToken(itemName, "twohandedaxe", "2haxe", "dualaxe")) { return 0; } return 1; } private static bool IsBattleaxe(string? itemName, SharedData? shared) { if (HasToken(itemName, "dualaxe")) { return false; } if (HasToken(itemName, "battleaxe")) { return true; } string value = shared?.m_attack?.m_attackAnimation ?? ""; string text = shared?.m_secondaryAttack?.m_attackAnimation ?? ""; if (IsTwoHanded(shared) && string.Equals(text, "battleaxe_secondary", StringComparison.OrdinalIgnoreCase) && !ContainsIgnoreCase(value, "dualaxe")) { if (!ContainsIgnoreCase(value, "battleaxe")) { return ContainsIgnoreCase(text, "battleaxe"); } return true; } return false; } private static int ClubDetail(string? itemName, SharedData? shared) { if (!IsTwoHanded(shared) && !HasToken(itemName, "sledge", "twohandedclub", "2hclub")) { return 0; } return 1; } private static bool IsTwoHanded(SharedData? shared) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (shared == null || (int)shared.m_itemType != 14) { if (shared == null) { return false; } return (int)shared.m_itemType == 22; } return true; } private static int ShieldDetail(string? itemName) { if (HasToken(itemName, "buckler")) { return 0; } if (!HasToken(itemName, "tower")) { return 1; } return 2; } private static bool IsSkillAttack(SharedData shared, SkillType skillType) { //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) if (shared.m_skillType == skillType && HasAttackAnimation(shared)) { return GetTotalDamage(shared) > 0f; } return false; } private static bool HasAttackAnimation(SharedData shared) { return !string.IsNullOrEmpty(shared.m_attack?.m_attackAnimation); } private static float GetTotalDamage(SharedData shared) { //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_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_001b: 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_0029: 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_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) //IL_0045: Unknown result type (might be due to invalid IL or missing references) DamageTypes damages = shared.m_damages; return damages.m_blunt + damages.m_slash + damages.m_pierce + damages.m_chop + damages.m_pickaxe + damages.m_fire + damages.m_frost + damages.m_lightning + damages.m_poison + damages.m_spirit; } private static bool IsItemTypeOrAttach(SharedData shared, ItemType itemType) { //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_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (shared.m_itemType != itemType) { return shared.m_attachOverride == itemType; } return true; } private static bool HasToken(string? itemName, SharedData? shared, params string[] tokens) { string[] array = new string[3] { itemName, shared?.m_name, StripLocalizationToken(shared?.m_name) }; for (int i = 0; i < array.Length; i++) { if (HasToken(array[i], tokens)) { return true; } } return false; } private static bool HasToken(string? value, params string[] tokens) { string text = DataForgeResourceMap.NormalizeResourceToken(value); if (text.Length == 0) { return false; } for (int i = 0; i < tokens.Length; i++) { string text2 = DataForgeResourceMap.NormalizeResourceToken(tokens[i]); if (text2.Length > 0 && text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static bool ContainsIgnoreCase(string value, string token) { return value.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0; } private static string StripLocalizationToken(string? value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); if (!text.StartsWith("$item_", StringComparison.OrdinalIgnoreCase)) { return text.TrimStart(new char[1] { '$' }); } return text.Substring("$item_".Length); } private static DataForgeItemSortGroup Group(int bigGroupRank, int subGroupRank, int detailRank = 0) { return new DataForgeItemSortGroup(bigGroupRank, subGroupRank, detailRank); } } internal static class DataForgeResourceMap { private readonly struct RecipeLookupEntry { internal Recipe Recipe { get; } internal int? Tier { get; } internal RecipeLookupEntry(Recipe recipe, int? tier) { Recipe = recipe; Tier = tier; } } private const string ResourceMapFileName = "z_resourcemap.txt"; private const int UnknownTierSortValue = 999999; private static readonly object Sync = new object(); private static Dictionary ResourceTierByToken = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary ItemDropCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static Dictionary? RecipeOutputLookup; private static readonly Dictionary ItemSortGroupCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary ItemTierSortValueCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static DateTime LoadedWriteTimeUtc = DateTime.MinValue; private static bool Loaded; private static int CachedObjectDbItemCount = -1; private static int CachedObjectDbRecipeCount = -1; private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); private static string ResourceMapPath => Path.Combine(ConfigDirectory, "z_resourcemap.txt"); internal static string BuildSortKey(int groupRank, int tierSortValue, string name) { return string.Join("|", Math.Max(0, groupRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, tierSortValue).ToString("D6", CultureInfo.InvariantCulture), name ?? ""); } internal static string BuildTierSortKey(int tierSortValue, string name) { return BuildSortKey(0, tierSortValue, name); } internal static string BuildItemSortKey(string? itemName, int tierSortValue, string name) { DataForgeItemSortGroup itemSortGroup = GetItemSortGroup(itemName); return string.Join("|", Math.Max(0, itemSortGroup.BigGroupRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, itemSortGroup.SubGroupRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, itemSortGroup.DetailRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, tierSortValue).ToString("D6", CultureInfo.InvariantCulture), name ?? ""); } private static DataForgeItemSortGroup GetItemSortGroup(string? itemName) { EnsureObjectDbCacheFresh(); string text = CleanPrefabName(itemName); if (text.Length > 0 && ItemSortGroupCache.TryGetValue(text, out var value)) { return value; } DataForgeItemSortGroup dataForgeItemSortGroup = DataForgeItemSortClassifier.Classify(itemName, ResolveItemDrop(itemName)?.m_itemData?.m_shared); if (text.Length > 0) { ItemSortGroupCache[text] = dataForgeItemSortGroup; } return dataForgeItemSortGroup; } internal static int GetItemTierSortValue(string? itemName) { EnsureObjectDbCacheFresh(); string text = CleanPrefabName(itemName); if (text.Length > 0 && ItemTierSortValueCache.TryGetValue(text, out var value)) { return value; } int? knownTierForItem = GetKnownTierForItem(itemName); int? knownTierForRecipe = GetKnownTierForRecipe(ResolveRecipeForItem(itemName)); int num = SortValue(MaxTier(knownTierForItem, knownTierForRecipe)); if (text.Length > 0) { ItemTierSortValueCache[text] = num; } return num; } internal static int GetResourceTierSortValue(IEnumerable resourceNames) { return SortValue(GetKnownTierForResourceNames(resourceNames)); } private static int SortValue(int? tier) { return tier ?? 999999; } private static int? GetKnownTierForRecipe(Recipe? recipe) { if (recipe?.m_resources == null) { return null; } return GetKnownTierForResourceNames(from requirement in recipe.m_resources where (Object)(object)requirement?.m_resItem != (Object)null && requirement.m_amount > 0 select ((Object)requirement.m_resItem).name); } private static int? GetKnownTierForItem(string? itemName) { ItemDrop val = ResolveItemDrop(itemName); List list = new List { itemName, ((Object)(object)val != (Object)null) ? ((Object)val).name : null, val?.m_itemData?.m_shared?.m_name, StripLocalizationToken(val?.m_itemData?.m_shared?.m_name) }; string text = val?.m_itemData?.m_shared?.m_name; if (text != null && text.Length > 0 && Localization.instance != null) { list.Add(Localization.instance.Localize(text)); } return GetKnownTierForResourceNames(list); } private static int? GetKnownTierForResourceNames(IEnumerable resourceNames) { EnsureLoaded(); int? num = null; foreach (string resourceName in resourceNames) { foreach (string resourceToken in GetResourceTokens(resourceName)) { if (ResourceTierByToken.TryGetValue(resourceToken, out var value)) { num = MaxTier(num, value); } } } return num; } private static int? MaxTier(int? left, int? right) { if (!left.HasValue) { return right; } if (!right.HasValue) { return left; } return Math.Max(left.Value, right.Value); } private static IEnumerable GetResourceTokens(string? value) { string cleaned = CleanPrefabName(value); if (cleaned.Length != 0) { yield return NormalizeResourceToken(cleaned); string text = StripLocalizationToken(cleaned); if (text.Length > 0) { yield return NormalizeResourceToken(text); } } } private static void EnsureLoaded() { Directory.CreateDirectory(ConfigDirectory); EnsureDefaultResourceMapFile(); DateTime dateTime = (File.Exists(ResourceMapPath) ? File.GetLastWriteTimeUtc(ResourceMapPath) : DateTime.MinValue); lock (Sync) { if (!Loaded || !(dateTime == LoadedWriteTimeUtc)) { ResourceTierByToken = LoadResourceMap(ResourceMapPath); LoadedWriteTimeUtc = dateTime; Loaded = true; ClearSortCaches(); } } } private static void EnsureObjectDbCacheFresh() { int num = ObjectDB.instance?.m_items?.Count ?? (-1); int num2 = ObjectDB.instance?.m_recipes?.Count ?? (-1); if (num != CachedObjectDbItemCount || num2 != CachedObjectDbRecipeCount) { CachedObjectDbItemCount = num; CachedObjectDbRecipeCount = num2; ClearSortCaches(); } } private static void ClearSortCaches() { ItemDropCache.Clear(); RecipeOutputLookup = null; ItemSortGroupCache.Clear(); ItemTierSortValueCache.Clear(); } private static Dictionary LoadResourceMap(string path) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!File.Exists(path)) { return dictionary; } int num = -1; string[] array = File.ReadAllLines(path); for (int i = 0; i < array.Length; i++) { string text = StripComment(array[i]).Trim(); if (text.Length == 0) { continue; } if (text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) { num++; continue; } if (num < 0) { num = 0; } string text2 = NormalizeResourceToken(text); if (text2.Length > 0 && !dictionary.ContainsKey(text2)) { dictionary[text2] = num; } } return dictionary; } private static void EnsureDefaultResourceMapFile() { if (!File.Exists(ResourceMapPath)) { File.WriteAllText(ResourceMapPath, DefaultResourceMapText()); } } private static string StripComment(string line) { int num = line.IndexOf('#'); if (num < 0) { return line; } return line.Substring(0, num); } internal static string NormalizeResourceToken(string? token) { string text = CleanPrefabName(token); 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 string CleanPrefabName(string? name) { if (!string.IsNullOrWhiteSpace(name)) { return name.Replace("(Clone)", "").Trim(); } return ""; } private static string StripLocalizationToken(string? value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); if (!text.StartsWith("$item_", StringComparison.OrdinalIgnoreCase)) { return text.TrimStart(new char[1] { '$' }); } return text.Substring("$item_".Length); } private static ItemDrop? ResolveItemDrop(string? itemName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(itemName)) { return null; } string normalized = CleanPrefabName(itemName); if (ItemDropCache.TryGetValue(normalized, out ItemDrop value)) { return value; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(normalized); ItemDrop val = default(ItemDrop); if ((Object)(object)itemPrefab != (Object)null && itemPrefab.TryGetComponent(ref val)) { ItemDropCache[normalized] = val; return val; } ItemDrop val2 = (from item in ObjectDB.instance.m_items where (Object)(object)item != (Object)null select item.GetComponent()).FirstOrDefault((Func)((ItemDrop itemDrop) => (Object)(object)itemDrop != (Object)null && ItemNameMatches(itemDrop, normalized))); ItemDropCache[normalized] = val2; return val2; } private static Recipe? ResolveRecipeForItem(string? itemName) { if (ObjectDB.instance?.m_recipes == null || string.IsNullOrWhiteSpace(itemName)) { return null; } string itemName2 = CleanPrefabName(itemName); if (TryGetRecipeOutputLookupEntry(GetRecipeOutputLookup(), itemName2, out var entry)) { return entry.Recipe; } return null; } private static Dictionary GetRecipeOutputLookup() { if (RecipeOutputLookup != null) { return RecipeOutputLookup; } EnsureLoaded(); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); IEnumerable enumerable = ObjectDB.instance?.m_recipes; foreach (Recipe item in enumerable ?? Enumerable.Empty()) { if ((Object)(object)item?.m_item == (Object)null) { continue; } int? knownTierForRecipe = GetKnownTierForRecipe(item); foreach (string itemLookupKey in GetItemLookupKeys(item.m_item)) { AddRecipeOutputLookupEntry(dictionary, itemLookupKey, item, knownTierForRecipe); } } RecipeOutputLookup = dictionary; return dictionary; } private static bool TryGetRecipeOutputLookupEntry(Dictionary lookup, string itemName, out RecipeLookupEntry entry) { foreach (string lookupKey in GetLookupKeys(itemName)) { if (lookup.TryGetValue(lookupKey, out entry)) { return true; } } entry = default(RecipeLookupEntry); return false; } private static void AddRecipeOutputLookupEntry(Dictionary lookup, string key, Recipe recipe, int? tier) { if (key.Length != 0 && (!lookup.TryGetValue(key, out var value) || (tier ?? (-1)) > (value.Tier ?? (-1)))) { lookup[key] = new RecipeLookupEntry(recipe, tier); } } private static IEnumerable GetItemLookupKeys(ItemDrop itemDrop) { if (itemDrop?.m_itemData?.m_shared == null) { yield break; } string[] array = new string[4] { ((Object)itemDrop).name, ((Object)((Component)itemDrop).gameObject).name, itemDrop.m_itemData.m_shared.m_name, StripLocalizationToken(itemDrop.m_itemData.m_shared.m_name) }; foreach (string value in array) { foreach (string lookupKey in GetLookupKeys(value)) { yield return lookupKey; } } } private static IEnumerable GetLookupKeys(string? value) { string clean = CleanPrefabName(value); if (clean.Length > 0) { yield return clean; } string text = NormalizeResourceToken(clean); if (text.Length > 0 && !string.Equals(text, clean, StringComparison.OrdinalIgnoreCase)) { yield return text; } } private static bool ItemNameMatches(ItemDrop itemDrop, string token) { if (itemDrop?.m_itemData?.m_shared == null) { return false; } string value = NormalizeResourceToken(token); string[] array = new string[4] { ((Object)itemDrop).name, ((Object)((Component)itemDrop).gameObject).name, itemDrop.m_itemData.m_shared.m_name, StripLocalizationToken(itemDrop.m_itemData.m_shared.m_name) }; foreach (string text in array) { if (CleanPrefabName(text).Equals(token, StringComparison.OrdinalIgnoreCase) || NormalizeResourceToken(text).Equals(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static string DefaultResourceMapText() { return string.Join(Environment.NewLine, "# DataForge resource tier map.", "# User-editable sorting data for generated item, recipe, and piece reference/full scaffold files.", "# Sections near the top are lower tier; sections near the bottom are higher tier.", "# Generated files use the same direction: lower tiers first, higher tiers later.", "# Edit this file to change item/recipe/piece generated file sorting.", "", "[Meadows]", "Wood", "Stone", "Resin", "Dandelion", "Flint", "LeatherScraps", "BoneFragments", "Honey", "Raspberry", "Blueberries", "DeerHide", "DeerMeat", "Feathers", "GreydwarfEye", "RawMeat", "", "[BlackForest]", "HardAntler", "Bronze", "BronzeNails", "Copper", "Tin", "Ectoplasm", "SurtlingCore", "TrollHide", "BjornHide", "FineWood", "AncientSeed", "Carrot", "BjornMeat", "BjornPaw", "RoundLog", "Thistle", "", "[Swamp]", "Iron", "Ooze", "Entrails", "Guck", "Bloodbag", "Chain", "ElderBark", "IronNails", "Root", "Turnip", "WitheredBone", "CuredSquirrelHamstring", "", "[Ocean]", "Chitin", "SerpentScale", "SerpentMeat", "", "[Mountain]", "Silver", "Crystal", "DragonEgg", "JuteRed", "Obsidian", "WolfClaw", "WolfFang", "WolfHairBundle", "WolfMeat", "WolfPelt", "", "[Plains]", "UndeadBjornRibcage", "BlackMetal", "DragonTear", "Barley", "BarleyFlour", "ChickenEgg", "ChickenMeat", "Flax", "GoblinTotem", "LinenThread", "LoxMeat", "LoxPelt", "Needle", "Tar", "", "[Mistlands]", "Eitr", "Bilebag", "BlackCore", "BlackMarble", "BugMeat", "Carapace", "DvergrKeyFragment", "DvergrNeedle", "GiantBloodSack", "HareMeat", "JuteBlue", "Mandible", "Sap", "ScaleHide", "Softtissue", "Wisp", "YagluthDrop", "YggdrasilWood", "", "[Ashlands]", "FlametalNew", "AskBladder", "AskHide", "AsksvinEgg", "AsksvinMeat", "Blackwood", "BoneMawSerpentMeat", "BonemawSerpentTooth", "CelestialFeather", "CeramicPlate", "CharcoalResin", "CharredBone", "CharredCogwheel", "Charredskull", "Grausten", "MoltenCore", "MorgenHeart", "MorgenSinew", "ProustitePowder", "SulfurStone", "VoltureEgg", "VoltureMeat", ""); } } internal static class DataForgeValue { internal static void Copy(string? value, Action assign) { if (value != null) { assign(value); } } internal static void Copy(bool? value, Action assign) { if (value.HasValue) { assign(value.Value); } } internal static void Copy(int? value, Action assign) { if (value.HasValue) { assign(value.Value); } } internal static void Copy(float? value, Action assign) { if (value.HasValue) { assign(value.Value); } } internal static string[] SplitTuple(string? value) { return (from part in value?.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray() ?? Array.Empty(); } internal static bool IsNone(string? value) { string text = value?.Trim() ?? ""; if (text.Length != 0 && !text.Equals("none", StringComparison.OrdinalIgnoreCase)) { return text.Equals("null", StringComparison.OrdinalIgnoreCase); } return true; } } internal static class DataForgeSync { internal static bool PublishPayload(CustomSyncedValue? syncedPayload, string domainName, string payload) { if (syncedPayload == null || string.Equals(syncedPayload.Value ?? "", payload, StringComparison.Ordinal)) { return false; } DataForgePlugin.Log.LogDebug((object)$"Publishing {domainName} payload ({Encoding.UTF8.GetByteCount(payload)} bytes)."); syncedPayload.AssignLocalValue(payload); return true; } } internal static class DataForgeReferenceState { private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); internal static bool ShouldSkip(string stateKey, string referencePath, string sourceSignature, string logicVersion) { if (!File.Exists(referencePath)) { return false; } string statePath = GetStatePath(stateKey); if (!File.Exists(statePath)) { return false; } try { string[] array = File.ReadAllLines(statePath); if (array.Length < 3) { return false; } return string.Equals(array[0].Trim(), sourceSignature, StringComparison.Ordinal) && string.Equals(array[1].Trim(), BuildFileStamp(referencePath), StringComparison.Ordinal) && string.Equals(array[2].Trim(), logicVersion, StringComparison.Ordinal); } catch { return false; } } internal static void Record(string stateKey, string referencePath, string sourceSignature, string logicVersion) { if (File.Exists(referencePath)) { string statePath = GetStatePath(stateKey); string directoryName = Path.GetDirectoryName(statePath); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } string content = sourceSignature.Trim() + Environment.NewLine + BuildFileStamp(referencePath) + Environment.NewLine + logicVersion + Environment.NewLine; GeneratedArtifactWriter.WriteTextIfChanged(statePath, content); } } internal static string BuildFileStamp(string path) { if (!File.Exists(path)) { return "missing"; } FileInfo fileInfo = new FileInfo(path); return fileInfo.Length.ToString(CultureInfo.InvariantCulture) + ":" + fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture); } internal static string ComputeStableHash(string value) { using SHA256 sHA = SHA256.Create(); byte[] bytes = Encoding.UTF8.GetBytes(value ?? ""); byte[] array = sHA.ComputeHash(bytes); StringBuilder stringBuilder = new StringBuilder(array.Length * 2); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } private static string GetStatePath(string stateKey) { return Path.Combine(ConfigDirectory, "cache", ".reference-state." + stateKey + ".txt"); } } internal static class DataForgeProfiler { internal static void Profile(string label, Action action) { if (!DataForgePlugin.LogStartupTimings) { action(); return; } Stopwatch stopwatch = Stopwatch.StartNew(); try { action(); } finally { stopwatch.Stop(); DataForgeConnectionProfiler.MarkProfiledPhase(label, stopwatch.Elapsed.TotalMilliseconds); DataForgePlugin.Log.LogInfo((object)$"DataForge profile: {label} took {stopwatch.Elapsed.TotalMilliseconds:0.###} ms."); } } } internal static class DataForgeConnectionProfiler { private readonly struct Milestone { internal string Label { get; } internal double ElapsedMs { get; } internal Milestone(string label, double elapsedMs) { Label = label; ElapsedMs = elapsedMs; } } private static readonly object Lock = new object(); private static readonly Stopwatch Stopwatch = new Stopwatch(); private static readonly List Milestones = new List(); private static readonly HashSet SeenOneShotMilestones = new HashSet(StringComparer.Ordinal); private static bool Active; private static bool Completed; internal static void Begin(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { Active = true; Completed = false; Milestones.Clear(); SeenOneShotMilestones.Clear(); Stopwatch.Restart(); AddMilestone(label); } } internal static void Mark(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (Active && !Completed) { AddMilestone(label); } } } internal static void MarkOnce(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (Active && !Completed && SeenOneShotMilestones.Add(label)) { AddMilestone(label); } } } internal static void MarkProfiledPhase(string label, double elapsedMs) { if (DataForgePlugin.LogStartupTimings) { Mark($"DataForge {label} ({elapsedMs:0.###} ms)"); } } internal static void Complete(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (Active && !Completed) { AddMilestone(label); Completed = true; Stopwatch.Stop(); LogSummary("completed"); Active = false; } } } internal static void Abort(string label) { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (Active && !Completed) { AddMilestone(label); Completed = true; Stopwatch.Stop(); LogSummary("aborted"); Active = false; } } } private static void AddMilestone(string label) { Milestones.Add(new Milestone(label, Stopwatch.Elapsed.TotalMilliseconds)); } private static void LogSummary(string result) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"DataForge lobby-to-world profile {result}: total {Stopwatch.Elapsed.TotalMilliseconds:0.###} ms."); double num = 0.0; foreach (Milestone milestone in Milestones) { stringBuilder.Append(" +"); stringBuilder.Append(milestone.ElapsedMs.ToString("0.###")); stringBuilder.Append(" ms"); stringBuilder.Append(" (delta "); stringBuilder.Append((milestone.ElapsedMs - num).ToString("0.###")); stringBuilder.Append(" ms): "); stringBuilder.AppendLine(milestone.Label); num = milestone.ElapsedMs; } DataForgePlugin.Log.LogInfo((object)stringBuilder.ToString().TrimEnd(Array.Empty())); } } [HarmonyPatch(typeof(FejdStartup), "JoinServer")] internal static class DataForgeFejdStartupJoinServerProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.Begin("FejdStartup.JoinServer"); } } [HarmonyPatch(typeof(FejdStartup), "OnStartGame")] internal static class DataForgeFejdStartupOnStartGameProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.Begin("FejdStartup.OnStartGame"); } } [HarmonyPatch(typeof(FejdStartup), "TransitionToMainScene")] internal static class DataForgeFejdStartupTransitionToMainSceneProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.Mark("FejdStartup.TransitionToMainScene"); } } [HarmonyPatch(typeof(FejdStartup), "LoadMainScene")] internal static class DataForgeFejdStartupLoadMainSceneProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.Mark("FejdStartup.LoadMainScene"); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] internal static class DataForgeFejdStartupShowConnectErrorProfilerPatch { private static void Postfix() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) DataForgeConnectionProfiler.Abort($"FejdStartup.ShowConnectError status={ZNet.GetConnectionStatus()}"); } } [HarmonyPatch(typeof(Game), "Awake")] internal static class DataForgeGameAwakeProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.MarkOnce("Game.Awake"); } } [HarmonyPatch(typeof(Game), "Start")] internal static class DataForgeGameStartProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.MarkOnce("Game.Start"); } } [HarmonyPatch(typeof(Game), "RequestRespawn")] internal static class DataForgeGameRequestRespawnProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.MarkOnce("Game.RequestRespawn"); } } [HarmonyPatch(typeof(Game), "SpawnPlayer")] internal static class DataForgeGameSpawnPlayerProfilerPatch { private static void Postfix() { DataForgeConnectionProfiler.Complete("Game.SpawnPlayer local player ready"); } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class DataForgeZNetAwakeProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.MarkOnce("ZNet.Awake"); } } [HarmonyPatch(typeof(ZNet), "Start")] internal static class DataForgeZNetStartProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.MarkOnce("ZNet.Start"); } } [HarmonyPatch(typeof(ZNet), "ClientConnect")] internal static class DataForgeZNetClientConnectProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.MarkOnce("ZNet.ClientConnect"); } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal static class DataForgeZNetOnNewConnectionProfilerPatch { private static void Postfix() { DataForgeConnectionProfiler.MarkOnce("ZNet.OnNewConnection"); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] internal static class DataForgeZNetRpcPeerInfoProfilerPatch { private static void Postfix() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) DataForgeConnectionProfiler.MarkOnce($"ZNet.RPC_PeerInfo status={ZNet.GetConnectionStatus()}"); } } [HarmonyPatch(typeof(ZoneSystem), "Awake")] internal static class DataForgeZoneSystemAwakeProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.MarkOnce("ZoneSystem.Awake"); } } [HarmonyPatch(typeof(ZoneSystem), "Start")] internal static class DataForgeZoneSystemStartProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.MarkOnce("ZoneSystem.Start"); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] internal static class DataForgeObjectDbAwakeProfilerPatch { [HarmonyPriority(800)] private static void Prefix() { DataForgeHarmonyPatchProfiler.BeginObjectDbAwake(); DataForgeConnectionProfiler.MarkOnce("ObjectDB.Awake"); } [HarmonyPriority(0)] private static void Finalizer() { DataForgeHarmonyPatchProfiler.ReportObjectDbAwake(); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class DataForgeZNetSceneAwakeProfilerPatch { [HarmonyPriority(800)] private static void Prefix() { DataForgeHarmonyPatchProfiler.InstallObjectDbAwakeProfiling(); DataForgeConnectionProfiler.MarkOnce("ZNetScene.Awake"); } } [HarmonyPatch(typeof(DungeonDB), "Start")] internal static class DataForgeDungeonDbStartProfilerPatch { private static void Prefix() { DataForgeConnectionProfiler.MarkOnce("DungeonDB.Start"); } } internal static class DataForgeHarmonyPatchProfiler { private readonly struct PatchDescriptor { internal string Owner { get; } internal string PatchType { get; } internal int Priority { get; } internal int Index { get; } internal string MethodName { get; } internal PatchDescriptor(string owner, string patchType, int priority, int index, string methodName) { Owner = owner; PatchType = patchType; Priority = priority; Index = index; MethodName = methodName; } } private sealed class PatchTiming { internal int Count { get; private set; } internal double ElapsedMs { get; private set; } internal void Add(double elapsedMs) { Count++; ElapsedMs += elapsedMs; } } private static readonly object Lock = new object(); private static readonly Harmony InstrumentationHarmony = new Harmony("sighsorry.DataForge.patchprofiler"); private static readonly HashSet InstrumentedMethods = new HashSet(); private static readonly Dictionary Descriptors = new Dictionary(); private static readonly Dictionary ObjectDbAwakeTimings = new Dictionary(); private static readonly Stopwatch ObjectDbAwakeStopwatch = new Stopwatch(); private static bool ObjectDbAwakeActive; private static bool ObjectDbAwakeReported; private static bool ObjectDbAwakeInstallAttempted; internal static void InstallObjectDbAwakeProfiling() { //IL_0144: 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_0166: Expected O, but got Unknown //IL_0166: Expected O, but got Unknown if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (ObjectDbAwakeInstallAttempted) { return; } ObjectDbAwakeInstallAttempted = true; } MethodBase methodBase = AccessTools.Method(typeof(ObjectDB), "Awake", (Type[])null, (Type[])null); Patches val = ((methodBase != null) ? Harmony.GetPatchInfo(methodBase) : null); if (methodBase == null || val == null) { return; } int num = 0; int num2 = 0; int num3 = 0; foreach (var item3 in EnumerateInvocationPatches(val)) { Patch item = item3.Patch; string item2 = item3.PatchType; MethodBase patchMethod = item.PatchMethod; if (!CanInstrument(patchMethod)) { num2++; continue; } try { lock (Lock) { if (!InstrumentedMethods.Add(patchMethod)) { num2++; continue; } Descriptors[patchMethod] = new PatchDescriptor(item.owner ?? "", item2, item.priority, item.index, FormatMethodName(patchMethod)); } InstrumentationHarmony.Patch(patchMethod, new HarmonyMethod(typeof(DataForgeHarmonyPatchProfiler), "ProfiledPatchPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(DataForgeHarmonyPatchProfiler), "ProfiledPatchFinalizer", (Type[])null), (HarmonyMethod)null); num++; } catch (Exception ex) { num3++; DataForgePlugin.Log.LogWarning((object)("Could not instrument ObjectDB.Awake " + item2 + " patch '" + FormatMethodName(patchMethod) + "': " + ex.Message)); } } DataForgePlugin.Log.LogInfo((object)$"ObjectDB.Awake patch profiler installed: instrumented={num}, skipped={num2}, failed={num3}."); } internal static void BeginObjectDbAwake() { if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { ObjectDbAwakeTimings.Clear(); ObjectDbAwakeReported = false; ObjectDbAwakeActive = true; ObjectDbAwakeStopwatch.Restart(); } } internal static void ReportObjectDbAwake() { if (!DataForgePlugin.LogStartupTimings) { return; } double totalMilliseconds; List<(PatchDescriptor, PatchTiming)> list; lock (Lock) { if (!ObjectDbAwakeActive || ObjectDbAwakeReported) { return; } ObjectDbAwakeStopwatch.Stop(); totalMilliseconds = ObjectDbAwakeStopwatch.Elapsed.TotalMilliseconds; ObjectDbAwakeActive = false; ObjectDbAwakeReported = true; list = (from pair in ObjectDbAwakeTimings select (Descriptors.TryGetValue(pair.Key, out var value) ? value : new PatchDescriptor("", "unknown", 0, 0, FormatMethodName(pair.Key)), Value: pair.Value) into pair orderby pair.Value.ElapsedMs descending select pair).ToList(); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"ObjectDB.Awake Harmony patch profile: total={totalMilliseconds:0.###} ms, measuredPatches={list.Count}."); if (list.Count == 0) { stringBuilder.AppendLine(" No instrumented ObjectDB.Awake patch methods were invoked."); } else { foreach (var (patchDescriptor, patchTiming) in list.Take(40)) { stringBuilder.Append(" "); stringBuilder.Append(patchTiming.ElapsedMs.ToString("0.###")); stringBuilder.Append(" ms"); if (patchTiming.Count > 1) { stringBuilder.Append(" x").Append(patchTiming.Count); } stringBuilder.Append(": "); stringBuilder.Append(patchDescriptor.PatchType); stringBuilder.Append(" "); stringBuilder.Append(string.IsNullOrWhiteSpace(patchDescriptor.Owner) ? "" : patchDescriptor.Owner); stringBuilder.Append(" [priority="); stringBuilder.Append(patchDescriptor.Priority); stringBuilder.Append(", index="); stringBuilder.Append(patchDescriptor.Index); stringBuilder.Append("] :: "); stringBuilder.AppendLine(patchDescriptor.MethodName); } if (list.Count > 40) { stringBuilder.Append(" ... "); stringBuilder.Append(list.Count - 40); stringBuilder.AppendLine(" more patch methods omitted."); } } DataForgePlugin.Log.LogInfo((object)stringBuilder.ToString().TrimEnd(Array.Empty())); } private static void ProfiledPatchPrefix(MethodBase __originalMethod, out long __state) { __state = 0L; if (!DataForgePlugin.LogStartupTimings) { return; } lock (Lock) { if (!ObjectDbAwakeActive) { return; } } __state = Stopwatch.GetTimestamp(); } private static Exception? ProfiledPatchFinalizer(MethodBase __originalMethod, long __state, Exception? __exception) { if (__state <= 0 || !DataForgePlugin.LogStartupTimings) { return __exception; } double elapsedMs = (double)(Stopwatch.GetTimestamp() - __state) * 1000.0 / (double)Stopwatch.Frequency; lock (Lock) { if (ObjectDbAwakeActive) { if (!ObjectDbAwakeTimings.TryGetValue(__originalMethod, out PatchTiming value)) { value = new PatchTiming(); ObjectDbAwakeTimings[__originalMethod] = value; } value.Add(elapsedMs); } } return __exception; } private static IEnumerable<(Patch Patch, string PatchType)> EnumerateInvocationPatches(Patches patchInfo) { foreach (Patch prefix in patchInfo.Prefixes) { yield return (Patch: prefix, PatchType: "prefix"); } foreach (Patch postfix in patchInfo.Postfixes) { yield return (Patch: postfix, PatchType: "postfix"); } foreach (Patch finalizer in patchInfo.Finalizers) { yield return (Patch: finalizer, PatchType: "finalizer"); } } private static bool CanInstrument(MethodBase? method) { if (method == null || method.DeclaringType == typeof(DataForgeHarmonyPatchProfiler) || method.DeclaringType == null || method.ContainsGenericParameters || method.IsAbstract) { return false; } return true; } private static string FormatMethodName(MethodBase method) { return (method.DeclaringType?.FullName ?? "") + "." + method.Name; } } internal static class DataForgeOverrideFiles { internal static IEnumerable GetOverrideFiles(string directory, Func isOverrideFile) { if (!Directory.Exists(directory)) { return Array.Empty(); } return Directory.GetFiles(directory, "*.yml").Concat(Directory.GetFiles(directory, "*.yaml")).Where(isOverrideFile) .OrderBy((string path) => path, StringComparer.OrdinalIgnoreCase) .ToArray(); } internal static List LoadEntries(IEnumerable files, Func> deserializeEntries) { List list = new List(); foreach (string file in files) { string arg = File.ReadAllText(file); list.AddRange(deserializeEntries(arg, file)); } return list; } internal static void EnsureDefaultOverride(string directory, string overrideFileName, Func> getOverrideFiles, Func buildDefaultTemplate) { Directory.CreateDirectory(directory); if (!getOverrideFiles().Any()) { File.WriteAllText(Path.Combine(directory, overrideFileName), buildDefaultTemplate()); } } } internal static class DataForgeFileWatcher { internal sealed class DebouncedAction : IDisposable { private readonly object _lock = new object(); private readonly Action _action; private readonly System.Timers.Timer _timer; private bool _disposed; internal DebouncedAction(long delayTicks, Action action) { _action = action; _timer = new System.Timers.Timer(Math.Max(1.0, TimeSpan.FromTicks(delayTicks).TotalMilliseconds)) { AutoReset = false, SynchronizingObject = ThreadingHelper.SynchronizingObject }; _timer.Elapsed += OnElapsed; } internal void Schedule() { lock (_lock) { if (!_disposed) { _timer.Stop(); _timer.Start(); } } } public void Dispose() { lock (_lock) { if (!_disposed) { _disposed = true; _timer.Stop(); _timer.Elapsed -= OnElapsed; _timer.Dispose(); } } } private void OnElapsed(object sender, ElapsedEventArgs e) { lock (_lock) { if (_disposed) { return; } } _action(); } } internal static FileSystemWatcher Create(string directory, string filter, bool includeSubdirectories, FileSystemEventHandler handler) { Directory.CreateDirectory(directory); FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(directory, filter); fileSystemWatcher.IncludeSubdirectories = includeSubdirectories; fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; fileSystemWatcher.Changed += handler; fileSystemWatcher.Created += handler; fileSystemWatcher.Deleted += handler; fileSystemWatcher.Renamed += delegate(object sender, RenamedEventArgs args) { handler(sender, args); }; fileSystemWatcher.EnableRaisingEvents = true; return fileSystemWatcher; } internal static DebouncedAction CreateDebouncedAction(long delayTicks, Action action) { return new DebouncedAction(delayTicks, action); } } internal static class DataForgeWorldLifecycle { internal static bool IsShuttingDown { get; private set; } internal static bool MarkStarting() { bool isShuttingDown = IsShuttingDown; IsShuttingDown = false; return isShuttingDown; } internal static void MarkShuttingDown() { IsShuttingDown = true; } } [HarmonyPatch(typeof(ObjectDB), "Awake")] internal static class DataForgeObjectDBAwakePatch { [HarmonyPriority(0)] private static void Postfix() { NotifyObjectDBReady(); } internal static void NotifyObjectDBReady() { if (!DataForgeWorldLifecycle.IsShuttingDown) { DataForgeProfiler.Profile("EnsureSourceOfTruthFileMode/ObjectDB", DataForgePlugin.EnsureSourceOfTruthFileMode); DataForgeProfiler.Profile("effects.OnObjectDBReady", StatusEffectOverrideManager.OnObjectDBReady); DataForgeProfiler.Profile("items.OnObjectDBReady", ItemOverrideManager.OnObjectDBReady); DataForgeProfiler.Profile("recipes.OnObjectDBReady", RecipeOverrideManager.OnObjectDBReady); DataForgeProfiler.Profile("pieces.OnObjectDBReady", PieceOverrideManager.OnObjectDBReady); } } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] internal static class DataForgeObjectDBCopyOtherDBPatch { [HarmonyPriority(0)] private static void Postfix() { if (!DataForgeWorldLifecycle.IsShuttingDown) { DataForgeObjectDBAwakePatch.NotifyObjectDBReady(); } } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class DataForgeRecipeZNetSceneAwakePatch { private static void Postfix() { if (DataForgeWorldLifecycle.MarkStarting() && (Object)(object)ObjectDB.instance != (Object)null) { DataForgeObjectDBAwakePatch.NotifyObjectDBReady(); } DataForgeProfiler.Profile("EnsureSourceOfTruthFileMode/ZNetScene(recipes)", DataForgePlugin.EnsureSourceOfTruthFileMode); DataForgeProfiler.Profile("recipes.OnZNetSceneReady", RecipeOverrideManager.OnZNetSceneReady); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class DataForgePieceZNetSceneAwakePatch { private static void Postfix() { DataForgeWorldLifecycle.MarkStarting(); DataForgeProfiler.Profile("EnsureSourceOfTruthFileMode/ZNetScene(domains)", DataForgePlugin.EnsureSourceOfTruthFileMode); DataForgeProfiler.Profile("effects.OnZNetSceneReady", StatusEffectOverrideManager.OnZNetSceneReady); DataForgeProfiler.Profile("items.OnZNetSceneReady", ItemOverrideManager.OnZNetSceneReady); DataForgeProfiler.Profile("pieces.OnGameDataReady", PieceOverrideManager.OnGameDataReady); } } [HarmonyPatch(typeof(ZNetScene), "Awake")] internal static class DataForgeMaterialReferenceZNetSceneAwakePatch { [HarmonyPriority(0)] private static void Postfix() { if (!DataForgeWorldLifecycle.IsShuttingDown) { DataForgeProfiler.Profile("materials.WriteReferenceIfReady", MaterialReferenceWriter.WriteReferenceIfReady); } } } [HarmonyPatch(typeof(DungeonDB), "Start")] internal static class DataForgePieceDungeonDbStartPatch { [HarmonyPriority(0)] private static void Postfix() { if (!DataForgeWorldLifecycle.IsShuttingDown) { DataForgeProfiler.Profile("pieces.OnPieceTablesReady", PieceOverrideManager.OnPieceTablesReady); } } } [HarmonyPatch(typeof(ZNet), "Shutdown")] internal static class DataForgeZNetShutdownCleanupPatch { [HarmonyPriority(800)] private static void Prefix() { try { DataForgeWorldLifecycle.MarkShuttingDown(); RecipeOverrideManager.OnWorldShutdown(); StatusEffectOverrideManager.OnWorldShutdown(); ItemOverrideManager.OnWorldShutdown(); PieceOverrideManager.OnWorldShutdown(); } catch (Exception arg) { DataForgePlugin.Log.LogWarning((object)$"Failed to clean up DataForge-created clones during world shutdown: {arg}"); } } } internal static class GeneratedArtifactWriter { internal static string GeneratedHeader(string domainName, string overrideFileName, string label) { return "# Generated by DataForge: " + domainName + " " + label + "." + Environment.NewLine + "# Do not edit this generated file directly. Copy entries into " + overrideFileName + " or " + OverrideWildcard(overrideFileName) + "." + Environment.NewLine; } internal static bool TryWriteFullScaffold(string path, string domainName, Func buildContent, out string error) { error = ""; try { WriteTextIfChanged(path, buildContent()); DataForgePlugin.Log.LogInfo((object)("Wrote " + domainName + " full scaffold to " + path + ".")); return true; } catch (Exception ex) { error = ex.Message; return false; } } internal static bool TryWriteFullScaffoldIfReady(string path, string domainName, bool isReady, string notReadyError, Func buildContent, out string error) { if (!isReady) { error = notReadyError; return false; } return TryWriteFullScaffold(path, domainName, buildContent, out error); } internal static bool WriteReference(string directory, string referenceFileName, string domainName, string overrideFileName, string content) { return WriteTextIfChanged(Path.Combine(directory, referenceFileName), GeneratedHeader(domainName, overrideFileName, "compact lookup") + content); } internal static bool WriteReferenceIfReady(bool isReady, string directory, string referenceFileName, string domainName, string overrideFileName, Func buildContent) { if (!isReady) { return false; } return WriteReference(directory, referenceFileName, domainName, overrideFileName, buildContent()); } internal static bool WriteTextIfChanged(string path, string content) { string directoryName = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } if (content == null) { content = ""; } if (File.Exists(path) && string.Equals(File.ReadAllText(path), content, StringComparison.Ordinal)) { return false; } File.WriteAllText(path, content); return true; } private static string OverrideWildcard(string overrideFileName) { string extension = Path.GetExtension(overrideFileName); return Path.GetFileNameWithoutExtension(overrideFileName) + "_*" + extension; } } [HarmonyPatch(typeof(Pickable), "RPC_Pick")] internal static class DataForgePickableRpcPickAmountMultiplierPatch { private readonly struct PickableAmountState { public int Amount { get; } public bool Changed { get; } public PickableAmountState(int amount, bool changed) { Amount = amount; Changed = changed; } } [HarmonyPriority(800)] private static void Prefix(Pickable __instance, out PickableAmountState __state) { __state = new PickableAmountState(__instance.m_amount, changed: false); float acquisitionAmountMultiplier = ItemOverrideManager.GetAcquisitionAmountMultiplier(__instance.m_itemPrefab); if (!(Math.Abs(acquisitionAmountMultiplier - 1f) <= 0.0001f)) { __instance.m_amount = ItemOverrideManager.MultiplyAmount(__instance.m_amount, acquisitionAmountMultiplier); __state = new PickableAmountState(__state.Amount, changed: true); } } [HarmonyPriority(0)] private static void Postfix(Pickable __instance, PickableAmountState __state) { if (__state.Changed) { __instance.m_amount = __state.Amount; } } } [HarmonyPatch(typeof(DropTable), "GetDropListItems")] internal static class DataForgeDropTableGetDropListItemsAmountMultiplierPatch { private static void Postfix(ref List __result) { if (__result == null || __result.Count == 0 || !DataForgePlugin.ItemOverridesEnabled) { return; } List list = new List(); bool flag = false; foreach (ItemData item in __result) { if (item == null) { flag = true; continue; } int num = ItemOverrideManager.ApplyAcquisitionAmountMultiplier(item, item.m_stack); if (num <= 0) { flag = true; } else if (num != item.m_stack) { ItemData val = item.Clone(); val.m_stack = num; list.Add(val); flag = true; } else { list.Add(item); } } if (flag) { __result = list; } } } [HarmonyPatch(typeof(DropTable), "GetDropList", new Type[] { typeof(int) })] internal static class DataForgeDropTableGetDropListAmountMultiplierPatch { private static void Postfix(ref List __result) { if (__result == null || __result.Count == 0 || !DataForgePlugin.ItemOverridesEnabled) { return; } List list = new List(); bool flag = false; foreach (GameObject item in __result) { int num = ItemOverrideManager.ApplyAcquisitionAmountMultiplier(item, 1); if (num <= 0) { flag = true; continue; } if (num != 1) { flag = true; } for (int i = 0; i < num; i++) { list.Add(item); } } if (flag) { __result = list; } } } [HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")] internal static class DataForgeCharacterDropGenerateDropListAmountMultiplierPatch { private static void Postfix(ref List> __result) { if (__result == null || __result.Count == 0 || !DataForgePlugin.ItemOverridesEnabled) { return; } List> list = new List>(); bool flag = false; foreach (KeyValuePair item in __result) { int num = ItemOverrideManager.ApplyAcquisitionAmountMultiplier(item.Key, item.Value); if (num <= 0) { flag = true; continue; } if (num != item.Value) { flag = true; } list.Add(new KeyValuePair(item.Key, num)); } if (flag) { __result = list; } } } [HarmonyPatch(typeof(Smelter), "Spawn")] internal static class DataForgeSmelterSpawnAmountMultiplierPatch { private static void Prefix(Smelter __instance, string ore, ref int stack) { ItemConversion itemConversion = __instance.GetItemConversion(ore); if (!((Object)(object)itemConversion?.m_to == (Object)null)) { stack = ItemOverrideManager.ApplyAcquisitionAmountMultiplier(((Component)itemConversion.m_to).gameObject, stack); } } } [HarmonyPatch(typeof(CookingStation), "RPC_RemoveDoneItem")] internal static class DataForgeCookingStationRemoveDoneItemAmountMultiplierPatch { private static readonly MethodInfo? SpawnItemMethod = AccessTools.DeclaredMethod(typeof(CookingStation), "SpawnItem", (Type[])null, (Type[])null); private static readonly MethodInfo SpawnItemWithMultiplierMethod = AccessTools.DeclaredMethod(typeof(DataForgeCookingStationRemoveDoneItemAmountMultiplierPatch), "SpawnItemWithMultiplier", (Type[])null, (Type[])null); private static IEnumerable Transpiler(IEnumerable instructions) { foreach (CodeInstruction instruction in instructions) { if (SpawnItemMethod != null && CodeInstructionExtensions.Calls(instruction, SpawnItemMethod)) { yield return new CodeInstruction(OpCodes.Call, (object)SpawnItemWithMultiplierMethod); } else { yield return instruction; } } } private static void SpawnItemWithMultiplier(CookingStation station, string prefabName, int slot, Vector3 userPoint) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) int num = ItemOverrideManager.ApplyAcquisitionAmountMultiplier(((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(prefabName) : null, 1); for (int i = 0; i < num; i++) { station.SpawnItem(prefabName, slot, userPoint); } } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class DataForgeInventoryGuiDoCraftingAmountMultiplierPatch { private static readonly MethodInfo? AddItemMethod = AccessTools.DeclaredMethod(typeof(Inventory), "AddItem", new Type[8] { typeof(string), typeof(int), typeof(int), typeof(int), typeof(long), typeof(string), typeof(Vector2i), typeof(bool) }, (Type[])null); private static readonly MethodInfo AddItemWithMultiplierMethod = AccessTools.DeclaredMethod(typeof(DataForgeInventoryGuiDoCraftingAmountMultiplierPatch), "AddItemWithMultiplier", (Type[])null, (Type[])null); private static IEnumerable Transpiler(IEnumerable instructions) { foreach (CodeInstruction instruction in instructions) { if (AddItemMethod != null && CodeInstructionExtensions.Calls(instruction, AddItemMethod)) { yield return new CodeInstruction(OpCodes.Call, (object)AddItemWithMultiplierMethod); } else { yield return instruction; } } } private static ItemData AddItemWithMultiplier(Inventory inventory, string name, int stack, int quality, int variant, long crafterID, string crafterName, Vector2i position, bool pickedUp) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) stack = ItemOverrideManager.ApplyAcquisitionAmountMultiplier(((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(name) : null, stack); return inventory.AddItem(name, stack, quality, variant, crafterID, crafterName, position, pickedUp); } } internal static class ItemVisualOverrides { private sealed class RendererMaterialSnapshot { public Renderer Renderer { get; } public Material[] Materials { get; } public RendererMaterialSnapshot(Renderer renderer, Material[] materials) { Renderer = renderer; Materials = materials; } } private sealed class IconCacheEntry { public DateTime LastWriteTimeUtc { get; } public Sprite Sprite { get; } public IconCacheEntry(DateTime lastWriteTimeUtc, Sprite sprite) { LastWriteTimeUtc = lastWriteTimeUtc; Sprite = sprite; } } internal const string AutoIconValue = "auto"; internal const string DefaultAutoIconRotationValue = "23, 51, 25.8"; private const string AutoIconRenderRevision = "jotunn-prefab-main-renderers-3"; private const int AutoIconSize = 128; private const int AutoIconLayer = 30; private static readonly Vector3 DefaultAutoIconRotation = new Vector3(23f, 51f, 25.8f); private static readonly Dictionary> OriginalMaterials = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary OriginalIcons = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary> CreatedMaterials = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary IconCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary MaterialLookupCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet MaterialLookupMissCache = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly MethodInfo? LoadImageMethod = ResolveLoadImageMethod(); private static readonly MethodInfo? EncodeToPngMethod = typeof(ImageConversion).GetMethod("EncodeToPNG", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Texture2D) }, null); private static bool MaterialLookupCacheBuilt; private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); internal static string IconDirectory => Path.Combine(ConfigDirectory, "icon"); private static string AutoIconCacheDirectory => Path.Combine(IconDirectory, "cache"); internal static void EnsureIconDirectory() { Directory.CreateDirectory(IconDirectory); } internal static bool IsIconFile(string path) { if (!Path.GetExtension(path).Equals(".png", StringComparison.OrdinalIgnoreCase)) { return false; } string fullPath = Path.GetFullPath(path); string fullPath2 = Path.GetFullPath(IconDirectory); char directorySeparatorChar = Path.DirectorySeparatorChar; return fullPath.StartsWith(fullPath2 + directorySeparatorChar, StringComparison.OrdinalIgnoreCase); } internal static void Apply(ItemDrop itemDrop, ItemOverrideManager.VisualDefinition? visual) { //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) if (visual == null) { return; } string prefabName = GetPrefabName(((Component)itemDrop).gameObject); bool flag = IsAutoIconValue(visual.Icon); bool num = !flag && !string.IsNullOrWhiteSpace(visual.Icon); bool flag2 = !string.IsNullOrWhiteSpace(visual.Material); Color color; bool flag3 = TryParseColor(visual.Color, out color); bool hasValue = visual.Emission.HasValue; if (num) { ApplyVisualIcon(prefabName, itemDrop, visual.Icon); } if (!flag2 && !flag3 && !hasValue) { return; } List itemVisualRenderers = GetItemVisualRenderers(((Component)itemDrop).gameObject); if (itemVisualRenderers.Count == 0) { DataForgeLogContext.Warning(prefabName + " has no item renderers for visual override."); return; } Material val = null; if (flag2) { string text = visual.Material.Trim(); val = ResolveMaterial(text); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(prefabName + " has unknown visual material '" + text + "'. Check z_materials.reference.txt."); return; } } StoreOriginalMaterials(prefabName, itemVisualRenderers); foreach (Renderer item in itemVisualRenderers) { Material[] sharedMaterials = item.sharedMaterials; Material[] array = sharedMaterials.ToArray(); for (int i = 0; i < sharedMaterials.Length; i++) { Material val2 = (((Object)(object)val != (Object)null) ? val : sharedMaterials[i]); if (val2 == null) { continue; } Material val3 = val2; if (flag3 || hasValue) { Material val4 = new Material(val3) { name = NormalizeMaterialName(((Object)val3).name) + "_DataForge_" + prefabName }; if (flag3) { ApplyMaterialColor(val4, color); } if (hasValue) { ApplyMaterialEmission(val4, Math.Max(0f, visual.Emission.Value), flag3 ? new Color?(color) : ((Color?)null)); } TrackCreatedMaterial(prefabName, val4); array[i] = val4; } else { array[i] = val3; } } item.sharedMaterials = array; } if (flag) { ApplyAutoVisualIcon(prefabName, itemDrop, visual); } } internal static void Restore(string prefabName, ItemDrop itemDrop) { if (OriginalMaterials.TryGetValue(prefabName, out List value)) { foreach (RendererMaterialSnapshot item in value) { if ((Object)(object)item.Renderer != (Object)null) { item.Renderer.sharedMaterials = item.Materials.ToArray(); } } } if (CreatedMaterials.TryGetValue(prefabName, out List value2)) { foreach (Material item2 in value2) { if ((Object)(object)item2 != (Object)null) { Object.Destroy((Object)(object)item2); } } value2.Clear(); CreatedMaterials.Remove(prefabName); } if (OriginalIcons.TryGetValue(prefabName, out Sprite[] value3)) { itemDrop.m_itemData.m_shared.m_icons = value3?.ToArray(); } } private static void ApplyVisualIcon(string prefabName, ItemDrop itemDrop, string? iconName) { if (!string.IsNullOrWhiteSpace(iconName)) { string text = iconName.Trim(); Sprite val = ResolveIconSprite(text); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(prefabName + " has unknown visual icon '" + text + "'. Expected a png under DataForge/icon."); return; } StoreOriginalIcons(prefabName, itemDrop.m_itemData.m_shared); itemDrop.m_itemData.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { val }; } } private static void ApplyAutoVisualIcon(string prefabName, ItemDrop itemDrop, ItemOverrideManager.VisualDefinition visual) { //IL_000f: 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_0018: Unknown result type (might be due to invalid IL or missing references) if (!IsHeadlessGraphics()) { Vector3 iconRotation = ParseIconRotation(visual.IconRotation, prefabName); Sprite val = ResolveAutoIconSprite(prefabName, itemDrop, visual, iconRotation); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(prefabName + " could not generate visual.icon auto. Keeping the current icon."); return; } StoreOriginalIcons(prefabName, itemDrop.m_itemData.m_shared); itemDrop.m_itemData.m_shared.m_icons = (Sprite[])(object)new Sprite[1] { val }; } } private static Sprite? ResolveAutoIconSprite(string prefabName, ItemDrop itemDrop, ItemOverrideManager.VisualDefinition visual, Vector3 iconRotation) { //IL_0002: 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) string autoIconCachePath = GetAutoIconCachePath(prefabName, visual, iconRotation); if (File.Exists(autoIconCachePath)) { return LoadSpriteFromPath(autoIconCachePath, prefabName + " auto icon"); } Sprite val = RenderAutoIconSprite(prefabName, itemDrop, iconRotation); if ((Object)(object)val == (Object)null) { return null; } TryWriteAutoIconCache(autoIconCachePath, val); return val; } private static Sprite? RenderAutoIconSprite(string prefabName, ItemDrop itemDrop, Vector3 iconRotation) { //IL_0016: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: 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_013a: Unknown result type (might be due to invalid IL or missing references) //IL_014b: 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) //IL_0191: 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_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: 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_0212: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Expected O, but got Unknown //IL_0283: 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_02a1: Unknown result type (might be due to invalid IL or missing references) GameObject val = null; Camera val2 = null; Light val3 = null; RenderTexture val4 = null; RenderTexture active = RenderTexture.active; try { val = SpawnAutoIconRenderClone(prefabName, ((Component)itemDrop).gameObject, iconRotation, out List renderers); if ((Object)(object)val == (Object)null || renderers.Count == 0) { return null; } Bounds bounds = renderers[0].bounds; foreach (Renderer item in renderers.Skip(1)) { ((Bounds)(ref bounds)).Encapsulate(item.bounds); } Vector3 size = ((Bounds)(ref bounds)).size; Transform transform = val.transform; transform.position -= ((Bounds)(ref bounds)).center; val2 = new GameObject("DataForge Auto Icon Camera", new Type[1] { typeof(Camera) }).GetComponent(); val2.backgroundColor = Color.clear; val2.clearFlags = (CameraClearFlags)2; val2.fieldOfView = 0.5f; val2.farClipPlane = 10000000f; val2.cullingMask = 1073741824; ((Component)val2).transform.rotation = Quaternion.Euler(0f, 180f, 0f); val3 = new GameObject("DataForge Auto Icon Light", new Type[1] { typeof(Light) }).GetComponent(); ((Component)val3).transform.position = Vector3.zero; ((Component)val3).transform.rotation = Quaternion.Euler(5f, 180f, 5f); val3.type = (LightType)1; val3.cullingMask = 1073741824; val3.intensity = 1.3f; float num = (Mathf.Max(size.x, size.y) + 0.1f) / Mathf.Tan(val2.fieldOfView * ((float)Math.PI / 180f)); if (float.IsNaN(num) || float.IsInfinity(num) || num <= 0f) { num = Mathf.Max(size.x, Mathf.Max(size.y, size.z)) + 1f; } ((Component)val2).transform.position = new Vector3(0f, 0f, num); val4 = (RenderTexture.active = (val2.targetTexture = RenderTexture.GetTemporary(128, 128, 24, (RenderTextureFormat)0))); val2.Render(); Texture2D val6 = new Texture2D(128, 128, (TextureFormat)4, false) { name = prefabName + "_DataForgeAutoIcon" }; Rect val7 = default(Rect); ((Rect)(ref val7))..ctor(0f, 0f, 128f, 128f); val6.ReadPixels(val7, 0, 0); val6.Apply(); Sprite obj = Sprite.Create(val6, val7, new Vector2(0.5f, 0.5f), 100f); ((Object)obj).name = ((Object)val6).name; return obj; } catch (Exception ex) { DataForgeLogContext.Warning(prefabName + " visual.icon auto failed: " + ex.Message); return null; } finally { RenderTexture.active = active; if ((Object)(object)val2 != (Object)null) { val2.targetTexture = null; Object.Destroy((Object)(object)((Component)val2).gameObject); } if ((Object)(object)val3 != (Object)null) { Object.Destroy((Object)(object)((Component)val3).gameObject); } if ((Object)(object)val4 != (Object)null) { RenderTexture.ReleaseTemporary(val4); } if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } } private static GameObject? SpawnAutoIconRenderClone(string prefabName, GameObject sourcePrefab, Vector3 iconRotation, out List renderers) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0061: 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) //IL_0072: Unknown result type (might be due to invalid IL or missing references) renderers = new List(); GameObject val = null; try { val = new GameObject(prefabName + "_DataForgeAutoIconRoot"); val.SetActive(false); GameObject val2 = Object.Instantiate(sourcePrefab, val.transform, false); ((Object)val2).name = prefabName + "_DataForgeAutoIcon"; StripAutoIconRenderClone(val2); val2.transform.SetParent((Transform)null, false); Object.DestroyImmediate((Object)(object)val); val = null; val2.transform.position = Vector3.zero; val2.transform.rotation = Quaternion.Euler(iconRotation); SetLayerRecursive(val2, 30); val2.SetActive(true); renderers = SelectAutoIconRenderers(val2); if (renderers.Count == 0) { Object.Destroy((Object)(object)val2); return null; } HashSet hashSet = new HashSet(renderers); Renderer[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (Renderer val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && !hashSet.Contains(val3)) { val3.enabled = false; } } return val2; } catch { if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } throw; } } private static void StripAutoIconRenderClone(GameObject renderObject) { Transform[] componentsInChildren = renderObject.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Component[] components = ((Component)componentsInChildren[i]).GetComponents(); for (int num = components.Length - 1; num >= 0; num--) { Component val = components[num]; if (!((Object)(object)val == (Object)null) && !IsAutoIconRenderComponent(val)) { try { Object.DestroyImmediate((Object)(object)val); } catch (Exception ex) { DataForgePlugin.Log.LogDebug((object)("Could not strip auto icon component '" + ((object)val).GetType().Name + "' from '" + ((Object)renderObject).name + "': " + ex.Message)); } } } } } private static bool IsAutoIconRenderComponent(Component component) { if (!(component is Transform) && !(component is MeshFilter) && !(component is MeshRenderer)) { return component is SkinnedMeshRenderer; } return true; } private static List SelectAutoIconRenderers(GameObject renderObject) { List list = renderObject.GetComponentsInChildren(false).Where(IsRenderableAutoIconRenderer).ToList(); if (list.Count <= 1) { return list; } List list2 = list.Where((Renderer renderer) => !LooksLikeEffectRenderer(renderer)).ToList(); if (list2.Count <= 0) { return list; } return list2; } private static bool LooksLikeEffectRenderer(Renderer renderer) { string text = GetTransformPath(((Component)renderer).transform).ToLowerInvariant(); if (!text.Contains("flare") && !text.Contains("glow") && !text.Contains("vfx") && !text.Contains("fx_") && !text.Contains("_fx") && !text.Contains("particle") && !text.Contains("spark") && !text.Contains("light") && !text.Contains("smoke") && !text.Contains("mist") && !text.Contains("aura")) { return text.Contains("beam"); } return true; } private static string GetTransformPath(Transform transform) { List list = new List(); Transform val = transform; while ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } private static Sprite? ResolveIconSprite(string iconName) { string text = ResolveIconPath(iconName); if (text == null || !File.Exists(text)) { return null; } return LoadSpriteFromPath(text, iconName); } private static Sprite? LoadSpriteFromPath(string iconPath, string iconName) { //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_0042: Expected O, but got Unknown //IL_0077: 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) DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(iconPath); if (IconCache.TryGetValue(iconPath, out IconCacheEntry value) && value.LastWriteTimeUtc == lastWriteTimeUtc) { return value.Sprite; } try { Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { name = Path.GetFileNameWithoutExtension(iconPath) }; if (!TryLoadImage(val, File.ReadAllBytes(iconPath))) { Object.Destroy((Object)(object)val); return null; } Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)val2).name = Path.GetFileNameWithoutExtension(iconPath); IconCache[iconPath] = new IconCacheEntry(lastWriteTimeUtc, val2); return val2; } catch (Exception ex) { DataForgeLogContext.Warning("Could not load visual icon '" + iconName + "' from '" + iconPath + "': " + ex.Message); return null; } } private static void TryWriteAutoIconCache(string cachePath, Sprite icon) { try { if (!((Object)(object)icon.texture == (Object)null) && TryEncodePng(icon.texture, out byte[] png)) { Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); File.WriteAllBytes(cachePath, png); } } catch (Exception ex) { DataForgePlugin.Log.LogDebug((object)("Could not write auto icon cache '" + cachePath + "': " + ex.Message)); } } private static bool TryLoadImage(Texture2D texture, byte[] data) { if (LoadImageMethod == null) { DataForgeLogContext.Warning("Could not locate UnityEngine.ImageConversion.LoadImage for visual.icon."); return false; } object[] parameters = ((LoadImageMethod.GetParameters().Length != 3) ? new object[2] { texture, data } : new object[3] { texture, data, false }); object obj = LoadImageMethod.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; } private static bool TryEncodePng(Texture2D texture, out byte[] png) { png = Array.Empty(); if (EncodeToPngMethod == null) { return false; } if (EncodeToPngMethod.Invoke(null, new object[1] { texture }) is byte[] array && array.Length != 0) { png = array; return true; } return false; } private static MethodInfo? ResolveLoadImageMethod() { return typeof(ImageConversion).GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }, null) ?? typeof(ImageConversion).GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(Texture2D), typeof(byte[]) }, null); } private static string? ResolveIconPath(string iconName) { if (string.IsNullOrWhiteSpace(iconName)) { return null; } string text = iconName.Trim().Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); if (!Path.HasExtension(text)) { text += ".png"; } string fullPath = Path.GetFullPath(Path.Combine(IconDirectory, text)); string fullPath2 = Path.GetFullPath(IconDirectory); char directorySeparatorChar = Path.DirectorySeparatorChar; if (!fullPath.StartsWith(fullPath2 + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { return null; } return fullPath; } private static string GetAutoIconCachePath(string prefabName, ItemOverrideManager.VisualDefinition visual, Vector3 iconRotation) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) string text = StringExtensionMethods.GetStableHashCode(string.Join("|", "1.0.3", "jotunn-prefab-main-renderers-3", prefabName, visual.Material?.Trim() ?? "", visual.Color?.Trim() ?? "", visual.Emission?.ToString(CultureInfo.InvariantCulture) ?? "", FormatVector3(iconRotation), 128.ToString(CultureInfo.InvariantCulture))).ToString("x8", CultureInfo.InvariantCulture); return Path.Combine(AutoIconCacheDirectory, SanitizeFileName(prefabName) + "-" + text + ".png"); } private static string FormatVector3(Vector3 value) { return string.Join(",", value.x.ToString("R", CultureInfo.InvariantCulture), value.y.ToString("R", CultureInfo.InvariantCulture), value.z.ToString("R", CultureInfo.InvariantCulture)); } private static string SanitizeFileName(string value) { char[] invalid = Path.GetInvalidFileNameChars(); return new string(value.Select((char character) => (!invalid.Contains(character)) ? character : '_').ToArray()); } private static bool IsAutoIconValue(string? iconName) { string text = iconName?.Trim(); if (text == null || text.Length == 0) { return true; } return text.Equals("auto", StringComparison.OrdinalIgnoreCase); } private static Vector3 ParseIconRotation(string? value, string prefabName) { //IL_0008: 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_005b: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(value)) { return DefaultAutoIconRotation; } string[] array = DataForgeValue.SplitTuple(value); if (array.Length < 3 || !TryParseIconRotationPart(array, 0, out var parsed) || !TryParseIconRotationPart(array, 1, out var parsed2) || !TryParseIconRotationPart(array, 2, out var parsed3)) { DataForgeLogContext.Warning(prefabName + " has invalid visual.iconRotation '" + value + "'. Expected 'x, y, z'; using 23, 51, 25.8."); return DefaultAutoIconRotation; } return new Vector3(parsed, parsed2, parsed3); } private static bool TryParseIconRotationPart(string[] parts, int index, out float parsed) { parsed = 0f; if (parts.Length > index && parts[index].Length > 0) { return float.TryParse(parts[index], NumberStyles.Float, CultureInfo.InvariantCulture, out parsed); } return false; } private static bool IsHeadlessGraphics() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 return (int)SystemInfo.graphicsDeviceType == 4; } private static void SetLayerRecursive(GameObject root, int layer) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) root.layer = layer; foreach (Transform item in root.transform) { SetLayerRecursive(((Component)item).gameObject, layer); } } private static bool TryParseColor(string? value, out Color color) { //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_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) color = Color.white; if (string.IsNullOrWhiteSpace(value)) { return false; } string[] array = DataForgeValue.SplitTuple(value); if (array.Length < 3) { DataForgeLogContext.Warning("Could not parse visual.color value '" + value + "'. Expected 'r, g, b, a'."); return false; } if (!TryParseColorPart(array, 0, value, out var parsed) || !TryParseColorPart(array, 1, value, out var parsed2) || !TryParseColorPart(array, 2, value, out var parsed3)) { return false; } float result = 1f; if (array.Length > 3 && array[3].Length > 0 && !float.TryParse(array[3], NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { DataForgeLogContext.Warning("Could not parse visual.color value '" + value + "'. Expected numeric alpha."); return false; } color = new Color(Mathf.Clamp01(parsed), Mathf.Clamp01(parsed2), Mathf.Clamp01(parsed3), Mathf.Clamp01(result)); return true; } private static bool TryParseColorPart(string[] parts, int index, string originalValue, out float parsed) { parsed = 0f; if (parts.Length <= index || parts[index].Length == 0) { DataForgeLogContext.Warning("Could not parse visual.color value '" + originalValue + "'. Expected 'r, g, b, a'."); return false; } if (float.TryParse(parts[index], NumberStyles.Float, CultureInfo.InvariantCulture, out parsed)) { return true; } DataForgeLogContext.Warning("Could not parse visual.color value '" + originalValue + "'. Expected numeric RGBA values."); return false; } private static void ApplyMaterialColor(Material material, Color color) { //IL_0013: 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) if (material.HasProperty("_Color")) { material.SetColor("_Color", color); } if (material.HasProperty("_BaseColor")) { material.SetColor("_BaseColor", color); } } private static void ApplyMaterialEmission(Material material, float intensity, Color? tint) { //IL_0014: 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_0019: 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_0024: 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_0051: Unknown result type (might be due to invalid IL or missing references) Color val = (Color)(((??)tint) ?? Color.white); Color val2 = default(Color); ((Color)(ref val2))..ctor(val.r * intensity, val.g * intensity, val.b * intensity, 1f); if (material.HasProperty("_EmissionColor")) { material.SetColor("_EmissionColor", val2); } if (intensity > 0f) { material.EnableKeyword("_EMISSION"); } else { material.DisableKeyword("_EMISSION"); } } private static Material? ResolveMaterial(string materialName) { string text = NormalizeMaterialName(materialName); if (text.Length == 0) { return null; } if (MaterialLookupMissCache.Contains(text)) { return null; } EnsureMaterialLookupCache(force: false); if (TryResolveCachedMaterial(materialName, text, out Material material)) { return material; } EnsureMaterialLookupCache(force: true); if (TryResolveCachedMaterial(materialName, text, out material)) { return material; } MaterialLookupMissCache.Add(text); return null; } private static bool TryResolveCachedMaterial(string materialName, string normalizedName, out Material? material) { if (MaterialLookupCache.TryGetValue(materialName, out Material value) && (Object)(object)value != (Object)null) { material = value; return true; } if (MaterialLookupCache.TryGetValue(normalizedName, out Material value2) && (Object)(object)value2 != (Object)null) { material = value2; return true; } material = null; return false; } private static void EnsureMaterialLookupCache(bool force) { if (MaterialLookupCacheBuilt && !force) { return; } MaterialLookupCache.Clear(); MaterialLookupMissCache.Clear(); Material[] array = Resources.FindObjectsOfTypeAll(); foreach (Material val in array) { if (!((Object)(object)val == (Object)null) && !string.IsNullOrWhiteSpace(((Object)val).name)) { if (!MaterialLookupCache.ContainsKey(((Object)val).name)) { MaterialLookupCache[((Object)val).name] = val; } string text = NormalizeMaterialName(((Object)val).name); if (text.Length > 0 && !MaterialLookupCache.ContainsKey(text)) { MaterialLookupCache[text] = val; } } } MaterialLookupCacheBuilt = true; } private static string NormalizeMaterialName(string name) { return (name ?? "").Replace("(Instance)", "").Replace("(Clone)", "").Trim(); } private static List GetItemVisualRenderers(GameObject itemPrefab) { List list = new List(); HashSet seen = new HashSet(); AddRenderers(itemPrefab.transform.Find("attach_skin"), list, seen); AddRenderers(itemPrefab.transform.Find("attach"), list, seen); AddRenderers(GetDropChild(itemPrefab), list, seen); if (list.Count == 0) { AddRenderers(itemPrefab.transform, list, seen); } return list; } private static void AddRenderers(Transform? root, List renderers, HashSet seen) { if ((Object)(object)root == (Object)null) { return; } Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (IsPotentialAutoIconRenderer(val) && seen.Add(val)) { renderers.Add(val); } } } private static bool IsPotentialAutoIconRenderer(Renderer? renderer) { if ((Object)(object)renderer != (Object)null && !(renderer is ParticleSystemRenderer)) { return renderer.enabled; } return false; } private static bool IsRenderableAutoIconRenderer(Renderer? renderer) { if (IsPotentialAutoIconRenderer(renderer)) { return ((Component)renderer).gameObject.activeInHierarchy; } return false; } private static Transform? GetDropChild(GameObject itemPrefab) { for (int i = 0; i < itemPrefab.transform.childCount; i++) { Transform child = itemPrefab.transform.GetChild(i); if (!((Object)child).name.Contains("attach")) { return child; } } return null; } private static void StoreOriginalMaterials(string prefabName, List renderers) { if (!OriginalMaterials.ContainsKey(prefabName)) { OriginalMaterials[prefabName] = renderers.Select((Renderer renderer) => new RendererMaterialSnapshot(renderer, renderer.sharedMaterials.ToArray())).ToList(); } } private static void StoreOriginalIcons(string prefabName, SharedData shared) { if (!OriginalIcons.ContainsKey(prefabName)) { OriginalIcons[prefabName] = shared.m_icons?.ToArray(); } } private static void TrackCreatedMaterial(string prefabName, Material material) { if (!CreatedMaterials.TryGetValue(prefabName, out List value)) { value = new List(); CreatedMaterials[prefabName] = value; } value.Add(material); } private static string GetPrefabName(GameObject gameObject) { return ((Object)gameObject).name.Replace("(Clone)", "").Trim(); } } internal static class MaterialReferenceWriter { private const string ReferenceFileName = "z_materials.reference.txt"; private static bool Written; private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); internal static void WriteReferenceIfReady() { if (Written || !DataForgePlugin.UsesLocalAuthorityFiles) { return; } List list = (from material in Resources.FindObjectsOfTypeAll() where (Object)(object)material != (Object)null select NormalizeMaterialName(((Object)material).name) into name where name.Length > 0 select name).Distinct(StringComparer.OrdinalIgnoreCase).OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count == 0) { return; } Directory.CreateDirectory(ConfigDirectory); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# DataForge material lookup."); stringBuilder.AppendLine("# Use these names with visual.material."); stringBuilder.AppendLine("# One material name per line."); stringBuilder.AppendLine(); foreach (string item in list) { stringBuilder.AppendLine(item); } GeneratedArtifactWriter.WriteTextIfChanged(Path.Combine(ConfigDirectory, "z_materials.reference.txt"), stringBuilder.ToString()); Written = true; } private static string NormalizeMaterialName(string name) { return (name ?? "").Replace("(Instance)", "").Replace("(Clone)", "").Trim(); } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] public static class RegisterAndCheckVersion { private static void Prefix(ZNetPeer peer, ref ZNet __instance) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown DataForgePlugin.Log.LogDebug((object)"Registering version RPC handler"); peer.m_rpc.Register("DataForge_VersionCheck", (Action)RpcHandlers.RPC_DataForge_Version); DataForgePlugin.Log.LogInfo((object)"Invoking version check"); ZPackage val = new ZPackage(); val.Write("1.0.3"); peer.m_rpc.Invoke("DataForge_VersionCheck", new object[1] { val }); } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] public static class VerifyClient { private static bool Prefix(ZRpc rpc, ZPackage pkg, ref ZNet __instance) { if (!__instance.IsServer() || RpcHandlers.ValidatedPeers.Contains(rpc)) { return true; } DataForgePlugin.Log.LogWarning((object)("Peer (" + rpc.m_socket.GetHostName() + ") never sent version or couldn't due to previous disconnect, disconnecting")); rpc.Invoke("Error", new object[1] { 3 }); return false; } private static void Postfix(ZNet __instance) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DataForgeRequestAdminSync", new object[1] { (object)new ZPackage() }); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] public class ShowConnectionError { private static void Postfix(FejdStartup __instance) { if (__instance.m_connectionFailedPanel.activeSelf) { __instance.m_connectionFailedError.fontSizeMax = 25f; __instance.m_connectionFailedError.fontSizeMin = 15f; TMP_Text connectionFailedError = __instance.m_connectionFailedError; connectionFailedError.text = connectionFailedError.text + "\n" + DataForgePlugin.ConnectionError; } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] public static class RemoveDisconnectedPeerFromVerified { private static void Prefix(ZNetPeer peer, ref ZNet __instance) { if (__instance.IsServer()) { DataForgePlugin.Log.LogInfo((object)("Peer (" + peer.m_rpc.m_socket.GetHostName() + ") disconnected, removing from validated list")); RpcHandlers.ValidatedPeers.Remove(peer.m_rpc); } } } public static class RpcHandlers { public static readonly List ValidatedPeers = new List(); public static void RPC_DataForge_Version(ZRpc rpc, ZPackage pkg) { string text = pkg.ReadString(); DataForgePlugin.Log.LogInfo((object)("Version check, local: 1.0.3, remote: " + text)); if (text != "1.0.3") { DataForgePlugin.ConnectionError = "DataForge Installed: 1.0.3\n Needed: " + text; if (ZNet.instance.IsServer()) { DataForgePlugin.Log.LogWarning((object)("Peer (" + rpc.m_socket.GetHostName() + ") has incompatible version, disconnecting...")); rpc.Invoke("Error", new object[1] { 3 }); } } else if (!ZNet.instance.IsServer()) { DataForgePlugin.Log.LogInfo((object)"Received same version from server!"); } else { DataForgePlugin.Log.LogInfo((object)("Adding peer (" + rpc.m_socket.GetHostName() + ") to validated list")); ValidatedPeers.Add(rpc); } } } internal static class ItemOverrideManager { private sealed class ItemOutputProfile { public bool EmitDurability { get; private set; } public bool EmitEquipment { get; private set; } public bool EmitFood { get; private set; } public bool EmitShield { get; private set; } public bool EmitCombat { get; private set; } public bool EmitEquipmentArmor { get; private set; } public bool EmitEquipmentSkillType { get; private set; } public bool EmitDamageTakenModifiers { get; private set; } public bool EmitDirectCombatStats { get; private set; } public bool EmitPrimaryAttack { get; private set; } public bool EmitSecondaryAttack { get; private set; } public bool EmitEffects { get; private set; } internal static ItemOutputProfile From(ItemDefinition definition) { bool flag = IsArmorLike(definition); bool flag2 = IsShieldLike(definition); bool flag3 = IsToolLike(definition); bool flag4 = IsWeaponLike(definition); bool flag5 = IsAmmoLike(definition); bool flag6 = IsMagicLike(definition); IsItemType(definition, (ItemType)19); bool flag7 = IsItemType(definition, (ItemType)2); bool flag8 = IsItemType(definition, (ItemType)6); bool flag9 = IsItemType(definition, (ItemType)13); bool flag10 = flag7 || flag8 || flag9; bool flag11 = flag9; bool flag12 = GetTotalDamage(definition.Combat?.Damage) > 0f; bool flag13 = flag7; bool num = IsPrimaryAttackSurface(definition); bool flag14 = IsShieldBlockSurface(definition); bool emitEquipmentArmor = IsEquipmentArmorSurface(definition); bool flag15 = IsEquipmentSkillTypeSurface(definition); bool flag16 = HasAttackSpecial(definition.Combat?.PrimaryAttack); bool flag17 = num || flag16; bool flag18 = num && HasAttackAnimation(definition.Combat?.SecondaryAttack); bool flag19 = flag4 || flag2 || flag6 || flag12 || (flag5 && flag12) || flag17 || flag18; bool flag20 = flag || flag4 || flag2 || flag3 || flag6; bool flag21 = definition.Combat?.IsBombLike ?? false; bool emitDamageTakenModifiers = flag || HasDamageTakenModifiers(definition.DamageTakenModifiers); return new ItemOutputProfile { EmitDurability = (!flag7 && (flag20 || HasDurabilityEnabled(definition.Durability))), EmitEquipment = (!flag11 && flag20), EmitFood = flag13, EmitShield = (!flag10 && flag14), EmitCombat = (!flag10 && !flag2 && flag19), EmitEquipmentArmor = emitEquipmentArmor, EmitEquipmentSkillType = (!flag10 && flag15), EmitDamageTakenModifiers = emitDamageTakenModifiers, EmitDirectCombatStats = !flag21, EmitPrimaryAttack = (!flag10 && flag17), EmitSecondaryAttack = (!flag10 && flag18), EmitEffects = (flag20 || flag19 || flag13 || HasAnyEffect(definition.Effects)) }; } } internal sealed class ItemEntry { internal string LogContext { get; private set; } = ""; public string Item { get; set; } = ""; public float? AmountMultiplier { get; set; } public bool Override { get; set; } = true; public string? CloneFrom { get; set; } public string? Name { get; set; } public string? Description { get; set; } public string? Subtitle { get; set; } public string? ItemType { get; set; } public string? Weight { get; set; } public int? Value { get; set; } public int? MaxStackSize { get; set; } public int? MaxQuality { get; set; } public bool? Teleportable { get; set; } public bool? Floating { get; set; } public string? Durability { get; set; } public EquipmentDefinition? Equipment { get; set; } public DamageTakenModifierDefinition? DamageTakenModifiers { get; set; } public string? Food { get; set; } public ShieldDefinition? Shield { get; set; } public DamageDefinition? Damage { get; set; } public PrimaryAttackDefinition? PrimaryAttack { get; set; } public AttackDefinition? SecondaryAttack { get; set; } public EffectsDefinition? Effects { get; set; } public VisualDefinition? Visual { get; set; } internal bool HasPrefabDefinition { get { if (!HasBasicsDefinition && Durability == null && Equipment == null && DamageTakenModifiers == null && Food == null && Shield == null && Damage == null && PrimaryAttack == null && SecondaryAttack == null && Effects == null) { return Visual != null; } return true; } } internal bool HasLiveSafeDefinition { get { if (!HasBasicsDefinition && Durability == null && Equipment == null && DamageTakenModifiers == null && Food == null && Shield == null && Damage == null && PrimaryAttack == null && SecondaryAttack == null) { return Effects != null; } return true; } } private bool HasBasicsDefinition { get { if (Name == null && Description == null && Subtitle == null && ItemType == null && Weight == null && !Value.HasValue && !MaxStackSize.HasValue && !MaxQuality.HasValue && !Teleportable.HasValue) { return Floating.HasValue; } return true; } } internal void SetLogContext(string value) { LogContext = value; } internal ItemDefinition ToDefinition() { return new ItemDefinition { Basics = ToBasicsDefinition(), Durability = Durability, Equipment = Equipment, DamageTakenModifiers = DamageTakenModifiers, Food = Food, Shield = Shield, Combat = ToCombatDefinition(), Effects = Effects, Visual = Visual }; } private CombatDefinition? ToCombatDefinition() { if (Damage == null && PrimaryAttack == null && SecondaryAttack == null) { return null; } return new CombatDefinition { Damage = Damage, BackstabBonus = Damage?.BackstabBonus, AttackForce = Damage?.AttackForce, PrimaryAttack = PrimaryAttack, SecondaryAttack = SecondaryAttack }; } private BasicsDefinition? ToBasicsDefinition() { if (!HasBasicsDefinition) { return null; } return new BasicsDefinition { Name = Name, Description = Description, Subtitle = Subtitle, ItemType = ItemType, Weight = Weight, Value = Value, MaxStackSize = MaxStackSize, MaxQuality = MaxQuality, Teleportable = Teleportable, Floating = Floating }; } } internal sealed class ItemReferenceEntry { public string Item { get; set; } = ""; public string? ItemType { get; set; } public string? Weight { get; set; } public int? Value { get; set; } public int? MaxStackSize { get; set; } public int? MaxQuality { get; set; } public bool? Teleportable { get; set; } public string? Durability { get; set; } public EquipmentDefinition? Equipment { get; set; } public DamageTakenModifierDefinition? DamageTakenModifiers { get; set; } public string? Food { get; set; } public ShieldDefinition? Shield { get; set; } public DamageDefinition? Damage { get; set; } public PrimaryAttackDefinition? PrimaryAttack { get; set; } public AttackDefinition? SecondaryAttack { get; set; } public EffectsDefinition? Effects { get; set; } internal static ItemReferenceEntry From(string prefab, ItemDefinition definition) { ItemOutputProfile itemOutputProfile = ItemOutputProfile.From(definition); BasicsDefinition basicsDefinition = ToReferenceItem(definition.Basics); ItemReferenceEntry itemReferenceEntry = new ItemReferenceEntry(); itemReferenceEntry.Item = prefab; itemReferenceEntry.Weight = basicsDefinition?.Weight; itemReferenceEntry.Value = basicsDefinition?.Value; itemReferenceEntry.MaxStackSize = basicsDefinition?.MaxStackSize; itemReferenceEntry.Teleportable = basicsDefinition?.Teleportable; itemReferenceEntry.Durability = ((itemOutputProfile.EmitDurability && HasDurabilityEnabled(definition.Durability)) ? FormatReferenceDurability(definition.Durability) : null); itemReferenceEntry.Equipment = (itemOutputProfile.EmitEquipment ? ToReferenceEquipment(definition.Equipment, itemOutputProfile) : null); itemReferenceEntry.DamageTakenModifiers = (itemOutputProfile.EmitDamageTakenModifiers ? definition.DamageTakenModifiers : null); itemReferenceEntry.Food = (itemOutputProfile.EmitFood ? definition.Food : null); itemReferenceEntry.Shield = ((itemOutputProfile.EmitShield && IsItemType(definition, (ItemType)5)) ? definition.Shield : null); itemReferenceEntry.Damage = ((itemOutputProfile.EmitCombat && itemOutputProfile.EmitDirectCombatStats) ? ToReferenceDamage(definition.Combat) : null); itemReferenceEntry.PrimaryAttack = (itemOutputProfile.EmitPrimaryAttack ? ToReferencePrimaryAttack(definition.Combat?.PrimaryAttack) : null); itemReferenceEntry.SecondaryAttack = (itemOutputProfile.EmitSecondaryAttack ? ToReferenceAttack(definition.Combat?.SecondaryAttack) : null); itemReferenceEntry.Effects = (itemOutputProfile.EmitEffects ? definition.Effects : null); return ReferenceValue.ClonePruned(itemReferenceEntry) ?? new ItemReferenceEntry { Item = prefab }; } private static DamageDefinition? ToReferenceDamage(CombatDefinition? combat) { if (combat == null) { return null; } return CreateDamageOutputDefinition(combat); } private static BasicsDefinition? ToReferenceItem(BasicsDefinition? basics) { if (basics == null) { return null; } return new BasicsDefinition { Weight = (IsReferenceDefaultWeight(basics.Weight) ? null : basics.Weight), Value = basics.Value, MaxStackSize = basics.MaxStackSize, Teleportable = basics.Teleportable }; } private static bool IsReferenceDefaultWeight(string? value) { if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return Math.Abs(result - 1f) <= 0.0001f; } return false; } private static string? FormatReferenceDurability(string? value) { if (string.IsNullOrWhiteSpace(value)) { return value; } string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length != 7 || !IsBoolPart(array[2], expected: true) || !IsFloatPart(array[3], 1f) || !IsBoolPart(array[4], expected: true) || !IsBoolPart(array[5], expected: false) || !IsFloatPart(array[6], 0f)) { return value; } return string.Join(", ", array.Take(2)); } private static EquipmentDefinition? ToReferenceEquipment(EquipmentDefinition? equipment, ItemOutputProfile output) { if (equipment == null) { return null; } return new EquipmentDefinition { SkillType = (output.EmitEquipmentSkillType ? equipment.SkillType : null), MovementModifier = equipment.MovementModifier, EitrRegenModifier = equipment.EitrRegenModifier, HeatResistanceModifier = equipment.HeatResistanceModifier, HomeItemsStaminaModifier = equipment.HomeItemsStaminaModifier, AttackStaminaModifier = equipment.AttackStaminaModifier, BlockStaminaModifier = equipment.BlockStaminaModifier, DodgeStaminaModifier = equipment.DodgeStaminaModifier, JumpStaminaModifier = equipment.JumpStaminaModifier, RunStaminaModifier = equipment.RunStaminaModifier, SneakStaminaModifier = equipment.SneakStaminaModifier, SwimStaminaModifier = equipment.SwimStaminaModifier, Armor = (output.EmitEquipmentArmor ? equipment.Armor : null), MaxAdrenaline = equipment.MaxAdrenaline }; } private static bool IsFloatPart(string value, float expected) { if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return Math.Abs(result - expected) <= 0.0001f; } return false; } private static bool IsBoolPart(string value, bool expected) { if (bool.TryParse(value, out var result)) { return result == expected; } return false; } private static PrimaryAttackDefinition? ToReferencePrimaryAttack(PrimaryAttackDefinition? attack) { if (attack == null) { return null; } return new PrimaryAttackDefinition { Animation = attack.Animation, ChainLevels = attack.ChainLevels, Cost = FormatReferenceAttackCost(attack.Cost), MissingHealth = attack.MissingHealth, SpawnOnHit = attack.SpawnOnHit, Draw = (ShouldExposeAttackDraw(attack.Draw) ? attack.Draw : null), Reload = (ShouldExposeAttackReload(attack.Reload) ? attack.Reload : null), DamageMultiplier = attack.DamageMultiplier, ForceMultiplier = attack.ForceMultiplier, StaggerMultiplier = attack.StaggerMultiplier, LastChainDamageMultiplier = (ShouldExposeLastChainDamageMultiplier(attack) ? attack.LastChainDamageMultiplier : ((float?)null)), RaiseSkillAmount = attack.RaiseSkillAmount }; } private static AttackDefinition? ToReferenceAttack(AttackDefinition? attack) { if (attack == null) { return null; } return new AttackDefinition { Animation = attack.Animation, ChainLevels = attack.ChainLevels, Cost = FormatReferenceAttackCost(attack.Cost), MissingHealth = attack.MissingHealth, SpawnOnHit = attack.SpawnOnHit, Draw = (ShouldExposeAttackDraw(attack.Draw) ? attack.Draw : null), Reload = (ShouldExposeAttackReload(attack.Reload) ? attack.Reload : null), DamageMultiplier = attack.DamageMultiplier, ForceMultiplier = attack.ForceMultiplier, StaggerMultiplier = attack.StaggerMultiplier, RaiseSkillAmount = attack.RaiseSkillAmount }; } } internal sealed class ItemDefinition { public BasicsDefinition? Basics { get; set; } public string? Durability { get; set; } public EquipmentDefinition? Equipment { get; set; } public DamageTakenModifierDefinition? DamageTakenModifiers { get; set; } public string? Food { get; set; } public ShieldDefinition? Shield { get; set; } public CombatDefinition? Combat { get; set; } public EffectsDefinition? Effects { get; set; } public VisualDefinition? Visual { get; set; } internal static ItemDefinition From(ItemDrop itemDrop) { SharedData shared = itemDrop.m_itemData.m_shared; return new ItemDefinition { Basics = BasicsDefinition.From(itemDrop), Durability = DurabilityDefinition.From(shared).ToString(), Equipment = EquipmentDefinition.From(shared), DamageTakenModifiers = DamageTakenModifierDefinition.From(shared.m_damageModifiers), Food = FoodDefinition.From(shared).ToString(), Shield = ShieldDefinition.From(shared), Combat = CombatDefinition.From(shared), Effects = EffectsDefinition.From(shared) }; } } internal sealed class VisualDefinition { public string? Icon { get; set; } public string? IconRotation { get; set; } public string? Material { get; set; } public string? Color { get; set; } public float? Emission { get; set; } } internal sealed class BasicsDefinition { public string? Name { get; set; } public string? Description { get; set; } public string? Subtitle { get; set; } public string? ItemType { get; set; } public string? Weight { get; set; } public int? Value { get; set; } public int? MaxStackSize { get; set; } public int? MaxQuality { get; set; } public bool? Teleportable { get; set; } public bool? Floating { get; set; } internal static BasicsDefinition From(ItemDrop itemDrop) { SharedData shared = itemDrop.m_itemData.m_shared; return new BasicsDefinition { Name = shared.m_name, Description = shared.m_description, Subtitle = shared.m_subtitle, ItemType = ((object)Unsafe.As(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString(), Weight = FormatFloat(shared.m_weight), Value = shared.m_value, MaxStackSize = shared.m_maxStackSize, MaxQuality = shared.m_maxQuality, Teleportable = shared.m_teleportable, Floating = ((Object)(object)((Component)itemDrop).GetComponent() != (Object)null) }; } } internal sealed class DurabilityDefinition { public bool? UseDurability { get; set; } public float? MaxDurability { get; set; } public float? DurabilityPerLevel { get; set; } public float? DurabilityDrain { get; set; } public float? UseDurabilityDrain { get; set; } public bool? CanBeRepaired { get; set; } public bool? DestroyBroken { get; set; } internal static DurabilityDefinition From(SharedData shared) { return new DurabilityDefinition { UseDurability = shared.m_useDurability, MaxDurability = shared.m_maxDurability, DurabilityPerLevel = shared.m_durabilityPerLevel, DurabilityDrain = shared.m_durabilityDrain, UseDurabilityDrain = shared.m_useDurabilityDrain, CanBeRepaired = shared.m_canBeReparied, DestroyBroken = shared.m_destroyBroken }; } public override string ToString() { return string.Join(", ", FormatFloat(MaxDurability), FormatFloat(DurabilityPerLevel), FormatBool(UseDurability), FormatFloat(UseDurabilityDrain), FormatBool(CanBeRepaired), FormatBool(DestroyBroken), FormatFloat(DurabilityDrain)); } } internal sealed class EquipmentDefinition { public string? SkillType { get; set; } public float? EquipDuration { get; set; } public float? MovementModifier { get; set; } public float? EitrRegenModifier { get; set; } public float? HeatResistanceModifier { get; set; } public float? HomeItemsStaminaModifier { get; set; } public float? AttackStaminaModifier { get; set; } public float? BlockStaminaModifier { get; set; } public float? DodgeStaminaModifier { get; set; } public float? JumpStaminaModifier { get; set; } public float? RunStaminaModifier { get; set; } public float? SneakStaminaModifier { get; set; } public float? SwimStaminaModifier { get; set; } public string? Armor { get; set; } public float? MaxAdrenaline { get; set; } internal static EquipmentDefinition From(SharedData shared) { return new EquipmentDefinition { SkillType = ((object)Unsafe.As(ref shared.m_skillType)/*cast due to .constrained prefix*/).ToString(), EquipDuration = shared.m_equipDuration, MovementModifier = shared.m_movementModifier, EitrRegenModifier = shared.m_eitrRegenModifier, HeatResistanceModifier = shared.m_heatResistanceModifier, HomeItemsStaminaModifier = shared.m_homeItemsStaminaModifier, AttackStaminaModifier = shared.m_attackStaminaModifier, BlockStaminaModifier = shared.m_blockStaminaModifier, DodgeStaminaModifier = shared.m_dodgeStaminaModifier, JumpStaminaModifier = shared.m_jumpStaminaModifier, RunStaminaModifier = shared.m_runStaminaModifier, SneakStaminaModifier = shared.m_sneakStaminaModifier, SwimStaminaModifier = shared.m_swimStaminaModifier, Armor = FormatFloatPair(shared.m_armor, shared.m_armorPerLevel), MaxAdrenaline = shared.m_maxAdrenaline }; } } internal sealed class FoodDefinition { public float? Health { get; set; } public float? Stamina { get; set; } public float? Eitr { get; set; } public float? Regen { get; set; } public float? BurnTime { get; set; } internal static FoodDefinition From(SharedData shared) { return new FoodDefinition { Health = shared.m_food, Stamina = shared.m_foodStamina, Eitr = shared.m_foodEitr, Regen = shared.m_foodRegen, BurnTime = shared.m_foodBurnTime }; } public override string ToString() { return string.Join(", ", FormatFloat(Health), FormatFloat(Stamina), FormatFloat(Eitr), FormatFloat(Regen), FormatFloat(BurnTime)); } } internal sealed class DamageTakenModifierDefinition { public string? Blunt { get; set; } public string? Slash { get; set; } public string? Pierce { get; set; } public string? Chop { get; set; } public string? Pickaxe { get; set; } public string? Fire { get; set; } public string? Frost { get; set; } public string? Lightning { get; set; } public string? Poison { get; set; } public string? Spirit { get; set; } internal unsafe static DamageTakenModifierDefinition From(List? mods) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) DamageModifiers val = default(DamageModifiers); if (mods != null) { ((DamageModifiers)(ref val)).Apply(mods); } return new DamageTakenModifierDefinition { Blunt = ((object)(*(DamageModifier*)(&val.m_blunt))/*cast due to .constrained prefix*/).ToString(), Slash = ((object)(*(DamageModifier*)(&val.m_slash))/*cast due to .constrained prefix*/).ToString(), Pierce = ((object)(*(DamageModifier*)(&val.m_pierce))/*cast due to .constrained prefix*/).ToString(), Chop = ((object)(*(DamageModifier*)(&val.m_chop))/*cast due to .constrained prefix*/).ToString(), Pickaxe = ((object)(*(DamageModifier*)(&val.m_pickaxe))/*cast due to .constrained prefix*/).ToString(), Fire = ((object)(*(DamageModifier*)(&val.m_fire))/*cast due to .constrained prefix*/).ToString(), Frost = ((object)(*(DamageModifier*)(&val.m_frost))/*cast due to .constrained prefix*/).ToString(), Lightning = ((object)(*(DamageModifier*)(&val.m_lightning))/*cast due to .constrained prefix*/).ToString(), Poison = ((object)(*(DamageModifier*)(&val.m_poison))/*cast due to .constrained prefix*/).ToString(), Spirit = ((object)(*(DamageModifier*)(&val.m_spirit))/*cast due to .constrained prefix*/).ToString() }; } } internal sealed class ShieldDefinition { public string? BlockPower { get; set; } public string? DeflectionForce { get; set; } public float? TimedBlockBonus { get; set; } internal static ShieldDefinition From(SharedData shared) { return new ShieldDefinition { BlockPower = FormatFloatPair(shared.m_blockPower, shared.m_blockPowerPerLevel), DeflectionForce = FormatFloatPair(shared.m_deflectionForce, shared.m_deflectionForcePerLevel), TimedBlockBonus = shared.m_timedBlockBonus }; } } internal sealed class CombatDefinition { public DamageDefinition? Damage { get; set; } public float? BackstabBonus { get; set; } public float? AttackForce { get; set; } public PrimaryAttackDefinition? PrimaryAttack { get; set; } public AttackDefinition? SecondaryAttack { get; set; } internal bool IsBombLike { get; set; } internal static CombatDefinition From(SharedData shared) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) return new CombatDefinition { Damage = DamageDefinition.From(shared.m_damages, shared.m_damagesPerLevel), BackstabBonus = shared.m_backstabBonus, AttackForce = shared.m_attackForce, PrimaryAttack = PrimaryAttackDefinition.FromPrimary(shared.m_attack), SecondaryAttack = AttackDefinition.From(shared.m_secondaryAttack), IsBombLike = IsBombProjectileAttack(shared.m_attack) }; } } internal sealed class DamageDefinition { public string? Blunt { get; set; } public string? Slash { get; set; } public string? Pierce { get; set; } public string? Chop { get; set; } public string? Pickaxe { get; set; } public string? Fire { get; set; } public string? Frost { get; set; } public string? Lightning { get; set; } public string? Poison { get; set; } public string? Spirit { get; set; } public float? BackstabBonus { get; set; } public float? AttackForce { get; set; } internal static DamageDefinition From(DamageTypes damage, DamageTypes damagePerLevel) { //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_001d: 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_0034: 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_004b: 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_0062: 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_0079: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: 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_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: 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) return new DamageDefinition { Blunt = FormatDamagePair(damage.m_blunt, damagePerLevel.m_blunt), Slash = FormatDamagePair(damage.m_slash, damagePerLevel.m_slash), Pierce = FormatDamagePair(damage.m_pierce, damagePerLevel.m_pierce), Chop = FormatDamagePair(damage.m_chop, damagePerLevel.m_chop), Pickaxe = FormatDamagePair(damage.m_pickaxe, damagePerLevel.m_pickaxe), Fire = FormatDamagePair(damage.m_fire, damagePerLevel.m_fire), Frost = FormatDamagePair(damage.m_frost, damagePerLevel.m_frost), Lightning = FormatDamagePair(damage.m_lightning, damagePerLevel.m_lightning), Poison = FormatDamagePair(damage.m_poison, damagePerLevel.m_poison), Spirit = FormatDamagePair(damage.m_spirit, damagePerLevel.m_spirit) }; } } internal class AttackDefinition { internal string? Animation { get; set; } internal int ChainLevels { get; set; } public string? Cost { get; set; } public string? MissingHealth { get; set; } public string? SpawnOnHit { get; set; } public string? Draw { get; set; } public string? Reload { get; set; } public float? DamageMultiplier { get; set; } public float? ForceMultiplier { get; set; } public float? StaggerMultiplier { get; set; } public float? RaiseSkillAmount { get; set; } internal static AttackDefinition? From(Attack attack) { if (attack == null) { return null; } return new AttackDefinition { Animation = attack.m_attackAnimation, ChainLevels = attack.m_attackChainLevels, Cost = FormatAttackCost(attack), MissingHealth = FormatMissingHealth(attack), SpawnOnHit = FormatSpawnOnHit(attack), Draw = FormatAttackDraw(attack), Reload = FormatAttackReload(attack), DamageMultiplier = attack.m_damageMultiplier, ForceMultiplier = attack.m_forceMultiplier, StaggerMultiplier = attack.m_staggerMultiplier, RaiseSkillAmount = attack.m_raiseSkillAmount }; } } internal sealed class PrimaryAttackDefinition : AttackDefinition { public float? LastChainDamageMultiplier { get; set; } internal static PrimaryAttackDefinition? FromPrimary(Attack attack) { if (attack == null) { return null; } return new PrimaryAttackDefinition { Animation = attack.m_attackAnimation, ChainLevels = attack.m_attackChainLevels, Cost = FormatAttackCost(attack), MissingHealth = FormatMissingHealth(attack), SpawnOnHit = FormatSpawnOnHit(attack), Draw = FormatAttackDraw(attack), Reload = FormatAttackReload(attack), DamageMultiplier = attack.m_damageMultiplier, ForceMultiplier = attack.m_forceMultiplier, StaggerMultiplier = attack.m_staggerMultiplier, LastChainDamageMultiplier = attack.m_lastChainDamageMultiplier, RaiseSkillAmount = attack.m_raiseSkillAmount }; } } internal sealed class EffectsDefinition { public string? EquipStatusEffect { get; set; } public string? Set { get; set; } public string? ConsumeStatusEffect { get; set; } public string? AttackStatusEffect { get; set; } public string? PerfectBlockStatusEffect { get; set; } public string? FullAdrenalineStatusEffect { get; set; } internal static EffectsDefinition From(SharedData shared) { return new EffectsDefinition { EquipStatusEffect = (((Object)(object)shared.m_equipStatusEffect != (Object)null) ? ((Object)shared.m_equipStatusEffect).name : null), Set = FormatSetEffect(shared.m_setName, shared.m_setSize, shared.m_setStatusEffect), ConsumeStatusEffect = (((Object)(object)shared.m_consumeStatusEffect != (Object)null) ? ((Object)shared.m_consumeStatusEffect).name : null), AttackStatusEffect = FormatAttackStatusEffect(shared.m_attackStatusEffect, shared.m_attackStatusEffectChance), PerfectBlockStatusEffect = (((Object)(object)shared.m_perfectBlockStatusEffect != (Object)null) ? ((Object)shared.m_perfectBlockStatusEffect).name : null), FullAdrenalineStatusEffect = (((Object)(object)shared.m_fullAdrenalineSE != (Object)null) ? ((Object)shared.m_fullAdrenalineSE).name : null) }; } } private const string DomainName = "items"; private const string OverrideFileName = "items.yml"; private const string ReferenceFileName = "items.reference.yml"; private const string FullScaffoldFileName = "items.full.yml"; private const string SyncedPayloadKey = "items"; private const string CloneRootName = "DataForge_ItemClones"; private const long ReloadDelayTicks = 10000000L; private const string ReferenceStateKey = "items"; private const string ReferenceLogicVersion = "2026-06-24-item-reference-state-v2"; private static readonly object StateLock = new object(); private static readonly Dictionary Baselines = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly MethodInfo? MemberwiseCloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly MethodInfo? SetupEquipmentMethod = AccessTools.Method(typeof(Humanoid), "SetupEquipment", (Type[])null, (Type[])null); private static GameObject? CloneRoot; private static readonly HashSet ReferenceVisiblePrefabs = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet CreatedClones = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary CreatedClonePrefabs = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet RuntimeAppliedItemKeys = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet LiveSafeAppliedItemKeys = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); private static readonly ISerializer SparseSerializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull).DisableAliases() .Build(); private static readonly ISerializer FullSerializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).DisableAliases().Build(); private static List ActiveEntries = new List(); private static Dictionary ActiveAmountMultipliers = new Dictionary(StringComparer.OrdinalIgnoreCase); private static Dictionary ActiveEntrySignaturesByItem = new Dictionary(StringComparer.OrdinalIgnoreCase); private static HashSet? PendingChangedItemKeys; private static bool HasPendingScopedApply; private static bool ForceNextFullApply = true; private static CustomSyncedValue? SyncedPayload; private static string? LastAppliedSyncedPayload; private static FileSystemWatcher? Watcher; private static DataForgeFileWatcher.DebouncedAction? ReloadDebouncer; private static bool ObjectDbReady; private static bool ZNetSceneReady; private static bool RuntimeStateWasApplied; private static bool GlobalMultiplierStateWasApplied; private static bool LiveSafeStateWasApplied; private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); internal static void Initialize(ConfigSync configSync) { SyncedPayload = new CustomSyncedValue(configSync, "items", ""); SyncedPayload.ValueChanged += OnSyncedPayloadChanged; } internal static void Dispose() { if (SyncedPayload != null) { SyncedPayload.ValueChanged -= OnSyncedPayloadChanged; } Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } internal static void SetupFileWatcher() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } else { EnsureConfigDirectoryAndDefaultOverride(); Watcher?.Dispose(); ReloadDebouncer?.Dispose(); ReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadYamlValues); Watcher = DataForgeFileWatcher.Create(ConfigDirectory, "*.*", includeSubdirectories: true, ReadYamlValues); } } internal static void ReloadFromDiskAndSync() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { ApplySyncedPayload(SyncedPayload?.Value ?? ""); return; } EnsureConfigDirectoryAndDefaultOverride(); List list = LoadEntriesFromDisk(); lock (StateLock) { SetActiveEntries(list); } PublishPayload(SerializeEntries(list)); ApplyCurrentConfiguration(); } internal static void OnObjectDBReady() { if (!((Object)(object)ObjectDB.instance == (Object)null)) { ObjectDbReady = true; if (!ShouldSkipRemoteClientBaselineWork()) { WriteGeneratedArtifacts(); ApplyCurrentConfiguration(); } } } internal static void OnZNetSceneReady() { if ((Object)(object)ZNetScene.instance == (Object)null) { return; } ZNetSceneReady = true; if (!ObjectDbReady || (Object)(object)ObjectDB.instance == (Object)null) { return; } List entries; HashSet hashSet; lock (StateLock) { entries = ActiveEntries.ToList(); hashSet = ConsumePendingChangedItemKeys(); } if (hashSet == null || hashSet.Count != 0) { CleanupCreatedPrefabs(entries, destroy: true); EnsureClonePrefabs(entries, warnIfMissingSource: true); Dictionary> entriesByItem = BuildEnabledEntriesByItem(entries); bool flag = HasGlobalItemMultiplierOverrides() || GlobalMultiplierStateWasApplied; HashSet hashSet2 = (flag ? null : GetRuntimeApplyKeys(entriesByItem, hashSet)); if (flag) { CaptureAllBaselinesIfNeeded(); } else { CaptureBaselinesForItemsIfNeeded(hashSet2); } ApplyToItemPrefabs(entriesByItem, hashSet2); RepairDropPrefabs(hashSet2); bool flag2 = ShouldRefreshExistingItems(entriesByItem, flag); if (flag2) { ApplyLiveSafeToExistingItems(entriesByItem, hashSet2); } ObjectDB.instance.UpdateRegisters(); UpdateRuntimeAppliedItemState(entriesByItem, HasGlobalItemMultiplierOverrides()); UpdateLiveSafeItemState(entriesByItem, flag2); } } internal static void ApplyCurrentConfiguration() { if (!ObjectDbReady || (Object)(object)ObjectDB.instance == (Object)null || ShouldSkipRemoteClientBaselineWork()) { return; } List entries; HashSet hashSet; lock (StateLock) { entries = ActiveEntries.ToList(); hashSet = ConsumePendingChangedItemKeys(); } if (hashSet == null || hashSet.Count != 0) { CleanupCreatedPrefabs(entries, destroy: true); EnsureClonePrefabs(entries, ZNetSceneReady); Dictionary> entriesByItem = BuildEnabledEntriesByItem(entries); bool flag = HasGlobalItemMultiplierOverrides() || GlobalMultiplierStateWasApplied; HashSet hashSet2 = (flag ? null : GetRuntimeApplyKeys(entriesByItem, hashSet)); if (flag) { CaptureAllBaselinesIfNeeded(); } else { CaptureBaselinesForItemsIfNeeded(hashSet2); } ApplyToItemPrefabs(entriesByItem, hashSet2); RepairDropPrefabs(hashSet2); bool flag2 = ShouldRefreshExistingItems(entriesByItem, flag); if (flag2) { ApplyLiveSafeToExistingItems(entriesByItem, hashSet2); } ObjectDB.instance.UpdateRegisters(); UpdateRuntimeAppliedItemState(entriesByItem, HasGlobalItemMultiplierOverrides()); UpdateLiveSafeItemState(entriesByItem, flag2); } } private static bool ShouldSkipRemoteClientBaselineWork() { if (!DataForgePlugin.IsRemoteServerClient || DataForgePlugin.StackableStackMultiplier != 1 || Math.Abs(DataForgePlugin.ItemWeightMultiplier - 1f) > 0.0001f) { return false; } lock (StateLock) { return ActiveEntries.Count == 0 && CreatedClones.Count == 0 && CreatedClonePrefabs.Count == 0; } } private static void ReadYamlValues(object sender, FileSystemEventArgs e) { if (ShouldReloadForFileEvent(e)) { ReloadDebouncer?.Schedule(); } } private static void ReloadYamlValues() { try { DataForgePlugin.Log.LogDebug((object)"Reloading item YAML files..."); ReloadFromDiskAndSync(); DataForgePlugin.Log.LogInfo((object)"Item YAML reload complete."); } catch (Exception arg) { DataForgePlugin.Log.LogError((object)$"Error reloading item YAML files: {arg}"); } } private static bool ShouldReloadForFileEvent(FileSystemEventArgs e) { if (!DataForgePlugin.UsesLocalAuthorityFiles) { return false; } if (IsOverrideFile(e.FullPath)) { return true; } if (ItemVisualOverrides.IsIconFile(e.FullPath)) { return true; } if (e is RenamedEventArgs e2) { if (!IsOverrideFile(e2.OldFullPath)) { return ItemVisualOverrides.IsIconFile(e2.OldFullPath); } return true; } return false; } private static void OnSyncedPayloadChanged() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { string payload = SyncedPayload?.Value ?? ""; DataForgeProfiler.Profile(string.Format("{0}.ApplySyncedPayload chars={1}", "items", payload.Length), delegate { ApplySyncedPayload(payload); }); } } private static void ApplySyncedPayload(string payload) { if (!string.Equals(LastAppliedSyncedPayload, payload, StringComparison.Ordinal)) { LastAppliedSyncedPayload = payload; List activeEntries = DeserializeEntries(payload, "synced item payload"); lock (StateLock) { SetActiveEntries(activeEntries); } ApplyCurrentConfiguration(); } } internal static void CleanupCreatedClonesForWorldTransition() { CleanupCreatedPrefabs(new List(), destroy: true); CreatedClones.Clear(); CreatedClonePrefabs.Clear(); } internal static void OnWorldShutdown() { ObjectDbReady = false; ZNetSceneReady = false; RuntimeStateWasApplied = false; GlobalMultiplierStateWasApplied = false; LiveSafeStateWasApplied = false; RuntimeAppliedItemKeys.Clear(); LiveSafeAppliedItemKeys.Clear(); CleanupCreatedClonesForWorldTransition(); } private static void PublishPayload(string payload) { DataForgeSync.PublishPayload(SyncedPayload, "items", payload); } private static List LoadEntriesFromDisk() { return DataForgeOverrideFiles.LoadEntries(GetOverrideFiles(), DeserializeEntries); } private static List DeserializeEntries(string yaml, string source) { if (string.IsNullOrWhiteSpace(yaml)) { return new List(); } try { return NormalizeEntries(Deserializer.Deserialize>(yaml), source); } catch (Exception ex) { DataForgePlugin.Log.LogError((object)("Failed to parse " + source + ": " + ex.Message)); return new List(); } } private static List NormalizeEntries(List? entries, string source) { List list = new List(); if (entries == null) { return list; } int num = 0; foreach (ItemEntry entry in entries) { num++; string text = DataForgeLogContext.FormatSource(source, num); if (string.IsNullOrWhiteSpace(entry.Item)) { DataForgeLogContext.Warning(text + ": Skipping item entry without item."); continue; } string[] array = DataForgeValue.SplitTuple(entry.Item); if (array.Length == 0 || string.IsNullOrWhiteSpace(array[0])) { DataForgeLogContext.Warning(text + ": Skipping item entry without item."); continue; } entry.Item = NormalizePrefabName(array[0]); if (array.Length > 1) { if (float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { entry.AmountMultiplier = Math.Max(0f, result); } else { DataForgeLogContext.Warning(text + ": Ignoring invalid item amount multiplier '" + array[1] + "' for '" + entry.Item + "'."); } } if (array.Length > 2) { DataForgeLogContext.Warning(text + ": Ignoring extra item tuple values after '" + entry.Item + ", " + array[1] + "'."); } string text2 = (entry.AmountMultiplier.HasValue ? (" amountMultiplier=" + FormatFloat(entry.AmountMultiplier)) : ""); entry.SetLogContext(text + " item=" + entry.Item + text2); string cloneFrom = entry.CloneFrom; if (cloneFrom != null && cloneFrom.Trim().Length > 0) { entry.CloneFrom = NormalizePrefabName(cloneFrom); } list.Add(entry); } return list; } private static void SetActiveEntries(List entries) { Dictionary dictionary = BuildEntrySignaturesByItem(entries); if (!ForceNextFullApply) { PendingChangedItemKeys = GetChangedKeys(ActiveEntrySignaturesByItem, dictionary); HasPendingScopedApply = true; } ActiveEntries = entries; ActiveAmountMultipliers = BuildAmountMultiplierMap(entries); ActiveEntrySignaturesByItem = dictionary; } private static HashSet? ConsumePendingChangedItemKeys() { if (ForceNextFullApply) { ForceNextFullApply = false; PendingChangedItemKeys = null; HasPendingScopedApply = false; return null; } if (!HasPendingScopedApply) { return null; } HashSet? result = PendingChangedItemKeys ?? new HashSet(StringComparer.OrdinalIgnoreCase); PendingChangedItemKeys = null; HasPendingScopedApply = false; return result; } private static Dictionary BuildEntrySignaturesByItem(List entries) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (ItemEntry entry in entries) { if (!string.IsNullOrWhiteSpace(entry.Item)) { if (!dictionary.TryGetValue(entry.Item, out var value)) { value = new List(); dictionary[entry.Item] = value; } value.Add(entry); } } Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair> item in dictionary) { dictionary2[item.Key] = SparseSerializer.Serialize(item.Value); } return dictionary2; } private static HashSet GetChangedKeys(Dictionary oldSignatures, Dictionary newSignatures) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair oldSignature in oldSignatures) { if (!newSignatures.TryGetValue(oldSignature.Key, out string value) || !string.Equals(oldSignature.Value, value, StringComparison.Ordinal)) { hashSet.Add(oldSignature.Key); } } foreach (KeyValuePair newSignature in newSignatures) { if (!oldSignatures.TryGetValue(newSignature.Key, out string value2) || !string.Equals(value2, newSignature.Value, StringComparison.Ordinal)) { hashSet.Add(newSignature.Key); } } return hashSet; } private static Dictionary BuildAmountMultiplierMap(List entries) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (ItemEntry entry in entries) { if (entry.Override && entry.AmountMultiplier.HasValue) { float num = Math.Max(0f, entry.AmountMultiplier.Value); if (IsDefaultAmountMultiplier(num)) { dictionary.Remove(entry.Item); } else { dictionary[entry.Item] = num; } } } return dictionary; } private static Dictionary> BuildEnabledEntriesByItem(List entries) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (ItemEntry entry in entries) { if (entry.Override && !string.IsNullOrWhiteSpace(entry.Item) && entry.HasPrefabDefinition) { if (!dictionary.TryGetValue(entry.Item, out var value)) { value = new List(); dictionary[entry.Item] = value; } value.Add(entry); } } return dictionary; } private static bool HasGlobalItemMultiplierOverrides() { if (DataForgePlugin.StackableStackMultiplier == 1) { return Math.Abs(DataForgePlugin.ItemWeightMultiplier - 1f) > 0.0001f; } return true; } private static HashSet GetRuntimeApplyKeys(Dictionary> entriesByItem, HashSet? changedItemKeys) { if (changedItemKeys != null) { return new HashSet(changedItemKeys, StringComparer.OrdinalIgnoreCase); } HashSet hashSet = new HashSet(entriesByItem.Keys, StringComparer.OrdinalIgnoreCase); if (RuntimeStateWasApplied) { foreach (string runtimeAppliedItemKey in RuntimeAppliedItemKeys) { hashSet.Add(runtimeAppliedItemKey); } } return hashSet; } private static void UpdateRuntimeAppliedItemState(Dictionary> entriesByItem, bool globalMultiplierActive) { RuntimeAppliedItemKeys.Clear(); foreach (string key in entriesByItem.Keys) { RuntimeAppliedItemKeys.Add(key); } RuntimeStateWasApplied = RuntimeAppliedItemKeys.Count > 0; GlobalMultiplierStateWasApplied = globalMultiplierActive; } private static bool ShouldRefreshExistingItems(Dictionary> entriesByItem, bool refreshAllForGlobalMultiplier) { if (refreshAllForGlobalMultiplier || LiveSafeStateWasApplied) { return true; } return entriesByItem.Values.SelectMany((List entries) => entries).Any((ItemEntry entry) => entry.HasLiveSafeDefinition); } private static void UpdateLiveSafeItemState(Dictionary> entriesByItem, bool refreshWasRun) { LiveSafeAppliedItemKeys.Clear(); foreach (KeyValuePair> item in entriesByItem) { if (item.Value.Any((ItemEntry entry) => entry.HasLiveSafeDefinition)) { LiveSafeAppliedItemKeys.Add(item.Key); } } LiveSafeStateWasApplied = refreshWasRun && LiveSafeAppliedItemKeys.Count > 0; } private static string SerializeEntries(List entries) { return SparseSerializer.Serialize(entries); } private static IEnumerable GetOverrideFiles() { return DataForgeOverrideFiles.GetOverrideFiles(ConfigDirectory, IsOverrideFile); } private static bool IsOverrideFile(string path) { string extension = Path.GetExtension(path); if (!extension.Equals(".yml", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".yaml", StringComparison.OrdinalIgnoreCase)) { return false; } string fileName = Path.GetFileName(path); if (fileName.Equals("items.reference.yml", StringComparison.OrdinalIgnoreCase) || fileName.Equals("items.full.yml", StringComparison.OrdinalIgnoreCase)) { return false; } if (!fileName.Equals("items.yml", StringComparison.OrdinalIgnoreCase)) { return fileName.StartsWith("items_", StringComparison.OrdinalIgnoreCase); } return true; } private static void EnsureConfigDirectoryAndDefaultOverride() { ItemVisualOverrides.EnsureIconDirectory(); DataForgeOverrideFiles.EnsureDefaultOverride(ConfigDirectory, "items.yml", GetOverrideFiles, DefaultOverrideTemplate); } private static string DefaultOverrideTemplate() { return string.Join(Environment.NewLine, "# DataForge item overrides.", "# Copy entries from items.reference.yml, or run `dataforge:full item` to generate items.full.yml for exhaustive field examples.", "# You can also create additional override files like items_asdf.yml; DataForge loads items.yml and items_*.yml together.", "# Omitted fields keep the current item value. Values below are common defaults or examples.", "#", "# Schema:", "# - item: SwordIron, 1 # required item prefab id; optional second value multiplies world loot/gather/drop amount.", "# override: true # default true; false skips this entire item entry.", "# cloneFrom: SwordBronze # optional; source item prefab used to clone a new item.", "# name: $item_sword_iron # display name token or literal text.", "# description: $item_sword_iron_desc # tooltip description token or literal text.", "# subtitle: # optional secondary text.", "# itemType: OneHandedWeapon # ItemDrop.ItemData.ItemType enum name.", "# weight: 1 # item weight.", "# value: 0 # item value shown to game systems.", "# maxStackSize: 1 # maximum stack size.", "# maxQuality: 1 # maximum item quality level.", "# teleportable: true # false prevents portal travel.", "# floating: true # false removes the Floating component so dropped items sink in water.", "# durability: 100, 50, true, 1, true, false, 0 # maxDurability, durabilityPerLevel, useDurability, useDurabilityDrain, canBeRepaired, destroyBroken, durabilityDrain.", "# equipment:", "# skillType: Swords # shown only for sword/axe/club/knife/spear/polearm/fists/shield/pickaxe/bow/crossbow/elemental magic/blood magic items.", "# equipDuration: 0 # seconds to equip.", "# movementModifier: 0 # movement modifier while equipped.", "# eitrRegenModifier: 0 # equipped eitr regen modifier.", "# heatResistanceModifier: 0 # heat resistance modifier.", "# homeItemsStaminaModifier: 0 # build/home item stamina use modifier.", "# attackStaminaModifier: 0 # attack stamina use modifier.", "# blockStaminaModifier: 0 # block stamina use modifier.", "# dodgeStaminaModifier: 0 # dodge stamina use modifier.", "# jumpStaminaModifier: 0 # jump stamina use modifier.", "# runStaminaModifier: 0 # run stamina use modifier.", "# sneakStaminaModifier: 0 # sneak stamina use modifier.", "# swimStaminaModifier: 0 # swim stamina use modifier.", "# armor: 12, 2 # shown only for Helmet/Chest/Legs/Shoulder/Utility; base armor, armor gained per quality level.", "# maxAdrenaline: 0 # max adrenaline bonus.", "# damageTakenModifiers:", "# fire: Resistant # item damage taken modifier; values include Normal, Resistant, VeryResistant, Weak, VeryWeak, Immune, Ignore, SlightlyResistant.", "# frost: Weak # set Normal to remove an existing modifier for that damage type.", "# food: 45, 15, 0, 2, 1800 # health, stamina, eitr, regen, burnTime.", "# shield:", "# shown in reference/full scaffold only for sword/axe/club/knife/spear/polearm/fists/shield/pickaxe/bow/crossbow/elemental magic/blood magic items.", "# blockPower: 40, 5 # base block power, block power gained per quality level.", "# deflectionForce: 30, 5 # base deflection force, deflection gained per quality level.", "# timedBlockBonus: 1.5 # parry/timed block multiplier.", "# damage:", "# blunt: 0, 0 # blunt base damage, damage gained per quality level.", "# slash: 0, 0 # slash base damage, damage gained per quality level.", "# pierce: 0, 0 # pierce base damage, damage gained per quality level.", "# chop: 0, 0 # chop/tree base damage, damage gained per quality level.", "# pickaxe: 0, 0 # pickaxe base damage, damage gained per quality level.", "# fire: 0, 0 # fire base damage, damage gained per quality level.", "# frost: 0, 0 # frost base damage, damage gained per quality level.", "# lightning: 0, 0 # lightning base damage, damage gained per quality level.", "# poison: 0, 0 # poison base damage, damage gained per quality level.", "# spirit: 0, 0 # spirit base damage, damage gained per quality level.", "# backstabBonus: 1 # backstab damage multiplier.", "# attackForce: 0 # knockback force.", "# primaryAttack:", "# # shown in reference/full scaffold only for InventorySlots-style sword/axe/club/knife/spear/polearm/fists/shield/pickaxe/tool/bow/crossbow/elemental magic/blood magic items.", "# cost: 0, 0, 0, 0 # stamina, eitr, health, healthPercentage costs.", "# missingHealth: 0, 0, 0 # damage multiplier per missing HP, damage multiplier by total missing health percent, stamina returned per missing HP.", "# spawnOnHit: None, 0 # prefab spawned on hit, chance from 0 to 1; ChainLightning, 0.2 => 20% chance.", "# draw: 0, 0, 0 # full draw time at skill 0, stamina drain/s while drawing, eitr drain/s while drawing.", "# reload: false, 0, 0, 0 # requires reload, reload time seconds, stamina drain while reloading, eitr drain while reloading.", "# damageMultiplier: 1 # attack damage multiplier.", "# forceMultiplier: 1 # attack force multiplier.", "# staggerMultiplier: 1 # stagger multiplier.", "# lastChainDamageMultiplier: 1 # final combo hit damage multiplier; scaffold shows it when chainLevels > 1; reference omits vanilla default 2.", "# raiseSkillAmount: 1 # skill experience raised by a successful attack.", "# secondaryAttack: # same fields as primaryAttack, except lastChainDamageMultiplier.", "# # shown in reference/full scaffold only when primaryAttack is eligible and secondary attack animation is not empty.", "# cost: 0, 0, 0, 0 # stamina, eitr, health, healthPercentage costs.", "# missingHealth: 0, 0, 0 # same missing-health tuple for secondary attack.", "# spawnOnHit: None, 0 # same spawn-on-hit tuple for secondary attack.", "# effects:", "# equipStatusEffect: Rested # status effect applied while equipped.", "# set: WolfArmor, 4, SetEffectName # setName, setSize, setStatusEffect applied when enough matching set items are equipped.", "# consumeStatusEffect: MeadHealthMedium # status effect applied when consumed.", "# attackStatusEffect: Burning, 0.25 # statusEffect, chance; 0.25 means 25% chance to apply on attack.", "# perfectBlockStatusEffect: # status effect applied on perfect block.", "# fullAdrenalineStatusEffect: # status effect applied at full adrenaline.", "# visual:", "# icon: auto # auto snapshots visually changed items; use MyIcon to load DataForge/icon/MyIcon.png; 256x256 PNG recommended.", "# iconRotation: 23, 51, 25.8 # x, y, z Euler rotation used only by icon: auto.", "# material: wood # material name from z_materials.reference.txt; replaces all item renderer material slots.", "# color: 1, 0.8, 0.6, 1 # RGBA tint applied to cloned item material instances.", "# emission: 0.5 # emission intensity; 0 disables glow, higher values glow more if the shader supports it.", "#", "# Example:", "# - item: SwordIronHeavy", "# cloneFrom: SwordIron", "# weight: 0.8", "# visual:", "# icon: auto", "# iconRotation: 23, 51, 25.8", "# material: blackmetal", "# color: 0.8, 0.85, 1, 1", "# emission: 0.15", "# damage:", "# slash: 55, 0") + Environment.NewLine; } private static void CaptureAllBaselinesIfNeeded() { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } int num = 0; foreach (var (prefabName, itemDrop) in GetItemDrops()) { if (CaptureBaseline(prefabName, itemDrop)) { num++; } } if (num > 0) { DataForgePlugin.Log.LogInfo((object)$"Captured {num} new item prefab baselines. Tracking {Baselines.Count} total."); } } private static void CaptureBaselinesForItemsIfNeeded(IEnumerable? itemNames) { if ((Object)(object)ObjectDB.instance == (Object)null || itemNames == null) { return; } int num = 0; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); ItemDrop itemDrop = default(ItemDrop); foreach (string itemName in itemNames) { string text = NormalizePrefabName(itemName); if (text.Length != 0 && hashSet.Add(text) && !Baselines.ContainsKey(text)) { GameObject val = ResolveItemPrefab(text); if (!((Object)(object)val == (Object)null) && val.TryGetComponent(ref itemDrop) && CaptureBaseline(GetPrefabName(val), itemDrop)) { num++; } } } if (num > 0) { DataForgePlugin.Log.LogInfo((object)$"Captured {num} targeted item prefab baselines. Tracking {Baselines.Count} total."); } } private static bool CaptureBaseline(string prefabName, ItemDrop itemDrop) { TrackReferenceVisibility(prefabName, itemDrop); if (Baselines.ContainsKey(prefabName)) { return false; } Baselines[prefabName] = ItemDefinition.From(itemDrop); return true; } private static IEnumerable<(string PrefabName, ItemDrop ItemDrop)> GetItemDrops() { if ((Object)(object)ObjectDB.instance == (Object)null) { yield break; } HashSet seen = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (GameObject item in ObjectDB.instance.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent(); if (!((Object)(object)component == (Object)null)) { string prefabName = GetPrefabName(item); if (seen.Add(prefabName)) { yield return (PrefabName: prefabName, ItemDrop: component); } } } } private static void EnsureClonePrefabs(List entries, bool warnIfMissingSource) { foreach (ItemEntry entry in entries) { if (entry.Override && !string.IsNullOrWhiteSpace(entry.CloneFrom)) { using (DataForgeLogContext.Push(entry.LogContext)) { EnsureClonePrefab(entry, warnIfMissingSource); } } } } private static void CleanupCreatedPrefabs(List entries, bool destroy) { HashSet hashSet = new HashSet(from entry in entries where entry.Override && !string.IsNullOrWhiteSpace(entry.CloneFrom) select entry.Item, StringComparer.OrdinalIgnoreCase); foreach (string item in CreatedClonePrefabs.Keys.ToList()) { if (!hashSet.Contains(item)) { RemoveCreatedClonePrefab(item, destroy); } } } private static void EnsureClonePrefab(ItemEntry entry, bool warnIfMissingSource) { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } GameObject val = ResolveItemPrefab(entry.Item); if ((Object)(object)val != (Object)null) { if (CreatedClones.Contains(entry.Item)) { CreatedClonePrefabs[entry.Item] = val; EnsureStoredClonePrefab(val); string cloneFrom = entry.CloneFrom; GameObject sourcePrefab = ((!string.IsNullOrWhiteSpace(cloneFrom)) ? ResolveItemPrefab(cloneFrom) : null); PrepareClonedItemData(val, sourcePrefab, entry.Item, entry.Name); } RegisterObjectDbItemPrefab(val); ItemDrop component = val.GetComponent(); TrackReferenceVisibility(entry.Item, component); if (!Baselines.ContainsKey(entry.Item)) { Baselines[entry.Item] = ItemDefinition.From(component); } return; } CreatedClones.Remove(entry.Item); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(entry.CloneFrom); if ((Object)(object)itemPrefab == (Object)null) { if (warnIfMissingSource) { DataForgeLogContext.Warning("Could not clone item '" + entry.Item + "': source '" + entry.CloneFrom + "' was not found."); } } else { GameObject val2 = InstantiateStoredClone(itemPrefab); ((Object)val2).name = entry.Item; PrepareClonedItemData(val2, itemPrefab, entry.Item, entry.Name); RegisterObjectDbItemPrefab(val2, addToItems: true); ItemDrop component2 = val2.GetComponent(); TrackReferenceVisibility(entry.Item, component2); Baselines[entry.Item] = ItemDefinition.From(component2); CreatedClones.Add(entry.Item); CreatedClonePrefabs[entry.Item] = val2; DataForgePlugin.Log.LogInfo((object)("Cloned item '" + entry.CloneFrom + "' as '" + entry.Item + "'.")); } } private static void RemoveCreatedClonePrefab(string cloneName, bool destroy) { if (!CreatedClonePrefabs.TryGetValue(cloneName, out GameObject value) || (Object)(object)value == (Object)null) { CreatedClonePrefabs.Remove(cloneName); CreatedClones.Remove(cloneName); Baselines.Remove(cloneName); ReferenceVisiblePrefabs.Remove(cloneName); return; } UnregisterObjectDbItemPrefab(cloneName, value); UnregisterZNetScenePrefab(cloneName, value); ItemVisualOverrides.Restore(cloneName, value.GetComponent()); CreatedClonePrefabs.Remove(cloneName); CreatedClones.Remove(cloneName); Baselines.Remove(cloneName); ReferenceVisiblePrefabs.Remove(cloneName); if (destroy) { Object.Destroy((Object)(object)value); } else { value.SetActive(false); } } private static GameObject InstantiateStoredClone(GameObject sourcePrefab) { GameObject cloneRoot = GetCloneRoot(); GameObject obj = Object.Instantiate(sourcePrefab, cloneRoot.transform, false); obj.SetActive(true); return obj; } private static void PrepareClonedItemData(GameObject clonePrefab, GameObject? sourcePrefab, string clonePrefabName, string? configuredName) { ItemDrop component = clonePrefab.GetComponent(); if (component?.m_itemData?.m_shared != null) { SharedData val = ((!((Object)(object)sourcePrefab != (Object)null)) ? null : sourcePrefab.GetComponent()?.m_itemData?.m_shared); SharedData val2 = component.m_itemData.m_shared; if (val != null && val2 == val) { val2 = CloneSharedData(val2); component.m_itemData.m_shared = val2; } EnsureIndependentSharedMembers(val2, val); if (string.IsNullOrWhiteSpace(configuredName) && (val == null || string.Equals(val2.m_name, val.m_name, StringComparison.Ordinal))) { val2.m_name = clonePrefabName; } component.m_itemData.m_dropPrefab = clonePrefab; } } private static SharedData CloneSharedData(SharedData source) { return ShallowClone(source) ?? source; } private static void EnsureIndependentSharedMembers(SharedData shared, SharedData? sourceShared) { if (shared.m_icons != null) { shared.m_icons = shared.m_icons.ToArray(); } if (shared.m_attack != null && (sourceShared == null || shared.m_attack == sourceShared.m_attack)) { shared.m_attack = ShallowClone(shared.m_attack) ?? shared.m_attack; } if (shared.m_secondaryAttack != null && (sourceShared == null || shared.m_secondaryAttack == sourceShared.m_secondaryAttack)) { shared.m_secondaryAttack = ShallowClone(shared.m_secondaryAttack) ?? shared.m_secondaryAttack; } if (shared.m_damageModifiers != null && (sourceShared == null || shared.m_damageModifiers == sourceShared.m_damageModifiers)) { shared.m_damageModifiers = shared.m_damageModifiers.ToList(); } } private static T? ShallowClone(T source) where T : class { if (MemberwiseCloneMethod == null) { return null; } try { return MemberwiseCloneMethod.Invoke(source, null) as T; } catch (Exception ex) { DataForgePlugin.Log.LogDebug((object)("Could not clone " + typeof(T).Name + ": " + ex.Message)); return null; } } private static void EnsureStoredClonePrefab(GameObject prefab) { GameObject cloneRoot = GetCloneRoot(); if ((Object)(object)prefab.transform.parent != (Object)(object)cloneRoot.transform) { prefab.transform.SetParent(cloneRoot.transform, false); } prefab.SetActive(true); } private static GameObject GetCloneRoot() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown if ((Object)(object)CloneRoot != (Object)null) { CloneRoot.SetActive(false); return CloneRoot; } CloneRoot = GameObject.Find("DataForge_ItemClones"); if ((Object)(object)CloneRoot == (Object)null) { CloneRoot = new GameObject("DataForge_ItemClones"); Object.DontDestroyOnLoad((Object)(object)CloneRoot); } CloneRoot.SetActive(false); return CloneRoot; } private static GameObject? ResolveItemPrefab(string prefabName) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); if ((Object)(object)itemPrefab != (Object)null) { return itemPrefab; } return ((IEnumerable)ObjectDB.instance.m_items).FirstOrDefault((Func)((GameObject item) => (Object)(object)item != (Object)null && string.Equals(NormalizePrefabName(((Object)item).name), prefabName, StringComparison.OrdinalIgnoreCase))); } private static GameObject? ResolvePrefab(string prefabName) { if (string.IsNullOrWhiteSpace(prefabName)) { return null; } string text = NormalizePrefabName(prefabName.Trim()); if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab != (Object)null) { return prefab; } int stableHashCode = StringExtensionMethods.GetStableHashCode(text); if (ZNetScene.instance.m_namedPrefabs.TryGetValue(stableHashCode, out var value) && (Object)(object)value != (Object)null) { return value; } } if ((Object)(object)ObjectDB.instance != (Object)null) { GameObject val = ResolveItemPrefab(text); if ((Object)(object)val != (Object)null) { return val; } } return null; } internal static void RepairDropPrefab(ItemDrop itemDrop) { if (!((Object)(object)itemDrop == (Object)null) && itemDrop.m_itemData != null && !((Object)(object)itemDrop.m_itemData.m_dropPrefab != (Object)null)) { GameObject val = ResolveItemPrefab(NormalizePrefabName(((Object)((Component)itemDrop).gameObject).name)) ?? ResolveItemPrefab(itemDrop.m_itemData); if ((Object)(object)val != (Object)null) { itemDrop.m_itemData.m_dropPrefab = val; } } } internal static bool IsCreatedCloneDrop(ItemDrop itemDrop) { if ((Object)(object)itemDrop == (Object)null || (Object)(object)((Component)itemDrop).gameObject == (Object)null) { return false; } return CreatedClones.Contains(NormalizePrefabName(((Object)((Component)itemDrop).gameObject).name)); } internal static void RepairDropPrefab(ItemData item) { if (item != null && !((Object)(object)item.m_dropPrefab != (Object)null)) { GameObject val = ResolveItemPrefab(item); if ((Object)(object)val != (Object)null) { item.m_dropPrefab = val; } } } private static GameObject? ResolveItemPrefab(ItemData item) { if ((Object)(object)ObjectDB.instance == (Object)null || item == null || item.m_shared == null) { return null; } foreach (GameObject item2 in ObjectDB.instance.m_items) { if (!((Object)(object)item2 == (Object)null)) { ItemDrop component = item2.GetComponent(); if ((Object)(object)component != (Object)null && component.m_itemData.m_shared == item.m_shared) { return item2; } } } return ResolveItemPrefabBySharedFields(item, preferCreatedClones: true) ?? ResolveItemPrefabBySharedFields(item, preferCreatedClones: false); } private static GameObject? ResolveItemPrefabBySharedFields(ItemData item, bool preferCreatedClones) { //IL_0051: 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) foreach (GameObject item2 in ObjectDB.instance.m_items) { if (!((Object)(object)item2 == (Object)null) && CreatedClones.Contains(((Object)item2).name) == preferCreatedClones) { ItemDrop component = item2.GetComponent(); if ((Object)(object)component != (Object)null && component.m_itemData.m_shared.m_itemType == item.m_shared.m_itemType && string.Equals(component.m_itemData.m_shared.m_name, item.m_shared.m_name, StringComparison.Ordinal)) { return item2; } } } return null; } private static void RepairDropPrefabs(HashSet? itemKeys = null) { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } if (itemKeys != null) { ItemDrop val2 = default(ItemDrop); foreach (string itemKey in itemKeys) { GameObject val = ResolveItemPrefab(itemKey); if (!((Object)(object)val == (Object)null) && val.TryGetComponent(ref val2)) { val2.m_itemData.m_dropPrefab = val; } } return; } foreach (GameObject item in ObjectDB.instance.m_items) { if (!((Object)(object)item == (Object)null)) { ItemDrop component = item.GetComponent(); if ((Object)(object)component != (Object)null) { component.m_itemData.m_dropPrefab = item; } } } } private static void RegisterObjectDbItemPrefab(GameObject prefab, bool addToItems = false) { if (!((Object)(object)ObjectDB.instance == (Object)null) && !((Object)(object)prefab == (Object)null)) { if (addToItems && !ObjectDB.instance.m_items.Contains(prefab)) { ObjectDB.instance.m_items.Add(prefab); } int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name); ObjectDB.instance.m_itemByHash[stableHashCode] = prefab; ItemDrop component = prefab.GetComponent(); if ((Object)(object)component != (Object)null) { component.m_itemData.m_dropPrefab = prefab; } RegisterZNetScenePrefab(prefab); } } private static void UnregisterObjectDbItemPrefab(string prefabName, GameObject prefab) { if (!((Object)(object)ObjectDB.instance == (Object)null) && !((Object)(object)prefab == (Object)null)) { while (ObjectDB.instance.m_items.Remove(prefab)) { } int stableHashCode = StringExtensionMethods.GetStableHashCode(prefabName); if (ObjectDB.instance.m_itemByHash.TryGetValue(stableHashCode, out var value) && value == prefab) { ObjectDB.instance.m_itemByHash.Remove(stableHashCode); } } } private static void RegisterZNetScenePrefab(GameObject prefab) { if (!((Object)(object)ZNetScene.instance == (Object)null) && !((Object)(object)prefab == (Object)null)) { List list = (((Object)(object)prefab.GetComponent() != (Object)null) ? ZNetScene.instance.m_prefabs : ZNetScene.instance.m_nonNetViewPrefabs); (((Object)(object)prefab.GetComponent() != (Object)null) ? ZNetScene.instance.m_nonNetViewPrefabs : ZNetScene.instance.m_prefabs).Remove(prefab); if (!list.Contains(prefab)) { list.Add(prefab); } int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name); ZNetScene.instance.m_namedPrefabs[stableHashCode] = prefab; } } private static void UnregisterZNetScenePrefab(string prefabName, GameObject prefab) { if (!((Object)(object)ZNetScene.instance == (Object)null) && !((Object)(object)prefab == (Object)null)) { while (ZNetScene.instance.m_prefabs.Remove(prefab)) { } while (ZNetScene.instance.m_nonNetViewPrefabs.Remove(prefab)) { } int stableHashCode = StringExtensionMethods.GetStableHashCode(prefabName); if (ZNetScene.instance.m_namedPrefabs.TryGetValue(stableHashCode, out var value) && value == prefab) { ZNetScene.instance.m_namedPrefabs.Remove(stableHashCode); } } } private static void ApplyToItemPrefabs(Dictionary> entriesByItem, HashSet? itemKeys = null) { foreach (var (text, val) in (itemKeys == null) ? GetItemDrops() : GetItemDrops(itemKeys)) { TrackReferenceVisibility(text, val); ItemVisualOverrides.Restore(text, val); if (Baselines.TryGetValue(text, out ItemDefinition value)) { ApplyDefinition(val, value); } if (!DataForgePlugin.ItemOverridesEnabled) { continue; } if (Baselines.TryGetValue(text, out value)) { ApplyGlobalItemMultipliers(val.m_itemData.m_shared, value); } if (!entriesByItem.TryGetValue(text, out List value2)) { continue; } foreach (ItemEntry item in value2) { using (DataForgeLogContext.Push(item.LogContext)) { ApplyDefinition(val, item.ToDefinition()); } } } } private static IEnumerable<(string PrefabName, ItemDrop ItemDrop)> GetItemDrops(IEnumerable prefabNames) { HashSet seen = new HashSet(StringComparer.OrdinalIgnoreCase); ItemDrop item = default(ItemDrop); foreach (string prefabName in prefabNames) { if (seen.Add(prefabName)) { GameObject val = ResolveItemPrefab(prefabName); if ((Object)(object)val != (Object)null && val.TryGetComponent(ref item)) { yield return (PrefabName: GetPrefabName(val), ItemDrop: item); } } } } private static void ApplyLiveSafeToExistingItems(Dictionary> entriesByItem, HashSet? itemKeys = null) { if ((Object)(object)ObjectDB.instance == (Object)null || (itemKeys != null && itemKeys.Count == 0)) { return; } HashSet hashSet = new HashSet(); int num = 0; if (Player.s_players != null) { foreach (Player s_player in Player.s_players) { if ((Object)(object)s_player == (Object)null) { continue; } Inventory inventory = ((Humanoid)s_player).GetInventory(); if (inventory != null) { int num2 = ApplyLiveSafeToInventory(inventory, entriesByItem, hashSet, itemKeys); if (num2 > 0) { num += num2; inventory.Changed(); RefreshPlayerEquipment(s_player); } } } } Container[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Container val in array) { if ((Object)(object)val == (Object)null) { continue; } Inventory inventory2 = val.GetInventory(); if (inventory2 != null) { int num3 = ApplyLiveSafeToInventory(inventory2, entriesByItem, hashSet, itemKeys); if (num3 > 0) { num += num3; inventory2.Changed(); } } } foreach (ItemDrop s_instance in ItemDrop.s_instances) { if (!((Object)(object)s_instance == (Object)null) && s_instance.m_itemData != null && hashSet.Add(s_instance.m_itemData) && ApplyLiveSafeToItemDrop(s_instance, entriesByItem, itemKeys)) { num++; } } if (num > 0) { DataForgePlugin.Log.LogDebug((object)$"Live-refreshed {num} existing item instances."); } } private static int ApplyLiveSafeToInventory(Inventory inventory, Dictionary> entriesByItem, HashSet seen, HashSet? itemKeys) { int num = 0; foreach (ItemData allItem in inventory.GetAllItems()) { if (allItem != null && seen.Add(allItem) && ApplyLiveSafeToItem(allItem, entriesByItem, itemKeys)) { num++; } } return num; } private static bool ApplyLiveSafeToItemDrop(ItemDrop itemDrop, Dictionary> entriesByItem, HashSet? itemKeys) { bool flag = ApplyLiveSafeToItem(itemDrop.m_itemData, entriesByItem, itemKeys); GameObject val = itemDrop.m_itemData.m_dropPrefab ?? ResolveItemPrefab(itemDrop.m_itemData); if ((Object)(object)val == (Object)null) { return flag; } string prefabName = GetPrefabName(val); if (itemKeys != null && !itemKeys.Contains(prefabName)) { return flag; } bool flag2 = false; if (Baselines.TryGetValue(prefabName, out ItemDefinition value)) { ApplyFloating(((Component)itemDrop).gameObject, value.Basics?.Floating, immediate: false); flag2 = value.Basics?.Floating.HasValue ?? false; } if (!DataForgePlugin.ItemOverridesEnabled) { return flag || flag2; } if (!entriesByItem.TryGetValue(prefabName, out List value2)) { return flag || flag2; } foreach (ItemEntry item in value2) { using (DataForgeLogContext.Push(item.LogContext)) { BasicsDefinition basics = item.ToDefinition().Basics; ApplyFloating(((Component)itemDrop).gameObject, basics?.Floating, immediate: false); flag2 = flag2 || (basics?.Floating.HasValue ?? false); } } return flag || flag2; } private static bool ApplyLiveSafeToItem(ItemData item, Dictionary> entriesByItem, HashSet? itemKeys = null) { if (item == null || item.m_shared == null) { return false; } RepairDropPrefab(item); GameObject val = item.m_dropPrefab ?? ResolveItemPrefab(item); if ((Object)(object)val == (Object)null) { return false; } if (item.m_dropPrefab == null) { item.m_dropPrefab = val; } string prefabName = GetPrefabName(val); if (itemKeys != null && !itemKeys.Contains(prefabName)) { return false; } bool result = false; if (Baselines.TryGetValue(prefabName, out ItemDefinition value)) { ApplyLiveSafeDefinition(item.m_shared, value); result = true; } if (!DataForgePlugin.ItemOverridesEnabled) { return result; } if (Baselines.TryGetValue(prefabName, out value)) { ApplyGlobalItemMultipliers(item.m_shared, value); result = true; } if (!entriesByItem.TryGetValue(prefabName, out List value2)) { return result; } foreach (ItemEntry item2 in value2) { using (DataForgeLogContext.Push(item2.LogContext)) { ApplyLiveSafeDefinition(item.m_shared, item2.ToDefinition()); } result = true; } return result; } private static void RefreshPlayerEquipment(Player player) { if (SetupEquipmentMethod == null) { return; } try { SetupEquipmentMethod.Invoke(player, null); } catch (Exception ex) { DataForgePlugin.Log.LogDebug((object)("Could not refresh player equipment after item live update: " + ex.Message)); } } private static void TrackReferenceVisibility(string prefabName, ItemDrop itemDrop) { if (IsReferenceVisibleItem(itemDrop)) { ReferenceVisiblePrefabs.Add(prefabName); } else { ReferenceVisiblePrefabs.Remove(prefabName); } } private static bool IsReferenceVisibleItem(ItemDrop itemDrop) { SharedData shared = itemDrop.m_itemData.m_shared; if (shared.m_icons != null) { return shared.m_icons.Length != 0; } return false; } private static void ApplyDefinition(ItemDrop itemDrop, ItemDefinition definition) { SharedData shared = itemDrop.m_itemData.m_shared; ApplyBasics(shared, definition.Basics); ApplyFloating(((Component)itemDrop).gameObject, definition.Basics?.Floating, immediate: true); ApplyDurability(shared, definition.Durability); ApplyEquipment(shared, definition.Equipment); ApplyDamageTakenModifiers(shared, definition.DamageTakenModifiers); ApplyFood(shared, definition.Food); ApplyShield(shared, definition.Shield); ApplyCombat(shared, definition.Combat); ApplyEffects(shared, definition.Effects); ItemVisualOverrides.Apply(itemDrop, definition.Visual); } private static void ApplyLiveSafeDefinition(SharedData shared, ItemDefinition definition) { ApplyLiveSafeBasics(shared, definition.Basics); ApplyDurability(shared, definition.Durability); ApplyLiveSafeEquipment(shared, definition.Equipment); ApplyDamageTakenModifiers(shared, definition.DamageTakenModifiers); ApplyFood(shared, definition.Food); ApplyShield(shared, definition.Shield); ApplyCombat(shared, definition.Combat); ApplyLiveSafeEffects(shared, definition.Effects); } private static void ApplyBasics(SharedData shared, BasicsDefinition? basics) { if (basics != null) { DataForgeValue.Copy(basics.Name, delegate(string value) { shared.m_name = value; }); DataForgeValue.Copy(basics.Description, delegate(string value) { shared.m_description = value; }); DataForgeValue.Copy(basics.Subtitle, delegate(string value) { shared.m_subtitle = value; }); CopyEnum(basics.ItemType, (Action)delegate(ItemType value) { //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) shared.m_itemType = value; }, "itemType"); CopyFloatValue(basics.Weight, delegate(float value) { shared.m_weight = value; }); DataForgeValue.Copy(basics.Value, delegate(int value) { shared.m_value = Math.Max(0, value); }); DataForgeValue.Copy(basics.MaxStackSize, delegate(int value) { shared.m_maxStackSize = Math.Max(1, value); }); DataForgeValue.Copy(basics.MaxQuality, delegate(int value) { shared.m_maxQuality = Math.Max(1, value); }); DataForgeValue.Copy(basics.Teleportable, delegate(bool value) { shared.m_teleportable = value; }); } } private static void ApplyLiveSafeBasics(SharedData shared, BasicsDefinition? basics) { if (basics != null) { DataForgeValue.Copy(basics.Name, delegate(string value) { shared.m_name = value; }); DataForgeValue.Copy(basics.Description, delegate(string value) { shared.m_description = value; }); DataForgeValue.Copy(basics.Subtitle, delegate(string value) { shared.m_subtitle = value; }); CopyFloatValue(basics.Weight, delegate(float value) { shared.m_weight = value; }); DataForgeValue.Copy(basics.Value, delegate(int value) { shared.m_value = Math.Max(0, value); }); DataForgeValue.Copy(basics.MaxStackSize, delegate(int value) { shared.m_maxStackSize = Math.Max(1, value); }); DataForgeValue.Copy(basics.Teleportable, delegate(bool value) { shared.m_teleportable = value; }); } } private static void ApplyGlobalItemMultipliers(SharedData shared, ItemDefinition baseline) { BasicsDefinition basics = baseline.Basics; if (basics != null) { int stackableStackMultiplier = DataForgePlugin.StackableStackMultiplier; if (stackableStackMultiplier != 1 && basics.MaxStackSize.HasValue && basics.MaxStackSize.Value > 1) { long val = (long)basics.MaxStackSize.Value * (long)stackableStackMultiplier; shared.m_maxStackSize = (int)Math.Min(2147483647L, Math.Max(1L, val)); } float itemWeightMultiplier = DataForgePlugin.ItemWeightMultiplier; if (!(Math.Abs(itemWeightMultiplier - 1f) <= 0.0001f) && float.TryParse(basics.Weight, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { shared.m_weight = Math.Max(0f, result * itemWeightMultiplier); } } } private static void ApplyFloating(GameObject gameObject, bool? floating, bool immediate) { if (!floating.HasValue || (Object)(object)gameObject == (Object)null) { return; } Floating component = gameObject.GetComponent(); if (floating.Value) { if ((Object)(object)component == (Object)null) { gameObject.AddComponent(); } } else { if ((Object)(object)component == (Object)null) { return; } try { if (immediate) { Object.DestroyImmediate((Object)(object)component); } else { Object.Destroy((Object)(object)component); } } catch (Exception ex) { DataForgePlugin.Log.LogDebug((object)("Could not remove Floating component from '" + GetPrefabName(gameObject) + "': " + ex.Message)); Object.Destroy((Object)(object)component); } } } private static void ApplyDurability(SharedData shared, string? value) { DurabilityDefinition durabilityDefinition = ParseDurability(value); if (durabilityDefinition != null) { DataForgeValue.Copy(durabilityDefinition.UseDurability, delegate(bool useDurability) { shared.m_useDurability = useDurability; }); DataForgeValue.Copy(durabilityDefinition.MaxDurability, delegate(float val) { shared.m_maxDurability = Math.Max(0f, val); }); DataForgeValue.Copy(durabilityDefinition.DurabilityPerLevel, delegate(float val) { shared.m_durabilityPerLevel = Math.Max(0f, val); }); DataForgeValue.Copy(durabilityDefinition.UseDurabilityDrain, delegate(float val) { shared.m_useDurabilityDrain = Math.Max(0f, val); }); DataForgeValue.Copy(durabilityDefinition.CanBeRepaired, delegate(bool canBeReparied) { shared.m_canBeReparied = canBeReparied; }); DataForgeValue.Copy(durabilityDefinition.DestroyBroken, delegate(bool destroyBroken) { shared.m_destroyBroken = destroyBroken; }); DataForgeValue.Copy(durabilityDefinition.DurabilityDrain, delegate(float val) { shared.m_durabilityDrain = Math.Max(0f, val); }); } } private static DurabilityDefinition? ParseDurability(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); DurabilityDefinition durabilityDefinition = new DurabilityDefinition(); if (array.Length != 0 && array[0].Length > 0 && float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { durabilityDefinition.MaxDurability = result; } if (array.Length > 1 && array[1].Length > 0 && float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { durabilityDefinition.DurabilityPerLevel = result2; } if (array.Length > 2 && array[2].Length > 0 && bool.TryParse(array[2], out var result3)) { durabilityDefinition.UseDurability = result3; } if (array.Length > 3 && array[3].Length > 0 && float.TryParse(array[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4)) { durabilityDefinition.UseDurabilityDrain = result4; } if (array.Length > 4 && array[4].Length > 0 && bool.TryParse(array[4], out var result5)) { durabilityDefinition.CanBeRepaired = result5; } if (array.Length > 5 && array[5].Length > 0 && bool.TryParse(array[5], out var result6)) { durabilityDefinition.DestroyBroken = result6; } if (array.Length > 6 && array[6].Length > 0 && float.TryParse(array[6], NumberStyles.Float, CultureInfo.InvariantCulture, out var result7)) { durabilityDefinition.DurabilityDrain = result7; } return durabilityDefinition; } private static void ApplyEquipment(SharedData shared, EquipmentDefinition? equipment) { if (equipment != null) { CopyEnum(equipment.SkillType, (Action)delegate(SkillType value) { //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) shared.m_skillType = value; }, "skillType"); DataForgeValue.Copy(equipment.EquipDuration, delegate(float value) { shared.m_equipDuration = Math.Max(0f, value); }); DataForgeValue.Copy(equipment.MovementModifier, delegate(float value) { shared.m_movementModifier = value; }); DataForgeValue.Copy(equipment.EitrRegenModifier, delegate(float value) { shared.m_eitrRegenModifier = value; }); DataForgeValue.Copy(equipment.HeatResistanceModifier, delegate(float value) { shared.m_heatResistanceModifier = value; }); DataForgeValue.Copy(equipment.HomeItemsStaminaModifier, delegate(float value) { shared.m_homeItemsStaminaModifier = value; }); DataForgeValue.Copy(equipment.AttackStaminaModifier, delegate(float value) { shared.m_attackStaminaModifier = value; }); DataForgeValue.Copy(equipment.BlockStaminaModifier, delegate(float value) { shared.m_blockStaminaModifier = value; }); DataForgeValue.Copy(equipment.DodgeStaminaModifier, delegate(float value) { shared.m_dodgeStaminaModifier = value; }); DataForgeValue.Copy(equipment.JumpStaminaModifier, delegate(float value) { shared.m_jumpStaminaModifier = value; }); DataForgeValue.Copy(equipment.RunStaminaModifier, delegate(float value) { shared.m_runStaminaModifier = value; }); DataForgeValue.Copy(equipment.SneakStaminaModifier, delegate(float value) { shared.m_sneakStaminaModifier = value; }); DataForgeValue.Copy(equipment.SwimStaminaModifier, delegate(float value) { shared.m_swimStaminaModifier = value; }); CopyFloatPair(equipment.Armor, delegate(float value) { shared.m_armor = value; }, delegate(float value) { shared.m_armorPerLevel = value; }); DataForgeValue.Copy(equipment.MaxAdrenaline, delegate(float value) { shared.m_maxAdrenaline = Math.Max(0f, value); }); } } private static void ApplyLiveSafeEquipment(SharedData shared, EquipmentDefinition? equipment) { if (equipment != null) { DataForgeValue.Copy(equipment.MovementModifier, delegate(float value) { shared.m_movementModifier = value; }); DataForgeValue.Copy(equipment.EitrRegenModifier, delegate(float value) { shared.m_eitrRegenModifier = value; }); DataForgeValue.Copy(equipment.HeatResistanceModifier, delegate(float value) { shared.m_heatResistanceModifier = value; }); DataForgeValue.Copy(equipment.HomeItemsStaminaModifier, delegate(float value) { shared.m_homeItemsStaminaModifier = value; }); DataForgeValue.Copy(equipment.AttackStaminaModifier, delegate(float value) { shared.m_attackStaminaModifier = value; }); DataForgeValue.Copy(equipment.BlockStaminaModifier, delegate(float value) { shared.m_blockStaminaModifier = value; }); DataForgeValue.Copy(equipment.DodgeStaminaModifier, delegate(float value) { shared.m_dodgeStaminaModifier = value; }); DataForgeValue.Copy(equipment.JumpStaminaModifier, delegate(float value) { shared.m_jumpStaminaModifier = value; }); DataForgeValue.Copy(equipment.RunStaminaModifier, delegate(float value) { shared.m_runStaminaModifier = value; }); DataForgeValue.Copy(equipment.SneakStaminaModifier, delegate(float value) { shared.m_sneakStaminaModifier = value; }); DataForgeValue.Copy(equipment.SwimStaminaModifier, delegate(float value) { shared.m_swimStaminaModifier = value; }); CopyFloatPair(equipment.Armor, delegate(float value) { shared.m_armor = value; }, delegate(float value) { shared.m_armorPerLevel = value; }); DataForgeValue.Copy(equipment.MaxAdrenaline, delegate(float value) { shared.m_maxAdrenaline = Math.Max(0f, value); }); } } private static void ApplyDamageTakenModifiers(SharedData shared, DamageTakenModifierDefinition? definition) { if (definition != null) { if (shared.m_damageModifiers == null) { shared.m_damageModifiers = new List(); } ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)1, definition.Blunt, "damageTakenModifiers.blunt"); ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)2, definition.Slash, "damageTakenModifiers.slash"); ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)4, definition.Pierce, "damageTakenModifiers.pierce"); ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)8, definition.Chop, "damageTakenModifiers.chop"); ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)16, definition.Pickaxe, "damageTakenModifiers.pickaxe"); ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)32, definition.Fire, "damageTakenModifiers.fire"); ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)64, definition.Frost, "damageTakenModifiers.frost"); ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)128, definition.Lightning, "damageTakenModifiers.lightning"); ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)256, definition.Poison, "damageTakenModifiers.poison"); ApplyDamageTakenModifier(shared.m_damageModifiers, (DamageType)512, definition.Spirit, "damageTakenModifiers.spirit"); } } private static void ApplyDamageTakenModifier(List modifiers, DamageType damageType, string? value, string fieldName) { //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_002b: 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_0095: 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_00a3: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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_00c5: 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) if (string.IsNullOrWhiteSpace(value)) { return; } string text = value.Trim(); bool flag = text.Equals("None", StringComparison.OrdinalIgnoreCase); DamageModifier result = (DamageModifier)0; if (!flag && !Enum.TryParse(text, ignoreCase: true, out result)) { DataForgeLogContext.Warning("Unknown item " + fieldName + " value '" + text + "'."); return; } int num = modifiers.FindIndex((DamageModPair pair) => pair.m_type == damageType); if (flag || (int)result == 0) { if (num >= 0) { modifiers.RemoveAt(num); } return; } DamageModPair val = new DamageModPair { m_type = damageType, m_modifier = result }; if (num >= 0) { modifiers[num] = val; } else { modifiers.Add(val); } } private static void CopyFloatValue(string? value, Action assign) { if (!string.IsNullOrWhiteSpace(value)) { string text = value.Split(new char[1] { ',' }, 2, StringSplitOptions.None)[0].Trim(); if (text.Length > 0 && float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { assign(Math.Max(0f, result)); } } } private static void CopyFloatPair(string? value, Action assignFirst, Action assignSecond) { if (!string.IsNullOrWhiteSpace(value)) { string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length != 0 && array[0].Length > 0 && float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { assignFirst(Math.Max(0f, result)); } if (array.Length > 1 && array[1].Length > 0 && float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { assignSecond(Math.Max(0f, result2)); } } } private static void ApplyFood(SharedData shared, string? value) { FoodDefinition foodDefinition = ParseFood(value); if (foodDefinition != null) { DataForgeValue.Copy(foodDefinition.Health, delegate(float val) { shared.m_food = Math.Max(0f, val); }); DataForgeValue.Copy(foodDefinition.Stamina, delegate(float val) { shared.m_foodStamina = Math.Max(0f, val); }); DataForgeValue.Copy(foodDefinition.Eitr, delegate(float val) { shared.m_foodEitr = Math.Max(0f, val); }); DataForgeValue.Copy(foodDefinition.Regen, delegate(float val) { shared.m_foodRegen = Math.Max(0f, val); }); DataForgeValue.Copy(foodDefinition.BurnTime, delegate(float val) { shared.m_foodBurnTime = Math.Max(0f, val); }); } } private static FoodDefinition? ParseFood(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length > 5) { DataForgeLogContext.Warning($"Ignoring item food tuple with {array.Length} values. Expected: health, stamina, eitr, regen, burnTime."); return null; } FoodDefinition foodDefinition = new FoodDefinition(); if (array.Length != 0 && array[0].Length > 0 && float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { foodDefinition.Health = result; } if (array.Length > 1 && array[1].Length > 0 && float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { foodDefinition.Stamina = result2; } if (array.Length > 2 && array[2].Length > 0 && float.TryParse(array[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result3)) { foodDefinition.Eitr = result3; } if (array.Length > 3 && array[3].Length > 0 && float.TryParse(array[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4)) { foodDefinition.Regen = result4; } if (array.Length > 4 && array[4].Length > 0 && float.TryParse(array[4], NumberStyles.Float, CultureInfo.InvariantCulture, out var result5)) { foodDefinition.BurnTime = result5; } return foodDefinition; } private static void ApplyShield(SharedData shared, ShieldDefinition? shield) { if (shield != null) { CopyFloatPair(shield.BlockPower, delegate(float value) { shared.m_blockPower = value; }, delegate(float value) { shared.m_blockPowerPerLevel = value; }); CopyFloatPair(shield.DeflectionForce, delegate(float value) { shared.m_deflectionForce = value; }, delegate(float value) { shared.m_deflectionForcePerLevel = value; }); DataForgeValue.Copy(shield.TimedBlockBonus, delegate(float value) { shared.m_timedBlockBonus = Math.Max(0f, value); }); } } private static void ApplyCombat(SharedData shared, CombatDefinition? combat) { if (combat != null) { ApplyDamage(shared, combat.Damage); DataForgeValue.Copy(combat.BackstabBonus, delegate(float value) { shared.m_backstabBonus = Math.Max(0f, value); }); DataForgeValue.Copy(combat.AttackForce, delegate(float value) { shared.m_attackForce = Math.Max(0f, value); }); ApplyAttack(shared.m_attack, combat.PrimaryAttack, "primaryAttack"); ApplyPrimaryAttack(shared.m_attack, combat.PrimaryAttack); ApplyAttack(shared.m_secondaryAttack, combat.SecondaryAttack, "secondaryAttack"); } } private static void ApplyMissingHealth(Attack attack, string? value) { if (attack != null && !string.IsNullOrWhiteSpace(value)) { string[] parts = DataForgeValue.SplitTuple(value); CopyTupleFloat(parts, 0, delegate(float parsed) { attack.m_damageMultiplierPerMissingHP = Math.Max(0f, parsed); }); CopyTupleFloat(parts, 1, delegate(float parsed) { attack.m_damageMultiplierByTotalHealthMissing = Math.Max(0f, parsed); }); CopyTupleFloat(parts, 2, delegate(float parsed) { attack.m_staminaReturnPerMissingHP = Math.Max(0f, parsed); }); } } private static void ApplySpawnOnHit(Attack attack, string? value, string fieldName) { if (attack == null || value == null) { return; } string[] array = DataForgeValue.SplitTuple(value); if (array.Length == 0) { return; } bool flag = false; string text = array[0]; if (text.Length > 0) { if (DataForgeValue.IsNone(text)) { attack.m_spawnOnHit = null; attack.m_spawnOnHitChance = 0f; flag = true; } else { GameObject val = ResolvePrefab(text); if ((Object)(object)val == (Object)null) { if (ZNetSceneReady) { DataForgeLogContext.Warning("Could not resolve item " + fieldName + " prefab '" + text + "'."); } return; } attack.m_spawnOnHit = val; flag = true; if (array.Length == 1 && attack.m_spawnOnHitChance <= 0f) { attack.m_spawnOnHitChance = 1f; } } } if (array.Length > 1 && array[1].Length > 0 && float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { attack.m_spawnOnHitChance = Mathf.Clamp01(result); } else if (flag && (Object)(object)attack.m_spawnOnHit == (Object)null) { attack.m_spawnOnHitChance = 0f; } } private static void ApplyDamage(SharedData shared, DamageDefinition? damage) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_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_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0187: 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_0193: Unknown result type (might be due to invalid IL or missing references) if (damage != null) { DamageTypes damageTarget = shared.m_damages; DamageTypes damagePerLevelTarget = shared.m_damagesPerLevel; CopyDamage(damage.Blunt, delegate(float value) { damageTarget.m_blunt = value; }, delegate(float value) { damagePerLevelTarget.m_blunt = value; }); CopyDamage(damage.Slash, delegate(float value) { damageTarget.m_slash = value; }, delegate(float value) { damagePerLevelTarget.m_slash = value; }); CopyDamage(damage.Pierce, delegate(float value) { damageTarget.m_pierce = value; }, delegate(float value) { damagePerLevelTarget.m_pierce = value; }); CopyDamage(damage.Chop, delegate(float value) { damageTarget.m_chop = value; }, delegate(float value) { damagePerLevelTarget.m_chop = value; }); CopyDamage(damage.Pickaxe, delegate(float value) { damageTarget.m_pickaxe = value; }, delegate(float value) { damagePerLevelTarget.m_pickaxe = value; }); CopyDamage(damage.Fire, delegate(float value) { damageTarget.m_fire = value; }, delegate(float value) { damagePerLevelTarget.m_fire = value; }); CopyDamage(damage.Frost, delegate(float value) { damageTarget.m_frost = value; }, delegate(float value) { damagePerLevelTarget.m_frost = value; }); CopyDamage(damage.Lightning, delegate(float value) { damageTarget.m_lightning = value; }, delegate(float value) { damagePerLevelTarget.m_lightning = value; }); CopyDamage(damage.Poison, delegate(float value) { damageTarget.m_poison = value; }, delegate(float value) { damagePerLevelTarget.m_poison = value; }); CopyDamage(damage.Spirit, delegate(float value) { damageTarget.m_spirit = value; }, delegate(float value) { damagePerLevelTarget.m_spirit = value; }); shared.m_damages = damageTarget; shared.m_damagesPerLevel = damagePerLevelTarget; } } private static void CopyDamage(string? value, Action assignDamage, Action assignDamagePerLevel) { if (!string.IsNullOrWhiteSpace(value)) { string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length != 0 && array[0].Length > 0 && float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { assignDamage(Math.Max(0f, result)); } if (array.Length > 1 && array[1].Length > 0 && float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { assignDamagePerLevel(Math.Max(0f, result2)); } } } private static void ApplyAttack(Attack attack, AttackDefinition? definition, string fieldName) { if (attack != null && definition != null) { ApplyAttackCost(attack, definition.Cost); ApplyMissingHealth(attack, definition.MissingHealth); ApplySpawnOnHit(attack, definition.SpawnOnHit, fieldName + ".spawnOnHit"); ApplyAttackDraw(attack, definition.Draw); ApplyAttackReload(attack, definition.Reload); DataForgeValue.Copy(definition.DamageMultiplier, delegate(float value) { attack.m_damageMultiplier = Math.Max(0f, value); }); DataForgeValue.Copy(definition.ForceMultiplier, delegate(float value) { attack.m_forceMultiplier = Math.Max(0f, value); }); DataForgeValue.Copy(definition.StaggerMultiplier, delegate(float value) { attack.m_staggerMultiplier = Math.Max(0f, value); }); DataForgeValue.Copy(definition.RaiseSkillAmount, delegate(float value) { attack.m_raiseSkillAmount = Math.Max(0f, value); }); } } private static void ApplyPrimaryAttack(Attack attack, PrimaryAttackDefinition? definition) { if (attack != null && definition != null) { DataForgeValue.Copy(definition.LastChainDamageMultiplier, delegate(float value) { attack.m_lastChainDamageMultiplier = Math.Max(0f, value); }); } } private static void ApplyAttackCost(Attack attack, string? value) { if (!string.IsNullOrWhiteSpace(value)) { string[] parts = DataForgeValue.SplitTuple(value); CopyTupleFloat(parts, 0, delegate(float parsed) { attack.m_attackStamina = Math.Max(0f, parsed); }); CopyTupleFloat(parts, 1, delegate(float parsed) { attack.m_attackEitr = Math.Max(0f, parsed); }); CopyTupleFloat(parts, 2, delegate(float parsed) { attack.m_attackHealth = Math.Max(0f, parsed); }); CopyTupleFloat(parts, 3, delegate(float parsed) { attack.m_attackHealthPercentage = Mathf.Clamp01(parsed); }); } } private static void ApplyAttackDraw(Attack attack, string? value) { if (!string.IsNullOrWhiteSpace(value)) { string[] parts = DataForgeValue.SplitTuple(value); CopyTupleFloat(parts, 0, delegate(float parsed) { attack.m_drawDurationMin = Math.Max(0f, parsed); }); CopyTupleFloat(parts, 1, delegate(float parsed) { attack.m_drawStaminaDrain = Math.Max(0f, parsed); }); CopyTupleFloat(parts, 2, delegate(float parsed) { attack.m_drawEitrDrain = Math.Max(0f, parsed); }); } } private static void ApplyAttackReload(Attack attack, string? value) { if (!string.IsNullOrWhiteSpace(value)) { string[] parts = DataForgeValue.SplitTuple(value); CopyTupleBool(parts, 0, delegate(bool parsed) { attack.m_requiresReload = parsed; }); CopyTupleFloat(parts, 1, delegate(float parsed) { attack.m_reloadTime = Math.Max(0f, parsed); }); CopyTupleFloat(parts, 2, delegate(float parsed) { attack.m_reloadStaminaDrain = Math.Max(0f, parsed); }); CopyTupleFloat(parts, 3, delegate(float parsed) { attack.m_reloadEitrDrain = Math.Max(0f, parsed); }); } } private static void CopyTupleFloat(string[] parts, int index, Action assign) { if (parts.Length > index && parts[index].Length > 0 && float.TryParse(parts[index], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { assign(result); } } private static void CopyTupleBool(string[] parts, int index, Action assign) { if (parts.Length > index && parts[index].Length > 0 && bool.TryParse(parts[index], out var result)) { assign(result); } } private static void ApplyEffects(SharedData shared, EffectsDefinition? effects) { if (effects != null) { CopyStatusEffect(effects.EquipStatusEffect, delegate(StatusEffect? value) { shared.m_equipStatusEffect = value; }); ApplySetEffect(shared, effects.Set); CopyStatusEffect(effects.ConsumeStatusEffect, delegate(StatusEffect? value) { shared.m_consumeStatusEffect = value; }); ApplyAttackStatusEffect(shared, effects.AttackStatusEffect); CopyStatusEffect(effects.PerfectBlockStatusEffect, delegate(StatusEffect? value) { shared.m_perfectBlockStatusEffect = value; }); CopyStatusEffect(effects.FullAdrenalineStatusEffect, delegate(StatusEffect? value) { shared.m_fullAdrenalineSE = value; }); } } private static void ApplyLiveSafeEffects(SharedData shared, EffectsDefinition? effects) { if (effects != null) { CopyStatusEffect(effects.ConsumeStatusEffect, delegate(StatusEffect? value) { shared.m_consumeStatusEffect = value; }); ApplyAttackStatusEffect(shared, effects.AttackStatusEffect); CopyStatusEffect(effects.PerfectBlockStatusEffect, delegate(StatusEffect? value) { shared.m_perfectBlockStatusEffect = value; }); CopyStatusEffect(effects.FullAdrenalineStatusEffect, delegate(StatusEffect? value) { shared.m_fullAdrenalineSE = value; }); } } private static void ApplySetEffect(SharedData shared, string? value) { if (string.IsNullOrWhiteSpace(value)) { return; } string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length != 0 && array[0].Length > 0) { shared.m_setName = (DataForgeValue.IsNone(array[0]) ? "" : array[0]); } if (array.Length > 1 && array[1].Length > 0 && int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { shared.m_setSize = Math.Max(0, result); } if (array.Length > 2 && array[2].Length > 0) { CopyStatusEffect(array[2], delegate(StatusEffect? statusEffect) { shared.m_setStatusEffect = statusEffect; }); } } private static void ApplyAttackStatusEffect(SharedData shared, string? value) { if (string.IsNullOrWhiteSpace(value)) { return; } string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length != 0 && array[0].Length > 0) { CopyStatusEffect(array[0], delegate(StatusEffect? statusEffect) { shared.m_attackStatusEffect = statusEffect; }); } if (array.Length > 1 && array[1].Length > 0 && float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { shared.m_attackStatusEffectChance = Mathf.Clamp01(result); } } private static void CopyStatusEffect(string? statusEffectName, Action assign) { if (statusEffectName == null) { return; } if (DataForgeValue.IsNone(statusEffectName)) { assign(null); return; } StatusEffect val = ResolveStatusEffect(statusEffectName); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning("Could not resolve status effect '" + statusEffectName + "'."); } else { assign(val); } } private static StatusEffect? ResolveStatusEffect(string statusEffectName) { if ((Object)(object)ObjectDB.instance == (Object)null) { return null; } StatusEffect statusEffect = ObjectDB.instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode(statusEffectName)); if ((Object)(object)statusEffect != (Object)null) { return statusEffect; } return ((IEnumerable)ObjectDB.instance.m_StatusEffects).FirstOrDefault((Func)((StatusEffect effect) => (Object)(object)effect != (Object)null && (((Object)effect).name.Equals(statusEffectName, StringComparison.OrdinalIgnoreCase) || effect.m_name.Equals(statusEffectName, StringComparison.OrdinalIgnoreCase)))); } internal static float GetAcquisitionAmountMultiplier(GameObject? itemPrefab) { if (!DataForgePlugin.ItemOverridesEnabled || (Object)(object)itemPrefab == (Object)null) { return 1f; } if ((Object)(object)itemPrefab.GetComponent() == (Object)null) { return 1f; } string prefabName = GetPrefabName(itemPrefab); lock (StateLock) { float value; return ActiveAmountMultipliers.TryGetValue(prefabName, out value) ? value : 1f; } } internal static int ApplyAcquisitionAmountMultiplier(GameObject? itemPrefab, int amount) { return MultiplyAmount(amount, GetAcquisitionAmountMultiplier(itemPrefab)); } internal static int ApplyAcquisitionAmountMultiplier(ItemData? item, int amount) { if (item == null) { return amount; } return ApplyAcquisitionAmountMultiplier(item.m_dropPrefab ?? ResolveItemPrefab(item), amount); } internal static int MultiplyAmount(int amount, float multiplier) { if (amount <= 0 || IsDefaultAmountMultiplier(multiplier)) { return amount; } return Mathf.Max(0, Mathf.FloorToInt((float)amount * Math.Max(0f, multiplier) + Random.Range(0f, 1f))); } private static bool IsDefaultAmountMultiplier(float multiplier) { return Math.Abs(multiplier - 1f) <= 0.0001f; } private static void CopyEnum(string? value, Action assign, string fieldName) where TEnum : struct { if (!string.IsNullOrWhiteSpace(value)) { if (Enum.TryParse(value, ignoreCase: true, out var result)) { assign(result); return; } DataForgeLogContext.Warning("Unknown " + fieldName + " enum value '" + value + "'."); } } private static void WriteGeneratedArtifacts() { if (DataForgePlugin.UsesLocalAuthorityFiles) { WriteReferenceArtifact(); } } internal static bool TryWriteFullScaffoldConfigurationFile(out string path, out string error) { path = Path.Combine(ConfigDirectory, "items.full.yml"); return GeneratedArtifactWriter.TryWriteFullScaffoldIfReady(path, "items", CanBuildGeneratedArtifacts(), "items game data is not ready yet.", delegate { EnsureConfigDirectoryAndDefaultOverride(); CaptureAllBaselinesIfNeeded(); var entries = (from pair in Baselines where ReferenceVisiblePrefabs.Contains(pair.Key) select new { Entry = CreateOutputEntryMap(pair.Key, pair.Value), OwnerKey = pair.Key, SortKey = DataForgeResourceMap.BuildItemSortKey(pair.Key, DataForgeResourceMap.GetItemTierSortValue(pair.Key), pair.Key) }).OrderBy(pair => pair.SortKey, StringComparer.OrdinalIgnoreCase).ToList(); return GeneratedArtifactWriter.GeneratedHeader("items", "items.yml", "full scaffold") + DataForgeReferenceSections.SerializeReferenceSections(entries, entry => entry.SortKey, entry => DataForgeOwnerResolver.GetPrefabOwnerName(entry.OwnerKey), entry => entry.Entry, FullSerializer); }, out error); } private static void WriteReferenceArtifact() { if (CanBuildGeneratedArtifacts()) { EnsureConfigDirectoryAndDefaultOverride(); CaptureAllBaselinesIfNeeded(); string sourceSignature = ComputeReferenceSourceSignature(); string text = Path.Combine(ConfigDirectory, "items.reference.yml"); if (!ShouldSkipReferenceUpdate(text, sourceSignature) && (GeneratedArtifactWriter.WriteReferenceIfReady(Baselines.Count > 0, ConfigDirectory, "items.reference.yml", "items", "items.yml", BuildReferenceArtifactContent) || File.Exists(text))) { RecordReferenceUpdateState(text, sourceSignature); } } } private static string BuildReferenceArtifactContent() { return DataForgeReferenceSections.SerializeReferenceSections((from pair in Baselines where ReferenceVisiblePrefabs.Contains(pair.Key) select new { Entry = ItemReferenceEntry.From(pair.Key, pair.Value), SortKey = DataForgeResourceMap.BuildItemSortKey(pair.Key, DataForgeResourceMap.GetItemTierSortValue(pair.Key), pair.Key) }).ToList(), entry => entry.SortKey, entry => DataForgeOwnerResolver.GetPrefabOwnerName(entry.Entry.Item), entry => entry.Entry, SparseSerializer); } private static bool CanBuildGeneratedArtifacts() { if (ObjectDbReady) { return (Object)(object)ObjectDB.instance != (Object)null; } return false; } private static string ComputeReferenceSourceSignature() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("2026-06-24-item-reference-state-v2"); stringBuilder.AppendLine(BuildFileStamp(Path.Combine(ConfigDirectory, "z_resourcemap.txt"))); foreach (var item2 in GetItemDrops().OrderBy<(string, ItemDrop), string>(((string PrefabName, ItemDrop ItemDrop) pair) => pair.PrefabName, StringComparer.OrdinalIgnoreCase)) { string item = item2.Item1; stringBuilder.AppendLine(item); } return ComputeStableHash(stringBuilder.ToString()); } private static bool ShouldSkipReferenceUpdate(string referencePath, string sourceSignature) { return DataForgeReferenceState.ShouldSkip("items", referencePath, sourceSignature, "2026-06-24-item-reference-state-v2"); } private static void RecordReferenceUpdateState(string referencePath, string sourceSignature) { DataForgeReferenceState.Record("items", referencePath, sourceSignature, "2026-06-24-item-reference-state-v2"); } private static string BuildFileStamp(string path) { return DataForgeReferenceState.BuildFileStamp(path); } private static string ComputeStableHash(string value) { return DataForgeReferenceState.ComputeStableHash(value); } private static string GetPrefabName(GameObject gameObject) { return NormalizePrefabName(((Object)gameObject).name); } private static string NormalizePrefabName(string prefabName) { return prefabName.Replace("(Clone)", "").Trim(); } private static Dictionary CreateOutputEntryMap(string prefab, ItemDefinition definition) { ItemOutputProfile itemOutputProfile = ItemOutputProfile.From(definition); Dictionary dictionary = new Dictionary { ["item"] = prefab, ["override"] = true }; AddBasics(dictionary, definition.Basics); if (itemOutputProfile.EmitDurability) { dictionary["durability"] = definition.Durability; } if (itemOutputProfile.EmitEquipment) { dictionary["equipment"] = CreateEquipmentOutputMap(definition.Equipment, itemOutputProfile); } if (itemOutputProfile.EmitDamageTakenModifiers) { dictionary["damageTakenModifiers"] = definition.DamageTakenModifiers; } if (itemOutputProfile.EmitFood) { dictionary["food"] = definition.Food; } if (itemOutputProfile.EmitShield) { dictionary["shield"] = definition.Shield; } if (itemOutputProfile.EmitCombat) { AddCombatOutputFields(dictionary, definition, itemOutputProfile); } if (itemOutputProfile.EmitEffects) { dictionary["effects"] = definition.Effects; } dictionary["visual"] = definition.Visual ?? new VisualDefinition { Icon = "auto", IconRotation = "23, 51, 25.8" }; return dictionary; } private static void AddBasics(Dictionary entry, BasicsDefinition? basics) { if (basics != null) { entry["name"] = basics.Name; entry["description"] = basics.Description; entry["subtitle"] = basics.Subtitle; entry["itemType"] = basics.ItemType; entry["weight"] = basics.Weight; entry["value"] = basics.Value; entry["maxStackSize"] = basics.MaxStackSize; entry["maxQuality"] = basics.MaxQuality; entry["teleportable"] = basics.Teleportable; entry["floating"] = basics.Floating; } } private static Dictionary? CreateEquipmentOutputMap(EquipmentDefinition? equipment, ItemOutputProfile output) { if (equipment == null) { return null; } Dictionary dictionary = new Dictionary { ["equipDuration"] = equipment.EquipDuration, ["movementModifier"] = equipment.MovementModifier, ["eitrRegenModifier"] = equipment.EitrRegenModifier, ["heatResistanceModifier"] = equipment.HeatResistanceModifier, ["homeItemsStaminaModifier"] = equipment.HomeItemsStaminaModifier, ["attackStaminaModifier"] = equipment.AttackStaminaModifier, ["blockStaminaModifier"] = equipment.BlockStaminaModifier, ["dodgeStaminaModifier"] = equipment.DodgeStaminaModifier, ["jumpStaminaModifier"] = equipment.JumpStaminaModifier, ["runStaminaModifier"] = equipment.RunStaminaModifier, ["sneakStaminaModifier"] = equipment.SneakStaminaModifier, ["swimStaminaModifier"] = equipment.SwimStaminaModifier }; if (output.EmitEquipmentArmor) { dictionary["armor"] = equipment.Armor; } dictionary["maxAdrenaline"] = equipment.MaxAdrenaline; if (output.EmitEquipmentSkillType) { dictionary = new Dictionary { ["skillType"] = equipment.SkillType }.Concat(dictionary).ToDictionary((KeyValuePair pair) => pair.Key, (KeyValuePair pair) => pair.Value); } return dictionary; } private static void AddCombatOutputFields(Dictionary entry, ItemDefinition definition, ItemOutputProfile output) { CombatDefinition combat = definition.Combat; if (combat != null) { if (output.EmitDirectCombatStats) { entry["damage"] = CreateDamageOutputDefinition(combat); } if (output.EmitPrimaryAttack) { entry["primaryAttack"] = CreatePrimaryAttackOutputMap(combat.PrimaryAttack); } if (output.EmitSecondaryAttack) { entry["secondaryAttack"] = CreateAttackOutputMap(combat.SecondaryAttack); } } } private static DamageDefinition? CreateDamageOutputDefinition(CombatDefinition combat) { DamageDefinition damage = combat.Damage; if (damage == null && !combat.BackstabBonus.HasValue && !combat.AttackForce.HasValue) { return null; } return new DamageDefinition { Blunt = damage?.Blunt, Slash = damage?.Slash, Pierce = damage?.Pierce, Chop = damage?.Chop, Pickaxe = damage?.Pickaxe, Fire = damage?.Fire, Frost = damage?.Frost, Lightning = damage?.Lightning, Poison = damage?.Poison, Spirit = damage?.Spirit, BackstabBonus = combat.BackstabBonus, AttackForce = combat.AttackForce }; } private static Dictionary? CreatePrimaryAttackOutputMap(PrimaryAttackDefinition? attack) { Dictionary dictionary = CreateAttackOutputMap(attack); if (dictionary == null) { return null; } if (attack.ChainLevels > 1) { dictionary["lastChainDamageMultiplier"] = attack.LastChainDamageMultiplier; } return dictionary; } private static Dictionary? CreateAttackOutputMap(AttackDefinition? attack) { if (attack == null) { return null; } Dictionary dictionary = new Dictionary { ["cost"] = attack.Cost, ["missingHealth"] = attack.MissingHealth, ["spawnOnHit"] = attack.SpawnOnHit, ["damageMultiplier"] = attack.DamageMultiplier, ["forceMultiplier"] = attack.ForceMultiplier, ["staggerMultiplier"] = attack.StaggerMultiplier, ["raiseSkillAmount"] = attack.RaiseSkillAmount }; if (ShouldExposeAttackDraw(attack.Draw)) { dictionary["draw"] = attack.Draw; } if (ShouldExposeAttackReload(attack.Reload)) { dictionary["reload"] = attack.Reload; } return dictionary; } private static bool IsArmorLike(ItemDefinition definition) { ItemType[] array = new ItemType[6]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); return IsItemType(definition, (ItemType[])(object)array); } private static bool IsShieldLike(ItemDefinition definition) { if (!IsItemType(definition, (ItemType)5)) { return IsSkillType(definition, (SkillType)6); } return true; } private static bool IsToolLike(ItemDefinition definition) { if (!IsItemType(definition, (ItemType)19, (ItemType)15)) { SkillType[] array = new SkillType[3]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); return IsSkillType(definition, (SkillType[])(object)array); } return true; } private static bool IsWeaponLike(ItemDefinition definition) { ItemType[] array = new ItemType[6]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); if (!IsItemType(definition, (ItemType[])(object)array)) { SkillType[] array2 = new SkillType[9]; RuntimeHelpers.InitializeArray(array2, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); return IsSkillAttack(definition, (SkillType[])(object)array2); } return true; } private static bool IsPrimaryAttackSurface(ItemDefinition definition) { if (!IsSwordLike(definition) && !IsAxeLike(definition) && !IsClubLike(definition) && !IsKnifeLike(definition) && !IsSpearLike(definition) && !IsPolearmLike(definition) && !IsFistsLike(definition) && !IsShieldLike(definition) && !IsPickaxeLike(definition) && !IsToolLike(definition) && !IsBowLike(definition) && !IsCrossbowLike(definition)) { return IsMagicLike(definition); } return true; } private static bool IsShieldBlockSurface(ItemDefinition definition) { if (!IsSwordLike(definition) && !IsAxeLike(definition) && !IsClubLike(definition) && !IsKnifeLike(definition) && !IsSpearLike(definition) && !IsPolearmLike(definition) && !IsFistsLike(definition) && !IsShieldLike(definition) && !IsPickaxeLike(definition) && !IsBowLike(definition) && !IsCrossbowLike(definition)) { return IsMagicLike(definition); } return true; } private static bool IsEquipmentSkillTypeSurface(ItemDefinition definition) { return IsShieldBlockSurface(definition); } private static bool IsEquipmentArmorSurface(ItemDefinition definition) { ItemType[] array = new ItemType[5]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); return IsItemType(definition, (ItemType[])(object)array); } private static bool IsSwordLike(ItemDefinition definition) { return IsSkillAttack(definition, (SkillType)1); } private static bool IsAxeLike(ItemDefinition definition) { return IsSkillAttack(definition, (SkillType)7); } private static bool IsClubLike(ItemDefinition definition) { return IsSkillAttack(definition, (SkillType)3); } private static bool IsKnifeLike(ItemDefinition definition) { return IsSkillAttack(definition, (SkillType)2); } private static bool IsSpearLike(ItemDefinition definition) { return IsSkillAttack(definition, (SkillType)5); } private static bool IsPolearmLike(ItemDefinition definition) { if (!IsSkillAttack(definition, (SkillType)4)) { return IsItemType(definition, (ItemType)20); } return true; } private static bool IsFistsLike(ItemDefinition definition) { return IsSkillAttack(definition, (SkillType)11); } private static bool IsPickaxeLike(ItemDefinition definition) { return IsSkillType(definition, (SkillType)12); } private static bool IsBowLike(ItemDefinition definition) { if (!IsSkillAttack(definition, (SkillType)8)) { if (IsItemType(definition, (ItemType)4)) { SkillType[] array = new SkillType[3]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); return !IsSkillType(definition, (SkillType[])(object)array); } return false; } return true; } private static bool IsCrossbowLike(ItemDefinition definition) { if (!IsAmmoLike(definition) && IsSkillType(definition, (SkillType)14)) { if (!IsItemType(definition, (ItemType)4)) { return HasAttackAnimation(definition.Combat?.PrimaryAttack); } return true; } return false; } private static bool IsAmmoLike(ItemDefinition definition) { return IsItemType(definition, (ItemType)9, (ItemType)23); } private static bool IsMagicLike(ItemDefinition definition) { return IsSkillType(definition, (SkillType)9, (SkillType)10); } private static bool IsFoodLike(ItemDefinition definition) { if (!IsItemType(definition, (ItemType)2) && !HasFoodStats(definition.Food)) { return HasStatusEffectValue(definition.Effects?.ConsumeStatusEffect); } return true; } private static bool HasDamageTakenModifiers(DamageTakenModifierDefinition? modifiers) { if (modifiers == null) { return false; } if (!IsNonDefaultDamageModifier(modifiers.Blunt) && !IsNonDefaultDamageModifier(modifiers.Slash) && !IsNonDefaultDamageModifier(modifiers.Pierce) && !IsNonDefaultDamageModifier(modifiers.Chop) && !IsNonDefaultDamageModifier(modifiers.Pickaxe) && !IsNonDefaultDamageModifier(modifiers.Fire) && !IsNonDefaultDamageModifier(modifiers.Frost) && !IsNonDefaultDamageModifier(modifiers.Lightning) && !IsNonDefaultDamageModifier(modifiers.Poison)) { return IsNonDefaultDamageModifier(modifiers.Spirit); } return true; } private static bool IsNonDefaultDamageModifier(string? value) { if (string.IsNullOrWhiteSpace(value)) { return false; } string text = value.Trim(); if (!text.Equals("Normal", StringComparison.OrdinalIgnoreCase)) { return !text.Equals("None", StringComparison.OrdinalIgnoreCase); } return false; } private static bool HasAttackSpecial(AttackDefinition? attack) { if (attack != null) { if (!HasMissingHealth(attack.MissingHealth)) { return HasSpawnOnHit(attack.SpawnOnHit); } return true; } return false; } private static bool HasMissingHealth(string? value) { string[] parts = DataForgeValue.SplitTuple(value); if (!(Math.Abs(GetTupleFloat(parts, 0)) > 0.0001f) && !(Math.Abs(GetTupleFloat(parts, 1)) > 0.0001f)) { return Math.Abs(GetTupleFloat(parts, 2)) > 0.0001f; } return true; } private static bool HasSpawnOnHit(string? value) { string[] array = DataForgeValue.SplitTuple(value); if (array.Length == 0 || array[0].Length == 0 || DataForgeValue.IsNone(array[0])) { return false; } return true; } private static bool IsSkillAttack(ItemDefinition definition, params SkillType[] skillTypes) { if (!IsAmmoLike(definition) && IsSkillType(definition, skillTypes)) { return GetTotalDamage(definition.Combat?.Damage) > 0f; } return false; } private static bool HasAttackAnimation(AttackDefinition? attack) { return !string.IsNullOrEmpty(attack?.Animation); } private static bool IsItemType(ItemDefinition definition, params ItemType[] itemTypes) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (TryGetItemType(definition, out var itemType)) { return itemTypes.Contains(itemType); } return false; } private static bool IsSkillType(ItemDefinition definition, params SkillType[] skillTypes) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (TryGetSkillType(definition, out var skillType)) { return skillTypes.Contains(skillType); } return false; } private static bool TryGetItemType(ItemDefinition definition, out ItemType itemType) { return Enum.TryParse(definition.Basics?.ItemType, ignoreCase: true, out itemType); } private static bool TryGetSkillType(ItemDefinition definition, out SkillType skillType) { return Enum.TryParse(definition.Equipment?.SkillType, ignoreCase: true, out skillType); } private static float GetTotalDamage(DamageDefinition? damage) { if (damage == null) { return 0f; } return GetFirstTupleFloat(damage.Blunt) + GetFirstTupleFloat(damage.Slash) + GetFirstTupleFloat(damage.Pierce) + GetFirstTupleFloat(damage.Chop) + GetFirstTupleFloat(damage.Pickaxe) + GetFirstTupleFloat(damage.Fire) + GetFirstTupleFloat(damage.Frost) + GetFirstTupleFloat(damage.Lightning) + GetFirstTupleFloat(damage.Poison) + GetFirstTupleFloat(damage.Spirit); } private static bool HasFoodStats(string? food) { string[] parts = DataForgeValue.SplitTuple(food); if (!(GetTupleFloat(parts, 0) > 0f) && !(GetTupleFloat(parts, 1) > 0f)) { return GetTupleFloat(parts, 2) > 0f; } return true; } private static bool HasDurabilityEnabled(string? durability) { string[] array = DataForgeValue.SplitTuple(durability); bool result = default(bool); return array.Length > 2 && bool.TryParse(array[2], out result) && result; } private static bool HasAnyEffect(EffectsDefinition? effects) { if (effects != null) { if (!HasStatusEffectValue(effects.EquipStatusEffect) && !HasStatusEffectValue(effects.Set) && !HasStatusEffectValue(effects.ConsumeStatusEffect) && !HasStatusEffectValue(effects.AttackStatusEffect) && !HasStatusEffectValue(effects.PerfectBlockStatusEffect)) { return HasStatusEffectValue(effects.FullAdrenalineStatusEffect); } return true; } return false; } private static bool HasStatusEffectValue(string? value) { if (string.IsNullOrWhiteSpace(value)) { return false; } string[] array = DataForgeValue.SplitTuple(value); foreach (string text in array) { if (text.Length > 0 && !DataForgeValue.IsNone(text) && !float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var _)) { return true; } } return false; } private static float GetFirstTupleFloat(string? value) { return GetTupleFloat(DataForgeValue.SplitTuple(value), 0); } private static float GetTupleFloat(string[] parts, int index) { if (index >= parts.Length || !float.TryParse(parts[index], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return 0f; } return result; } private static string? FormatReferenceAttackCost(string? value) { string[] array = DataForgeValue.SplitTuple(value); if (array.Length == 0) { return value; } int num = array.Length - 1; while (num >= 0 && IsZeroTupleFloat(array[num])) { num--; } if (num < 0) { return null; } return string.Join(", ", array.Take(num + 1)); } private static bool ShouldExposeAttackDraw(string? value) { string[] array = DataForgeValue.SplitTuple(value); if (array.Length == 0 || string.IsNullOrWhiteSpace(array[0])) { return false; } if (float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return Math.Abs(result) > 0.0001f; } return true; } private static bool ShouldExposeAttackReload(string? value) { string[] array = DataForgeValue.SplitTuple(value); if (array.Length == 0 || string.IsNullOrWhiteSpace(array[0])) { return false; } bool result; return !bool.TryParse(array[0], out result) || result; } private static bool ShouldExposeLastChainDamageMultiplier(PrimaryAttackDefinition? attack) { if (attack != null && attack.ChainLevels > 1 && attack.LastChainDamageMultiplier.HasValue) { return Math.Abs(attack.LastChainDamageMultiplier.Value - 2f) > 0.0001f; } return false; } private static bool IsZeroTupleFloat(string value) { if (!string.IsNullOrWhiteSpace(value)) { if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return Math.Abs(result) <= 0.0001f; } return false; } return true; } private static string FormatBool(bool? value) { return (value == true).ToString().ToLowerInvariant(); } private static string FormatFloat(float? value) { return value.GetValueOrDefault().ToString("0.###", CultureInfo.InvariantCulture); } private static bool IsBombProjectileAttack(Attack? attack) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 GameObject val = attack?.m_attackProjectile; if ((Object)(object)val == (Object)null) { return false; } if ((Object)(object)val.GetComponent() != (Object)null && ((int)attack.m_attackType == 2 || val.GetComponent() != null)) { return string.Equals(attack.m_attackAnimation, "throw_bomb", StringComparison.OrdinalIgnoreCase); } return false; } private static string FormatDamagePair(float damage, float damagePerLevel) { return FormatFloatPair(damage, damagePerLevel); } private static string FormatFloatPair(float first, float second) { return FormatFloat(first) + ", " + FormatFloat(second); } private static string? FormatMissingHealth(Attack attack) { if (attack == null) { return null; } return string.Join(", ", FormatFloat(attack.m_damageMultiplierPerMissingHP), FormatFloat(attack.m_damageMultiplierByTotalHealthMissing), FormatFloat(attack.m_staminaReturnPerMissingHP)); } private static string? FormatSpawnOnHit(Attack attack) { if (attack == null) { return null; } return (((Object)(object)attack.m_spawnOnHit != (Object)null) ? GetPrefabName(attack.m_spawnOnHit) : "None") + ", " + FormatFloat(Mathf.Clamp01(attack.m_spawnOnHitChance)); } private static string FormatAttackCost(Attack attack) { return string.Join(", ", FormatFloat(attack.m_attackStamina), FormatFloat(attack.m_attackEitr), FormatFloat(attack.m_attackHealth), FormatFloat(attack.m_attackHealthPercentage)); } private static string FormatAttackDraw(Attack attack) { return string.Join(", ", FormatFloat(attack.m_drawDurationMin), FormatFloat(attack.m_drawStaminaDrain), FormatFloat(attack.m_drawEitrDrain)); } private static string FormatAttackReload(Attack attack) { return string.Join(", ", FormatBool(attack.m_requiresReload), FormatFloat(attack.m_reloadTime), FormatFloat(attack.m_reloadStaminaDrain), FormatFloat(attack.m_reloadEitrDrain)); } private static string? FormatSetEffect(string? setName, int setSize, StatusEffect? setStatusEffect) { string text = (((Object)(object)setStatusEffect != (Object)null) ? ((Object)setStatusEffect).name : ""); if (string.IsNullOrWhiteSpace(setName) && setSize <= 0 && string.IsNullOrWhiteSpace(text)) { return null; } return setName + ", " + Math.Max(0, setSize).ToString(CultureInfo.InvariantCulture) + ", " + text; } private static string? FormatAttackStatusEffect(StatusEffect? statusEffect, float chance) { string text = (((Object)(object)statusEffect != (Object)null) ? ((Object)statusEffect).name : ""); if (string.IsNullOrWhiteSpace(text)) { return null; } return text + ", " + FormatFloat(Mathf.Clamp01(chance)); } } [HarmonyPatch(typeof(ItemDrop), "Awake")] internal static class DataForgeItemDropAwakePatch { private static void Postfix(ItemDrop __instance) { ItemOverrideManager.RepairDropPrefab(__instance); } } [HarmonyPatch(typeof(ItemDrop), "DropItem")] internal static class DataForgeItemDropDropItemPatch { private static void Prefix(ItemData item) { ItemOverrideManager.RepairDropPrefab(item); } private static void Postfix(ItemDrop __result) { if (!((Object)(object)__result == (Object)null)) { ItemOverrideManager.RepairDropPrefab(__result); if ((Object)(object)((Component)__result).gameObject != (Object)null && !((Component)__result).gameObject.activeSelf && ItemOverrideManager.IsCreatedCloneDrop(__result)) { ((Component)__result).gameObject.SetActive(true); } } } } internal static class RecipeOverrideManager { private sealed class RecipeKeyCandidate { internal string RecipeName { get; set; } = ""; internal string RecipeKey { get; set; } = ""; internal string ItemName { get; set; } = ""; } internal sealed class RecipeEntry { internal string LogContext { get; private set; } = ""; public string Recipe { get; set; } = ""; public bool Override { get; set; } = true; public bool Remove { get; set; } public string? CraftingStation { get; set; } public string? RequireOnlyOneIngredient { get; set; } public int? ListSortWeight { get; set; } public List? Resources { get; set; } public List? QualityBonus { get; set; } internal bool HasDefinition { get { if (!ParseRecipeAmount(Recipe).HasValue && CraftingStation == null && RequireOnlyOneIngredient == null && !ListSortWeight.HasValue && Resources == null) { return QualityBonus != null; } return true; } } internal void SetLogContext(string value) { LogContext = value; } } internal sealed class RecipeFullEntry { public string Recipe { get; set; } = ""; public bool Override { get; set; } = true; public bool Remove { get; set; } public string? CraftingStation { get; set; } public string? RequireOnlyOneIngredient { get; set; } public int? ListSortWeight { get; set; } public List? Resources { get; set; } public List? QualityBonus { get; set; } internal static RecipeFullEntry From(string name, RecipeDefinition definition, Dictionary referenceKeys) { return new RecipeFullEntry { Recipe = FormatRecipeHeader(ToReferenceRecipeKey(name, definition, referenceKeys), definition.Amount, includeDefaultAmount: true), Override = true, Remove = false, CraftingStation = FormatStation(definition.CraftingStation, definition.MinStationLevel), RequireOnlyOneIngredient = definition.RequireOnlyOneIngredient, ListSortWeight = definition.ListSortWeight, Resources = definition.Resources, QualityBonus = definition.QualityBonus }; } } internal sealed class RecipeReferenceEntry { public string Recipe { get; set; } = ""; public string? CraftingStation { get; set; } public string? RequireOnlyOneIngredient { get; set; } public int? ListSortWeight { get; set; } public List? Resources { get; set; } internal static RecipeReferenceEntry From(string name, RecipeDefinition definition, Dictionary referenceKeys) { bool includeAmountPerLevel = IsResultItemUpgradeable(definition.Item); bool includeExtraAmountOnlyOneIngredient = IsRequireOnlyOneIngredient(definition.RequireOnlyOneIngredient); return ReferenceValue.ClonePruned(new RecipeReferenceEntry { Recipe = FormatRecipeHeader(ToReferenceRecipeKey(name, definition, referenceKeys), definition.Amount, includeDefaultAmount: false), CraftingStation = FormatStation(definition.CraftingStation, definition.MinStationLevel), RequireOnlyOneIngredient = definition.RequireOnlyOneIngredient, ListSortWeight = definition.ListSortWeight, Resources = definition.Resources?.Select((RequirementDefinition resource) => ResourceReferenceDefinition.From(resource, includeAmountPerLevel, includeExtraAmountOnlyOneIngredient)).ToList() }); } } internal sealed class ResourceReferenceDefinition : Dictionary { internal static ResourceReferenceDefinition From(RequirementDefinition definition, bool includeAmountPerLevel, bool includeExtraAmountOnlyOneIngredient) { ResourceReferenceDefinition resourceReferenceDefinition = new ResourceReferenceDefinition(); string key = definition.Item ?? ""; List list = new List(); if (definition.Amount.HasValue) { list.Add(definition.Amount.Value.ToString(CultureInfo.InvariantCulture)); } if (includeAmountPerLevel && definition.AmountPerLevel.HasValue && definition.AmountPerLevel.Value != 0) { list.Add(definition.AmountPerLevel.Value.ToString(CultureInfo.InvariantCulture)); } if (includeExtraAmountOnlyOneIngredient && definition.ExtraAmountOnlyOneIngredient.HasValue && definition.ExtraAmountOnlyOneIngredient.Value != 0) { if (!definition.Amount.HasValue) { list.Add("0"); } if (!includeAmountPerLevel || !definition.AmountPerLevel.HasValue || definition.AmountPerLevel.Value == 0) { list.Add("0"); } list.Add(definition.ExtraAmountOnlyOneIngredient.Value.ToString(CultureInfo.InvariantCulture)); } resourceReferenceDefinition[key] = string.Join(", ", list); return resourceReferenceDefinition; } } internal sealed class RecipeDefinition { public string? Item { get; set; } public int? Amount { get; set; } public string? CraftingStation { get; set; } public int? MinStationLevel { get; set; } public string? RequireOnlyOneIngredient { get; set; } public int? ListSortWeight { get; set; } public List? Resources { get; set; } public List? QualityBonus { get; set; } internal static RecipeDefinition From(RecipeEntry entry) { return new RecipeDefinition { Amount = ParseRecipeAmount(entry.Recipe), CraftingStation = entry.CraftingStation, RequireOnlyOneIngredient = entry.RequireOnlyOneIngredient, ListSortWeight = entry.ListSortWeight, Resources = entry.Resources, QualityBonus = entry.QualityBonus }; } internal static RecipeDefinition From(Recipe recipe) { return new RecipeDefinition { Item = GetItemName(recipe.m_item), Amount = recipe.m_amount, CraftingStation = GetStationName(recipe.m_craftingStation), MinStationLevel = recipe.m_minStationLevel, RequireOnlyOneIngredient = FormatRequireOnlyOneIngredient(recipe.m_requireOnlyOneIngredient, recipe.m_qualityResultAmountMultiplier), ListSortWeight = recipe.m_listSortWeight, Resources = (recipe.m_resources?.Select(RequirementDefinition.From).ToList() ?? new List()), QualityBonus = null }; } } internal sealed class RequirementDefinition { public string? Item { get; set; } public int? Amount { get; set; } public int? AmountPerLevel { get; set; } public int? ExtraAmountOnlyOneIngredient { get; set; } internal static RequirementDefinition From(Requirement requirement) { return new RequirementDefinition { Item = GetItemName(requirement.m_resItem), Amount = requirement.m_amount, AmountPerLevel = requirement.m_amountPerLevel, ExtraAmountOnlyOneIngredient = requirement.m_extraAmountOnlyOneIngredient }; } } internal sealed class QualityBonusDefinition { public string? Item { get; set; } public float? AmountPerLevel { get; set; } } private sealed class QualityBonusRule { internal string Input { get; } internal string PrefabName { get; } internal string SharedName { get; } internal float AmountPerLevel { get; } internal QualityBonusRule(string input, string prefabName, string sharedName, float amountPerLevel) { Input = input; PrefabName = prefabName; SharedName = sharedName; AmountPerLevel = amountPerLevel; } } private sealed class RequirementDefinitionYamlConverter : IYamlTypeConverter { public bool Accepts(Type type) { return type == typeof(RequirementDefinition); } public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { if (parser.TryConsume(out var @event)) { List> list = new List>(); MappingEnd event2; while (!parser.Accept(out event2)) { YamlDotNet.Core.Events.Scalar scalar = parser.Consume(); if (parser.Accept(out @event) || parser.Accept(out var _)) { throw new YamlException(scalar.Start, scalar.End, "Unsupported nested resource shorthand for '" + scalar.Value + "'."); } YamlDotNet.Core.Events.Scalar scalar2 = parser.Consume(); list.Add(new KeyValuePair(scalar.Value, scalar2.Value)); } parser.Consume(); if (list.Count == 1 && !IsRequirementProperty(list[0].Key)) { return ParseShorthandRequirement(list[0].Key, list[0].Value); } throw new YamlException("Recipe resources must use shorthand, for example '- Iron: 20, 10, 0'."); } YamlDotNet.Core.Events.Scalar scalar3 = parser.Consume(); throw new YamlException(scalar3.Start, scalar3.End, "Recipe resources must use mapping shorthand, for example '- Iron: 20, 10, 0'."); } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) { RequirementDefinition requirementDefinition = (RequirementDefinition)value; emitter.Emit(new MappingStart()); emitter.Emit(new YamlDotNet.Core.Events.Scalar(requirementDefinition.Item ?? "")); emitter.Emit(new YamlDotNet.Core.Events.Scalar(FormatShorthandRequirementValue(requirementDefinition))); emitter.Emit(new MappingEnd()); } private static RequirementDefinition ParseShorthandRequirement(string item, string value) { string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select part.Trim()).ToArray(); RequirementDefinition requirementDefinition = new RequirementDefinition { Item = item }; if (array.Length != 0 && int.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { requirementDefinition.Amount = result; } if (array.Length > 1 && int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { requirementDefinition.AmountPerLevel = result2; } if (array.Length > 2 && int.TryParse(array[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result3)) { requirementDefinition.ExtraAmountOnlyOneIngredient = result3; } return requirementDefinition; } private static string FormatShorthandRequirementValue(RequirementDefinition requirement) { List values = new List { requirement.Amount.GetValueOrDefault().ToString(CultureInfo.InvariantCulture), requirement.AmountPerLevel.GetValueOrDefault().ToString(CultureInfo.InvariantCulture), requirement.ExtraAmountOnlyOneIngredient.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) }; return string.Join(", ", values); } private static bool IsRequirementProperty(string key) { if (!key.Equals("item", StringComparison.OrdinalIgnoreCase) && !key.Equals("amount", StringComparison.OrdinalIgnoreCase) && !key.Equals("amountPerLevel", StringComparison.OrdinalIgnoreCase)) { return key.Equals("extraAmountOnlyOneIngredient", StringComparison.OrdinalIgnoreCase); } return true; } } private sealed class QualityBonusDefinitionYamlConverter : IYamlTypeConverter { public bool Accepts(Type type) { return type == typeof(QualityBonusDefinition); } public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) { if (!parser.TryConsume(out var @event)) { YamlDotNet.Core.Events.Scalar scalar = parser.Consume(); throw new YamlException(scalar.Start, scalar.End, "Recipe qualityBonus entries must use shorthand, for example '- Fish1: 1'."); } List> list = new List>(); MappingEnd event2; while (!parser.Accept(out event2)) { YamlDotNet.Core.Events.Scalar scalar2 = parser.Consume(); if (parser.Accept(out @event) || parser.Accept(out var _)) { throw new YamlException(scalar2.Start, scalar2.End, "Unsupported nested qualityBonus shorthand for '" + scalar2.Value + "'."); } YamlDotNet.Core.Events.Scalar scalar3 = parser.Consume(); list.Add(new KeyValuePair(scalar2.Value, scalar3.Value)); } parser.Consume(); if (list.Count != 1) { throw new YamlException("Recipe qualityBonus entries must use shorthand, for example '- Fish1: 1'."); } return ParseQualityBonus(list[0].Key, list[0].Value); } public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) { QualityBonusDefinition qualityBonusDefinition = (QualityBonusDefinition)value; emitter.Emit(new MappingStart()); emitter.Emit(new YamlDotNet.Core.Events.Scalar(qualityBonusDefinition.Item ?? "")); emitter.Emit(new YamlDotNet.Core.Events.Scalar(qualityBonusDefinition.AmountPerLevel.GetValueOrDefault().ToString("0.###", CultureInfo.InvariantCulture))); emitter.Emit(new MappingEnd()); } private static QualityBonusDefinition ParseQualityBonus(string item, string value) { QualityBonusDefinition qualityBonusDefinition = new QualityBonusDefinition { Item = item }; if (float.TryParse(value.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { qualityBonusDefinition.AmountPerLevel = result; } return qualityBonusDefinition; } } private const string DomainName = "recipes"; private const string OverrideFileName = "recipes.yml"; private const string ReferenceFileName = "recipes.reference.yml"; private const string FullScaffoldFileName = "recipes.full.yml"; private const string SyncedPayloadKey = "recipes"; private const long ReloadDelayTicks = 10000000L; private const string ReferenceStateKey = "recipes"; private const string ReferenceLogicVersion = "2026-06-24-recipe-reference-state-v2"; private static readonly object StateLock = new object(); private static readonly Dictionary Baselines = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary BaselineRecipes = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet CreatedRecipes = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet RuntimeAppliedRecipeKeys = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary> ActiveQualityBonuses = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly List EmptyQualityBonusRules = new List(); private static Dictionary>? RecipeLookupCache; private static readonly MethodInfo? UpdateKnownRecipesListMethod = AccessTools.Method(typeof(Player), "UpdateKnownRecipesList", (Type[])null, (Type[])null); private static readonly MethodInfo? InventoryGuiUpdateRecipeMethod = AccessTools.Method(typeof(InventoryGui), "UpdateRecipe", (Type[])null, (Type[])null); private static readonly MethodInfo? InventoryGuiUpdateCraftingPanelMethod = AccessTools.Method(typeof(InventoryGui), "UpdateCraftingPanel", (Type[])null, (Type[])null); private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).WithTypeConverter(new RequirementDefinitionYamlConverter()).WithTypeConverter(new QualityBonusDefinitionYamlConverter()) .Build(); private static readonly ISerializer SparseSerializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).WithTypeConverter(new RequirementDefinitionYamlConverter()).WithTypeConverter(new QualityBonusDefinitionYamlConverter()) .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull) .DisableAliases() .Build(); private static readonly ISerializer FullSerializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).WithTypeConverter(new RequirementDefinitionYamlConverter()).WithTypeConverter(new QualityBonusDefinitionYamlConverter()) .DisableAliases() .Build(); private static List ActiveEntries = new List(); private static CustomSyncedValue? SyncedPayload; private static string? LastAppliedSyncedPayload; private static FileSystemWatcher? Watcher; private static DataForgeFileWatcher.DebouncedAction? ReloadDebouncer; private static bool ObjectDbReady; private static bool ZNetSceneReady; private static int ActiveQualityBonusRecipeCount; private static bool RecipeLookupCacheDirty = true; private static bool RuntimeStateWasApplied; private static Dictionary ActiveEntrySignaturesByRecipe = new Dictionary(StringComparer.OrdinalIgnoreCase); private static HashSet? PendingChangedRecipeKeys; private static bool HasPendingScopedApply; private static bool ForceNextFullApply = true; private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); internal static void Initialize(ConfigSync configSync) { SyncedPayload = new CustomSyncedValue(configSync, "recipes", ""); SyncedPayload.ValueChanged += OnSyncedPayloadChanged; } internal static void Dispose() { if (SyncedPayload != null) { SyncedPayload.ValueChanged -= OnSyncedPayloadChanged; } Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } internal static void SetupFileWatcher() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } else { EnsureConfigDirectoryAndDefaultOverride(); Watcher?.Dispose(); ReloadDebouncer?.Dispose(); ReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadYamlValues); Watcher = DataForgeFileWatcher.Create(ConfigDirectory, "*.*", includeSubdirectories: false, ReadYamlValues); } } internal static void ReloadFromDiskAndSync() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { ApplySyncedPayload(SyncedPayload?.Value ?? ""); return; } EnsureConfigDirectoryAndDefaultOverride(); List list = LoadEntriesFromDisk(); lock (StateLock) { SetActiveEntries(list); } PublishPayload(SerializeEntries(list)); ApplyCurrentConfiguration(); } internal static void OnObjectDBReady() { if (!((Object)(object)ObjectDB.instance == (Object)null)) { ObjectDbReady = true; if (!ShouldSkipRemoteClientBaselineWork()) { WriteGeneratedArtifacts(); ApplyCurrentConfiguration(); } } } internal static void OnZNetSceneReady() { if (!((Object)(object)ZNetScene.instance == (Object)null)) { ZNetSceneReady = true; if (!ShouldSkipRemoteClientBaselineWork()) { ApplyCurrentConfiguration(); WriteGeneratedArtifacts(); } } } internal static void ApplyCurrentConfiguration() { if (!ObjectDbReady || (Object)(object)ObjectDB.instance == (Object)null || !ZNetSceneReady || (Object)(object)ZNetScene.instance == (Object)null || ShouldSkipRemoteClientBaselineWork()) { return; } InvalidateRecipeLookupCache(); List entries; HashSet hashSet; lock (StateLock) { entries = ActiveEntries.ToList(); hashSet = ConsumePendingChangedRecipeKeys(); } if (hashSet != null && hashSet.Count == 0) { return; } List list = FilterEntries(entries, hashSet); CaptureBaselinesForEntriesIfNeeded(list); HashSet runtimeApplyRecipeKeys = GetRuntimeApplyRecipeKeys(list); HashSet hashSet2 = CleanupCreatedRecipes(entries); EnsureAddedRecipes(entries); RestoreBaselineRecipes(runtimeApplyRecipeKeys); ClearActiveQualityBonuses(); if (!DataForgePlugin.RecipeOverridesEnabled) { RefreshLiveRecipeState(); UpdateRuntimeAppliedRecipeState(new List()); return; } foreach (RecipeEntry item in list) { using (DataForgeLogContext.Push(item.LogContext)) { string text = ToRecipeName(item.Recipe); if (!item.Override) { continue; } if (item.Remove) { RemoveRecipe(item.Recipe, !hashSet2.Contains(ToRecipeName(item.Recipe))); continue; } Recipe val = ResolveRecipe(item.Recipe); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning("Could not find recipe '" + text + "'."); } else if (item.HasDefinition) { ApplyDefinition(val, RecipeDefinition.From(item)); ApplyQualityBonuses(val, item.QualityBonus); } } } RefreshLiveRecipeState(); UpdateRuntimeAppliedRecipeState(entries); } private static void RefreshLiveRecipeState() { RefreshKnownRecipes(); RefreshInventoryGuiRecipes(); } private static bool ShouldSkipRemoteClientBaselineWork() { if (!DataForgePlugin.IsRemoteServerClient) { return false; } lock (StateLock) { return ActiveEntries.Count == 0 && CreatedRecipes.Count == 0 && ActiveQualityBonusRecipeCount == 0; } } private static void RefreshKnownRecipes() { if (UpdateKnownRecipesListMethod == null || Player.s_players == null) { return; } foreach (Player s_player in Player.s_players) { if (!((Object)(object)s_player == (Object)null)) { try { UpdateKnownRecipesListMethod.Invoke(s_player, null); } catch (Exception ex) { DataForgePlugin.Log.LogDebug((object)("Could not refresh known recipes after recipe update: " + ex.Message)); } } } } private static void RefreshInventoryGuiRecipes() { InventoryGui instance = InventoryGui.instance; if (!((Object)(object)instance == (Object)null)) { InvokeInventoryGuiRefresh(instance, InventoryGuiUpdateRecipeMethod, "UpdateRecipe"); InvokeInventoryGuiRefresh(instance, InventoryGuiUpdateCraftingPanelMethod, "UpdateCraftingPanel"); } } private static void InvokeInventoryGuiRefresh(InventoryGui gui, MethodInfo? method, string methodName) { if (method == null) { return; } try { method.Invoke(gui, null); } catch (Exception ex) { DataForgePlugin.Log.LogDebug((object)("Could not refresh InventoryGui." + methodName + " after recipe update: " + ex.Message)); } } private static void ReadYamlValues(object sender, FileSystemEventArgs e) { if (ShouldReloadForFileEvent(e)) { ReloadDebouncer?.Schedule(); } } private static void ReloadYamlValues() { try { DataForgePlugin.Log.LogDebug((object)"Reloading recipe YAML files..."); ReloadFromDiskAndSync(); DataForgePlugin.Log.LogInfo((object)"Recipe YAML reload complete."); } catch (Exception arg) { DataForgePlugin.Log.LogError((object)$"Error reloading recipe YAML files: {arg}"); } } private static bool ShouldReloadForFileEvent(FileSystemEventArgs e) { if (!DataForgePlugin.UsesLocalAuthorityFiles) { return false; } if (IsOverrideFile(e.FullPath)) { return true; } if (e is RenamedEventArgs e2) { return IsOverrideFile(e2.OldFullPath); } return false; } private static void OnSyncedPayloadChanged() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { string payload = SyncedPayload?.Value ?? ""; DataForgeProfiler.Profile(string.Format("{0}.ApplySyncedPayload chars={1}", "recipes", payload.Length), delegate { ApplySyncedPayload(payload); }); } } private static void ApplySyncedPayload(string payload) { if (!string.Equals(LastAppliedSyncedPayload, payload, StringComparison.Ordinal)) { LastAppliedSyncedPayload = payload; List activeEntries = DeserializeEntries(payload, "synced recipe payload"); lock (StateLock) { SetActiveEntries(activeEntries); } ApplyCurrentConfiguration(); } } private static void SetActiveEntries(List entries) { Dictionary dictionary = BuildEntrySignaturesByRecipe(entries); if (!ForceNextFullApply) { PendingChangedRecipeKeys = GetChangedKeys(ActiveEntrySignaturesByRecipe, dictionary); HasPendingScopedApply = true; } ActiveEntries = entries; ActiveEntrySignaturesByRecipe = dictionary; } private static HashSet? ConsumePendingChangedRecipeKeys() { if (ForceNextFullApply) { ForceNextFullApply = false; PendingChangedRecipeKeys = null; HasPendingScopedApply = false; return null; } if (!HasPendingScopedApply) { return null; } HashSet? result = PendingChangedRecipeKeys ?? new HashSet(StringComparer.OrdinalIgnoreCase); PendingChangedRecipeKeys = null; HasPendingScopedApply = false; return result; } private static Dictionary BuildEntrySignaturesByRecipe(List entries) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (RecipeEntry entry in entries) { if (!string.IsNullOrWhiteSpace(entry.Recipe)) { if (!dictionary.TryGetValue(entry.Recipe, out var value)) { value = new List(); dictionary[entry.Recipe] = value; } value.Add(entry); } } Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair> item in dictionary) { dictionary2[item.Key] = SparseSerializer.Serialize(item.Value); } return dictionary2; } private static HashSet GetChangedKeys(Dictionary oldSignatures, Dictionary newSignatures) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair oldSignature in oldSignatures) { if (!newSignatures.TryGetValue(oldSignature.Key, out string value) || !string.Equals(oldSignature.Value, value, StringComparison.Ordinal)) { hashSet.Add(oldSignature.Key); } } foreach (KeyValuePair newSignature in newSignatures) { if (!oldSignatures.TryGetValue(newSignature.Key, out string value2) || !string.Equals(value2, newSignature.Value, StringComparison.Ordinal)) { hashSet.Add(newSignature.Key); } } return hashSet; } private static List FilterEntries(List entries, HashSet? recipeKeys) { if (recipeKeys != null) { return entries.Where((RecipeEntry entry) => recipeKeys.Contains(entry.Recipe)).ToList(); } return entries; } private static void PublishPayload(string payload) { DataForgeSync.PublishPayload(SyncedPayload, "recipes", payload); } private static List LoadEntriesFromDisk() { return DataForgeOverrideFiles.LoadEntries(GetOverrideFiles(), DeserializeEntries); } private static List DeserializeEntries(string yaml, string source) { if (string.IsNullOrWhiteSpace(yaml)) { return new List(); } try { return NormalizeEntries(Deserializer.Deserialize>(yaml), source); } catch (Exception ex) { DataForgePlugin.Log.LogError((object)("Failed to parse " + source + ": " + ex.Message)); return new List(); } } private static List NormalizeEntries(List? entries, string source) { List list = new List(); if (entries == null) { return list; } int num = 0; foreach (RecipeEntry entry in entries) { num++; string text = DataForgeLogContext.FormatSource(source, num); if (string.IsNullOrWhiteSpace(entry.Recipe)) { DataForgeLogContext.Warning(text + ": Skipping recipe entry without recipe."); continue; } using (DataForgeLogContext.Push(text)) { if (!TryNormalizeRecipeHeader(entry.Recipe, out string normalizedRecipe, out string error)) { DataForgeLogContext.Warning(text + ": Skipping recipe entry '" + entry.Recipe + "'. " + error); continue; } entry.Recipe = normalizedRecipe; } entry.SetLogContext(text + " recipe=" + ToRecipeKey(entry.Recipe)); list.Add(entry); } return list; } private static string SerializeEntries(List entries) { return SparseSerializer.Serialize(entries); } private static IEnumerable GetOverrideFiles() { return DataForgeOverrideFiles.GetOverrideFiles(ConfigDirectory, IsOverrideFile); } private static bool IsOverrideFile(string path) { string extension = Path.GetExtension(path); if (!extension.Equals(".yml", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".yaml", StringComparison.OrdinalIgnoreCase)) { return false; } string fileName = Path.GetFileName(path); if (fileName.Equals("recipes.reference.yml", StringComparison.OrdinalIgnoreCase) || fileName.Equals("recipes.full.yml", StringComparison.OrdinalIgnoreCase)) { return false; } if (!fileName.Equals("recipes.yml", StringComparison.OrdinalIgnoreCase)) { return fileName.StartsWith("recipes_", StringComparison.OrdinalIgnoreCase); } return true; } private static void EnsureConfigDirectoryAndDefaultOverride() { DataForgeOverrideFiles.EnsureDefaultOverride(ConfigDirectory, "recipes.yml", GetOverrideFiles, DefaultOverrideTemplate); } private static string DefaultOverrideTemplate() { return string.Join(Environment.NewLine, "# DataForge recipe overrides.", "# Copy entries from recipes.reference.yml, or run `dataforge:full recipe` to generate recipes.full.yml for exhaustive field examples.", "# You can also create additional override files like recipes_asdf.yml; DataForge loads recipes.yml and recipes_*.yml together.", "# Omitted fields keep the current recipe value. Values below are common defaults or examples.", "#", "# Schema:", "# - recipe: SwordIron, 1 # result item prefab; use SwordIron;1 / SwordIron;2 when reference lists multiple recipes. Custom additions can use SwordIron;myVariant.", "# # Variant ids after ';' should be one word; use letters, numbers, '_' or '-' rather than spaces.", "# override: true # default true; false skips this entire entry, including remove.", "# remove: false # default false; true removes this recipe from ObjectDB.m_recipes.", "# craftingStation: forge, 2 # station prefab and optional min station level. Use none for hand craft.", "# requireOnlyOneIngredient: false, 1 # true, 1 => any one listed ingredient can craft; selected ingredient quality increases output by ceil((quality - 1) * amount * 1). If false, the multiplier is effectively unused.", "# listSortWeight: 100 # UI sort weight.", "# resources:", "# - Iron: 20, 10, 0 # shorthand: itemPrefab: amount, upgradeAmount, extraAmountOnlyOneIngredient. Reference only shows upgradeAmount when the result item has maxQuality > 1.", "# - Wood: 5 # shorthand: itemPrefab: amount.", "# qualityBonus:", "# - Fish1: 1 # DataForge extension: if this resource is consumed at quality 3, add ceil((3 - 1) * 1) result items per craft.", "#", "# Example:", "# - recipe: SwordIron, 1", "# craftingStation: forge, 2", "# resources:", "# - Iron: 20, 10", "# - Wood: 5") + Environment.NewLine; } private static void CaptureAllBaselinesIfNeeded() { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } int num = 0; foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if (CaptureBaseline(recipe)) { num++; } } if (num > 0) { DataForgePlugin.Log.LogInfo((object)$"Captured {num} new recipe baselines. Tracking {Baselines.Count} total."); } } private static void CaptureBaselinesForEntriesIfNeeded(List entries) { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } int num = 0; foreach (RecipeEntry entry in entries) { if (entry.Override && !string.IsNullOrWhiteSpace(entry.Recipe) && CaptureBaseline(ResolveRecipe(entry.Recipe))) { num++; } } if (num > 0) { DataForgePlugin.Log.LogInfo((object)$"Captured {num} targeted recipe baselines. Tracking {Baselines.Count} total."); } } private static bool CaptureBaseline(Recipe? recipe) { if ((Object)(object)ObjectDB.instance == (Object)null || (Object)(object)recipe == (Object)null || !recipe.m_enabled || string.IsNullOrWhiteSpace(((Object)recipe).name)) { return false; } if (Baselines.ContainsKey(((Object)recipe).name)) { if (BaselineRecipes.TryGetValue(((Object)recipe).name, out Recipe value) && value != recipe && !ObjectDB.instance.m_recipes.Contains(value)) { Baselines[((Object)recipe).name] = RecipeDefinition.From(recipe); BaselineRecipes[((Object)recipe).name] = recipe; } return false; } Baselines[((Object)recipe).name] = RecipeDefinition.From(recipe); BaselineRecipes[((Object)recipe).name] = recipe; return true; } private static HashSet GetRuntimeApplyRecipeKeys(List entries) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (RecipeEntry entry in entries) { if (!entry.Override || string.IsNullOrWhiteSpace(entry.Recipe)) { continue; } Recipe val = ResolveRecipe(entry.Recipe); if ((Object)(object)val != (Object)null && !string.IsNullOrWhiteSpace(((Object)val).name)) { hashSet.Add(((Object)val).name); continue; } string text = ToRecipeName(entry.Recipe); if (text.Length > 0) { hashSet.Add(text); } } if (RuntimeStateWasApplied) { foreach (string runtimeAppliedRecipeKey in RuntimeAppliedRecipeKeys) { hashSet.Add(runtimeAppliedRecipeKey); } } return hashSet; } private static void UpdateRuntimeAppliedRecipeState(List entries) { RuntimeAppliedRecipeKeys.Clear(); foreach (RecipeEntry entry in entries) { if (!entry.Override || string.IsNullOrWhiteSpace(entry.Recipe)) { continue; } Recipe val = ResolveRecipe(entry.Recipe); if ((Object)(object)val != (Object)null && !string.IsNullOrWhiteSpace(((Object)val).name)) { RuntimeAppliedRecipeKeys.Add(((Object)val).name); continue; } string text = ToRecipeName(entry.Recipe); if (text.Length > 0) { RuntimeAppliedRecipeKeys.Add(text); } } RuntimeStateWasApplied = RuntimeAppliedRecipeKeys.Count > 0; } private static HashSet CleanupCreatedRecipes(List entries) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if ((Object)(object)ObjectDB.instance == (Object)null) { return hashSet; } HashSet hashSet2 = new HashSet(from entry in entries where entry.Override && !entry.Remove && ShouldCreateRecipe(entry) select ToRecipeName(entry.Recipe), StringComparer.OrdinalIgnoreCase); foreach (string item in CreatedRecipes.ToList()) { if (!hashSet2.Contains(item)) { RemoveCreatedRecipe(item, destroy: false); hashSet.Add(item); } } return hashSet; } internal static void CleanupCreatedRecipesForWorldTransition() { if ((Object)(object)ObjectDB.instance == (Object)null) { CreatedRecipes.Clear(); return; } foreach (string item in CreatedRecipes.ToList()) { RemoveCreatedRecipe(item, destroy: true); } } internal static void OnWorldShutdown() { ObjectDbReady = false; ZNetSceneReady = false; RuntimeStateWasApplied = false; RuntimeAppliedRecipeKeys.Clear(); CleanupCreatedRecipesForWorldTransition(); } private static void RemoveCreatedRecipe(string recipeName, bool destroy) { Recipe val = FindRecipeByExactName(recipeName) ?? ResolveRecipe(recipeName); if ((Object)(object)val != (Object)null && (Object)(object)ObjectDB.instance != (Object)null) { ObjectDB.instance.m_recipes.Remove(val); InvalidateRecipeLookupCache(); if (destroy) { Object.Destroy((Object)(object)val); } } CreatedRecipes.Remove(recipeName); Baselines.Remove(recipeName); BaselineRecipes.Remove(recipeName); } private static void EnsureAddedRecipes(List entries) { foreach (RecipeEntry entry in entries) { if (entry.Override && !entry.Remove && ShouldCreateRecipe(entry)) { using (DataForgeLogContext.Push(entry.LogContext)) { EnsureAddedRecipe(entry); } } } } private static void EnsureAddedRecipe(RecipeEntry entry) { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } string text = ToRecipeName(entry.Recipe); if (!((Object)(object)FindRecipeByExactName(text) != (Object)null) && !((Object)(object)ResolveRecipe(entry.Recipe) != (Object)null)) { Recipe val = ScriptableObject.CreateInstance(); val.m_resources = Array.Empty(); val.m_amount = 1; val.m_minStationLevel = 1; ((Object)val).name = text; ItemDrop val2 = ResolveItemFromRecipeKey(entry.Recipe); if ((Object)(object)val2 == (Object)null) { DataForgeLogContext.Warning("Could not add recipe '" + text + "': recipe key must start with a result item prefab."); Object.Destroy((Object)(object)val); return; } val.m_item = val2; Object.DontDestroyOnLoad((Object)(object)val); ObjectDB.instance.m_recipes.Add(val); InvalidateRecipeLookupCache(); Baselines[text] = RecipeDefinition.From(val); BaselineRecipes[text] = val; CreatedRecipes.Add(text); DataForgePlugin.Log.LogInfo((object)("Added recipe '" + text + "'.")); } } private static void RestoreBaselineRecipes(IReadOnlyCollection recipeNames) { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } foreach (string recipeName in recipeNames) { if (!BaselineRecipes.TryGetValue(recipeName, out Recipe value)) { continue; } if (!ObjectDB.instance.m_recipes.Contains(value)) { Recipe val = FindRecipeByExactName(recipeName); if ((Object)(object)val != (Object)null) { BaselineRecipes[recipeName] = val; Baselines[recipeName] = RecipeDefinition.From(val); continue; } ObjectDB.instance.m_recipes.Add(value); InvalidateRecipeLookupCache(); } if (Baselines.TryGetValue(recipeName, out RecipeDefinition value2)) { ApplyDefinition(BaselineRecipes[recipeName], value2); } } } private static void RemoveRecipe(string recipeName, bool warnIfMissing = true) { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } Recipe val = ResolveRecipe(recipeName); if ((Object)(object)val == (Object)null) { if (warnIfMissing) { DataForgeLogContext.Warning("Could not remove recipe '" + recipeName + "': recipe was not found."); } return; } ObjectDB.instance.m_recipes.Remove(val); InvalidateRecipeLookupCache(); if (!string.IsNullOrWhiteSpace(((Object)val).name) && CreatedRecipes.Remove(((Object)val).name)) { Baselines.Remove(((Object)val).name); BaselineRecipes.Remove(((Object)val).name); } } private static Recipe? ResolveRecipe(string? recipeName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(recipeName)) { return null; } string text = ToRecipeKey(recipeName); if (!GetRecipeLookup().TryGetValue(text, out List value)) { Recipe val = FindRecipeByExactName(ToRecipeName(text)); if ((Object)(object)val != (Object)null) { return val; } if (!HasRecipeVariant(text) && CountRecipesByResultItem(text) > 1) { DataForgeLogContext.Warning("Recipe key '" + text + "' matched multiple recipes. Use the exact key from recipes.reference.yml."); } return null; } if (value.Count == 1) { return value[0]; } if (value.Count > 1) { DataForgeLogContext.Warning("Recipe key '" + text + "' matched multiple recipes. Use a numbered recipe key from recipes.reference.yml."); } return null; } private static Recipe? FindRecipeByExactName(string? recipeName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(recipeName)) { return null; } return ((IEnumerable)ObjectDB.instance.m_recipes).FirstOrDefault((Func)((Recipe recipe) => (Object)(object)recipe != (Object)null && ((Object)recipe).name != null && ((Object)recipe).name.Equals(recipeName, StringComparison.OrdinalIgnoreCase))); } private static int CountRecipesByResultItem(string? itemName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(itemName)) { return 0; } string normalized = ToRecipeItemKey(itemName); return ObjectDB.instance.m_recipes.Count((Recipe recipe) => (Object)(object)recipe != (Object)null && (Object)(object)recipe.m_item != (Object)null && GetItemName(recipe.m_item).Equals(normalized, StringComparison.OrdinalIgnoreCase)); } private static bool HasAnyRecipeForReferenceKey(string? recipeName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(recipeName)) { return false; } string text = ToRecipeKey(recipeName); if (GetRecipeLookup().TryGetValue(text, out List value) && value.Count > 0) { return true; } if ((Object)(object)FindRecipeByExactName(ToRecipeName(text)) != (Object)null) { return true; } if (!HasRecipeVariant(text)) { return CountRecipesByResultItem(text) > 0; } return false; } private static bool HasNonCreatedRecipeForReferenceKey(string? recipeName, string createdName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(recipeName)) { return false; } string text = ToRecipeKey(recipeName); if (GetRecipeLookup().TryGetValue(text, out List value) && value.Any((Recipe recipe) => (Object)(object)recipe != (Object)null && !string.Equals(((Object)recipe).name, createdName, StringComparison.OrdinalIgnoreCase))) { return true; } Recipe val = FindRecipeByExactName(ToRecipeName(text)); if ((Object)(object)val != (Object)null && !string.Equals(((Object)val).name, createdName, StringComparison.OrdinalIgnoreCase)) { return true; } if (!HasRecipeVariant(text)) { return CountRecipesByResultItem(text) > 0; } return false; } private static Dictionary> GetRecipeLookup() { if (!RecipeLookupCacheDirty && RecipeLookupCache != null) { return RecipeLookupCache; } Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); if ((Object)(object)ObjectDB.instance == (Object)null) { RecipeLookupCache = dictionary; RecipeLookupCacheDirty = false; return dictionary; } List list = ObjectDB.instance.m_recipes.Where((Recipe recipe) => (Object)(object)recipe != (Object)null).ToList(); Dictionary referenceKeys = BuildReferenceKeyMap(list); foreach (Recipe item in list) { string text = ToReferenceRecipeKey(((Object)item).name, RecipeDefinition.From(item), referenceKeys); if (text.Length != 0) { if (!dictionary.TryGetValue(text, out var value)) { value = (dictionary[text] = new List()); } value.Add(item); } } RecipeLookupCache = dictionary; RecipeLookupCacheDirty = false; return dictionary; } private static void InvalidateRecipeLookupCache() { RecipeLookupCacheDirty = true; } private static bool ShouldCreateRecipe(RecipeEntry entry) { if (!entry.HasDefinition) { return false; } string text = ToRecipeName(entry.Recipe); if (!CreatedRecipes.Contains(text)) { return !HasAnyRecipeForReferenceKey(entry.Recipe); } return !HasNonCreatedRecipeForReferenceKey(entry.Recipe, text); } private static void ApplyDefinition(Recipe recipe, RecipeDefinition definition) { var (value, num) = ParseStation(definition.CraftingStation); DataForgeValue.Copy(definition.Item, delegate(string itemName) { ItemDrop val = ResolveItem(itemName); if ((Object)(object)val != (Object)null) { recipe.m_item = val; } }); DataForgeValue.Copy(definition.Amount, delegate(int val) { recipe.m_amount = Math.Max(1, val); }); DataForgeValue.Copy(value, delegate(string stationName) { recipe.m_craftingStation = ResolveCraftingStation(stationName); }); DataForgeValue.Copy(num ?? definition.MinStationLevel, delegate(int val) { recipe.m_minStationLevel = Math.Max(1, val); }); ApplyRequireOnlyOneIngredient(recipe, definition.RequireOnlyOneIngredient); DataForgeValue.Copy(definition.ListSortWeight, delegate(int listSortWeight) { recipe.m_listSortWeight = listSortWeight; }); if (definition.Resources != null) { recipe.m_resources = BuildRequirements(definition.Resources).ToArray(); } } private static List BuildRequirements(List definitions) { //IL_0068: 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_0074: 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_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown List list = new List(); foreach (RequirementDefinition definition in definitions) { if (string.IsNullOrWhiteSpace(definition.Item)) { DataForgeLogContext.Warning("Skipping recipe requirement without item."); continue; } ItemDrop val = ResolveItem(definition.Item); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning("Skipping recipe requirement for unknown item '" + definition.Item + "'."); continue; } list.Add(new Requirement { m_resItem = val, m_amount = Math.Max(0, definition.Amount.GetValueOrDefault()), m_amountPerLevel = Math.Max(0, definition.AmountPerLevel.GetValueOrDefault()), m_extraAmountOnlyOneIngredient = Math.Max(0, definition.ExtraAmountOnlyOneIngredient.GetValueOrDefault()), m_recover = true }); } return list; } private static void ApplyQualityBonuses(Recipe recipe, List? definitions) { if (definitions == null || definitions.Count == 0) { return; } List list = new List(); foreach (QualityBonusDefinition definition in definitions) { string text = definition.Item?.Trim() ?? ""; if (string.IsNullOrWhiteSpace(text)) { DataForgeLogContext.Warning("Skipping qualityBonus entry without item on recipe '" + ((Object)recipe).name + "'."); continue; } float num = Math.Max(0f, definition.AmountPerLevel.GetValueOrDefault()); if (!(num <= 0f)) { ItemDrop val = ResolveItem(text); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning("Skipping qualityBonus for unknown item '" + text + "' on recipe '" + ((Object)recipe).name + "'."); } else { list.Add(new QualityBonusRule(text, GetItemName(val), val.m_itemData.m_shared.m_name, num)); } } } if (list.Count == 0) { return; } lock (StateLock) { ActiveQualityBonuses[((Object)recipe).name] = list; ActiveQualityBonusRecipeCount = ActiveQualityBonuses.Count; } } private static void ClearActiveQualityBonuses() { lock (StateLock) { ActiveQualityBonuses.Clear(); ActiveQualityBonusRecipeCount = 0; } } internal static int GetQualityBonusAmount(Recipe recipe, int qualityLevel, ItemData? singleReqItem, int craftMultiplier) { if (ActiveQualityBonusRecipeCount == 0 || (Object)(object)Player.m_localPlayer == (Object)null) { return 0; } List activeQualityBonusRules = GetActiveQualityBonusRules(recipe); if (activeQualityBonusRules.Count == 0) { return 0; } int num = Math.Max(1, craftMultiplier); int num2 = 0; if (recipe.m_requireOnlyOneIngredient) { if (singleReqItem == null) { return 0; } foreach (QualityBonusRule item in activeQualityBonusRules) { if (RuleMatchesItemData(item, singleReqItem)) { num2 += CalculateQualityBonus(singleReqItem.m_quality, item.AmountPerLevel); } } return num2 * num; } Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory(); foreach (QualityBonusRule item2 in activeQualityBonusRules) { Requirement matchedRequirement; ItemData val = FindQualifyingItemForBonus(recipe, item2, inventory, qualityLevel, num, out matchedRequirement); if (val != null) { num2 += CalculateQualityBonus(val.m_quality, item2.AmountPerLevel); } } return num2 * num; } internal static bool TryConsumeQualityBonusResources(Player player, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier) { if (ActiveQualityBonusRecipeCount == 0 || itemQuality >= 0 || requirements == null) { return false; } Recipe val = FindRecipeByRequirements(requirements); if ((Object)(object)val == (Object)null) { return false; } List activeQualityBonusRules = GetActiveQualityBonusRules(val); if (activeQualityBonusRules.Count == 0) { return false; } Inventory inventory = ((Humanoid)player).GetInventory(); int num = Math.Max(1, multiplier); foreach (Requirement requirement in requirements) { if (!Object.op_Implicit((Object)(object)requirement.m_resItem)) { continue; } int num2 = requirement.GetAmount(qualityLevel) * num; if (num2 <= 0) { continue; } int num3 = itemQuality; if (activeQualityBonusRules.Any((QualityBonusRule rule) => RuleMatchesItemDrop(rule, requirement.m_resItem))) { ItemData val2 = FindQualifyingInventoryItem(inventory, requirement.m_resItem, num2); if (val2 != null) { num3 = val2.m_quality; } } inventory.RemoveItem(requirement.m_resItem.m_itemData.m_shared.m_name, num2, num3, true); } return true; } private static Recipe? FindRecipeByRequirements(Requirement[] requirements) { if (ActiveQualityBonusRecipeCount == 0 || (Object)(object)ObjectDB.instance == (Object)null) { return null; } foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if (!((Object)(object)recipe == (Object)null) && recipe.m_resources == requirements && GetActiveQualityBonusRules(recipe).Count > 0) { return recipe; } } return null; } private static List GetActiveQualityBonusRules(Recipe recipe) { if (ActiveQualityBonusRecipeCount == 0) { return EmptyQualityBonusRules; } lock (StateLock) { List value; return ActiveQualityBonuses.TryGetValue(((Object)recipe).name, out value) ? value : EmptyQualityBonusRules; } } private static ItemData? FindQualifyingItemForBonus(Recipe recipe, QualityBonusRule rule, Inventory inventory, int qualityLevel, int craftMultiplier, out Requirement? matchedRequirement) { matchedRequirement = null; Requirement[] array = recipe.m_resources ?? Array.Empty(); foreach (Requirement val in array) { if (Object.op_Implicit((Object)(object)val.m_resItem) && RuleMatchesItemDrop(rule, val.m_resItem)) { matchedRequirement = val; int requiredAmount = val.GetAmount(qualityLevel) * Math.Max(1, craftMultiplier); return FindQualifyingInventoryItem(inventory, val.m_resItem, requiredAmount); } } return null; } private static ItemData? FindQualifyingInventoryItem(Inventory inventory, ItemDrop item, int requiredAmount) { if (requiredAmount <= 0) { return null; } string name = item.m_itemData.m_shared.m_name; for (int num = Math.Max(1, item.m_itemData.m_shared.m_maxQuality); num >= 1; num--) { if (inventory.CountItems(name, num, true) >= requiredAmount) { return inventory.GetItem(name, num, false); } } return null; } private static int CalculateQualityBonus(int itemQuality, float amountPerLevel) { return Mathf.CeilToInt((float)Math.Max(0, itemQuality - 1) * amountPerLevel); } private static bool RuleMatchesItemDrop(QualityBonusRule rule, ItemDrop item) { if (!rule.PrefabName.Equals(GetItemName(item), StringComparison.OrdinalIgnoreCase) && !rule.SharedName.Equals(item.m_itemData.m_shared.m_name, StringComparison.OrdinalIgnoreCase) && !rule.Input.Equals(GetItemName(item), StringComparison.OrdinalIgnoreCase)) { return rule.Input.Equals(item.m_itemData.m_shared.m_name, StringComparison.OrdinalIgnoreCase); } return true; } private static bool RuleMatchesItemData(QualityBonusRule rule, ItemData item) { string value = (((Object)(object)item.m_dropPrefab != (Object)null) ? GetPrefabName(item.m_dropPrefab) : ""); if (!rule.PrefabName.Equals(value, StringComparison.OrdinalIgnoreCase) && !rule.SharedName.Equals(item.m_shared.m_name, StringComparison.OrdinalIgnoreCase) && !rule.Input.Equals(value, StringComparison.OrdinalIgnoreCase)) { return rule.Input.Equals(item.m_shared.m_name, StringComparison.OrdinalIgnoreCase); } return true; } private static void ApplyRequireOnlyOneIngredient(Recipe recipe, string? value) { if (string.IsNullOrWhiteSpace(value)) { return; } string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length == 0 || array[0].Length == 0) { return; } if (!bool.TryParse(array[0], out var result)) { DataForgeLogContext.Warning("Could not parse requireOnlyOneIngredient value '" + array[0] + "'. Expected true or false."); return; } float result2 = 1f; if (array.Length > 1 && array[1].Length > 0 && !float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out result2)) { DataForgeLogContext.Warning("Could not parse requireOnlyOneIngredient multiplier '" + array[1] + "'. Expected a number."); return; } recipe.m_requireOnlyOneIngredient = result; recipe.m_qualityResultAmountMultiplier = Math.Max(0f, result2); } private static (string? Station, int? MinStationLevel) ParseStation(string? value) { if (value == null) { return (Station: null, MinStationLevel: null); } string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select part.Trim()).ToArray(); if (array.Length == 0) { return (Station: value, MinStationLevel: null); } int? item = null; if (array.Length > 1 && int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { item = result; } return (Station: array[0], MinStationLevel: item); } private static ItemDrop? ResolveItem(string? itemName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(itemName)) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(itemName); if ((Object)(object)itemPrefab == (Object)null) { DataForgeLogContext.Warning("Could not resolve recipe item '" + itemName + "'."); return null; } return itemPrefab.GetComponent(); } private static ItemDrop? ResolveItemFromRecipeKey(string recipeKey) { if ((Object)(object)ObjectDB.instance == (Object)null) { return null; } string text = ToRecipeItemKey(recipeKey); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text); if ((Object)(object)itemPrefab != (Object)null) { return itemPrefab.GetComponent(); } foreach (GameObject item in from prefab in ObjectDB.instance.m_items where (Object)(object)prefab != (Object)null orderby GetPrefabName(prefab).Length descending select prefab) { string prefabName = GetPrefabName(item); if (prefabName.Length != 0 && (text.Equals(prefabName, StringComparison.OrdinalIgnoreCase) || text.StartsWith(prefabName + "_", StringComparison.OrdinalIgnoreCase))) { return item.GetComponent(); } } return null; } private static CraftingStation? ResolveCraftingStation(string? stationName) { if (DataForgeValue.IsNone(stationName)) { return null; } if ((Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(stationName); if ((Object)(object)prefab == (Object)null) { DataForgeLogContext.Warning("Could not resolve crafting station '" + stationName + "'."); return null; } CraftingStation component = prefab.GetComponent(); if ((Object)(object)component == (Object)null) { DataForgeLogContext.Warning("Prefab '" + stationName + "' does not have a CraftingStation component."); } return component; } private static void WriteGeneratedArtifacts() { if (DataForgePlugin.UsesLocalAuthorityFiles) { WriteReferenceArtifact(); } } internal static bool TryWriteFullScaffoldConfigurationFile(out string path, out string error) { path = Path.Combine(ConfigDirectory, "recipes.full.yml"); return GeneratedArtifactWriter.TryWriteFullScaffoldIfReady(path, "recipes", CanBuildGeneratedArtifacts(), "recipes game data is not ready yet.", delegate { EnsureConfigDirectoryAndDefaultOverride(); CaptureAllBaselinesIfNeeded(); Dictionary referenceKeys = BuildReferenceKeyMap(Baselines); var entries = Enumerable.Select(Baselines, (KeyValuePair pair) => new { Entry = RecipeFullEntry.From(pair.Key, pair.Value, referenceKeys), OwnerKey = (pair.Value.Item ?? ToRecipeKey(pair.Key)), SortKey = DataForgeResourceMap.BuildItemSortKey(pair.Value.Item ?? ToRecipeKey(pair.Key), DataForgeResourceMap.GetResourceTierSortValue(pair.Value.Resources?.Select((RequirementDefinition resource) => resource.Item) ?? Array.Empty()), ToReferenceRecipeKey(pair.Key, pair.Value, referenceKeys)) }).OrderBy(pair => pair.SortKey, StringComparer.OrdinalIgnoreCase).ToList(); return GeneratedArtifactWriter.GeneratedHeader("recipes", "recipes.yml", "full scaffold") + DataForgeReferenceSections.SerializeReferenceSections(entries, entry => entry.SortKey, entry => DataForgeOwnerResolver.GetPrefabOwnerName(entry.OwnerKey), entry => entry.Entry, FullSerializer); }, out error); } private static void WriteReferenceArtifact() { if (CanBuildGeneratedArtifacts()) { EnsureConfigDirectoryAndDefaultOverride(); CaptureAllBaselinesIfNeeded(); string sourceSignature = ComputeReferenceSourceSignature(); string text = Path.Combine(ConfigDirectory, "recipes.reference.yml"); if (!ShouldSkipReferenceUpdate(text, sourceSignature) && (GeneratedArtifactWriter.WriteReferenceIfReady(Baselines.Count > 0, ConfigDirectory, "recipes.reference.yml", "recipes", "recipes.yml", BuildReferenceArtifactContent) || File.Exists(text))) { RecordReferenceUpdateState(text, sourceSignature); } } } private static string BuildReferenceArtifactContent() { Dictionary referenceKeys = BuildReferenceKeyMap(Baselines); return DataForgeReferenceSections.SerializeReferenceSections(Enumerable.Select(Baselines, (KeyValuePair pair) => new { Entry = RecipeReferenceEntry.From(pair.Key, pair.Value, referenceKeys), OwnerKey = (pair.Value.Item ?? ToRecipeKey(pair.Key)), SortKey = DataForgeResourceMap.BuildItemSortKey(pair.Value.Item ?? ToRecipeKey(pair.Key), DataForgeResourceMap.GetResourceTierSortValue(pair.Value.Resources?.Select((RequirementDefinition resource) => resource.Item) ?? Array.Empty()), ToReferenceRecipeKey(pair.Key, pair.Value, referenceKeys)) }).ToList(), entry => entry.SortKey, entry => DataForgeOwnerResolver.GetPrefabOwnerName(entry.OwnerKey), entry => entry.Entry, SparseSerializer); } private static bool CanBuildGeneratedArtifacts() { if (ObjectDbReady) { return (Object)(object)ObjectDB.instance != (Object)null; } return false; } private static string ComputeReferenceSourceSignature() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("2026-06-24-recipe-reference-state-v2"); stringBuilder.AppendLine(BuildFileStamp(Path.Combine(ConfigDirectory, "z_resourcemap.txt"))); if ((Object)(object)ObjectDB.instance != (Object)null) { foreach (Recipe item in ObjectDB.instance.m_recipes.Where((Recipe recipe) => (Object)(object)recipe != (Object)null && !string.IsNullOrWhiteSpace(((Object)recipe).name)).OrderBy((Recipe recipe) => ((Object)recipe).name, StringComparer.OrdinalIgnoreCase)) { stringBuilder.Append(((Object)item).name.Trim()); stringBuilder.Append('|'); stringBuilder.AppendLine(SparseSerializer.Serialize(RecipeDefinition.From(item))); } } return ComputeStableHash(stringBuilder.ToString()); } private static bool ShouldSkipReferenceUpdate(string referencePath, string sourceSignature) { return DataForgeReferenceState.ShouldSkip("recipes", referencePath, sourceSignature, "2026-06-24-recipe-reference-state-v2"); } private static void RecordReferenceUpdateState(string referencePath, string sourceSignature) { DataForgeReferenceState.Record("recipes", referencePath, sourceSignature, "2026-06-24-recipe-reference-state-v2"); } private static string BuildFileStamp(string path) { return DataForgeReferenceState.BuildFileStamp(path); } private static string ComputeStableHash(string value) { return DataForgeReferenceState.ComputeStableHash(value); } private static string GetItemName(ItemDrop? item) { if (!((Object)(object)item != (Object)null)) { return ""; } return GetPrefabName(((Component)item).gameObject); } private static string GetStationName(CraftingStation? station) { if (!((Object)(object)station != (Object)null)) { return "none"; } return GetPrefabName(((Component)station).gameObject); } private static string GetPrefabName(GameObject gameObject) { return ((Object)gameObject).name.Replace("(Clone)", "").Trim(); } private static Dictionary BuildReferenceKeyMap(IEnumerable> definitions) { return BuildReferenceKeyMap(definitions.Select, RecipeKeyCandidate>((KeyValuePair pair) => new RecipeKeyCandidate { RecipeName = pair.Key, RecipeKey = ToRecipeKey(pair.Key), ItemName = (pair.Value.Item?.Trim() ?? "") })); } private static Dictionary BuildReferenceKeyMap(IEnumerable recipes) { return BuildReferenceKeyMap(recipes.Where((Recipe recipe) => (Object)(object)recipe != (Object)null).Select(delegate(Recipe recipe) { RecipeDefinition recipeDefinition = RecipeDefinition.From(recipe); return new RecipeKeyCandidate { RecipeName = (((Object)recipe).name ?? ""), RecipeKey = ToRecipeKey(((Object)recipe).name ?? ""), ItemName = (recipeDefinition.Item?.Trim() ?? "") }; })); } private static Dictionary BuildReferenceKeyMap(IEnumerable candidates) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (IGrouping item in candidates.Where((RecipeKeyCandidate candidate) => !string.IsNullOrWhiteSpace(candidate.RecipeName)).GroupBy((RecipeKeyCandidate candidate) => candidate.ItemName, StringComparer.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(item.Key)) { foreach (RecipeKeyCandidate item2 in item) { dictionary[item2.RecipeName] = item2.RecipeKey; } continue; } List list = item.OrderBy(GetRecipeVariantSortRank).ThenBy((RecipeKeyCandidate candidate) => candidate.RecipeKey, StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count == 1 && !TryGetCreatedRecipeVariant(list[0], out string _)) { dictionary[list[0].RecipeName] = item.Key; continue; } HashSet hashSet = new HashSet(); List list2 = new List(); foreach (RecipeKeyCandidate item3 in list) { int recipeIndex; if (TryGetCreatedRecipeVariant(item3, out string variant2)) { dictionary[item3.RecipeName] = item.Key + ";" + variant2; } else if (TryGetExplicitRecipeIndex(item3, out recipeIndex) && hashSet.Add(recipeIndex)) { dictionary[item3.RecipeName] = item.Key + ";" + recipeIndex.ToString(CultureInfo.InvariantCulture); } else { list2.Add(item3); } } int num = 1; foreach (RecipeKeyCandidate item4 in list2) { for (; hashSet.Contains(num); num++) { } dictionary[item4.RecipeName] = item.Key + ";" + num.ToString(CultureInfo.InvariantCulture); hashSet.Add(num); } } return dictionary; } private static bool TryGetCreatedRecipeVariant(RecipeKeyCandidate candidate, out string variant) { variant = ""; if (!CreatedRecipes.Contains(candidate.RecipeName) || string.IsNullOrWhiteSpace(candidate.ItemName) || string.IsNullOrWhiteSpace(candidate.RecipeKey)) { return false; } string[] array = new string[2] { candidate.ItemName + "_Recipe_", candidate.ItemName + "_" }; foreach (string text in array) { if (candidate.RecipeKey.StartsWith(text, StringComparison.OrdinalIgnoreCase)) { variant = candidate.RecipeKey.Substring(text.Length).Trim(); return variant.Length > 0; } } return false; } private static bool TryGetExplicitRecipeIndex(RecipeKeyCandidate candidate, out int recipeIndex) { recipeIndex = 0; if (string.IsNullOrWhiteSpace(candidate.ItemName) || string.IsNullOrWhiteSpace(candidate.RecipeKey)) { return false; } string[] array = new string[2] { candidate.ItemName + "_Recipe_", candidate.ItemName + "_" }; foreach (string text in array) { if (candidate.RecipeKey.StartsWith(text, StringComparison.OrdinalIgnoreCase)) { if (int.TryParse(candidate.RecipeKey.Substring(text.Length).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out recipeIndex)) { return recipeIndex > 0; } return false; } } return false; } private static int GetRecipeVariantSortRank(RecipeKeyCandidate candidate) { string recipeKey = candidate.RecipeKey; string itemName = candidate.ItemName; if (recipeKey.Equals(itemName, StringComparison.OrdinalIgnoreCase) || recipeKey.Equals(itemName + "_Default", StringComparison.OrdinalIgnoreCase) || recipeKey.Equals(itemName + "_Recipe_Default", StringComparison.OrdinalIgnoreCase)) { return 0; } return 10; } private static string ToRecipeName(string recipeKey) { string text = ToRecipeKey(recipeKey).Replace(';', '_'); if (!text.StartsWith("Recipe_", StringComparison.OrdinalIgnoreCase)) { return "Recipe_" + text; } return text; } private static string ToRecipeKey(string recipeName) { string text = recipeName.Split(new char[1] { ',' }, 2, StringSplitOptions.None)[0].Trim(); if (!text.StartsWith("Recipe_", StringComparison.OrdinalIgnoreCase)) { return text; } return text.Substring("Recipe_".Length); } private static string ToRecipeItemKey(string recipeName) { string text = ToRecipeKey(recipeName); int num = text.IndexOf(';'); if (num < 0) { return text; } return text.Substring(0, num).Trim(); } private static bool HasRecipeVariant(string recipeName) { return ToRecipeKey(recipeName).IndexOf(';') >= 0; } private static string ToReferenceRecipeKey(string recipeName, RecipeDefinition definition, Dictionary? referenceKeys = null) { if (referenceKeys != null && referenceKeys.TryGetValue(recipeName, out string value)) { return value; } string result = ToRecipeKey(recipeName); if (string.IsNullOrWhiteSpace(definition.Item)) { return result; } return definition.Item.Trim(); } private static bool TryNormalizeRecipeHeader(string recipeHeader, out string normalizedRecipe, out string error) { normalizedRecipe = ""; error = ""; string[] array = (from part in recipeHeader.Split(new char[1] { ',' }, 2, StringSplitOptions.None) select part.Trim()).ToArray(); if (!TryParseRecipeKey(ToRecipeKey(array[0]), out string itemPrefab, out string variant, out error)) { return false; } string text = ((variant == null) ? itemPrefab : (itemPrefab + ";" + variant)); normalizedRecipe = ((array.Length > 1 && array[1].Length > 0) ? (text + ", " + array[1]) : text); return true; } private static bool TryParseRecipeKey(string recipeKey, out string itemPrefab, out string? variant, out string error) { itemPrefab = ""; variant = null; error = ""; string[] array = (from part in recipeKey.Split(new char[1] { ';' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length == 1) { if (array[0].Length == 0) { error = "Recipe key must include an item prefab."; return false; } itemPrefab = array[0]; return true; } if (array.Length != 2 || array[0].Length == 0 || array[1].Length == 0) { error = "Recipe keys must use 'ItemPrefab' or 'ItemPrefab;variant' format."; return false; } if (array[1].IndexOfAny(new char[2] { ',', ';' }) >= 0) { error = "Recipe variant must not contain ',' or ';'."; return false; } itemPrefab = array[0]; variant = array[1]; return true; } private static int? ParseRecipeAmount(string recipeHeader) { string[] array = (from part in recipeHeader.Split(new char[1] { ',' }, 2, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length < 2 || array[1].Length == 0) { return null; } if (int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return Math.Max(1, result); } DataForgeLogContext.Warning("Could not parse recipe amount '" + array[1] + "' in '" + recipeHeader + "'. Expected 'recipe: Prefab, amount'."); return null; } private static string FormatRecipeHeader(string recipeKey, int? amount, bool includeDefaultAmount) { if (!amount.HasValue || (!includeDefaultAmount && amount.Value == 1)) { return recipeKey; } return recipeKey + ", " + Math.Max(1, amount.Value).ToString(CultureInfo.InvariantCulture); } private static string? FormatStation(string? station, int? minStationLevel) { if (string.IsNullOrWhiteSpace(station) || DataForgeValue.IsNone(station)) { return null; } if (!minStationLevel.HasValue || minStationLevel.Value <= 1) { return station; } return station + ", " + minStationLevel.Value.ToString(CultureInfo.InvariantCulture); } private static string FormatRequireOnlyOneIngredient(bool? requireOnlyOneIngredient, float? qualityResultAmountMultiplier) { return (requireOnlyOneIngredient == true).ToString().ToLowerInvariant() + ", " + (qualityResultAmountMultiplier ?? 1f).ToString("0.###", CultureInfo.InvariantCulture); } private static bool IsResultItemUpgradeable(string? itemName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(itemName)) { return false; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(itemName); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); if ((Object)(object)val != (Object)null) { return val.m_itemData.m_shared.m_maxQuality > 1; } return false; } private static bool IsRequireOnlyOneIngredient(string? value) { if (string.IsNullOrWhiteSpace(value)) { return false; } bool result; return bool.TryParse(value.Split(new char[1] { ',' }, 2, StringSplitOptions.None)[0].Trim(), out result) && result; } } [HarmonyPatch(typeof(Recipe), "GetAmount")] internal static class DataForgeRecipeGetAmountPatch { private static void Postfix(Recipe __instance, int quality, ref int __result, ref ItemData singleReqItem, int craftMultiplier) { __result += RecipeOverrideManager.GetQualityBonusAmount(__instance, quality, singleReqItem, craftMultiplier); } } [HarmonyPatch(typeof(Player), "ConsumeResources")] internal static class DataForgeRecipeConsumeResourcesPatch { private static bool Prefix(Player __instance, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier) { return !RecipeOverrideManager.TryConsumeQualityBonusResources(__instance, requirements, qualityLevel, itemQuality, multiplier); } } internal static class ArcheryTargetGuard { private static readonly HashSet LoggedInstances = new HashSet(); internal static bool HasRequiredNetworkView(ArcheryTarget target) { if ((Object)(object)target == (Object)null) { return true; } return (Object)(object)((Component)target).GetComponentInParent() != (Object)null; } internal static void LogMissingNetworkViewOnce(ArcheryTarget target) { if (!((Object)(object)target == (Object)null) && LoggedInstances.Add(((Object)target).GetInstanceID())) { GameObject gameObject = ((Component)target).gameObject; DataForgePlugin.Log.LogWarning((object)("ArcheryTarget.Start skipped because the target has no parent ZNetView. This usually means a prefab/preview/clone object was activated outside the normal ZNetScene placement path. " + DescribeObject(gameObject) + " " + DescribePrefabRegistration(gameObject))); } } private static string DescribeObject(GameObject gameObject) { //IL_0054: 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_006b: 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) if ((Object)(object)gameObject == (Object)null) { return "object=."; } Transform root = gameObject.transform.root; string[] obj = new string[11] { "object='", BuildPath(gameObject.transform), "', root='", BuildPath(root), "', scene='", null, null, null, null, null, null }; Scene scene = gameObject.scene; object obj2; if (!((Scene)(ref scene)).IsValid()) { obj2 = ""; } else { scene = gameObject.scene; obj2 = ((Scene)(ref scene)).name; } obj[5] = (string)obj2; obj[6] = "', "; obj[7] = $"activeSelf={gameObject.activeSelf}, activeInHierarchy={gameObject.activeInHierarchy}, "; obj[8] = $"rootHasZNetView={(Object)(object)((Component)root).GetComponent() != (Object)null}, "; obj[9] = $"rootHasPiece={(Object)(object)((Component)root).GetComponent() != (Object)null}, "; obj[10] = $"rootHasWearNTear={(Object)(object)((Component)root).GetComponent() != (Object)null}."; return string.Concat(obj); } private static string DescribePrefabRegistration(GameObject gameObject) { ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null || (Object)(object)gameObject == (Object)null) { return "ZNetScene=."; } string text = NormalizePrefabName(((Object)gameObject).name); int stableHashCode = StringExtensionMethods.GetStableHashCode(text); GameObject prefab = instance.GetPrefab(stableHashCode); string text2 = (((Object)(object)prefab != (Object)null) ? DescribeRegisteredPrefab(prefab) : ""); string text3 = DescribeListMatches(instance.m_prefabs, text); string text4 = DescribeListMatches(instance.m_nonNetViewPrefabs, text); return "prefabName='" + text + "', namedPrefab=" + text2 + ", m_prefabs=" + text3 + ", m_nonNetViewPrefabs=" + text4 + "."; } private static string DescribeListMatches(List prefabs, string prefabName) { List list = new List(); for (int i = 0; i < prefabs.Count; i++) { GameObject val = prefabs[i]; if (!((Object)(object)val == (Object)null) && NormalizePrefabName(((Object)val).name).Equals(prefabName, StringComparison.OrdinalIgnoreCase)) { list.Add($"[{i}] {DescribeRegisteredPrefab(val)}"); } } if (list.Count <= 0) { return ""; } return string.Join("; ", list); } private static string DescribeRegisteredPrefab(GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return ""; } return "'" + ((Object)prefab).name + "'" + $"(hasZNetView={(Object)(object)prefab.GetComponent() != (Object)null}, " + $"hasPiece={(Object)(object)prefab.GetComponent() != (Object)null}, " + $"hasWearNTear={(Object)(object)prefab.GetComponent() != (Object)null})"; } private static string BuildPath(Transform transform) { List list = new List(); Transform val = transform; while ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } private static string NormalizePrefabName(string name) { return (name ?? "").Replace("(Clone)", "").Trim(); } } [HarmonyPatch(typeof(ArcheryTarget), "Start")] internal static class DataForgeArcheryTargetStartGuardPatch { [HarmonyPriority(800)] private static bool Prefix(ArcheryTarget __instance) { if (ArcheryTargetGuard.HasRequiredNetworkView(__instance)) { return true; } ArcheryTargetGuard.LogMissingNetworkViewOnce(__instance); ((Behaviour)__instance).enabled = false; return false; } } internal static class PieceComfortHudBadges { private const string BadgeObjectName = "DataForgeComfortBadge"; private const string GroupHighlightObjectName = "DataForgeComfortGroupHighlight"; private const string StationExtensionHighlightObjectName = "DataForgeStationExtensionHighlight"; private static readonly Color BadgeColor = new Color(1f, 0.42f, 0f, 1f); private static readonly Color GroupHighlightColor = new Color(1f, 0.42f, 0f, 0.3f); private static readonly Color StationExtensionHighlightColor = new Color(0.25f, 0.95f, 1f, 0.26f); private static bool AnyBadgeVisible; private static bool AnyGroupHighlightVisible; private static bool AnyStationExtensionHighlightVisible; internal static void RefreshVisibleHud() { Hud instance = Hud.m_instance; Player localPlayer = Player.m_localPlayer; if (!((Object)(object)instance == (Object)null) && !((Object)(object)localPlayer == (Object)null) && instance.m_pieceSelectionWindow.activeSelf) { Refresh(instance, localPlayer); } } internal static void Refresh(Hud hud, Player player) { if ((Object)(object)hud == (Object)null || (Object)(object)player == (Object)null || hud.m_pieceIcons == null) { return; } if (!DataForgePlugin.ShowPieceComfortInHammer && !DataForgePlugin.HighlightStationExtensionsInHammer) { HideVisibleBadges(hud); HideVisibleGroupHighlights(hud); HideVisibleStationExtensionHighlights(hud); return; } List buildPieces = player.GetBuildPieces(); if (DataForgePlugin.ShowPieceComfortInHammer) { RefreshComfortBadges(hud, player, buildPieces); RefreshGroupHighlights(hud, player, buildPieces); } else { HideVisibleBadges(hud); HideVisibleGroupHighlights(hud); } if (DataForgePlugin.HighlightStationExtensionsInHammer) { RefreshStationExtensionHighlights(hud, player, buildPieces); } else { HideVisibleStationExtensionHighlights(hud); } } private static void RefreshComfortBadges(Hud hud, Player player, List buildPieces) { bool anyBadgeVisible = false; for (int i = 0; i < hud.m_pieceIcons.Count; i++) { PieceIconData iconData = hud.m_pieceIcons[i]; Piece val = ((i < buildPieces.Count) ? buildPieces[i] : null); if ((Object)(object)val == (Object)null || val.m_comfort <= 0 || VeiledRecipesSoftCompat.ShouldMaskPiece(player, val)) { HideBadge(iconData); continue; } TMP_Text orCreateBadge = GetOrCreateBadge(hud, iconData); if (!((Object)(object)orCreateBadge == (Object)null)) { string text = val.m_comfort.ToString(CultureInfo.InvariantCulture); if (!string.Equals(orCreateBadge.text, text, StringComparison.Ordinal)) { orCreateBadge.text = text; } if (!((Component)orCreateBadge).gameObject.activeSelf) { ((Component)orCreateBadge).gameObject.SetActive(true); } orCreateBadge.transform.SetAsLastSibling(); anyBadgeVisible = true; } } AnyBadgeVisible = anyBadgeVisible; } private static void RefreshGroupHighlights(Hud hud, Player player, List buildPieces) { //IL_001a: 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) //IL_007a: 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_00a7: Unknown result type (might be due to invalid IL or missing references) Piece hoveredPiece = hud.m_hoveredPiece; if ((Object)(object)hoveredPiece == (Object)null || hoveredPiece.m_comfort <= 0 || (int)hoveredPiece.m_comfortGroup == 0 || VeiledRecipesSoftCompat.ShouldMaskPiece(player, hoveredPiece)) { HideVisibleGroupHighlights(hud); return; } ComfortGroup comfortGroup = hoveredPiece.m_comfortGroup; bool anyGroupHighlightVisible = false; for (int i = 0; i < hud.m_pieceIcons.Count; i++) { PieceIconData iconData = hud.m_pieceIcons[i]; Piece val = ((i < buildPieces.Count) ? buildPieces[i] : null); if (!((Object)(object)val != (Object)null) || val.m_comfort <= 0 || val.m_comfortGroup != comfortGroup || VeiledRecipesSoftCompat.ShouldMaskPiece(player, val)) { HideHighlight(iconData, "DataForgeComfortGroupHighlight"); continue; } Image orCreateHighlight = GetOrCreateHighlight(iconData, "DataForgeComfortGroupHighlight", GroupHighlightColor); if (!((Object)(object)orCreateHighlight == (Object)null)) { if (!((Component)orCreateHighlight).gameObject.activeSelf) { ((Component)orCreateHighlight).gameObject.SetActive(true); } ((Component)orCreateHighlight).transform.SetAsFirstSibling(); anyGroupHighlightVisible = true; } } AnyGroupHighlightVisible = anyGroupHighlightVisible; } private static void RefreshStationExtensionHighlights(Hud hud, Player player, List buildPieces) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) Piece hoveredPiece = hud.m_hoveredPiece; if ((Object)(object)hoveredPiece == (Object)null || VeiledRecipesSoftCompat.ShouldMaskPiece(player, hoveredPiece) || !TryGetRelatedCraftingStation(hoveredPiece, out CraftingStation station)) { HideVisibleStationExtensionHighlights(hud); return; } bool anyStationExtensionHighlightVisible = false; for (int i = 0; i < hud.m_pieceIcons.Count; i++) { PieceIconData iconData = hud.m_pieceIcons[i]; Piece val = ((i < buildPieces.Count) ? buildPieces[i] : null); if (!((Object)(object)val != (Object)null) || VeiledRecipesSoftCompat.ShouldMaskPiece(player, val) || !IsRelatedToCraftingStation(val, station)) { HideHighlight(iconData, "DataForgeStationExtensionHighlight"); continue; } Image orCreateHighlight = GetOrCreateHighlight(iconData, "DataForgeStationExtensionHighlight", StationExtensionHighlightColor); if (!((Object)(object)orCreateHighlight == (Object)null)) { if (!((Component)orCreateHighlight).gameObject.activeSelf) { ((Component)orCreateHighlight).gameObject.SetActive(true); } ((Component)orCreateHighlight).transform.SetAsFirstSibling(); anyStationExtensionHighlightVisible = true; } } AnyStationExtensionHighlightVisible = anyStationExtensionHighlightVisible; } private static bool TryGetRelatedCraftingStation(Piece piece, out CraftingStation station) { station = null; if ((Object)(object)piece == (Object)null) { return false; } CraftingStation component = ((Component)piece).GetComponent(); if ((Object)(object)component != (Object)null) { station = component; return true; } StationExtension component2 = ((Component)piece).GetComponent(); if ((Object)(object)component2?.m_craftingStation != (Object)null) { station = component2.m_craftingStation; return true; } return false; } private static bool IsRelatedToCraftingStation(Piece piece, CraftingStation targetStation) { if ((Object)(object)piece == (Object)null || (Object)(object)targetStation == (Object)null) { return false; } if (IsSameCraftingStation(((Component)piece).GetComponent(), targetStation)) { return true; } return IsSameCraftingStation(((Component)piece).GetComponent()?.m_craftingStation, targetStation); } private static bool IsSameCraftingStation(CraftingStation? left, CraftingStation? right) { if ((Object)(object)left == (Object)null || (Object)(object)right == (Object)null) { return false; } if (left == right) { return true; } string prefabName = Utils.GetPrefabName(((Component)left).gameObject); string prefabName2 = Utils.GetPrefabName(((Component)right).gameObject); if (!string.IsNullOrWhiteSpace(prefabName) && !string.IsNullOrWhiteSpace(prefabName2) && string.Equals(prefabName, prefabName2, StringComparison.OrdinalIgnoreCase)) { return true; } if (!string.IsNullOrWhiteSpace(left.m_name) && !string.IsNullOrWhiteSpace(right.m_name)) { return string.Equals(left.m_name, right.m_name, StringComparison.OrdinalIgnoreCase); } return false; } private static TMP_Text GetOrCreateBadge(Hud hud, PieceIconData iconData) { //IL_00a8: 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_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)iconData?.m_go == (Object)null) { return null; } Transform val = iconData.m_go.transform.Find("DataForgeComfortBadge"); TMP_Text val2 = default(TMP_Text); if ((Object)(object)val != (Object)null && ((Component)val).TryGetComponent(ref val2)) { if ((Object)(object)val2.font == (Object)null) { TMP_Text val3 = FindTemplateText(hud, iconData); if ((Object)(object)val3 == (Object)null || (Object)(object)val3.font == (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); return null; } ConfigureBadgeText(val2, val3); } return val2; } TMP_Text val4 = FindTemplateText(hud, iconData); if ((Object)(object)val4 == (Object)null || (Object)(object)val4.font == (Object)null) { return null; } GameObject val5 = new GameObject("DataForgeComfortBadge"); val5.SetActive(false); val5.transform.SetParent(iconData.m_go.transform, false); RectTransform obj = val5.AddComponent(); obj.anchorMin = Vector2.one; obj.anchorMax = Vector2.one; obj.pivot = Vector2.one; obj.anchoredPosition = new Vector2(-8f, -2f); obj.sizeDelta = new Vector2(32f, 24f); TextMeshProUGUI obj2 = val5.AddComponent(); ConfigureBadgeText((TMP_Text)(object)obj2, val4); return (TMP_Text)(object)obj2; } private static void HideVisibleBadges(Hud hud) { if (!AnyBadgeVisible || hud.m_pieceIcons == null) { return; } foreach (PieceIconData pieceIcon in hud.m_pieceIcons) { HideBadge(pieceIcon); } AnyBadgeVisible = false; } private static void HideVisibleGroupHighlights(Hud hud) { if (!AnyGroupHighlightVisible || hud.m_pieceIcons == null) { return; } foreach (PieceIconData pieceIcon in hud.m_pieceIcons) { HideHighlight(pieceIcon, "DataForgeComfortGroupHighlight"); } AnyGroupHighlightVisible = false; } private static void HideVisibleStationExtensionHighlights(Hud hud) { if (!AnyStationExtensionHighlightVisible || hud.m_pieceIcons == null) { return; } foreach (PieceIconData pieceIcon in hud.m_pieceIcons) { HideHighlight(pieceIcon, "DataForgeStationExtensionHighlight"); } AnyStationExtensionHighlightVisible = false; } private static void HideBadge(PieceIconData iconData) { if (!((Object)(object)iconData?.m_go == (Object)null)) { Transform val = iconData.m_go.transform.Find("DataForgeComfortBadge"); if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(false); } } } private static Image GetOrCreateHighlight(PieceIconData iconData, string objectName, Color color) { //IL_004c: 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_0058: 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_0076: 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_0096: 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_00ab: 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_00bd: 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_00f5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)iconData?.m_go == (Object)null) { return null; } Transform val = iconData.m_go.transform.Find(objectName); Image val2 = default(Image); if ((Object)(object)val != (Object)null && ((Component)val).TryGetComponent(ref val2)) { ((Graphic)val2).color = color; ((Graphic)val2).raycastTarget = false; return val2; } GameObject val3 = new GameObject(objectName); val3.SetActive(false); val3.transform.SetParent(iconData.m_go.transform, false); RectTransform obj = val3.AddComponent(); obj.anchorMin = Vector2.zero; obj.anchorMax = Vector2.one; obj.pivot = new Vector2(0.5f, 0.5f); obj.offsetMin = Vector2.zero; obj.offsetMax = Vector2.zero; Image val4 = val3.AddComponent(); ((Graphic)val4).color = color; ((Graphic)val4).raycastTarget = false; Image val5 = default(Image); if (iconData.m_go.TryGetComponent(ref val5) && (Object)(object)val5.sprite != (Object)null) { val4.sprite = val5.sprite; val4.type = val5.type; val4.pixelsPerUnitMultiplier = val5.pixelsPerUnitMultiplier; } val3.transform.SetAsFirstSibling(); return val4; } private static void HideHighlight(PieceIconData iconData, string objectName) { if (!((Object)(object)iconData?.m_go == (Object)null)) { Transform val = iconData.m_go.transform.Find(objectName); if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(false); } } } private static void ConfigureBadgeText(TMP_Text text, TMP_Text template) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)template == (Object)null) && !((Object)(object)template.font == (Object)null)) { text.font = template.font; if ((Object)(object)template.fontSharedMaterial != (Object)null) { text.fontSharedMaterial = template.fontSharedMaterial; } else if ((Object)(object)((TMP_Asset)template.font).material != (Object)null) { text.fontSharedMaterial = ((TMP_Asset)template.font).material; } ((Graphic)text).raycastTarget = false; text.textWrappingMode = (TextWrappingModes)0; text.overflowMode = (TextOverflowModes)0; text.alignment = (TextAlignmentOptions)260; text.fontSize = 20f; text.fontStyle = (FontStyles)1; ((Graphic)text).color = BadgeColor; ConfigureShadow(text); text.text = ""; } } private static void ConfigureShadow(TMP_Text text) { //IL_002b: 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) Shadow val = default(Shadow); if (!((Component)text).TryGetComponent(ref val)) { val = ((Component)text).gameObject.AddComponent(); } val.effectColor = new Color(0f, 0f, 0f, 0.9f); val.effectDistance = new Vector2(1.5f, -1.5f); val.useGraphicAlpha = true; } private static TMP_Text FindTemplateText(Hud hud, PieceIconData iconData) { if ((Object)(object)hud != (Object)null) { if ((Object)(object)hud.m_buildSelection != (Object)null && (Object)(object)hud.m_buildSelection.font != (Object)null) { return hud.m_buildSelection; } if ((Object)(object)hud.m_pieceDescription != (Object)null && (Object)(object)hud.m_pieceDescription.font != (Object)null) { return hud.m_pieceDescription; } TMP_Text val = FindFirstUsableText(((Component)hud).transform); if ((Object)(object)val != (Object)null) { return val; } } if (!((Object)(object)iconData?.m_go != (Object)null)) { return null; } return FindFirstUsableText(iconData.m_go.transform); } private static TMP_Text FindFirstUsableText(Transform root) { if ((Object)(object)root == (Object)null) { return null; } TMP_Text[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (TMP_Text val in componentsInChildren) { if ((Object)(object)val != (Object)null && (Object)(object)val.font != (Object)null && ((Object)((Component)val).gameObject).name != "DataForgeComfortBadge") { return val; } } return null; } } internal static class VeiledRecipesSoftCompat { private delegate bool ShouldMaskPieceDelegate(Player player, Piece piece); private const string CompatTypeName = "VeiledRecipes.VeiledRecipesCompat"; private const string CompatAssemblyQualifiedName = "VeiledRecipes.VeiledRecipesCompat, VeiledRecipes"; private static bool Initialized; private static bool LoggedFailure; private static ShouldMaskPieceDelegate? ShouldMaskPieceMethod; internal static bool ShouldMaskPiece(Player player, Piece piece) { if ((Object)(object)player == (Object)null || (Object)(object)piece == (Object)null) { return false; } EnsureInitialized(); if (ShouldMaskPieceMethod == null) { return false; } try { return ShouldMaskPieceMethod(player, piece); } catch (Exception ex) { if (!LoggedFailure) { LoggedFailure = true; DataForgePlugin.Log.LogDebug((object)("VeiledRecipes piece mask check failed: " + ex.Message)); } return false; } } private static void EnsureInitialized() { if (Initialized) { return; } Initialized = true; Type type = Type.GetType("VeiledRecipes.VeiledRecipesCompat, VeiledRecipes", throwOnError: false); if (type == null) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { type = assemblies[i].GetType("VeiledRecipes.VeiledRecipesCompat", throwOnError: false); if (type != null) { break; } } } MethodInfo methodInfo = type?.GetMethod("ShouldMaskPiece", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(Player), typeof(Piece) }, null); if (methodInfo != null) { ShouldMaskPieceMethod = Delegate.CreateDelegate(typeof(ShouldMaskPieceDelegate), methodInfo, throwOnBindFailure: false) as ShouldMaskPieceDelegate; } } } [HarmonyPatch(typeof(Hud), "UpdatePieceList")] internal static class DataForgeHudUpdatePieceListComfortBadgePatch { private static void Postfix(Hud __instance, Player player) { PieceComfortHudBadges.Refresh(__instance, player); } } internal static class FireplaceFuelOverflow { internal static bool IsEnabledFor(Fireplace fireplace, out int maxStoredFuel) { maxStoredFuel = DataForgePlugin.MaxStoredFireplaceFuel; if (maxStoredFuel > 0 && (Object)(object)fireplace != (Object)null && fireplace.m_canRefill && !fireplace.m_infiniteFuel && (Object)(object)fireplace.m_fuelItem != (Object)null) { return (float)maxStoredFuel > fireplace.m_maxFuel; } return false; } internal static bool TryGetFuel(Fireplace fireplace, out float fuel) { fuel = 0f; if ((Object)(object)fireplace.m_nview == (Object)null || !fireplace.m_nview.IsValid() || fireplace.m_nview.GetZDO() == null) { return false; } fuel = fireplace.m_nview.GetZDO().GetFloat(ZDOVars.s_fuel, 0f); return true; } internal static bool CanAcceptMore(float fuel, int maxStoredFuel) { return Mathf.CeilToInt(fuel) < maxStoredFuel; } internal static bool IsVanillaFull(Fireplace fireplace, float fuel) { return (float)Mathf.CeilToInt(fuel) >= fireplace.m_maxFuel; } internal static void InvokeSetFuel(Fireplace fireplace, float fuel, int maxStoredFuel) { fireplace.m_nview.InvokeRPC("RPC_SetFuelAmount", new object[1] { Mathf.Clamp(fuel, 0f, (float)maxStoredFuel) }); } internal static void SetOwnedFuel(Fireplace fireplace, float fuel, int maxStoredFuel) { //IL_002e: 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) fireplace.m_nview.GetZDO().Set(ZDOVars.s_fuel, Mathf.Clamp(fuel, 0f, (float)maxStoredFuel)); fireplace.m_fuelAddedEffects.Create(((Component)fireplace).transform.position, ((Component)fireplace).transform.rotation, (Transform)null, 1f, -1); fireplace.UpdateState(); } internal static void ShowCannotAddMore(Humanoid user, string fuelName) { ((Character)user).Message((MessageType)2, Localization.instance.Localize("$msg_cantaddmore", new string[1] { fuelName }), 0, (Sprite)null); } internal static void ShowOutOfFuel(Humanoid user, string fuelName) { ((Character)user).Message((MessageType)2, "$msg_outof " + fuelName, 0, (Sprite)null); } internal static void ShowAddingFuel(Humanoid user, string fuelName) { ((Character)user).Message((MessageType)2, Localization.instance.Localize("$msg_fireadding", new string[1] { fuelName }), 0, (Sprite)null); } internal static void RefundFuel(Fireplace fireplace) { //IL_00a0: 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_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) if (!IsEnabledFor(fireplace, out var maxStoredFuel) || !TryGetFuel(fireplace, out var fuel) || !fireplace.m_nview.IsOwner()) { return; } float num = Mathf.Min(Mathf.Max(0f, fuel), (float)maxStoredFuel) - Mathf.Max(0f, fireplace.m_startFuel); int num2 = Mathf.FloorToInt(Mathf.Max(0f, num)); if (num2 <= 0) { return; } GameObject val = ResolveFuelPrefab(fireplace); ItemDrop component = val.GetComponent(); if (!((Object)(object)component == (Object)null)) { Piece component2 = ((Component)fireplace).GetComponent(); float num3 = (((Object)(object)component2 != (Object)null) ? component2.m_returnResourceHeightOffset : 1f); Vector3 val2 = ((Component)fireplace).transform.position + Vector3.up * num3; int num4 = Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize); while (num2 > 0) { ItemDrop component3 = Object.Instantiate(val, val2, Quaternion.identity).GetComponent(); int num5 = Mathf.Min(num2, num4); component3.SetStack(num5); ItemDrop.OnCreateNew(component3); num2 -= num5; } } } private static GameObject ResolveFuelPrefab(Fireplace fireplace) { string text = NormalizePrefabName(((Object)((Component)fireplace.m_fuelItem).gameObject).name); GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(text) : null); if (!((Object)(object)val != (Object)null)) { return ((Component)fireplace.m_fuelItem).gameObject; } return val; } private static string NormalizePrefabName(string prefabName) { return prefabName.Replace("(Clone)", "").Trim(); } } [HarmonyPatch(typeof(Fireplace), "Interact")] internal static class DataForgeFireplaceOverflowInteractPatch { private static bool Prefix(Fireplace __instance, Humanoid user, bool hold, bool alt, ref bool __result) { if (!FireplaceFuelOverflow.IsEnabledFor(__instance, out var maxStoredFuel) || !FireplaceFuelOverflow.TryGetFuel(__instance, out var fuel) || !FireplaceFuelOverflow.IsVanillaFull(__instance, fuel)) { return true; } if (hold) { if (__instance.m_holdRepeatInterval <= 0f) { __result = false; return false; } if (Time.time - __instance.m_lastUseTime < __instance.m_holdRepeatInterval) { __result = false; return false; } } if (!__instance.m_nview.HasOwner()) { __instance.m_nview.ClaimOwnership(); } if (__instance.m_canTurnOff && !hold && !alt && fuel > 0f) { return true; } Inventory inventory = user.GetInventory(); string name = __instance.m_fuelItem.m_itemData.m_shared.m_name; if (inventory == null) { __result = false; return false; } if (!inventory.HaveItem(name, true)) { FireplaceFuelOverflow.ShowOutOfFuel(user, name); __result = false; return false; } if (!FireplaceFuelOverflow.CanAcceptMore(fuel, maxStoredFuel)) { FireplaceFuelOverflow.ShowCannotAddMore(user, name); __result = false; return false; } FireplaceFuelOverflow.ShowAddingFuel(user, name); inventory.RemoveItem(name, 1, -1, true); FireplaceFuelOverflow.InvokeSetFuel(__instance, fuel + 1f, maxStoredFuel); __result = true; return false; } } [HarmonyPatch(typeof(Fireplace), "UseItem")] internal static class DataForgeFireplaceOverflowUseItemPatch { private static bool Prefix(Fireplace __instance, Humanoid user, ItemData item, ref bool __result) { if (!FireplaceFuelOverflow.IsEnabledFor(__instance, out var maxStoredFuel) || !FireplaceFuelOverflow.TryGetFuel(__instance, out var fuel) || !FireplaceFuelOverflow.IsVanillaFull(__instance, fuel)) { return true; } string name = __instance.m_fuelItem.m_itemData.m_shared.m_name; if (item.m_shared.m_name != name) { return true; } if (!FireplaceFuelOverflow.CanAcceptMore(fuel, maxStoredFuel)) { FireplaceFuelOverflow.ShowCannotAddMore(user, item.m_shared.m_name); __result = true; return false; } Inventory inventory = user.GetInventory(); FireplaceFuelOverflow.ShowAddingFuel(user, item.m_shared.m_name); inventory.RemoveItem(item, 1); FireplaceFuelOverflow.InvokeSetFuel(__instance, fuel + 1f, maxStoredFuel); __result = true; return false; } } [HarmonyPatch(typeof(Fireplace), "CanUseItems")] internal static class DataForgeFireplaceOverflowCanUseItemsPatch { private static bool Prefix(Fireplace __instance, Player player, bool sendErrorMessage, ref bool __result) { if (!FireplaceFuelOverflow.IsEnabledFor(__instance, out var maxStoredFuel) || !FireplaceFuelOverflow.TryGetFuel(__instance, out var fuel) || !FireplaceFuelOverflow.IsVanillaFull(__instance, fuel)) { return true; } string name = __instance.m_fuelItem.m_itemData.m_shared.m_name; if (!((Humanoid)player).GetInventory().HaveItem(name, true)) { if (sendErrorMessage) { FireplaceFuelOverflow.ShowOutOfFuel((Humanoid)(object)player, name); } __result = false; return false; } if (FireplaceFuelOverflow.CanAcceptMore(fuel, maxStoredFuel)) { __result = true; return false; } if (sendErrorMessage) { FireplaceFuelOverflow.ShowCannotAddMore((Humanoid)(object)player, name); } __result = false; return false; } } [HarmonyPatch(typeof(Fireplace), "AddFuel")] internal static class DataForgeFireplaceOverflowAddFuelPatch { private static bool Prefix(Fireplace __instance, float fuel) { if (!FireplaceFuelOverflow.IsEnabledFor(__instance, out var maxStoredFuel) || !FireplaceFuelOverflow.TryGetFuel(__instance, out var fuel2)) { return true; } if ((fuel < 0f && fuel2 > 0f) || (fuel > 0f && fuel2 < (float)maxStoredFuel)) { __instance.m_nview.InvokeRPC("RPC_AddFuelAmount", new object[1] { fuel }); } return false; } } [HarmonyPatch(typeof(Fireplace), "SetFuel")] internal static class DataForgeFireplaceOverflowSetFuelPatch { private static bool Prefix(Fireplace __instance, float fuel) { if (!FireplaceFuelOverflow.IsEnabledFor(__instance, out var maxStoredFuel) || !FireplaceFuelOverflow.TryGetFuel(__instance, out var fuel2)) { return true; } float num = Mathf.Clamp(fuel, 0f, (float)maxStoredFuel); if (!Mathf.Approximately(num, fuel2)) { __instance.m_nview.InvokeRPC("RPC_SetFuelAmount", new object[1] { num }); } return false; } } [HarmonyPatch(typeof(Fireplace), "RPC_AddFuel")] internal static class DataForgeFireplaceOverflowRpcAddFuelPatch { private static bool Prefix(Fireplace __instance) { if (!FireplaceFuelOverflow.IsEnabledFor(__instance, out var maxStoredFuel) || !FireplaceFuelOverflow.TryGetFuel(__instance, out var fuel)) { return true; } if (__instance.m_nview.IsOwner() && FireplaceFuelOverflow.CanAcceptMore(fuel, maxStoredFuel)) { FireplaceFuelOverflow.SetOwnedFuel(__instance, fuel + 1f, maxStoredFuel); } return false; } } [HarmonyPatch(typeof(Fireplace), "RPC_AddFuelAmount")] internal static class DataForgeFireplaceOverflowRpcAddFuelAmountPatch { private static bool Prefix(Fireplace __instance, float amount) { if (!FireplaceFuelOverflow.IsEnabledFor(__instance, out var maxStoredFuel) || !FireplaceFuelOverflow.TryGetFuel(__instance, out var fuel)) { return true; } if (__instance.m_nview.IsOwner()) { FireplaceFuelOverflow.SetOwnedFuel(__instance, fuel + amount, maxStoredFuel); } return false; } } [HarmonyPatch(typeof(Fireplace), "RPC_SetFuelAmount")] internal static class DataForgeFireplaceOverflowRpcSetFuelAmountPatch { private static bool Prefix(Fireplace __instance, float fuel) { if (!FireplaceFuelOverflow.IsEnabledFor(__instance, out var maxStoredFuel)) { return true; } if (__instance.m_nview.IsOwner()) { FireplaceFuelOverflow.SetOwnedFuel(__instance, fuel, maxStoredFuel); } return false; } } [HarmonyPatch(typeof(WearNTear), "Destroy")] internal static class DataForgeFireplaceOverflowWearNTearDestroyPatch { private static void Prefix(WearNTear __instance, bool blockDrop) { if (!blockDrop) { Fireplace component = ((Component)__instance).GetComponent(); if ((Object)(object)component != (Object)null) { FireplaceFuelOverflow.RefundFuel(component); } } } } [HarmonyPatch(typeof(StationExtension), "OtherExtensionInRange")] internal static class DataForgeStationExtensionSpacingBypassPatch { private static bool Prefix(ref bool __result) { if (!DataForgePlugin.IgnoreStationExtensionSpacing) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(StationExtension), "StartConnectionEffect", new Type[] { typeof(Vector3), typeof(float) })] internal static class DataForgeStationExtensionConnectionEffectPatch { private static bool Prefix(StationExtension __instance) { return (Object)(object)__instance.m_connectionPrefab != (Object)null; } } [HarmonyPatch(typeof(StationExtension), "FindExtensions")] internal static class DataForgeStationExtensionFindExtensionsPatch { private static bool Prefix(CraftingStation station, Vector3 pos, List extensions) { //IL_0074: 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_007a: 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) if (!DataForgePlugin.IgnoreStationExtensionSpacing) { return true; } if ((Object)(object)station == (Object)null || extensions == null) { return false; } for (int num = StationExtension.m_allExtensions.Count - 1; num >= 0; num--) { StationExtension val = StationExtension.m_allExtensions[num]; if ((Object)(object)val == (Object)null || (Object)(object)val.m_craftingStation == (Object)null || (Object)(object)val.m_piece == (Object)null) { StationExtension.m_allExtensions.RemoveAt(num); } else { float maxStationDistance = val.m_maxStationDistance; Vector3 val2 = ((Component)val).transform.position - pos; if (((Vector3)(ref val2)).sqrMagnitude < maxStationDistance * maxStationDistance && val.m_craftingStation.m_name == station.m_name && (val.m_stack || !StationExtension.ExtensionInList(extensions, val))) { extensions.Add(val); } } } return false; } } [HarmonyPatch(typeof(CraftingStation), "GetExtensions")] internal static class DataForgeCraftingStationGetExtensionsPatch { private static bool Prefix(CraftingStation __instance, ref List __result) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!DataForgePlugin.IgnoreStationExtensionSpacing) { return true; } if (__instance.m_attachedExtensions == null) { __instance.m_attachedExtensions = new List(); } if (__instance.m_updateExtensionTimer >= 2f) { __instance.m_updateExtensionTimer = 0f; __instance.m_attachedExtensions.Clear(); AddValidExtensions(__instance, ((Component)__instance).transform.position, __instance.m_attachedExtensions); __instance.m_buildRange = __instance.m_rangeBuild + (float)__instance.m_attachedExtensions.Count * __instance.m_extraRangePerLevel; UpdateAreaMarker(__instance); } __result = __instance.m_attachedExtensions; return false; } private static void AddValidExtensions(CraftingStation station, Vector3 position, List extensions) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: 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) for (int num = StationExtension.m_allExtensions.Count - 1; num >= 0; num--) { StationExtension val = StationExtension.m_allExtensions[num]; if ((Object)(object)val == (Object)null || (Object)(object)val.m_craftingStation == (Object)null || (Object)(object)val.m_piece == (Object)null) { StationExtension.m_allExtensions.RemoveAt(num); } else { float maxStationDistance = val.m_maxStationDistance; Vector3 val2 = ((Component)val).transform.position - position; if (((Vector3)(ref val2)).sqrMagnitude < maxStationDistance * maxStationDistance && val.m_craftingStation.m_name == station.m_name && (val.m_stack || !StationExtension.ExtensionInList(extensions, val))) { extensions.Add(val); } } } } private static void UpdateAreaMarker(CraftingStation station) { if (!((Object)(object)station.m_areaMarker == (Object)null)) { if ((Object)(object)station.m_areaMarkerCircle == (Object)null) { station.m_areaMarkerCircle = station.m_areaMarker.GetComponent(); } if ((Object)(object)station.m_areaMarkerCircle != (Object)null) { station.m_areaMarkerCircle.m_radius = station.m_buildRange; } } } } internal static class PieceTableCategoryGuard { private static readonly Dictionary CategoryLabels = new Dictionary(); private static readonly FieldInfo? SelectedCategoryField = AccessTools.Field(typeof(PieceTable), "m_selectedCategory"); private static readonly FieldInfo? AvailablePiecesField = AccessTools.Field(typeof(PieceTable), "m_availablePieces"); internal static void Normalize(PieceTable? pieceTable) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)pieceTable) || pieceTable.m_categories == null) { return; } RemoveInvalidPieces(pieceTable); if (pieceTable.m_categoryLabels == null) { pieceTable.m_categoryLabels = new List(); } while (pieceTable.m_categoryLabels.Count > pieceTable.m_categories.Count) { pieceTable.m_categoryLabels.RemoveAt(pieceTable.m_categoryLabels.Count - 1); } for (int i = 0; i < pieceTable.m_categories.Count; i++) { string label = GetLabel(pieceTable.m_categories[i]); if (i >= pieceTable.m_categoryLabels.Count) { pieceTable.m_categoryLabels.Add(label); } else if (string.IsNullOrWhiteSpace(pieceTable.m_categoryLabels[i])) { pieceTable.m_categoryLabels[i] = label; } } NormalizeSelectedCategory(pieceTable); } internal static void EnsureSelectedCategory(PieceTable? pieceTable) { if (Object.op_Implicit((Object)(object)pieceTable) && pieceTable.m_categories != null) { NormalizeSelectedCategory(pieceTable); } } private static void RemoveInvalidPieces(PieceTable pieceTable) { if (pieceTable.m_pieces == null) { return; } for (int num = pieceTable.m_pieces.Count - 1; num >= 0; num--) { GameObject val = pieceTable.m_pieces[num]; if (!Object.op_Implicit((Object)(object)val) || (Object)(object)val.GetComponent() == (Object)null) { pieceTable.m_pieces.RemoveAt(num); } } } internal static void EnsureCategory(PieceTable? pieceTable, PieceCategory category) { //IL_0009: 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_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) if (Object.op_Implicit((Object)(object)pieceTable) && IsSelectableCategory(pieceTable, category)) { PieceTable val = pieceTable; if (val.m_categories == null) { val.m_categories = new List(); } val = pieceTable; if (val.m_categoryLabels == null) { val.m_categoryLabels = new List(); } Normalize(pieceTable); if (!pieceTable.m_categories.Contains(category)) { pieceTable.m_categories.Add(category); pieceTable.m_categoryLabels.Add(GetLabel(category)); NormalizeSelectedCategory(pieceTable); } } } private static void NormalizeSelectedCategory(PieceTable pieceTable) { //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_002d: 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_0049: Unknown result type (might be due to invalid IL or missing references) if (!(SelectedCategoryField == null) && SelectedCategoryField.GetValue(pieceTable) is PieceCategory val && (!IsSelectableCategory(pieceTable, val) || pieceTable.m_categories.Count <= 0 || !pieceTable.m_categories.Contains(val))) { SelectedCategoryField.SetValue(pieceTable, FindFallbackCategory(pieceTable)); } } private static PieceCategory FindFallbackCategory(PieceTable pieceTable) { //IL_0010: 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_0017: 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_003e: Unknown result type (might be due to invalid IL or missing references) foreach (PieceCategory category in pieceTable.m_categories) { if (IsSelectableCategory(pieceTable, category)) { return category; } } return (PieceCategory)0; } private static bool IsSelectableCategory(PieceTable pieceTable, PieceCategory category) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected I4, but got Unknown if (((int)category == 8 || (int)category == 100) ? true : false) { return false; } int num = (int)category; if (num >= 0) { return num < GetAvailableCategorySlotCount(pieceTable); } return false; } private static int GetAvailableCategorySlotCount(PieceTable pieceTable) { if (AvailablePiecesField?.GetValue(pieceTable) is ICollection { Count: >0 } collection) { return collection.Count; } return Math.Max(0, 8); } internal unsafe static string GetLabel(PieceCategory category) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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_008b: Unknown result type (might be due to invalid IL or missing references) if (CategoryLabels.TryGetValue(category, out string value)) { return value; } PieceTable[] array = Resources.FindObjectsOfTypeAll(); foreach (PieceTable val in array) { if (!Object.op_Implicit((Object)(object)val) || val.m_categories == null || val.m_categoryLabels == null) { continue; } int num = Math.Min(val.m_categories.Count, val.m_categoryLabels.Count); for (int j = 0; j < num; j++) { if (val.m_categories[j] == category && !string.IsNullOrWhiteSpace(val.m_categoryLabels[j])) { CategoryLabels[category] = val.m_categoryLabels[j]; return val.m_categoryLabels[j]; } } } value = ((object)(*(PieceCategory*)(&category))/*cast due to .constrained prefix*/).ToString(); CategoryLabels[category] = value; return value; } } [HarmonyPatch(typeof(PieceTable), "GetSelectedCategory")] internal static class DataForgePieceTableGetSelectedCategoryGuardPatch { [HarmonyPriority(800)] private static void Prefix(PieceTable __instance) { PieceTableCategoryGuard.EnsureSelectedCategory(__instance); } } [HarmonyPatch(typeof(Player), "SetPlaceMode")] internal static class DataForgePlayerSetPlaceModePieceTableCategoryGuardPatch { [HarmonyPriority(800)] private static void Prefix(PieceTable buildPieces) { PieceTableCategoryGuard.Normalize(buildPieces); } } [HarmonyPatch(typeof(PieceTable), "UpdateAvailable")] internal static class DataForgePieceTableUpdateAvailableCategoryGuardPatch { [HarmonyPriority(800)] private static void Prefix(PieceTable __instance) { PieceTableCategoryGuard.Normalize(__instance); } } [HarmonyPatch(typeof(PieceTable), "SetCategory")] internal static class DataForgePieceTableSetCategoryGuardPatch { [HarmonyPriority(800)] private static bool Prefix(PieceTable __instance, int index) { PieceTableCategoryGuard.Normalize(__instance); if (index >= 0 && __instance.m_categories != null) { return index < __instance.m_categories.Count; } return false; } } internal static class VneiPrefabCleanupGuard { private static bool VneiIndexAllPatchInstalled; private static bool VneiIndexAllPatchFailed; private static DateTime NextPatchAttemptUtc = DateTime.MinValue; internal static void TryPatchVneiIndexAll(Harmony harmony) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown if (VneiIndexAllPatchInstalled || VneiIndexAllPatchFailed) { return; } DateTime utcNow = DateTime.UtcNow; if (utcNow < NextPatchAttemptUtc) { return; } NextPatchAttemptUtc = utcNow.AddSeconds(5.0); Type type = FindLoadedType("VNEI.Logic.Indexing"); if (!(type == null)) { MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "IndexAll", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(VneiPrefabCleanupGuard), "RemoveInvalidEntriesBeforeVnei", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { VneiIndexAllPatchFailed = true; DataForgePlugin.Log.LogWarning((object)"Could not install VNEI invalid prefab cleanup patch."); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); VneiIndexAllPatchInstalled = true; } } } private static Type? FindLoadedType(string fullName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(fullName, throwOnError: false); if (type != null) { return type; } } return null; } private static void RemoveInvalidEntriesBeforeVnei() { ZNetScene instance = ZNetScene.instance; if (Object.op_Implicit((Object)(object)instance)) { RemoveInvalidPrefabListEntries(instance.m_prefabs); RemoveInvalidNamedPrefabEntries(instance.m_namedPrefabs); RemoveInvalidPieceTableEntries(instance); } } private static void RemoveInvalidPrefabListEntries(List prefabs) { for (int num = prefabs.Count - 1; num >= 0; num--) { if (!IsAlive((Object?)(object)prefabs[num])) { prefabs.RemoveAt(num); } } } private static void RemoveInvalidNamedPrefabEntries(Dictionary namedPrefabs) { foreach (int item in (from pair in namedPrefabs where !IsAlive((Object?)(object)pair.Value) select pair.Key).ToList()) { namedPrefabs.Remove(item); } } private static void RemoveInvalidPieceTableEntries(ZNetScene scene) { foreach (PieceTable item in CollectBuildPieceTables(scene)) { if (!Object.op_Implicit((Object)(object)item)) { continue; } List pieces = item.m_pieces; for (int num = pieces.Count - 1; num >= 0; num--) { if (!IsAlive((Object?)(object)pieces[num])) { pieces.RemoveAt(num); } } } } private static IEnumerable CollectBuildPieceTables(ZNetScene scene) { List list = new List(); ItemDrop val = default(ItemDrop); foreach (GameObject vneiPrefabCandidate in GetVneiPrefabCandidates(scene)) { if (vneiPrefabCandidate.TryGetComponent(ref val)) { PieceTable val2 = val.m_itemData?.m_shared?.m_buildPieces; if (Object.op_Implicit((Object)(object)val2) && !list.Contains(val2)) { list.Add(val2); } } } return list; } private static IEnumerable GetVneiPrefabCandidates(ZNetScene scene) { HashSet hashSet = new HashSet(); foreach (GameObject prefab in scene.m_prefabs) { if (IsAlive((Object?)(object)prefab)) { hashSet.Add(prefab); } } foreach (GameObject value in scene.m_namedPrefabs.Values) { if (IsAlive((Object?)(object)value)) { hashSet.Add(value); } } return hashSet; } private static bool IsAlive(Object? unityObject) { if (unityObject != null) { return Object.op_Implicit(unityObject); } return false; } } internal static class ReferenceDefaultRules { private const float FloatEpsilon = 0.0001f; internal static bool IsDefaultString(string value, string propertyName) { if (string.IsNullOrWhiteSpace(value)) { return true; } string text = value.Trim(); if (IsDefaultSkillValuePair(text, propertyName)) { return true; } if (IsDefaultOverTimeTuple(text, propertyName)) { return true; } if (IsDefaultDamageTakenModifier(text, propertyName)) { return true; } if (IsDefaultCraftingStationTuple(text, propertyName)) { return true; } if (IsDefaultDamageTuple(text, propertyName)) { return true; } if (text.Equals("none", StringComparison.OrdinalIgnoreCase) || text.Equals("null", StringComparison.OrdinalIgnoreCase) || text.Equals("undefined", StringComparison.OrdinalIgnoreCase) || text.Equals("topLeft", StringComparison.OrdinalIgnoreCase)) { return true; } if (propertyName.Equals("Category", StringComparison.OrdinalIgnoreCase) && text.Equals("Misc", StringComparison.OrdinalIgnoreCase)) { return true; } if (propertyName.Equals("MaterialType", StringComparison.OrdinalIgnoreCase)) { return text.Equals("Wood", StringComparison.OrdinalIgnoreCase); } return false; } internal static bool DefaultBool(string propertyName) { if (!propertyName.Equals("Override", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Teleportable", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Floating", StringComparison.OrdinalIgnoreCase)) { return propertyName.Equals("CanBeRemoved", StringComparison.OrdinalIgnoreCase); } return true; } internal static int DefaultInt(string propertyName) { if (propertyName.Equals("ListSortWeight", StringComparison.OrdinalIgnoreCase)) { return 100; } if (!propertyName.Equals("Amount", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("MinStationLevel", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("MaxStackSize", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("MaxQuality", StringComparison.OrdinalIgnoreCase)) { return 0; } return 1; } internal static bool IsDefaultFloat(float value, string propertyName) { float num = 0f; if (propertyName.EndsWith("Multiplier", StringComparison.OrdinalIgnoreCase) || propertyName.Equals("RaiseSkillAmount", StringComparison.OrdinalIgnoreCase)) { num = 1f; } else if (propertyName.Equals("TimedBlockBonus", StringComparison.OrdinalIgnoreCase)) { num = 2f; } else if (propertyName.Equals("LastChainDamageMultiplier", StringComparison.OrdinalIgnoreCase)) { num = 2f; } return Math.Abs(value - num) <= 0.0001f; } private static bool IsDefaultCraftingStationTuple(string value, string propertyName) { if (!propertyName.Equals("CraftingStation", StringComparison.OrdinalIgnoreCase)) { return false; } string[] array = SplitTuple(value); if (array.Length > 2) { return false; } if (array.Length == 0 || array[0].Length == 0 || array[0].Equals("None", StringComparison.OrdinalIgnoreCase) || array[0].Equals("Null", StringComparison.OrdinalIgnoreCase)) { return IsDefaultIntPart(array, 1, 1); } return false; } private static bool IsDefaultDamageTuple(string value, string propertyName) { if (IsDamageTypeProperty(propertyName)) { return IsDefaultFloatTuple(value, default(float), default(float)); } return false; } private static bool IsDefaultDamageTakenModifier(string value, string propertyName) { if (IsDamageTypeProperty(propertyName)) { return value.Equals("normal", StringComparison.OrdinalIgnoreCase); } return false; } private static bool IsDamageTypeProperty(string propertyName) { if (!propertyName.Equals("Blunt", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Slash", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Pierce", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Chop", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Pickaxe", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Fire", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Frost", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Lightning", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("Poison", StringComparison.OrdinalIgnoreCase)) { return propertyName.Equals("Spirit", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsDefaultSkillValuePair(string value, string propertyName) { if (!propertyName.Equals("RaiseSkill", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("SkillLevel", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("SkillLevel2", StringComparison.OrdinalIgnoreCase) && !propertyName.Equals("AttackDamage", StringComparison.OrdinalIgnoreCase)) { return false; } string[] array = (from part in value.Split(new char[1] { ',' }, 2, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length == 0 || !array[0].Equals("None", StringComparison.OrdinalIgnoreCase)) { return false; } float num = (propertyName.Equals("AttackDamage", StringComparison.OrdinalIgnoreCase) ? 1f : 0f); if (array.Length == 1) { return Math.Abs(num) <= 0.0001f; } if (float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return Math.Abs(result - num) <= 0.0001f; } return false; } private static bool IsDefaultOverTimeTuple(string value, string propertyName) { if (propertyName.Equals("UpFront", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float), default(float), default(float)); } if (propertyName.Equals("RegenMultiplier", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, 1f, 1f, 1f); } if (propertyName.Equals("RequireOnlyOneIngredient", StringComparison.OrdinalIgnoreCase)) { return IsDefaultBoolFloatTuple(value, defaultFirst: false, 1f); } if (propertyName.Equals("HealthPerTick", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatFloatStringTuple(value, 0f, 0f, "Undefined"); } if (propertyName.Equals("HealthOverTime", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, 0f, 0f, 5f); } if (propertyName.Equals("Time", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float)); } if (propertyName.Equals("IconFlags", StringComparison.OrdinalIgnoreCase)) { return IsDefaultBoolTuple(value, default(bool), default(bool)); } if (propertyName.Equals("EitrOverTime", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float)); } if (propertyName.Equals("Draw", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float), default(float)); } if (propertyName.Equals("Cost", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float), default(float), default(float)); } if (propertyName.Equals("MissingHealth", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float), default(float)); } if (propertyName.Equals("SpawnOnHit", StringComparison.OrdinalIgnoreCase)) { return IsDefaultSpawnOnHitTuple(value); } if (propertyName.Equals("Reload", StringComparison.OrdinalIgnoreCase)) { return IsDefaultBoolFloatTuple(value, false, default(float), default(float), default(float)); } if (propertyName.Equals("Armor", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float)); } if (propertyName.Equals("Block", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float)); } if (propertyName.Equals("Fall", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float)); } if (propertyName.Equals("Sneak", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float)); } if (propertyName.Equals("WindRun", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float)); } if (propertyName.Equals("JumpModifier", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float), default(float)); } if (propertyName.Equals("Weight", StringComparison.OrdinalIgnoreCase) || propertyName.Equals("BuildRange", StringComparison.OrdinalIgnoreCase) || propertyName.Equals("BlockPower", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, default(float), default(float)); } if (propertyName.Equals("Comfort", StringComparison.OrdinalIgnoreCase)) { return IsDefaultIntStringTuple(value, 0, "None"); } if (propertyName.Equals("DeflectionForce", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatTuple(value, 20f, 5f); } if (propertyName.Equals("Durability", StringComparison.OrdinalIgnoreCase)) { return IsDefaultDurabilityTuple(value); } if (propertyName.Equals("Food", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFoodTuple(value); } if (propertyName.Equals("Fuel", StringComparison.OrdinalIgnoreCase)) { return IsDefaultCookingStationFuelTuple(value); } if (propertyName.Equals("StaminaOverTime", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatBoolTuple(value, 0f, 0f, defaultThird: false); } return false; } private static bool IsDefaultCookingStationFuelTuple(string value) { string[] array = SplitTuple(value); if (array.Length != 4) { return false; } if ((array[0].Length == 0 || array[0].Equals("None", StringComparison.OrdinalIgnoreCase) || array[0].Equals("Null", StringComparison.OrdinalIgnoreCase)) && IsDefaultBoolPart(array, 1, defaultValue: false) && IsDefaultIntPart(array, 2, 0)) { return IsDefaultIntPart(array, 3, 0); } return false; } private static bool IsDefaultFoodTuple(string value) { string[] array = SplitTuple(value); if (array.Length > 5) { return false; } if (IsDefaultFloatPart(array, 0, 0f) && IsDefaultFloatPart(array, 1, 0f) && IsDefaultFloatPart(array, 2, 0f) && IsDefaultFloatPart(array, 3, 0f)) { return IsDefaultFloatPart(array, 4, 0f); } return false; } private static bool IsDefaultDurabilityTuple(string value) { string[] array = SplitTuple(value); if (array.Length > 7) { return false; } if (IsDefaultFloatPart(array, 0, 0f) && IsDefaultFloatPart(array, 1, 0f) && IsDefaultBoolPart(array, 2, defaultValue: false) && IsDefaultFloatPart(array, 3, 0f) && IsDefaultBoolPart(array, 4, defaultValue: false) && IsDefaultBoolPart(array, 5, defaultValue: false)) { return IsDefaultFloatPart(array, 6, 0f); } return false; } private static bool IsDefaultBoolPart(string[] parts, int index, bool defaultValue) { if (index >= parts.Length || parts[index].Length == 0) { return true; } if (bool.TryParse(parts[index], out var result)) { return result == defaultValue; } return false; } private static bool IsDefaultIntPart(string[] parts, int index, int defaultValue) { if (index >= parts.Length || parts[index].Length == 0) { return true; } if (int.TryParse(parts[index], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result == defaultValue; } return false; } private static bool IsDefaultFloatPart(string[] parts, int index, float defaultValue) { if (index >= parts.Length || parts[index].Length == 0) { return true; } if (float.TryParse(parts[index], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return Math.Abs(result - defaultValue) <= 0.0001f; } return false; } private static bool IsDefaultFloatFloatStringTuple(string value, float defaultFirst, float defaultSecond, string defaultThird) { string[] array = SplitTuple(value); if (array.Length > 3) { return false; } if (!IsDefaultFloatTuple(string.Join(", ", array.Take(Math.Min(array.Length, 2))), defaultFirst, defaultSecond)) { return false; } if (array.Length > 2 && array[2].Length != 0) { return array[2].Equals(defaultThird, StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsDefaultIntStringTuple(string value, int defaultFirst, string defaultSecond) { string[] array = SplitTuple(value); if (array.Length > 2) { return false; } if (!IsDefaultIntPart(array, 0, defaultFirst)) { return false; } if (array.Length > 1 && array[1].Length != 0) { return array[1].Equals(defaultSecond, StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsDefaultSpawnOnHitTuple(string value) { string[] array = SplitTuple(value); if (array.Length > 2) { return false; } if (array.Length == 0 || array[0].Length == 0 || array[0].Equals("None", StringComparison.OrdinalIgnoreCase) || array[0].Equals("Null", StringComparison.OrdinalIgnoreCase)) { return IsDefaultFloatPart(array, 1, 0f); } return false; } private static bool IsDefaultBoolFloatTuple(string value, bool defaultFirst, float defaultSecond) { string[] array = SplitTuple(value); if (array.Length > 2) { return false; } bool result = defaultFirst; if (array.Length != 0 && array[0].Length > 0 && !bool.TryParse(array[0], out result)) { return false; } float result2 = defaultSecond; if (array.Length > 1 && array[1].Length > 0 && !float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out result2)) { return false; } if (result == defaultFirst) { return Math.Abs(result2 - defaultSecond) <= 0.0001f; } return false; } private static bool IsDefaultBoolTuple(string value, params bool[] defaults) { string[] array = SplitTuple(value); if (array.Length > defaults.Length) { return false; } for (int i = 0; i < defaults.Length; i++) { bool result = defaults[i]; if (i < array.Length && array[i].Length > 0 && !bool.TryParse(array[i], out result)) { return false; } if (result != defaults[i]) { return false; } } return true; } private static bool IsDefaultBoolFloatTuple(string value, bool defaultFirst, params float[] defaultRest) { string[] array = SplitTuple(value); if (array.Length > defaultRest.Length + 1) { return false; } bool result = defaultFirst; if (array.Length != 0 && array[0].Length > 0 && !bool.TryParse(array[0], out result)) { return false; } if (result != defaultFirst) { return false; } for (int i = 0; i < defaultRest.Length; i++) { int num = i + 1; float result2 = defaultRest[i]; if (num < array.Length && array[num].Length > 0 && !float.TryParse(array[num], NumberStyles.Float, CultureInfo.InvariantCulture, out result2)) { return false; } if (Math.Abs(result2 - defaultRest[i]) > 0.0001f) { return false; } } return true; } private static bool IsDefaultFloatTuple(string value, params float[] defaults) { string[] array = SplitTuple(value); if (array.Length > defaults.Length) { return false; } for (int i = 0; i < defaults.Length; i++) { float result = defaults[i]; if (i < array.Length && array[i].Length > 0 && !float.TryParse(array[i], NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { return false; } if (Math.Abs(result - defaults[i]) > 0.0001f) { return false; } } return true; } private static bool IsDefaultFloatBoolTuple(string value, float defaultFirst, float defaultSecond, bool defaultThird) { string[] array = SplitTuple(value); if (array.Length > 3) { return false; } if (!IsDefaultFloatTuple(string.Join(", ", array.Take(Math.Min(array.Length, 2))), defaultFirst, defaultSecond)) { return false; } bool result = defaultThird; if (array.Length > 2 && array[2].Length != 0) { if (bool.TryParse(array[2], out result)) { return result == defaultThird; } return false; } return true; } private static string[] SplitTuple(string value) { return (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); } } internal static class ReferenceValue { internal static T? ClonePruned(T? source) where T : class { return CloneValue(source, typeof(T), "") as T; } private static object? CloneValue(object? source, Type targetType, string propertyName) { if (source == null) { return null; } Type type = Nullable.GetUnderlyingType(targetType) ?? targetType; if (type == typeof(string)) { if (!ReferenceDefaultRules.IsDefaultString((string)source, propertyName)) { return source; } return null; } if (type == typeof(bool)) { bool flag = (bool)source; if (flag != ReferenceDefaultRules.DefaultBool(propertyName)) { return flag; } return null; } if (type == typeof(int)) { int num = (int)source; if (num != ReferenceDefaultRules.DefaultInt(propertyName)) { return num; } return null; } if (type == typeof(float)) { float num2 = (float)source; if (!ReferenceDefaultRules.IsDefaultFloat(num2, propertyName)) { return num2; } return null; } if (source is IDictionary dictionary) { if (dictionary.Count != 0) { return source; } return null; } if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string)) { return CloneList(source); } if (type.IsValueType) { object obj = Activator.CreateInstance(type); if (!source.Equals(obj)) { return source; } return null; } object obj2 = Activator.CreateInstance(type); bool flag2 = false; PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.CanRead && propertyInfo.CanWrite && propertyInfo.GetIndexParameters().Length == 0) { object obj3 = CloneValue(propertyInfo.GetValue(source), propertyInfo.PropertyType, propertyInfo.Name); if (obj3 != null) { propertyInfo.SetValue(obj2, obj3); flag2 = true; } } } if (!flag2) { return null; } return obj2; } private static object? CloneList(object source) { Type type = FindElementType(source.GetType()); if (type == null) { return null; } IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type)); foreach (object item in (IEnumerable)source) { object obj = CloneValue(item, type, ""); if (obj != null) { list.Add(obj); } } if (list.Count <= 0) { return null; } return list; } private static Type? FindElementType(Type type) { if (type.IsArray) { return type.GetElementType(); } if (type.IsGenericType && type.GetGenericArguments().Length == 1) { return type.GetGenericArguments()[0]; } return (from interfaceType in type.GetInterfaces() where interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>) select interfaceType.GetGenericArguments()[0]).FirstOrDefault(); } } public static class DataForgeStatusEffectOwnership { public static event Action? StatusEffectOverridesWillApply; public static event Action? StatusEffectOverridesApplied; public static bool HasActiveStatusEffectOverride(string effectName) { return StatusEffectOverrideManager.HasActiveStatusEffectOverride(effectName); } internal static void NotifyStatusEffectOverridesWillApply() { Notify(DataForgeStatusEffectOwnership.StatusEffectOverridesWillApply, "will-apply"); } internal static void NotifyStatusEffectOverridesApplied() { Notify(DataForgeStatusEffectOwnership.StatusEffectOverridesApplied, "applied"); } private static void Notify(Action? handlers, string phase) { if (handlers == null) { return; } Delegate[] invocationList = handlers.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(); } catch (Exception ex) { DataForgePlugin.Log.LogWarning((object)("Status effect ownership " + phase + " subscriber failed: " + ex.GetType().Name + ": " + ex.Message)); } } } } internal static class StatusEffectOverrideManager { internal sealed class StatusEffectEntry { internal string LogContext { get; private set; } = ""; public string Effect { get; set; } = ""; public bool Override { get; set; } = true; public string? CloneFrom { get; set; } public string? DisplayName { get; set; } public string? Tooltip { get; set; } public string? Icon { get; set; } public string? StartEffects { get; set; } public string? StopEffects { get; set; } public string? Category { get; set; } public string? Time { get; set; } public string? IconFlags { get; set; } public float? RepeatInterval { get; set; } public string? StartMessage { get; set; } public string? StopMessage { get; set; } public string? RepeatMessage { get; set; } public string? StartMessageType { get; set; } public string? StopMessageType { get; set; } public string? RepeatMessageType { get; set; } public string? Attributes { get; set; } public StatsDefinition? Stats { get; set; } public StaminaDrainModifierDefinition? StaminaDrainModifier { get; set; } public DamageTakenModifierDefinition? DamageTakenModifiers { get; set; } public StatusDamageDefinition? PercentageDamageModifiers { get; set; } public PoisonDefinition? Poison { get; set; } public ShieldStatusDefinition? Shield { get; set; } public FrostDefinition? Frost { get; set; } public RestedDefinition? Rested { get; set; } public HealthUpgradeDefinition? HealthUpgrade { get; set; } internal bool HasDefinition { get { if (!HasBaseDefinition && Stats == null && StaminaDrainModifier == null && DamageTakenModifiers == null && PercentageDamageModifiers == null && Poison == null && Shield == null && Frost == null && Rested == null) { return HealthUpgrade != null; } return true; } } private bool HasBaseDefinition { get { if (DisplayName == null && Tooltip == null && Icon == null && StartEffects == null && StopEffects == null && Category == null && Time == null && IconFlags == null && !RepeatInterval.HasValue && StartMessage == null && StopMessage == null && RepeatMessage == null && StartMessageType == null && StopMessageType == null && RepeatMessageType == null) { return Attributes != null; } return true; } } internal void SetLogContext(string value) { LogContext = value; } internal StatusEffectDefinition ToDefinition() { return new StatusEffectDefinition { Base = ToBaseDefinition(), Stats = Stats, StaminaDrainModifier = StaminaDrainModifier, DamageTakenModifiers = DamageTakenModifiers, PercentageDamageModifiers = PercentageDamageModifiers, Poison = Poison, Shield = Shield, Frost = Frost, Rested = Rested, HealthUpgrade = HealthUpgrade }; } private StatusEffectBaseDefinition? ToBaseDefinition() { if (!HasBaseDefinition) { return null; } return new StatusEffectBaseDefinition { DisplayName = DisplayName, Tooltip = Tooltip, Icon = Icon, StartEffects = StartEffects, StopEffects = StopEffects, Category = Category, Time = Time, IconFlags = IconFlags, RepeatInterval = RepeatInterval, StartMessage = StartMessage, StopMessage = StopMessage, RepeatMessage = RepeatMessage, StartMessageType = StartMessageType, StopMessageType = StopMessageType, RepeatMessageType = RepeatMessageType, Attributes = Attributes }; } internal static StatusEffectEntry FromDefinition(string name, StatusEffectDefinition definition, bool overrideEntry) { StatusEffectBaseDefinition statusEffectBaseDefinition = definition.Base; return new StatusEffectEntry { Effect = name, Override = overrideEntry, DisplayName = statusEffectBaseDefinition?.DisplayName, Tooltip = statusEffectBaseDefinition?.Tooltip, Icon = statusEffectBaseDefinition?.Icon, StartEffects = statusEffectBaseDefinition?.StartEffects, StopEffects = statusEffectBaseDefinition?.StopEffects, Category = statusEffectBaseDefinition?.Category, Time = statusEffectBaseDefinition?.Time, IconFlags = statusEffectBaseDefinition?.IconFlags, RepeatInterval = statusEffectBaseDefinition?.RepeatInterval, StartMessage = statusEffectBaseDefinition?.StartMessage, StopMessage = statusEffectBaseDefinition?.StopMessage, RepeatMessage = statusEffectBaseDefinition?.RepeatMessage, StartMessageType = statusEffectBaseDefinition?.StartMessageType, StopMessageType = statusEffectBaseDefinition?.StopMessageType, RepeatMessageType = statusEffectBaseDefinition?.RepeatMessageType, Attributes = statusEffectBaseDefinition?.Attributes, Stats = definition.Stats, StaminaDrainModifier = definition.StaminaDrainModifier, DamageTakenModifiers = definition.DamageTakenModifiers, PercentageDamageModifiers = definition.PercentageDamageModifiers, Poison = definition.Poison, Shield = definition.Shield, Frost = definition.Frost, Rested = definition.Rested, HealthUpgrade = definition.HealthUpgrade }; } } internal sealed class StatusEffectReferenceEntry { public string Effect { get; set; } = ""; public string? DisplayName { get; set; } public string? Tooltip { get; set; } public string? Icon { get; set; } public string? StartEffects { get; set; } public string? StopEffects { get; set; } public string? Category { get; set; } public string? Time { get; set; } public string? IconFlags { get; set; } public float? RepeatInterval { get; set; } public string? StartMessage { get; set; } public string? StopMessage { get; set; } public string? RepeatMessage { get; set; } public string? StartMessageType { get; set; } public string? StopMessageType { get; set; } public string? RepeatMessageType { get; set; } public string? Attributes { get; set; } public StatsDefinition? Stats { get; set; } public StaminaDrainModifierDefinition? StaminaDrainModifier { get; set; } public DamageTakenModifierDefinition? DamageTakenModifiers { get; set; } public StatusDamageDefinition? PercentageDamageModifiers { get; set; } public PoisonDefinition? Poison { get; set; } public ShieldStatusDefinition? Shield { get; set; } public FrostDefinition? Frost { get; set; } public RestedDefinition? Rested { get; set; } public HealthUpgradeDefinition? HealthUpgrade { get; set; } internal static StatusEffectReferenceEntry From(string name, StatusEffectDefinition definition) { StatusEffectReferenceEntry statusEffectReferenceEntry = new StatusEffectReferenceEntry(); statusEffectReferenceEntry.Effect = name; statusEffectReferenceEntry.Stats = definition.Stats; statusEffectReferenceEntry.StaminaDrainModifier = definition.StaminaDrainModifier; statusEffectReferenceEntry.DamageTakenModifiers = definition.DamageTakenModifiers; statusEffectReferenceEntry.PercentageDamageModifiers = definition.PercentageDamageModifiers; statusEffectReferenceEntry.Poison = definition.Poison; statusEffectReferenceEntry.Shield = definition.Shield; statusEffectReferenceEntry.Frost = definition.Frost; statusEffectReferenceEntry.Rested = definition.Rested; statusEffectReferenceEntry.HealthUpgrade = definition.HealthUpgrade; statusEffectReferenceEntry.ApplyBase(ToReferenceBase(definition.Base)); return ReferenceValue.ClonePruned(statusEffectReferenceEntry) ?? new StatusEffectReferenceEntry { Effect = name }; } private void ApplyBase(StatusEffectBaseDefinition? definition) { if (definition != null) { DisplayName = definition.DisplayName; Tooltip = definition.Tooltip; Icon = definition.Icon; StartEffects = definition.StartEffects; StopEffects = definition.StopEffects; Category = definition.Category; Time = definition.Time; IconFlags = definition.IconFlags; RepeatInterval = definition.RepeatInterval; StartMessage = definition.StartMessage; StopMessage = definition.StopMessage; RepeatMessage = definition.RepeatMessage; StartMessageType = definition.StartMessageType; StopMessageType = definition.StopMessageType; RepeatMessageType = definition.RepeatMessageType; Attributes = definition.Attributes; } } private static StatusEffectBaseDefinition? ToReferenceBase(StatusEffectBaseDefinition? definition) { if (definition == null) { return null; } return new StatusEffectBaseDefinition { Category = definition.Category, StopEffects = definition.StopEffects, Time = definition.Time, IconFlags = definition.IconFlags, Attributes = definition.Attributes }; } } internal sealed class StatusEffectDefinition { public StatusEffectBaseDefinition? Base { get; set; } public StatsDefinition? Stats { get; set; } public StaminaDrainModifierDefinition? StaminaDrainModifier { get; set; } public DamageTakenModifierDefinition? DamageTakenModifiers { get; set; } public StatusDamageDefinition? PercentageDamageModifiers { get; set; } public PoisonDefinition? Poison { get; set; } public ShieldStatusDefinition? Shield { get; set; } public FrostDefinition? Frost { get; set; } public RestedDefinition? Rested { get; set; } public HealthUpgradeDefinition? HealthUpgrade { get; set; } internal static StatusEffectDefinition From(StatusEffect statusEffect) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) StatusEffectDefinition obj = new StatusEffectDefinition { Base = StatusEffectBaseDefinition.From(statusEffect) }; SE_Stats val = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null); obj.Stats = ((val != null) ? StatsDefinition.From(val) : null); SE_Stats val2 = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null); obj.StaminaDrainModifier = ((val2 != null) ? StaminaDrainModifierDefinition.From(val2) : null); SE_Stats val3 = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null); obj.DamageTakenModifiers = ((val3 != null) ? DamageTakenModifierDefinition.From(val3.m_mods) : null); SE_Stats val4 = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null); obj.PercentageDamageModifiers = ((val4 != null) ? StatusDamageDefinition.From(val4.m_percentigeDamageModifiers) : null); SE_Poison val5 = (SE_Poison)(object)((statusEffect is SE_Poison) ? statusEffect : null); obj.Poison = ((val5 != null) ? PoisonDefinition.From(val5) : null); SE_Shield val6 = (SE_Shield)(object)((statusEffect is SE_Shield) ? statusEffect : null); obj.Shield = ((val6 != null) ? ShieldStatusDefinition.From(val6) : null); SE_Frost val7 = (SE_Frost)(object)((statusEffect is SE_Frost) ? statusEffect : null); obj.Frost = ((val7 != null) ? FrostDefinition.From(val7) : null); SE_Rested val8 = (SE_Rested)(object)((statusEffect is SE_Rested) ? statusEffect : null); obj.Rested = ((val8 != null) ? RestedDefinition.From(val8) : null); SE_HealthUpgrade val9 = (SE_HealthUpgrade)(object)((statusEffect is SE_HealthUpgrade) ? statusEffect : null); obj.HealthUpgrade = ((val9 != null) ? HealthUpgradeDefinition.From(val9) : null); return obj; } } internal sealed class StatusEffectBaseDefinition { public string? DisplayName { get; set; } public string? Tooltip { get; set; } public string? Icon { get; set; } public string? StartEffects { get; set; } public string? StopEffects { get; set; } public string? Category { get; set; } public string? Time { get; set; } public string? IconFlags { get; set; } public float? RepeatInterval { get; set; } public string? StartMessage { get; set; } public string? StopMessage { get; set; } public string? RepeatMessage { get; set; } public string? StartMessageType { get; set; } public string? StopMessageType { get; set; } public string? RepeatMessageType { get; set; } public string? Attributes { get; set; } internal static StatusEffectBaseDefinition From(StatusEffect statusEffect) { return new StatusEffectBaseDefinition { DisplayName = statusEffect.m_name, Tooltip = statusEffect.m_tooltip, StartEffects = FormatEffectList(statusEffect.m_startEffects), StopEffects = FormatEffectList(statusEffect.m_stopEffects), Category = statusEffect.m_category, Time = FormatFloatTuple(statusEffect.m_ttl, statusEffect.m_cooldown, null), IconFlags = FormatBoolTuple(statusEffect.m_cooldownIcon, statusEffect.m_flashIcon), RepeatInterval = statusEffect.m_repeatInterval, StartMessage = statusEffect.m_startMessage, StopMessage = statusEffect.m_stopMessage, RepeatMessage = statusEffect.m_repeatMessage, StartMessageType = ((object)Unsafe.As(ref statusEffect.m_startMessageType)/*cast due to .constrained prefix*/).ToString(), StopMessageType = ((object)Unsafe.As(ref statusEffect.m_stopMessageType)/*cast due to .constrained prefix*/).ToString(), RepeatMessageType = ((object)Unsafe.As(ref statusEffect.m_repeatMessageType)/*cast due to .constrained prefix*/).ToString(), Attributes = ((object)Unsafe.As(ref statusEffect.m_attributes)/*cast due to .constrained prefix*/).ToString() }; } } internal sealed class StatsDefinition { public string? UpFront { get; set; } public string? HealthPerTick { get; set; } public string? HealthOverTime { get; set; } public string? StaminaOverTime { get; set; } public string? EitrOverTime { get; set; } public string? RegenMultiplier { get; set; } public float? StaminaDrainPerSec { get; set; } public float? AdrenalineModifier { get; set; } public float? SpeedModifier { get; set; } public float? SwimSpeedModifier { get; set; } public string? JumpModifier { get; set; } public string? WindRun { get; set; } public string? Sneak { get; set; } public string? Fall { get; set; } public string? Armor { get; set; } public string? Block { get; set; } public float? StaggerModifier { get; set; } public float? AddMaxCarryWeight { get; set; } public string? AttackDamage { get; set; } public string? RaiseSkill { get; set; } public string? SkillLevel { get; set; } public string? SkillLevel2 { get; set; } internal static StatsDefinition From(SE_Stats stats) { //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) return new StatsDefinition { UpFront = FormatFloatTuple(stats.m_healthUpFront, stats.m_staminaUpFront, stats.m_eitrUpFront, stats.m_adrenalineUpFront), HealthPerTick = FormatHealthPerTick(stats), HealthOverTime = FormatFloatTuple(stats.m_healthOverTime, stats.m_healthOverTimeDuration, stats.m_healthOverTimeInterval), StaminaOverTime = FormatFloatBoolTuple(stats.m_staminaOverTime, stats.m_staminaOverTimeDuration, stats.m_staminaOverTimeIsFraction), EitrOverTime = FormatFloatTuple(stats.m_eitrOverTime, stats.m_eitrOverTimeDuration, null), RegenMultiplier = FormatFloatTuple(stats.m_healthRegenMultiplier, stats.m_staminaRegenMultiplier, stats.m_eitrRegenMultiplier), StaminaDrainPerSec = stats.m_staminaDrainPerSec, AdrenalineModifier = stats.m_adrenalineModifier, SpeedModifier = stats.m_speedModifier, SwimSpeedModifier = stats.m_swimSpeedModifier, JumpModifier = FormatFloatTuple(stats.m_jumpModifier.x, stats.m_jumpModifier.y, stats.m_jumpModifier.z), WindRun = FormatFloatTuple(stats.m_windMovementModifier, stats.m_windRunStaminaModifier, null), Sneak = FormatFloatTuple(stats.m_stealthModifier, stats.m_noiseModifier, null), Fall = FormatFloatTuple(stats.m_fallDamageModifier, stats.m_maxMaxFallSpeed, null), Armor = FormatFloatTuple(stats.m_addArmor, stats.m_armorMultiplier, null), Block = FormatFloatTuple(stats.m_timedBlockBonus, stats.m_blockStaminaUseFlatValue, null), StaggerModifier = stats.m_staggerModifier, AddMaxCarryWeight = stats.m_addMaxCarryWeight, AttackDamage = FormatSkillValuePair(stats.m_modifyAttackSkill, stats.m_damageModifier), RaiseSkill = FormatSkillValuePair(stats.m_raiseSkill, stats.m_raiseSkillModifier), SkillLevel = FormatSkillValuePair(stats.m_skillLevel, stats.m_skillLevelModifier), SkillLevel2 = FormatSkillValuePair(stats.m_skillLevel2, stats.m_skillLevelModifier2) }; } } internal sealed class StaminaDrainModifierDefinition { public float? Run { get; set; } public float? Attack { get; set; } public float? Block { get; set; } public float? Dodge { get; set; } public float? Jump { get; set; } public float? Sneak { get; set; } public float? Swim { get; set; } public float? HomeItem { get; set; } internal static StaminaDrainModifierDefinition From(SE_Stats stats) { return new StaminaDrainModifierDefinition { Run = stats.m_runStaminaDrainModifier, Attack = stats.m_attackStaminaUseModifier, Block = stats.m_blockStaminaUseModifier, Dodge = stats.m_dodgeStaminaUseModifier, Jump = stats.m_jumpStaminaUseModifier, Sneak = stats.m_sneakStaminaUseModifier, Swim = stats.m_swimStaminaUseModifier, HomeItem = stats.m_homeItemStaminaUseModifier }; } } internal sealed class PoisonDefinition { public float? BaseTtl { get; set; } public float? DamageInterval { get; set; } public float? DamagePerHit { get; set; } public float? TtlPerDamage { get; set; } public float? TtlPerDamagePlayer { get; set; } public float? TtlPower { get; set; } internal static PoisonDefinition From(SE_Poison poison) { return new PoisonDefinition { BaseTtl = poison.m_baseTTL, DamageInterval = poison.m_damageInterval, DamagePerHit = poison.m_damagePerHit, TtlPerDamage = poison.m_TTLPerDamage, TtlPerDamagePlayer = poison.m_TTLPerDamagePlayer, TtlPower = poison.m_TTLPower }; } } internal sealed class ShieldStatusDefinition { public float? AbsorbDamage { get; set; } public float? AbsorbDamagePerSkillLevel { get; set; } public float? AbsorbDamageWorldLevel { get; set; } public int? TtlPerItemLevel { get; set; } public float? LevelUpSkillFactor { get; set; } public string? LevelUpSkillOnBreak { get; set; } internal static ShieldStatusDefinition From(SE_Shield shield) { return new ShieldStatusDefinition { AbsorbDamage = shield.m_absorbDamage, AbsorbDamagePerSkillLevel = shield.m_absorbDamagePerSkillLevel, AbsorbDamageWorldLevel = shield.m_absorbDamageWorldLevel, TtlPerItemLevel = shield.m_ttlPerItemLevel, LevelUpSkillFactor = shield.m_levelUpSkillFactor, LevelUpSkillOnBreak = ((object)Unsafe.As(ref shield.m_levelUpSkillOnBreak)/*cast due to .constrained prefix*/).ToString() }; } } internal sealed class FrostDefinition { public float? FreezeTimeEnemy { get; set; } public float? FreezeTimePlayer { get; set; } public float? MinSpeedFactor { get; set; } internal static FrostDefinition From(SE_Frost frost) { return new FrostDefinition { FreezeTimeEnemy = frost.m_freezeTimeEnemy, FreezeTimePlayer = frost.m_freezeTimePlayer, MinSpeedFactor = frost.m_minSpeedFactor }; } } internal sealed class RestedDefinition { public float? BaseTtl { get; set; } public float? TtlPerComfortLevel { get; set; } internal static RestedDefinition From(SE_Rested rested) { return new RestedDefinition { BaseTtl = rested.m_baseTTL, TtlPerComfortLevel = rested.m_TTLPerComfortLevel }; } } internal sealed class HealthUpgradeDefinition { public float? MoreHealth { get; set; } public float? MoreStamina { get; set; } internal static HealthUpgradeDefinition From(SE_HealthUpgrade healthUpgrade) { return new HealthUpgradeDefinition { MoreHealth = healthUpgrade.m_moreHealth, MoreStamina = healthUpgrade.m_moreStamina }; } } internal sealed class StatusDamageDefinition { public float? Blunt { get; set; } public float? Slash { get; set; } public float? Pierce { get; set; } public float? Chop { get; set; } public float? Pickaxe { get; set; } public float? Fire { get; set; } public float? Frost { get; set; } public float? Lightning { get; set; } public float? Poison { get; set; } public float? Spirit { get; set; } internal static StatusDamageDefinition From(DamageTypes damage) { //IL_0006: 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_0028: 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_004a: 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_006c: 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_008e: 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) return new StatusDamageDefinition { Blunt = damage.m_blunt, Slash = damage.m_slash, Pierce = damage.m_pierce, Chop = damage.m_chop, Pickaxe = damage.m_pickaxe, Fire = damage.m_fire, Frost = damage.m_frost, Lightning = damage.m_lightning, Poison = damage.m_poison, Spirit = damage.m_spirit }; } } internal sealed class DamageTakenModifierDefinition { public string? Blunt { get; set; } public string? Slash { get; set; } public string? Pierce { get; set; } public string? Chop { get; set; } public string? Pickaxe { get; set; } public string? Fire { get; set; } public string? Frost { get; set; } public string? Lightning { get; set; } public string? Poison { get; set; } public string? Spirit { get; set; } internal unsafe static DamageTakenModifierDefinition From(List mods) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) DamageModifiers val = default(DamageModifiers); ((DamageModifiers)(ref val)).Apply(mods); return new DamageTakenModifierDefinition { Blunt = ((object)(*(DamageModifier*)(&val.m_blunt))/*cast due to .constrained prefix*/).ToString(), Slash = ((object)(*(DamageModifier*)(&val.m_slash))/*cast due to .constrained prefix*/).ToString(), Pierce = ((object)(*(DamageModifier*)(&val.m_pierce))/*cast due to .constrained prefix*/).ToString(), Chop = ((object)(*(DamageModifier*)(&val.m_chop))/*cast due to .constrained prefix*/).ToString(), Pickaxe = ((object)(*(DamageModifier*)(&val.m_pickaxe))/*cast due to .constrained prefix*/).ToString(), Fire = ((object)(*(DamageModifier*)(&val.m_fire))/*cast due to .constrained prefix*/).ToString(), Frost = ((object)(*(DamageModifier*)(&val.m_frost))/*cast due to .constrained prefix*/).ToString(), Lightning = ((object)(*(DamageModifier*)(&val.m_lightning))/*cast due to .constrained prefix*/).ToString(), Poison = ((object)(*(DamageModifier*)(&val.m_poison))/*cast due to .constrained prefix*/).ToString(), Spirit = ((object)(*(DamageModifier*)(&val.m_spirit))/*cast due to .constrained prefix*/).ToString() }; } } private sealed class IconCacheEntry { public DateTime LastWriteTimeUtc { get; } public Sprite Sprite { get; } public IconCacheEntry(DateTime lastWriteTimeUtc, Sprite sprite) { LastWriteTimeUtc = lastWriteTimeUtc; Sprite = sprite; } } private const string DomainName = "effects"; private const string OverrideFileName = "effects.yml"; private const string ReferenceFileName = "effects.reference.yml"; private const string FullScaffoldFileName = "effects.full.yml"; private const string SyncedPayloadKey = "effects"; private const long ReloadDelayTicks = 10000000L; private const string ReferenceStateKey = "effects"; private const string ReferenceLogicVersion = "2026-06-24-effect-reference-state-v2"; private static readonly object StateLock = new object(); private static readonly Dictionary Baselines = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary BaselineEffects = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary BaselineIcons = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary BaselineStartEffects = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary BaselineStopEffects = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary IconCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet CreatedClones = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet RuntimeAppliedEffectKeys = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly MethodInfo? LoadImageMethod = ResolveLoadImageMethod(); private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); private static readonly ISerializer SparseSerializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull).DisableAliases() .Build(); private static readonly ISerializer FullSerializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).DisableAliases().Build(); private static List ActiveEntries = new List(); private static Dictionary ActiveEntrySignaturesByEffect = new Dictionary(StringComparer.OrdinalIgnoreCase); private static HashSet? PendingChangedEffectKeys; private static bool HasPendingScopedApply; private static bool ForceNextFullApply = true; private static CustomSyncedValue? SyncedPayload; private static string? LastAppliedSyncedPayload; private static FileSystemWatcher? Watcher; private static DataForgeFileWatcher.DebouncedAction? ReloadDebouncer; private static bool ObjectDbReady; private static bool RuntimeStateWasApplied; private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); private static string IconDirectory => Path.Combine(ConfigDirectory, "icon"); internal static void Initialize(ConfigSync configSync) { SyncedPayload = new CustomSyncedValue(configSync, "effects", ""); SyncedPayload.ValueChanged += OnSyncedPayloadChanged; } internal static void Dispose() { if (SyncedPayload != null) { SyncedPayload.ValueChanged -= OnSyncedPayloadChanged; } Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } internal static void SetupFileWatcher() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } else { EnsureConfigDirectoryAndDefaultOverride(); Watcher?.Dispose(); ReloadDebouncer?.Dispose(); ReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadYamlValues); Watcher = DataForgeFileWatcher.Create(ConfigDirectory, "*.*", includeSubdirectories: true, ReadYamlValues); } } internal static void ReloadFromDiskAndSync() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { ApplySyncedPayload(SyncedPayload?.Value ?? ""); return; } EnsureConfigDirectoryAndDefaultOverride(); List list = LoadEntriesFromDisk(); lock (StateLock) { SetActiveEntries(list); } PublishPayload(SerializeEntries(list)); ApplyCurrentConfiguration(); } internal static void OnObjectDBReady() { if (!((Object)(object)ObjectDB.instance == (Object)null)) { ObjectDbReady = true; if (!ShouldSkipRemoteClientBaselineWork()) { WriteGeneratedArtifacts(); ApplyCurrentConfiguration(); } } } internal static void OnZNetSceneReady() { ApplyCurrentConfiguration(); } internal static void ApplyCurrentConfiguration() { if (!ObjectDbReady || (Object)(object)ObjectDB.instance == (Object)null || ShouldSkipRemoteClientBaselineWork()) { return; } List entries; HashSet hashSet; lock (StateLock) { entries = ActiveEntries.ToList(); hashSet = ConsumePendingChangedEffectKeys(); } if (hashSet != null && hashSet.Count == 0) { return; } List list = FilterEntries(entries, hashSet); Dictionary> entriesByEffect = BuildEnabledDefinitionEntriesByEffect(list); HashSet runtimeApplyEffectKeys = GetRuntimeApplyEffectKeys(entriesByEffect); DataForgeStatusEffectOwnership.NotifyStatusEffectOverridesWillApply(); CaptureBaselinesForEntriesIfNeeded(list); CleanupCreatedEffects(entries); EnsureCloneEffects(entries); RestoreBaselineEffects(runtimeApplyEffectKeys); if (!DataForgePlugin.StatusEffectOverridesEnabled) { ApplyLiveSafeToActiveStatusEffects(entriesByEffect); UpdateRuntimeAppliedEffectState(new Dictionary>(StringComparer.OrdinalIgnoreCase)); DataForgeStatusEffectOwnership.NotifyStatusEffectOverridesApplied(); return; } foreach (StatusEffectEntry item in list) { using (DataForgeLogContext.Push(item.LogContext)) { if (item.Override && item.HasDefinition) { StatusEffect val = ResolveStatusEffect(item.Effect); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning("Could not find status effect '" + item.Effect + "'."); } else { ApplyDefinition(val, item.ToDefinition()); } } } } ApplyLiveSafeToActiveStatusEffects(entriesByEffect); UpdateRuntimeAppliedEffectState(entriesByEffect); DataForgeStatusEffectOwnership.NotifyStatusEffectOverridesApplied(); } internal static bool HasActiveStatusEffectOverride(string? effectName) { if (!DataForgePlugin.StatusEffectOverridesEnabled || string.IsNullOrWhiteSpace(effectName)) { return false; } string normalized = NormalizeStatusEffectName(Utils.GetPrefabName(effectName)); if (normalized.Length == 0) { return false; } lock (StateLock) { return ActiveEntries.Any((StatusEffectEntry entry) => entry.Override && entry.HasDefinition && string.Equals(NormalizeStatusEffectName(entry.Effect), normalized, StringComparison.OrdinalIgnoreCase)); } } private static bool ShouldSkipRemoteClientBaselineWork() { if (!DataForgePlugin.IsRemoteServerClient) { return false; } lock (StateLock) { return ActiveEntries.Count == 0 && CreatedClones.Count == 0; } } private static Dictionary> BuildEnabledDefinitionEntriesByEffect(List entries) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (StatusEffectEntry entry in entries) { if (entry.Override && entry.HasDefinition && !string.IsNullOrWhiteSpace(entry.Effect)) { if (!dictionary.TryGetValue(entry.Effect, out var value)) { value = new List(); dictionary[entry.Effect] = value; } value.Add(entry); } } return dictionary; } private static HashSet GetRuntimeApplyEffectKeys(Dictionary> entriesByEffect) { HashSet hashSet = new HashSet(entriesByEffect.Keys, StringComparer.OrdinalIgnoreCase); if (RuntimeStateWasApplied) { foreach (string runtimeAppliedEffectKey in RuntimeAppliedEffectKeys) { hashSet.Add(runtimeAppliedEffectKey); } } return hashSet; } private static void UpdateRuntimeAppliedEffectState(Dictionary> entriesByEffect) { RuntimeAppliedEffectKeys.Clear(); foreach (string key in entriesByEffect.Keys) { RuntimeAppliedEffectKeys.Add(key); } RuntimeStateWasApplied = RuntimeAppliedEffectKeys.Count > 0; } private static void ApplyLiveSafeToActiveStatusEffects(Dictionary> entriesByEffect) { if (Player.s_players == null) { return; } int num = 0; foreach (Player s_player in Player.s_players) { if ((Object)(object)s_player == (Object)null) { continue; } SEMan sEMan = ((Character)s_player).GetSEMan(); if (sEMan == null) { continue; } foreach (StatusEffect statusEffect in sEMan.GetStatusEffects()) { if (!((Object)(object)statusEffect == (Object)null) && ApplyLiveSafeToActiveStatusEffect(statusEffect, entriesByEffect)) { num++; } } } if (num > 0) { DataForgePlugin.Log.LogDebug((object)$"Live-refreshed {num} active status effect instances."); } } private static bool ApplyLiveSafeToActiveStatusEffect(StatusEffect statusEffect, Dictionary> entriesByEffect) { string text = NormalizeStatusEffectName(((Object)statusEffect).name); if (text.Length == 0) { return false; } bool result = false; if (Baselines.TryGetValue(text, out StatusEffectDefinition value)) { ApplyLiveSafeDefinition(statusEffect, value); if (BaselineIcons.TryGetValue(text, out Sprite value2)) { statusEffect.m_icon = value2; } result = true; } if (!DataForgePlugin.StatusEffectOverridesEnabled) { return result; } if (!entriesByEffect.TryGetValue(text, out List value3)) { return result; } foreach (StatusEffectEntry item in value3) { using (DataForgeLogContext.Push(item.LogContext)) { ApplyLiveSafeDefinition(statusEffect, item.ToDefinition()); } result = true; } return result; } private static string NormalizeStatusEffectName(string statusEffectName) { return (statusEffectName ?? "").Replace("(Clone)", "").Trim(); } private static void ReadYamlValues(object sender, FileSystemEventArgs e) { if (ShouldReloadForFileEvent(e)) { ReloadDebouncer?.Schedule(); } } private static void ReloadYamlValues() { try { DataForgePlugin.Log.LogDebug((object)"Reloading status effect YAML files..."); ReloadFromDiskAndSync(); DataForgePlugin.Log.LogInfo((object)"Status effect YAML reload complete."); } catch (Exception arg) { DataForgePlugin.Log.LogError((object)$"Error reloading status effect YAML files: {arg}"); } } private static bool ShouldReloadForFileEvent(FileSystemEventArgs e) { if (!DataForgePlugin.UsesLocalAuthorityFiles) { return false; } if (IsOverrideFile(e.FullPath)) { return true; } if (IsIconFile(e.FullPath)) { return true; } if (e is RenamedEventArgs e2) { if (!IsOverrideFile(e2.OldFullPath)) { return IsIconFile(e2.OldFullPath); } return true; } return false; } private static void OnSyncedPayloadChanged() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { string payload = SyncedPayload?.Value ?? ""; DataForgeProfiler.Profile(string.Format("{0}.ApplySyncedPayload chars={1}", "effects", payload.Length), delegate { ApplySyncedPayload(payload); }); } } private static void ApplySyncedPayload(string payload) { if (!string.Equals(LastAppliedSyncedPayload, payload, StringComparison.Ordinal)) { LastAppliedSyncedPayload = payload; List activeEntries = DeserializeEntries(payload, "synced status effect payload"); lock (StateLock) { SetActiveEntries(activeEntries); } ApplyCurrentConfiguration(); } } private static void SetActiveEntries(List entries) { Dictionary dictionary = BuildEntrySignaturesByEffect(entries); if (!ForceNextFullApply) { PendingChangedEffectKeys = GetChangedKeys(ActiveEntrySignaturesByEffect, dictionary); HasPendingScopedApply = true; } ActiveEntries = entries; ActiveEntrySignaturesByEffect = dictionary; } private static HashSet? ConsumePendingChangedEffectKeys() { if (ForceNextFullApply) { ForceNextFullApply = false; PendingChangedEffectKeys = null; HasPendingScopedApply = false; return null; } if (!HasPendingScopedApply) { return null; } HashSet? result = PendingChangedEffectKeys ?? new HashSet(StringComparer.OrdinalIgnoreCase); PendingChangedEffectKeys = null; HasPendingScopedApply = false; return result; } private static Dictionary BuildEntrySignaturesByEffect(List entries) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (StatusEffectEntry entry in entries) { if (!string.IsNullOrWhiteSpace(entry.Effect)) { if (!dictionary.TryGetValue(entry.Effect, out var value)) { value = new List(); dictionary[entry.Effect] = value; } value.Add(entry); } } Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair> item in dictionary) { dictionary2[item.Key] = SparseSerializer.Serialize(item.Value); } return dictionary2; } private static HashSet GetChangedKeys(Dictionary oldSignatures, Dictionary newSignatures) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair oldSignature in oldSignatures) { if (!newSignatures.TryGetValue(oldSignature.Key, out string value) || !string.Equals(oldSignature.Value, value, StringComparison.Ordinal)) { hashSet.Add(oldSignature.Key); } } foreach (KeyValuePair newSignature in newSignatures) { if (!oldSignatures.TryGetValue(newSignature.Key, out string value2) || !string.Equals(value2, newSignature.Value, StringComparison.Ordinal)) { hashSet.Add(newSignature.Key); } } return hashSet; } private static List FilterEntries(List entries, HashSet? effectKeys) { if (effectKeys != null) { return entries.Where((StatusEffectEntry entry) => effectKeys.Contains(entry.Effect)).ToList(); } return entries; } private static void PublishPayload(string payload) { DataForgeSync.PublishPayload(SyncedPayload, "effects", payload); } private static List LoadEntriesFromDisk() { return DataForgeOverrideFiles.LoadEntries(GetOverrideFiles(), DeserializeEntries); } private static List DeserializeEntries(string yaml, string source) { if (string.IsNullOrWhiteSpace(yaml)) { return new List(); } try { return NormalizeEntries(Deserializer.Deserialize>(yaml), source); } catch (Exception ex) { DataForgePlugin.Log.LogError((object)("Failed to parse " + source + ": " + ex.Message)); return new List(); } } private static List NormalizeEntries(List? entries, string source) { List list = new List(); if (entries == null) { return list; } int num = 0; foreach (StatusEffectEntry entry in entries) { num++; string text = DataForgeLogContext.FormatSource(source, num); if (string.IsNullOrWhiteSpace(entry.Effect)) { DataForgeLogContext.Warning(text + ": Skipping status effect entry without effect."); continue; } entry.Effect = entry.Effect.Trim(); entry.SetLogContext(text + " effect=" + entry.Effect); string cloneFrom = entry.CloneFrom; if (cloneFrom != null && cloneFrom.Trim().Length > 0) { entry.CloneFrom = cloneFrom.Trim(); } list.Add(entry); } return list; } private static string SerializeEntries(List entries) { return SparseSerializer.Serialize(entries); } private static IEnumerable GetOverrideFiles() { return DataForgeOverrideFiles.GetOverrideFiles(ConfigDirectory, IsOverrideFile); } private static bool IsOverrideFile(string path) { string extension = Path.GetExtension(path); if (!extension.Equals(".yml", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".yaml", StringComparison.OrdinalIgnoreCase)) { return false; } string fileName = Path.GetFileName(path); if (fileName.Equals("effects.reference.yml", StringComparison.OrdinalIgnoreCase) || fileName.Equals("effects.full.yml", StringComparison.OrdinalIgnoreCase)) { return false; } if (!fileName.Equals("effects.yml", StringComparison.OrdinalIgnoreCase)) { return fileName.StartsWith("effects_", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsIconFile(string path) { if (!Path.GetExtension(path).Equals(".png", StringComparison.OrdinalIgnoreCase)) { return false; } string fullPath = Path.GetFullPath(path); string fullPath2 = Path.GetFullPath(IconDirectory); char directorySeparatorChar = Path.DirectorySeparatorChar; return fullPath.StartsWith(fullPath2 + directorySeparatorChar, StringComparison.OrdinalIgnoreCase); } private static void EnsureConfigDirectoryAndDefaultOverride() { Directory.CreateDirectory(IconDirectory); DataForgeOverrideFiles.EnsureDefaultOverride(ConfigDirectory, "effects.yml", GetOverrideFiles, DefaultOverrideTemplate); } private static string DefaultOverrideTemplate() { return string.Join(Environment.NewLine, "# DataForge effect overrides.", "# Copy entries from effects.reference.yml, or run `dataforge:full effect` to generate effects.full.yml for exhaustive field examples.", "# You can also create additional override files like effects_asdf.yml; DataForge loads effects.yml and effects_*.yml together.", "# Omitted fields keep the current status effect value. Inline comments show example values and their effect.", "#", "# Schema:", "# - effect: Rested # e.g. Rested => override the Rested status effect.", "# override: true # default true; false skips this entire status effect entry.", "# cloneFrom: Rested # MyRested, cloneFrom: Rested => create a new effect from Rested.", "# displayName: $se_rested_name # $se_custom_name => use this localization token or literal text.", "# tooltip: $se_rested_tooltip # $se_custom_tooltip => use this tooltip token or literal text.", "# icon: MyEffectIcon # MyEffectIcon => load DataForge/icon/MyEffectIcon.png as this status effect icon; 256x256 PNG recommended.", "# startEffects: vfx_A, sfx_A # vfx_A, sfx_A => replace start EffectList with these prefabs; None => clear.", "# stopEffects: vfx_B, sfx_B # vfx_B, sfx_B => replace stop EffectList with these prefabs; None => clear.", "# category: # food => group/category string shown by game logic.", "# time: 0, 0 # 600, 30 => lasts 600s, then cannot reapply for 30s.", "# iconFlags: false, false # true, false => show cooldown-style icon, do not flash the icon.", "# repeatInterval: 0 # 5 => repeat message/tick every 5 seconds.", "# startMessage: # You feel strong => shown when the effect starts.", "# stopMessage: # Strength fades => shown when the effect ends.", "# repeatMessage: # Still strong => shown each repeatInterval.", "# startMessageType: TopLeft # Center => show startMessage in the center.", "# stopMessageType: TopLeft # TopLeft => show stopMessage in the top-left feed.", "# repeatMessageType: TopLeft # TopLeft => show repeatMessage in the top-left feed.", "# attributes: None # ColdResistance => grant that StatusAttribute flag.", "# stats:", "# upFront: 0, 0, 0, 0 # 50, 40, 25, 20 => instantly give 50 health, 40 stamina, 25 eitr, 20 adrenaline on start.", "# healthPerTick: 0, 0, Undefined # -5, 0.1, Poison => deal 5 Poison damage each existing effect tick, stopping below 10% health; hitType is mostly meaningful for damage, not healing.", "# healthOverTime: 0, 0, 5 # 100, 20, 5 => heal 100 total health over 20s, every 5s.", "# staminaOverTime: 0, 0, false # 60, 10, false => restore 60 stamina over 10s; 0.5, 10, true => restore 50% max stamina over 10s.", "# eitrOverTime: 0, 0 # 60, 6 => restore 60 eitr over 6s.", "# regenMultiplier: 1, 1, 1 # 1.5, 0.5, 0 => health regen +50%, stamina regen x0.5, eitr regen disabled.", "# staminaDrainPerSec: 0 # 2 => drain 2 stamina per second.", "# adrenalineModifier: 0 # 0.25 => adrenaline gain/use value +25%.", "# speedModifier: 0 # 0.2 => movement speed +20%.", "# swimSpeedModifier: 0 # 0.3 => swim speed +30%.", "# jumpModifier: 0, 0, 0 # 0, 0.25, 0 => jump height +25%; 0.2, 0, 0.2 => jump distance +20%.", "# windRun: 0, 0 # 0.2, -0.25 => tailwind movement speed up to +20%, run stamina drain up to -25%.", "# sneak: 0, 0 # 0.25, -0.5 => stealth +25%, generated noise -50%; not sneak stamina cost.", "# fall: 0, 0 # -0.5, 8 => fall damage -50%, cap downward fall speed to 8 m/s.", "# armor: 0, 0 # 10, 0.25 => (armor + 10) * 1.25.", "# block: 0, 0 # 0.5, -5 => timed block/parry bonus +50%, block stamina cost -5 flat.", "# staggerModifier: 0 # -0.25 => stagger taken -25%.", "# addMaxCarryWeight: 0 # 100 => carry weight +100.", "# Skill values for attackDamage/raiseSkill/skillLevel/skillLevel2: None, Swords, Knives, Clubs, Polearms, Spears, Blocking, Axes, Bows, ElementalMagic, BloodMagic, Unarmed, Pickaxes, WoodCutting, Crossbows, Jump, Sneak, Run, Swim, Fishing, Cooking, Farming, Crafting, Dodge, Ride, All.", "# attackDamage: None, 1 # Swords, 1.25 => Swords attacks deal x1.25 (*125%) total damage.", "# raiseSkill: None, 0 # Swords, 1.0 => Swords skill XP gain +100%.", "# skillLevel: None, 0 # Swords, 15 => treat Swords as +15 levels while active.", "# skillLevel2: None, 0 # Blocking, 10 => treat Blocking as +10 levels while active.", "# staminaDrainModifier:", "# run: 0 # -0.25 => running stamina drain -25%.", "# attack: 0 # -0.2 => attack stamina cost -20%.", "# block: 0 # -0.2 => block stamina cost -20%.", "# dodge: 0 # -0.2 => dodge stamina cost -20%.", "# jump: 0 # -0.2 => jump stamina cost -20%.", "# sneak: 0 # -0.2 => sneak stamina cost -20%.", "# swim: 0 # -0.2 => swim stamina cost -20%.", "# homeItem: 0 # -0.2 => hammer/build stamina cost -20%.", "# damageTakenModifiers:", "# blunt: Normal # Resistant => take 50% blunt damage; Normal => remove this effect's blunt modifier.", "# slash: Normal # Weak => take 150% slash damage.", "# pierce: Normal # VeryResistant => take 25% pierce damage.", "# chop: Normal # SlightlyWeak => take 125% chop damage.", "# pickaxe: Normal # Immune => take 0 pickaxe damage.", "# fire: Normal # Resistant => take 50% fire damage.", "# frost: Normal # VeryResistant => take 25% frost damage.", "# lightning: Normal # SlightlyResistant => take 75% lightning damage.", "# poison: Normal # Weak => take 150% poison damage.", "# spirit: Normal # Immune => take 0 spirit damage.", "# percentageDamageModifiers:", "# blunt: 0 # 0.25 => blunt damage modifier +25%.", "# slash: 0 # 0.25 => slash damage modifier +25%.", "# pierce: 0 # 0.25 => pierce damage modifier +25%.", "# chop: 0 # 0.25 => chop damage modifier +25%.", "# pickaxe: 0 # 0.25 => pickaxe damage modifier +25%.", "# fire: 0 # 0.25 => fire damage modifier +25%.", "# frost: 0 # 0.25 => frost damage modifier +25%.", "# lightning: 0 # 0.25 => lightning damage modifier +25%.", "# poison: 0 # 0.25 => poison damage modifier +25%.", "# spirit: 0 # 0.25 => spirit damage modifier +25%.", "# poison:", "# baseTtl: 0 # 10 => poison lasts at least 10 seconds.", "# damageInterval: 0 # 1 => poison ticks every second.", "# damagePerHit: 0 # 5 => poison deals 5 damage per tick/hit.", "# ttlPerDamage: 0 # 0.2 => poison duration scales by damage against non-players.", "# ttlPerDamagePlayer: 0 # 0.1 => poison duration scales by damage against players.", "# ttlPower: 0 # 0.5 => apply duration power curve.", "# shield:", "# absorbDamage: 0 # 200 => shield absorbs 200 damage.", "# absorbDamagePerSkillLevel: 0 # 2 => absorb +2 per relevant skill level.", "# absorbDamageWorldLevel: 0 # 50 => absorb +50 per world level.", "# ttlPerItemLevel: 0 # 5 => duration +5 seconds per item level.", "# levelUpSkillFactor: 0 # 0.5 => shield use grants skill XP at 50% factor.", "# levelUpSkillOnBreak: None # BloodMagic => raise BloodMagic when shield breaks.", "# frost:", "# freezeTimeEnemy: 0 # 5 => enemies freeze for 5 seconds.", "# freezeTimePlayer: 0 # 2 => players freeze for 2 seconds.", "# minSpeedFactor: 0 # 0.2 => slowed target keeps at least 20% speed.", "# rested:", "# baseTtl: 0 # 480 => rested starts at 8 minutes.", "# ttlPerComfortLevel: 0 # 60 => each comfort level adds 60 seconds.", "# healthUpgrade:", "# moreHealth: 0 # 25 => max/current health +25 while active.", "# moreStamina: 0 # 25 => max/current stamina +25 while active.", "#", "# Example:", "# - effect: Rested", "# time: 600, 0", "# stats:", "# regenMultiplier: 1, 1.5, 1", "# raiseSkill: Swords, 1.0", "# attackDamage: Swords, 1.25", "# staminaDrainModifier:", "# run: -0.25") + Environment.NewLine; } private static bool CaptureAllBaselinesIfNeeded() { if ((Object)(object)ObjectDB.instance == (Object)null) { return false; } int num = 0; int num2 = 0; foreach (StatusEffect statusEffect in ObjectDB.instance.m_StatusEffects) { CaptureBaseline(statusEffect, out var added, out var refreshed); num += (added ? 1 : 0); num2 += (refreshed ? 1 : 0); } if (num > 0 || num2 > 0) { DataForgePlugin.Log.LogInfo((object)$"Captured {num} new status effect baselines. Tracking {Baselines.Count} total."); } return num > 0; } private static void CaptureBaselinesForEntriesIfNeeded(List entries) { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } int num = 0; int num2 = 0; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (StatusEffectEntry entry in entries) { if (entry.Override && !string.IsNullOrWhiteSpace(entry.Effect) && hashSet.Add(entry.Effect)) { CaptureBaseline(ResolveStatusEffect(entry.Effect), out var added, out var refreshed); num += (added ? 1 : 0); num2 += (refreshed ? 1 : 0); } } if (num > 0 || num2 > 0) { DataForgePlugin.Log.LogInfo((object)$"Captured {num} targeted status effect baselines. Tracking {Baselines.Count} total."); } } private static void CaptureBaseline(StatusEffect? statusEffect, out bool added, out bool refreshed) { added = false; refreshed = false; if (!((Object)(object)statusEffect == (Object)null) && !string.IsNullOrWhiteSpace(((Object)statusEffect).name)) { string key = ((Object)statusEffect).name.Trim(); if (!Baselines.ContainsKey(key)) { Baselines[key] = StatusEffectDefinition.From(statusEffect); BaselineIcons[key] = statusEffect.m_icon; BaselineStartEffects[key] = CloneEffectList(statusEffect.m_startEffects); BaselineStopEffects[key] = CloneEffectList(statusEffect.m_stopEffects); added = true; } if (!BaselineEffects.TryGetValue(key, out StatusEffect value) || value != statusEffect) { BaselineEffects[key] = statusEffect; BaselineStartEffects[key] = CloneEffectList(statusEffect.m_startEffects); BaselineStopEffects[key] = CloneEffectList(statusEffect.m_stopEffects); refreshed = true; } } } private static void CleanupCreatedEffects(List entries) { if ((Object)(object)ObjectDB.instance == (Object)null) { return; } HashSet hashSet = new HashSet(from entry in entries where entry.Override && !string.IsNullOrWhiteSpace(entry.CloneFrom) select entry.Effect, StringComparer.OrdinalIgnoreCase); foreach (string item in CreatedClones.ToList()) { if (!hashSet.Contains(item)) { RemoveCreatedEffectClone(item, destroy: false); } } } internal static void CleanupCreatedClonesForWorldTransition() { if ((Object)(object)ObjectDB.instance == (Object)null) { CreatedClones.Clear(); return; } foreach (string item in CreatedClones.ToList()) { RemoveCreatedEffectClone(item, destroy: true); } } internal static void OnWorldShutdown() { ObjectDbReady = false; RuntimeStateWasApplied = false; RuntimeAppliedEffectKeys.Clear(); CleanupCreatedClonesForWorldTransition(); } private static void RemoveCreatedEffectClone(string cloneName, bool destroy) { StatusEffect val = ResolveStatusEffect(cloneName); if ((Object)(object)val != (Object)null && (Object)(object)ObjectDB.instance != (Object)null) { ObjectDB.instance.m_StatusEffects.Remove(val); if (destroy) { Object.Destroy((Object)(object)val); } } CreatedClones.Remove(cloneName); Baselines.Remove(cloneName); BaselineEffects.Remove(cloneName); BaselineIcons.Remove(cloneName); BaselineStartEffects.Remove(cloneName); BaselineStopEffects.Remove(cloneName); } private static void EnsureCloneEffects(List entries) { foreach (StatusEffectEntry entry in entries) { if (entry.Override && !string.IsNullOrWhiteSpace(entry.CloneFrom)) { using (DataForgeLogContext.Push(entry.LogContext)) { EnsureCloneEffect(entry); } } } } private static void EnsureCloneEffect(StatusEffectEntry entry) { if ((Object)(object)ObjectDB.instance == (Object)null || CreatedClones.Contains(entry.Effect)) { return; } if ((Object)(object)ResolveStatusEffect(entry.Effect) != (Object)null) { CreatedClones.Add(entry.Effect); return; } StatusEffect val = ResolveStatusEffect(entry.CloneFrom); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning("Could not clone status effect '" + entry.Effect + "': source '" + entry.CloneFrom + "' was not found."); return; } StatusEffect val2 = Object.Instantiate(val); ((Object)val2).name = entry.Effect; val2.m_nameHash = StringExtensionMethods.GetStableHashCode(entry.Effect); Object.DontDestroyOnLoad((Object)(object)val2); ObjectDB.instance.m_StatusEffects.Add(val2); Baselines[entry.Effect] = StatusEffectDefinition.From(val2); BaselineEffects[entry.Effect] = val2; BaselineIcons[entry.Effect] = val2.m_icon; BaselineStartEffects[entry.Effect] = CloneEffectList(val2.m_startEffects); BaselineStopEffects[entry.Effect] = CloneEffectList(val2.m_stopEffects); CreatedClones.Add(entry.Effect); DataForgePlugin.Log.LogInfo((object)("Cloned status effect '" + entry.CloneFrom + "' as '" + entry.Effect + "'.")); } private static void RestoreBaselineEffects(IReadOnlyCollection effectNames) { foreach (string effectName in effectNames) { if (BaselineEffects.TryGetValue(effectName, out StatusEffect value) && Baselines.TryGetValue(effectName, out StatusEffectDefinition value2)) { ApplyDefinition(value, value2, applyEffectLists: false); if (BaselineIcons.TryGetValue(effectName, out Sprite value3)) { value.m_icon = value3; } if (BaselineStartEffects.TryGetValue(effectName, out EffectList value4)) { value.m_startEffects = CloneEffectList(value4); } if (BaselineStopEffects.TryGetValue(effectName, out EffectList value5)) { value.m_stopEffects = CloneEffectList(value5); } } } } private static StatusEffect? ResolveStatusEffect(string? statusEffectName) { if ((Object)(object)ObjectDB.instance == (Object)null || string.IsNullOrWhiteSpace(statusEffectName)) { return null; } StatusEffect statusEffect = ObjectDB.instance.GetStatusEffect(StringExtensionMethods.GetStableHashCode(statusEffectName)); if ((Object)(object)statusEffect != (Object)null) { return statusEffect; } return ((IEnumerable)ObjectDB.instance.m_StatusEffects).FirstOrDefault((Func)((StatusEffect effect) => (Object)(object)effect != (Object)null && (((Object)effect).name.Equals(statusEffectName, StringComparison.OrdinalIgnoreCase) || effect.m_name.Equals(statusEffectName, StringComparison.OrdinalIgnoreCase)))); } private static void ApplyDefinition(StatusEffect statusEffect, StatusEffectDefinition definition, bool applyEffectLists = true) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) ApplyBase(statusEffect, definition.Base, applyEffectLists); SE_Stats val = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null); if (val != null) { ApplyStats(val, definition.Stats); ApplyStaminaDrainModifier(val, definition.StaminaDrainModifier); ApplyDamageTakenModifiers(val.m_mods, definition.DamageTakenModifiers); ApplyDamage(val.m_percentigeDamageModifiers, definition.PercentageDamageModifiers); } SE_Poison val2 = (SE_Poison)(object)((statusEffect is SE_Poison) ? statusEffect : null); if (val2 != null) { ApplyPoison(val2, definition.Poison); } SE_Shield val3 = (SE_Shield)(object)((statusEffect is SE_Shield) ? statusEffect : null); if (val3 != null) { ApplyShield(val3, definition.Shield); } SE_Frost val4 = (SE_Frost)(object)((statusEffect is SE_Frost) ? statusEffect : null); if (val4 != null) { ApplyFrost(val4, definition.Frost); } SE_Rested val5 = (SE_Rested)(object)((statusEffect is SE_Rested) ? statusEffect : null); if (val5 != null) { ApplyRested(val5, definition.Rested); } SE_HealthUpgrade val6 = (SE_HealthUpgrade)(object)((statusEffect is SE_HealthUpgrade) ? statusEffect : null); if (val6 != null) { ApplyHealthUpgrade(val6, definition.HealthUpgrade); } } private static void ApplyLiveSafeDefinition(StatusEffect statusEffect, StatusEffectDefinition definition) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) ApplyLiveSafeBase(statusEffect, definition.Base); SE_Stats val = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null); if (val != null) { ApplyLiveSafeStats(val, definition.Stats); ApplyStaminaDrainModifier(val, definition.StaminaDrainModifier); ApplyDamageTakenModifiers(val.m_mods, definition.DamageTakenModifiers); ApplyDamage(val.m_percentigeDamageModifiers, definition.PercentageDamageModifiers); } } private static void ApplyLiveSafeBase(StatusEffect statusEffect, StatusEffectBaseDefinition? definition) { if (definition != null) { DataForgeValue.Copy(definition.DisplayName, delegate(string value) { statusEffect.m_name = value; }); DataForgeValue.Copy(definition.Tooltip, delegate(string value) { statusEffect.m_tooltip = value; }); ApplyBaseIcon(statusEffect, definition.Icon); DataForgeValue.Copy(definition.Category, delegate(string value) { statusEffect.m_category = value; }); CopyIconFlags(definition.IconFlags, statusEffect); } } private static void ApplyBase(StatusEffect statusEffect, StatusEffectBaseDefinition? definition, bool applyEffectLists) { if (definition == null) { return; } DataForgeValue.Copy(definition.DisplayName, delegate(string value) { statusEffect.m_name = value; }); DataForgeValue.Copy(definition.Tooltip, delegate(string value) { statusEffect.m_tooltip = value; }); ApplyBaseIcon(statusEffect, definition.Icon); if (applyEffectLists) { ApplyEffectList(((Object)statusEffect).name, definition.StartEffects, delegate(EffectList value) { statusEffect.m_startEffects = value; }, "startEffects"); ApplyEffectList(((Object)statusEffect).name, definition.StopEffects, delegate(EffectList value) { statusEffect.m_stopEffects = value; }, "stopEffects"); } DataForgeValue.Copy(definition.Category, delegate(string value) { statusEffect.m_category = value; }); CopyBaseTime(definition.Time, statusEffect); DataForgeValue.Copy(definition.RepeatInterval, delegate(float value) { statusEffect.m_repeatInterval = Math.Max(0f, value); }); DataForgeValue.Copy(definition.StartMessage, delegate(string value) { statusEffect.m_startMessage = value; }); DataForgeValue.Copy(definition.StopMessage, delegate(string value) { statusEffect.m_stopMessage = value; }); DataForgeValue.Copy(definition.RepeatMessage, delegate(string value) { statusEffect.m_repeatMessage = value; }); CopyIconFlags(definition.IconFlags, statusEffect); CopyEnum(definition.StartMessageType, (Action)delegate(MessageType value) { //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) statusEffect.m_startMessageType = value; }, "startMessageType"); CopyEnum(definition.StopMessageType, (Action)delegate(MessageType value) { //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) statusEffect.m_stopMessageType = value; }, "stopMessageType"); CopyEnum(definition.RepeatMessageType, (Action)delegate(MessageType value) { //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) statusEffect.m_repeatMessageType = value; }, "repeatMessageType"); CopyEnum(definition.Attributes, (Action)delegate(StatusAttribute value) { //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) statusEffect.m_attributes = value; }, "attributes"); } private static void CopyBaseTime(string? value, StatusEffect statusEffect) { CopyFloatTuple(value, delegate(float ttl) { statusEffect.m_ttl = Math.Max(0f, ttl); }, delegate(float cooldown) { statusEffect.m_cooldown = Math.Max(0f, cooldown); }, null, "time", 0f, 0f, null); } private static void CopyIconFlags(string? value, StatusEffect statusEffect) { if (!string.IsNullOrWhiteSpace(value)) { string[] parts = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (TryParseBoolPart(parts, 0, defaultValue: false, "iconFlags", out var value2) && TryParseBoolPart(parts, 1, defaultValue: false, "iconFlags", out var value3)) { statusEffect.m_cooldownIcon = value2; statusEffect.m_flashIcon = value3; } } } private static void ApplyBaseIcon(StatusEffect statusEffect, string? iconName) { if (!string.IsNullOrWhiteSpace(iconName)) { string text = iconName.Trim(); Sprite val = ResolveIconSprite(text); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(((Object)statusEffect).name + " has unknown status effect icon '" + text + "'. Expected a png under DataForge/icon."); } else { statusEffect.m_icon = val; } } } private static Sprite? ResolveIconSprite(string iconName) { //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_0056: Expected O, but got Unknown //IL_008b: 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) string text = ResolveIconPath(iconName); if (text == null || !File.Exists(text)) { return null; } DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(text); if (IconCache.TryGetValue(text, out IconCacheEntry value) && value.LastWriteTimeUtc == lastWriteTimeUtc) { return value.Sprite; } try { Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { name = Path.GetFileNameWithoutExtension(text) }; if (!TryLoadImage(val, File.ReadAllBytes(text))) { Object.Destroy((Object)(object)val); return null; } Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)val2).name = Path.GetFileNameWithoutExtension(text); IconCache[text] = new IconCacheEntry(lastWriteTimeUtc, val2); return val2; } catch (Exception ex) { DataForgeLogContext.Warning("Could not load status effect icon '" + iconName + "' from '" + text + "': " + ex.Message); return null; } } private static bool TryLoadImage(Texture2D texture, byte[] data) { if (LoadImageMethod == null) { DataForgeLogContext.Warning("Could not locate UnityEngine.ImageConversion.LoadImage for status effect icon."); return false; } object[] parameters = ((LoadImageMethod.GetParameters().Length != 3) ? new object[2] { texture, data } : new object[3] { texture, data, false }); object obj = LoadImageMethod.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; } private static MethodInfo? ResolveLoadImageMethod() { return typeof(ImageConversion).GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }, null) ?? typeof(ImageConversion).GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(Texture2D), typeof(byte[]) }, null); } private static string? ResolveIconPath(string iconName) { if (string.IsNullOrWhiteSpace(iconName)) { return null; } string text = iconName.Trim().Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); if (!Path.HasExtension(text)) { text += ".png"; } string fullPath = Path.GetFullPath(Path.Combine(IconDirectory, text)); string fullPath2 = Path.GetFullPath(IconDirectory); char directorySeparatorChar = Path.DirectorySeparatorChar; if (!fullPath.StartsWith(fullPath2 + directorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { return null; } return fullPath; } private static void ApplyEffectList(string statusEffectName, string? value, Action assign, string fieldName) { if (value != null) { EffectList val = ParseEffectList(statusEffectName, value, fieldName); if (val != null) { assign(val); } } } private static EffectList? ParseEffectList(string statusEffectName, string value, string fieldName) { //IL_00dd: 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_00ef: Expected O, but got Unknown //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_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown string text = value.Trim(); if (text.Length == 0 || text.Equals("None", StringComparison.OrdinalIgnoreCase)) { return EmptyEffectList(); } List list = new List(); string[] array = text.Split(new char[1] { ',' }, StringSplitOptions.None); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length != 0 && !text2.Equals("None", StringComparison.OrdinalIgnoreCase)) { GameObject val = ResolveEffectPrefab(text2); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(statusEffectName + " has unknown " + fieldName + " prefab '" + text2 + "'."); return null; } list.Add(new EffectData { m_prefab = val, m_enabled = true, m_variant = -1 }); } } return new EffectList { m_effectPrefabs = list.ToArray() }; } private static EffectList EmptyEffectList() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown return new EffectList { m_effectPrefabs = Array.Empty() }; } private static EffectList CloneEffectList(EffectList? source) { //IL_001d: 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_0078: Expected O, but got Unknown if (source?.m_effectPrefabs == null || source.m_effectPrefabs.Length == 0) { return EmptyEffectList(); } return new EffectList { m_effectPrefabs = source.m_effectPrefabs.Where((EffectData effect) => effect != null).Select(CloneEffectData).ToArray() }; } private static EffectData CloneEffectData(EffectData source) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_0041: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown return new EffectData { m_prefab = source.m_prefab, m_enabled = source.m_enabled, m_variant = source.m_variant, m_attach = source.m_attach, m_follow = source.m_follow, m_inheritParentRotation = source.m_inheritParentRotation, m_inheritParentScale = source.m_inheritParentScale, m_multiplyParentVisualScale = source.m_multiplyParentVisualScale, m_randomRotation = source.m_randomRotation, m_scale = source.m_scale, m_childTransform = source.m_childTransform }; } private static string? FormatEffectList(EffectList? effectList) { if (effectList?.m_effectPrefabs == null || effectList.m_effectPrefabs.Length == 0) { return null; } List list = (from effect in effectList.m_effectPrefabs where effect != null && effect.m_enabled && (Object)(object)effect.m_prefab != (Object)null select Utils.GetPrefabName(((Object)effect.m_prefab).name) into name where !string.IsNullOrWhiteSpace(name) select name).ToList(); if (list.Count <= 0) { return null; } return string.Join(", ", list); } private static GameObject? ResolveEffectPrefab(string prefabName) { if ((Object)(object)ZNetScene.instance == (Object)null || string.IsNullOrWhiteSpace(prefabName)) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(prefabName); if ((Object)(object)prefab != (Object)null) { return prefab; } int stableHashCode = StringExtensionMethods.GetStableHashCode(prefabName); if (!ZNetScene.instance.m_namedPrefabs.TryGetValue(stableHashCode, out var value)) { return null; } return value; } private static void ApplyStats(SE_Stats stats, StatsDefinition? definition) { if (definition != null) { CopyFloatTuple(definition.UpFront, delegate(float value) { stats.m_healthUpFront = value; }, delegate(float value) { stats.m_staminaUpFront = value; }, delegate(float value) { stats.m_eitrUpFront = value; }, delegate(float value) { stats.m_adrenalineUpFront = value; }, "upFront", 0f, 0f, 0f, 0f); CopyHealthPerTick(definition.HealthPerTick, stats); CopyFloatTuple(definition.HealthOverTime, delegate(float value) { stats.m_healthOverTime = value; }, delegate(float value) { stats.m_healthOverTimeDuration = Math.Max(0f, value); }, delegate(float value) { stats.m_healthOverTimeInterval = Math.Max(0f, value); }, "healthOverTime", 0f, 0f, 5f); CopyFloatBoolTuple(definition.StaminaOverTime, delegate(float value) { stats.m_staminaOverTime = value; }, delegate(float value) { stats.m_staminaOverTimeDuration = Math.Max(0f, value); }, delegate(bool value) { stats.m_staminaOverTimeIsFraction = value; }, "staminaOverTime", 0f, 0f, defaultThird: false); CopyFloatTuple(definition.EitrOverTime, delegate(float value) { stats.m_eitrOverTime = value; }, delegate(float value) { stats.m_eitrOverTimeDuration = Math.Max(0f, value); }, null, "eitrOverTime", 0f, 0f, null); CopyFloatTuple(definition.RegenMultiplier, delegate(float value) { stats.m_healthRegenMultiplier = Math.Max(0f, value); }, delegate(float value) { stats.m_staminaRegenMultiplier = Math.Max(0f, value); }, delegate(float value) { stats.m_eitrRegenMultiplier = Math.Max(0f, value); }, "regenMultiplier", 1f, 1f, 1f); DataForgeValue.Copy(definition.StaminaDrainPerSec, delegate(float value) { stats.m_staminaDrainPerSec = value; }); DataForgeValue.Copy(definition.AdrenalineModifier, delegate(float value) { stats.m_adrenalineModifier = value; }); DataForgeValue.Copy(definition.SpeedModifier, delegate(float value) { stats.m_speedModifier = value; }); DataForgeValue.Copy(definition.SwimSpeedModifier, delegate(float value) { stats.m_swimSpeedModifier = value; }); CopyFloatTuple(definition.JumpModifier, delegate(float value) { stats.m_jumpModifier.x = value; }, delegate(float value) { stats.m_jumpModifier.y = value; }, delegate(float value) { stats.m_jumpModifier.z = value; }, "jumpModifier", 0f, 0f, 0f); CopyFloatTuple(definition.WindRun, delegate(float value) { stats.m_windMovementModifier = value; }, delegate(float value) { stats.m_windRunStaminaModifier = value; }, null, "windRun", 0f, 0f, null); CopyFloatTuple(definition.Sneak, delegate(float value) { stats.m_stealthModifier = value; }, delegate(float value) { stats.m_noiseModifier = value; }, null, "sneak", 0f, 0f, null); CopyFloatTuple(definition.Fall, delegate(float value) { stats.m_fallDamageModifier = value; }, delegate(float value) { stats.m_maxMaxFallSpeed = Math.Max(0f, value); }, null, "fall", 0f, 0f, null); CopyFloatTuple(definition.Armor, delegate(float value) { stats.m_addArmor = value; }, delegate(float value) { stats.m_armorMultiplier = value; }, null, "armor", 0f, 0f, null); CopyFloatTuple(definition.Block, delegate(float value) { stats.m_timedBlockBonus = value; }, delegate(float value) { stats.m_blockStaminaUseFlatValue = value; }, null, "block", 0f, 0f, null); DataForgeValue.Copy(definition.StaggerModifier, delegate(float value) { stats.m_staggerModifier = value; }); DataForgeValue.Copy(definition.AddMaxCarryWeight, delegate(float value) { stats.m_addMaxCarryWeight = value; }); CopySkillValuePair(definition.AttackDamage, delegate(SkillType value) { //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) stats.m_modifyAttackSkill = value; }, delegate(float value) { stats.m_damageModifier = value; }, "attackDamage", 1f); CopySkillValuePair(definition.RaiseSkill, delegate(SkillType value) { //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) stats.m_raiseSkill = value; }, delegate(float value) { stats.m_raiseSkillModifier = value; }, "raiseSkill", 0f); CopySkillValuePair(definition.SkillLevel, delegate(SkillType value) { //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) stats.m_skillLevel = value; }, delegate(float value) { stats.m_skillLevelModifier = value; }, "skillLevel", 0f); CopySkillValuePair(definition.SkillLevel2, delegate(SkillType value) { //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) stats.m_skillLevel2 = value; }, delegate(float value) { stats.m_skillLevelModifier2 = value; }, "skillLevel2", 0f); } } private static void ApplyLiveSafeStats(SE_Stats stats, StatsDefinition? definition) { if (definition != null) { CopyFloatTuple(definition.RegenMultiplier, delegate(float value) { stats.m_healthRegenMultiplier = Math.Max(0f, value); }, delegate(float value) { stats.m_staminaRegenMultiplier = Math.Max(0f, value); }, delegate(float value) { stats.m_eitrRegenMultiplier = Math.Max(0f, value); }, "regenMultiplier", 1f, 1f, 1f); DataForgeValue.Copy(definition.StaminaDrainPerSec, delegate(float value) { stats.m_staminaDrainPerSec = value; }); DataForgeValue.Copy(definition.AdrenalineModifier, delegate(float value) { stats.m_adrenalineModifier = value; }); DataForgeValue.Copy(definition.SpeedModifier, delegate(float value) { stats.m_speedModifier = value; }); DataForgeValue.Copy(definition.SwimSpeedModifier, delegate(float value) { stats.m_swimSpeedModifier = value; }); CopyFloatTuple(definition.JumpModifier, delegate(float value) { stats.m_jumpModifier.x = value; }, delegate(float value) { stats.m_jumpModifier.y = value; }, delegate(float value) { stats.m_jumpModifier.z = value; }, "jumpModifier", 0f, 0f, 0f); CopyFloatTuple(definition.WindRun, delegate(float value) { stats.m_windMovementModifier = value; }, delegate(float value) { stats.m_windRunStaminaModifier = value; }, null, "windRun", 0f, 0f, null); CopyFloatTuple(definition.Sneak, delegate(float value) { stats.m_stealthModifier = value; }, delegate(float value) { stats.m_noiseModifier = value; }, null, "sneak", 0f, 0f, null); CopyFloatTuple(definition.Fall, delegate(float value) { stats.m_fallDamageModifier = value; }, delegate(float value) { stats.m_maxMaxFallSpeed = Math.Max(0f, value); }, null, "fall", 0f, 0f, null); CopyFloatTuple(definition.Armor, delegate(float value) { stats.m_addArmor = value; }, delegate(float value) { stats.m_armorMultiplier = value; }, null, "armor", 0f, 0f, null); CopyFloatTuple(definition.Block, delegate(float value) { stats.m_timedBlockBonus = value; }, delegate(float value) { stats.m_blockStaminaUseFlatValue = value; }, null, "block", 0f, 0f, null); DataForgeValue.Copy(definition.StaggerModifier, delegate(float value) { stats.m_staggerModifier = value; }); DataForgeValue.Copy(definition.AddMaxCarryWeight, delegate(float value) { stats.m_addMaxCarryWeight = value; }); CopySkillValuePair(definition.AttackDamage, delegate(SkillType value) { //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) stats.m_modifyAttackSkill = value; }, delegate(float value) { stats.m_damageModifier = value; }, "attackDamage", 1f); CopySkillValuePair(definition.RaiseSkill, delegate(SkillType value) { //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) stats.m_raiseSkill = value; }, delegate(float value) { stats.m_raiseSkillModifier = value; }, "raiseSkill", 0f); CopySkillValuePair(definition.SkillLevel, delegate(SkillType value) { //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) stats.m_skillLevel = value; }, delegate(float value) { stats.m_skillLevelModifier = value; }, "skillLevel", 0f); CopySkillValuePair(definition.SkillLevel2, delegate(SkillType value) { //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) stats.m_skillLevel2 = value; }, delegate(float value) { stats.m_skillLevelModifier2 = value; }, "skillLevel2", 0f); } } private static void ApplyStaminaDrainModifier(SE_Stats stats, StaminaDrainModifierDefinition? definition) { if (definition != null) { DataForgeValue.Copy(definition.Run, delegate(float value) { stats.m_runStaminaDrainModifier = value; }); DataForgeValue.Copy(definition.Attack, delegate(float value) { stats.m_attackStaminaUseModifier = value; }); DataForgeValue.Copy(definition.Block, delegate(float value) { stats.m_blockStaminaUseModifier = value; }); DataForgeValue.Copy(definition.Dodge, delegate(float value) { stats.m_dodgeStaminaUseModifier = value; }); DataForgeValue.Copy(definition.Jump, delegate(float value) { stats.m_jumpStaminaUseModifier = value; }); DataForgeValue.Copy(definition.Sneak, delegate(float value) { stats.m_sneakStaminaUseModifier = value; }); DataForgeValue.Copy(definition.Swim, delegate(float value) { stats.m_swimStaminaUseModifier = value; }); DataForgeValue.Copy(definition.HomeItem, delegate(float value) { stats.m_homeItemStaminaUseModifier = value; }); } } private static void ApplyPoison(SE_Poison poison, PoisonDefinition? definition) { if (definition != null) { DataForgeValue.Copy(definition.BaseTtl, delegate(float value) { poison.m_baseTTL = Math.Max(0f, value); }); DataForgeValue.Copy(definition.DamageInterval, delegate(float value) { poison.m_damageInterval = Math.Max(0f, value); }); DataForgeValue.Copy(definition.DamagePerHit, delegate(float value) { poison.m_damagePerHit = Math.Max(0f, value); }); DataForgeValue.Copy(definition.TtlPerDamage, delegate(float value) { poison.m_TTLPerDamage = Math.Max(0f, value); }); DataForgeValue.Copy(definition.TtlPerDamagePlayer, delegate(float value) { poison.m_TTLPerDamagePlayer = Math.Max(0f, value); }); DataForgeValue.Copy(definition.TtlPower, delegate(float value) { poison.m_TTLPower = value; }); } } private static void ApplyShield(SE_Shield shield, ShieldStatusDefinition? definition) { if (definition != null) { DataForgeValue.Copy(definition.AbsorbDamage, delegate(float value) { shield.m_absorbDamage = Math.Max(0f, value); }); DataForgeValue.Copy(definition.AbsorbDamagePerSkillLevel, delegate(float value) { shield.m_absorbDamagePerSkillLevel = Math.Max(0f, value); }); DataForgeValue.Copy(definition.AbsorbDamageWorldLevel, delegate(float value) { shield.m_absorbDamageWorldLevel = Math.Max(0f, value); }); DataForgeValue.Copy(definition.TtlPerItemLevel, delegate(int value) { shield.m_ttlPerItemLevel = Math.Max(0, value); }); DataForgeValue.Copy(definition.LevelUpSkillFactor, delegate(float value) { shield.m_levelUpSkillFactor = Math.Max(0f, value); }); CopyEnum(definition.LevelUpSkillOnBreak, (Action)delegate(SkillType value) { //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) shield.m_levelUpSkillOnBreak = value; }, "levelUpSkillOnBreak"); } } private static void ApplyFrost(SE_Frost frost, FrostDefinition? definition) { if (definition != null) { DataForgeValue.Copy(definition.FreezeTimeEnemy, delegate(float value) { frost.m_freezeTimeEnemy = Math.Max(0f, value); }); DataForgeValue.Copy(definition.FreezeTimePlayer, delegate(float value) { frost.m_freezeTimePlayer = Math.Max(0f, value); }); DataForgeValue.Copy(definition.MinSpeedFactor, delegate(float value) { frost.m_minSpeedFactor = value; }); } } private static void ApplyRested(SE_Rested rested, RestedDefinition? definition) { if (definition != null) { DataForgeValue.Copy(definition.BaseTtl, delegate(float value) { rested.m_baseTTL = Math.Max(0f, value); }); DataForgeValue.Copy(definition.TtlPerComfortLevel, delegate(float value) { rested.m_TTLPerComfortLevel = Math.Max(0f, value); }); } } private static void ApplyHealthUpgrade(SE_HealthUpgrade healthUpgrade, HealthUpgradeDefinition? definition) { if (definition != null) { DataForgeValue.Copy(definition.MoreHealth, delegate(float value) { healthUpgrade.m_moreHealth = value; }); DataForgeValue.Copy(definition.MoreStamina, delegate(float value) { healthUpgrade.m_moreStamina = value; }); } } private static void ApplyDamage(DamageTypes target, StatusDamageDefinition? damage) { //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) if (damage != null) { DataForgeValue.Copy(damage.Blunt, delegate(float value) { target.m_blunt = value; }); DataForgeValue.Copy(damage.Slash, delegate(float value) { target.m_slash = value; }); DataForgeValue.Copy(damage.Pierce, delegate(float value) { target.m_pierce = value; }); DataForgeValue.Copy(damage.Chop, delegate(float value) { target.m_chop = value; }); DataForgeValue.Copy(damage.Pickaxe, delegate(float value) { target.m_pickaxe = value; }); DataForgeValue.Copy(damage.Fire, delegate(float value) { target.m_fire = value; }); DataForgeValue.Copy(damage.Frost, delegate(float value) { target.m_frost = value; }); DataForgeValue.Copy(damage.Lightning, delegate(float value) { target.m_lightning = value; }); DataForgeValue.Copy(damage.Poison, delegate(float value) { target.m_poison = value; }); DataForgeValue.Copy(damage.Spirit, delegate(float value) { target.m_spirit = value; }); } } private static void ApplyDamageTakenModifiers(List modifiers, DamageTakenModifierDefinition? definition) { if (definition != null) { ApplyDamageTakenModifier(modifiers, (DamageType)1, definition.Blunt, "damageTakenModifiers.blunt"); ApplyDamageTakenModifier(modifiers, (DamageType)2, definition.Slash, "damageTakenModifiers.slash"); ApplyDamageTakenModifier(modifiers, (DamageType)4, definition.Pierce, "damageTakenModifiers.pierce"); ApplyDamageTakenModifier(modifiers, (DamageType)8, definition.Chop, "damageTakenModifiers.chop"); ApplyDamageTakenModifier(modifiers, (DamageType)16, definition.Pickaxe, "damageTakenModifiers.pickaxe"); ApplyDamageTakenModifier(modifiers, (DamageType)32, definition.Fire, "damageTakenModifiers.fire"); ApplyDamageTakenModifier(modifiers, (DamageType)64, definition.Frost, "damageTakenModifiers.frost"); ApplyDamageTakenModifier(modifiers, (DamageType)128, definition.Lightning, "damageTakenModifiers.lightning"); ApplyDamageTakenModifier(modifiers, (DamageType)256, definition.Poison, "damageTakenModifiers.poison"); ApplyDamageTakenModifier(modifiers, (DamageType)512, definition.Spirit, "damageTakenModifiers.spirit"); } } private static void ApplyDamageTakenModifier(List modifiers, DamageType damageType, string? value, string fieldName) { //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_0065: 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_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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: 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_0093: 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_009a: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(value)) { return; } if (!Enum.TryParse(value, ignoreCase: true, out DamageModifier result)) { DataForgeLogContext.Warning("Unknown " + fieldName + " value '" + value + "'."); return; } int num = modifiers.FindIndex((DamageModPair pair) => pair.m_type == damageType); if ((int)result == 0) { if (num >= 0) { modifiers.RemoveAt(num); } return; } DamageModPair val = new DamageModPair { m_type = damageType, m_modifier = result }; if (num >= 0) { modifiers[num] = val; } else { modifiers.Add(val); } } private static void CopyEnum(string? value, Action assign, string fieldName) where TEnum : struct { if (!string.IsNullOrWhiteSpace(value)) { if (Enum.TryParse(value, ignoreCase: true, out var result)) { assign(result); return; } DataForgeLogContext.Warning("Unknown " + fieldName + " enum value '" + value + "'."); } } private static void CopySkillValuePair(string? value, Action assignSkill, Action assignValue, string fieldName, float defaultValue) { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(value)) { return; } string[] array = (from part in value.Split(new char[1] { ',' }, 2, StringSplitOptions.None) select part.Trim()).ToArray(); if (array.Length == 0 || string.IsNullOrWhiteSpace(array[0])) { return; } if (!Enum.TryParse(array[0], ignoreCase: true, out SkillType result)) { DataForgeLogContext.Warning("Unknown " + fieldName + " skill value '" + array[0] + "'."); return; } float result2 = defaultValue; if (array.Length > 1 && array[1].Length > 0 && !float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out result2)) { DataForgeLogContext.Warning("Could not parse " + fieldName + " value '" + array[1] + "'. Expected '" + fieldName + ": Skill, value'."); } else { assignSkill(result); assignValue(result2); } } private static void CopyHealthPerTick(string? value, SE_Stats stats) { //IL_0071: 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_00b8: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(value)) { return; } string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (TryParseFloatPart(array, 0, 0f, "healthPerTick", out var value2) && TryParseFloatPart(array, 1, 0f, "healthPerTick", out var value3)) { HitType result = (HitType)0; if (array.Length > 2 && array[2].Length > 0 && !Enum.TryParse(array[2], ignoreCase: true, out result)) { DataForgeLogContext.Warning("Unknown healthPerTick hitType value '" + array[2] + "'."); return; } stats.m_healthPerTick = value2; stats.m_healthPerTickMinHealthPercentage = value3; stats.m_hitType = result; } } private static void CopyFloatTuple(string? value, Action assignFirst, Action assignSecond, Action? assignThird, string fieldName, float defaultFirst, float defaultSecond, float? defaultThird) { if (string.IsNullOrWhiteSpace(value)) { return; } string[] parts = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (TryParseFloatPart(parts, 0, defaultFirst, fieldName, out var value2) && TryParseFloatPart(parts, 1, defaultSecond, fieldName, out var value3)) { float value4 = defaultThird.GetValueOrDefault(); if (assignThird == null || !defaultThird.HasValue || TryParseFloatPart(parts, 2, defaultThird.Value, fieldName, out value4)) { assignFirst(value2); assignSecond(value3); assignThird?.Invoke(value4); } } } private static void CopyFloatTuple(string? value, Action assignFirst, Action assignSecond, Action assignThird, Action assignFourth, string fieldName, float defaultFirst, float defaultSecond, float defaultThird, float defaultFourth) { if (!string.IsNullOrWhiteSpace(value)) { string[] parts = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (TryParseFloatPart(parts, 0, defaultFirst, fieldName, out var value2) && TryParseFloatPart(parts, 1, defaultSecond, fieldName, out var value3) && TryParseFloatPart(parts, 2, defaultThird, fieldName, out var value4) && TryParseFloatPart(parts, 3, defaultFourth, fieldName, out var value5)) { assignFirst(value2); assignSecond(value3); assignThird(value4); assignFourth(value5); } } } private static void CopyFloatBoolTuple(string? value, Action assignFirst, Action assignSecond, Action assignThird, string fieldName, float defaultFirst, float defaultSecond, bool defaultThird) { if (string.IsNullOrWhiteSpace(value)) { return; } string[] array = (from part in value.Split(new char[1] { ',' }, StringSplitOptions.None) select part.Trim()).ToArray(); if (TryParseFloatPart(array, 0, defaultFirst, fieldName, out var value2) && TryParseFloatPart(array, 1, defaultSecond, fieldName, out var value3)) { bool result = defaultThird; if (array.Length > 2 && array[2].Length > 0 && !bool.TryParse(array[2], out result)) { DataForgeLogContext.Warning("Could not parse " + fieldName + " value '" + array[2] + "'. Expected true or false."); } else { assignFirst(value2); assignSecond(value3); assignThird(result); } } } private static bool TryParseFloatPart(string[] parts, int index, float defaultValue, string fieldName, out float value) { value = defaultValue; if (parts.Length <= index || parts[index].Length == 0) { return true; } if (float.TryParse(parts[index], NumberStyles.Float, CultureInfo.InvariantCulture, out value)) { return true; } DataForgeLogContext.Warning("Could not parse " + fieldName + " value '" + parts[index] + "'. Expected a number."); return false; } private static bool TryParseBoolPart(string[] parts, int index, bool defaultValue, string fieldName, out bool value) { value = defaultValue; if (parts.Length <= index || parts[index].Length == 0) { return true; } if (bool.TryParse(parts[index], out value)) { return true; } DataForgeLogContext.Warning("Could not parse " + fieldName + " value '" + parts[index] + "'. Expected true or false."); return false; } private static string FormatSkillValuePair(SkillType skill, float value) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return string.Format("{0}, {1}", skill, value.ToString("0.###", CultureInfo.InvariantCulture)); } private static string FormatHealthPerTick(SE_Stats stats) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) return $"{FormatFloat(stats.m_healthPerTick)}, {FormatFloat(stats.m_healthPerTickMinHealthPercentage)}, {stats.m_hitType}"; } private static string FormatFloatTuple(float first, float second, float? third) { if (!third.HasValue) { return FormatFloat(first) + ", " + FormatFloat(second); } return FormatFloat(first) + ", " + FormatFloat(second) + ", " + FormatFloat(third.Value); } private static string FormatFloatTuple(float first, float second, float third, float fourth) { return FormatFloat(first) + ", " + FormatFloat(second) + ", " + FormatFloat(third) + ", " + FormatFloat(fourth); } private static string FormatFloatBoolTuple(float first, float second, bool third) { return FormatFloat(first) + ", " + FormatFloat(second) + ", " + third.ToString().ToLowerInvariant(); } private static string FormatBoolTuple(bool first, bool second) { return first.ToString().ToLowerInvariant() + ", " + second.ToString().ToLowerInvariant(); } private static string FormatFloat(float value) { return value.ToString("0.###", CultureInfo.InvariantCulture); } private static void WriteGeneratedArtifacts() { if (DataForgePlugin.UsesLocalAuthorityFiles) { WriteReferenceArtifact(); } } internal static bool TryWriteFullScaffoldConfigurationFile(out string path, out string error) { path = Path.Combine(ConfigDirectory, "effects.full.yml"); return GeneratedArtifactWriter.TryWriteFullScaffoldIfReady(path, "effects", CanBuildGeneratedArtifacts(), "effects game data is not ready yet.", delegate { EnsureConfigDirectoryAndDefaultOverride(); CaptureAllBaselinesIfNeeded(); var entries = (from pair in Baselines.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.OrdinalIgnoreCase) select new { Entry = StatusEffectEntry.FromDefinition(pair.Key, pair.Value, overrideEntry: true), OwnerKey = pair.Key, SortKey = pair.Key }).ToList(); return GeneratedArtifactWriter.GeneratedHeader("effects", "effects.yml", "full scaffold") + DataForgeReferenceSections.SerializeReferenceSections(entries, entry => entry.SortKey, entry => DataForgeOwnerResolver.GetAssetOwnerName(entry.OwnerKey), entry => entry.Entry, FullSerializer); }, out error); } private static void WriteReferenceArtifact() { if (CanBuildGeneratedArtifacts()) { EnsureConfigDirectoryAndDefaultOverride(); CaptureAllBaselinesIfNeeded(); string sourceSignature = ComputeReferenceSourceSignature(); string text = Path.Combine(ConfigDirectory, "effects.reference.yml"); if (!ShouldSkipReferenceUpdate(text, sourceSignature) && (GeneratedArtifactWriter.WriteReferenceIfReady(Baselines.Count > 0, ConfigDirectory, "effects.reference.yml", "effects", "effects.yml", BuildReferenceArtifactContent) || File.Exists(text))) { RecordReferenceUpdateState(text, sourceSignature); } } } private static string BuildReferenceArtifactContent() { return DataForgeReferenceSections.SerializeReferenceSections((from pair in Baselines.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.OrdinalIgnoreCase) select StatusEffectReferenceEntry.From(pair.Key, pair.Value)).ToList(), (StatusEffectReferenceEntry entry) => entry.Effect, (StatusEffectReferenceEntry entry) => DataForgeOwnerResolver.GetAssetOwnerName(entry.Effect), (StatusEffectReferenceEntry entry) => entry, SparseSerializer); } private static bool CanBuildGeneratedArtifacts() { if (ObjectDbReady) { return (Object)(object)ObjectDB.instance != (Object)null; } return false; } private static string ComputeReferenceSourceSignature() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("2026-06-24-effect-reference-state-v2"); if ((Object)(object)ObjectDB.instance != (Object)null) { foreach (StatusEffect item in ObjectDB.instance.m_StatusEffects.Where((StatusEffect statusEffect) => (Object)(object)statusEffect != (Object)null && !string.IsNullOrWhiteSpace(((Object)statusEffect).name)).OrderBy((StatusEffect statusEffect) => ((Object)statusEffect).name, StringComparer.OrdinalIgnoreCase)) { stringBuilder.Append(((Object)item).name.Trim()); stringBuilder.Append('|'); stringBuilder.AppendLine(SparseSerializer.Serialize(StatusEffectDefinition.From(item))); } } return ComputeStableHash(stringBuilder.ToString()); } private static bool ShouldSkipReferenceUpdate(string referencePath, string sourceSignature) { return DataForgeReferenceState.ShouldSkip("effects", referencePath, sourceSignature, "2026-06-24-effect-reference-state-v2"); } private static void RecordReferenceUpdateState(string referencePath, string sourceSignature) { DataForgeReferenceState.Record("effects", referencePath, sourceSignature, "2026-06-24-effect-reference-state-v2"); } private static string BuildFileStamp(string path) { return DataForgeReferenceState.BuildFileStamp(path); } private static string ComputeStableHash(string value) { return DataForgeReferenceState.ComputeStableHash(value); } } internal static class PieceOverrideManager { internal sealed class PieceEntry { internal string LogContext { get; private set; } = ""; public string Piece { get; set; } = ""; public bool Override { get; set; } = true; public bool Remove { get; set; } public string? Name { get; set; } public string? Description { get; set; } public string? PieceTable { get; set; } public string? Category { get; set; } public int? SortOrder { get; set; } public string? NeedStation { get; set; } public bool? CanBeRemoved { get; set; } public float? Health { get; set; } public string? Comfort { get; set; } public List? Resources { get; set; } public string? SapCollector { get; set; } public string? Beehive { get; set; } public FermenterDefinition? Fermenter { get; set; } public CookingStationDefinition? CookingStation { get; set; } public SmelterDefinition? Smelter { get; set; } public string? Container { get; set; } public string? StationExtension { get; set; } public CraftingStationComponentDefinition? CraftingStation { get; set; } internal bool HasDefinition { get { if (!Remove && PieceTable == null && !SortOrder.HasValue) { return HasRuntimeDefinition; } return true; } } internal bool HasRuntimeDefinition { get { if (!HasBaseDefinition && SapCollector == null && Beehive == null && Fermenter == null && CookingStation == null && Smelter == null && Container == null && StationExtension == null) { return CraftingStation != null; } return true; } } private bool HasBaseDefinition { get { if (Name == null && Description == null && Category == null && NeedStation == null && !CanBeRemoved.HasValue && !Health.HasValue && Comfort == null) { return Resources != null; } return true; } } internal void SetLogContext(string value) { LogContext = value; } internal PieceComponentDefinition? ToPieceComponentDefinition() { if (!HasBaseDefinition) { return null; } return new PieceComponentDefinition { Name = Name, Description = Description, Category = Category, NeedStation = NeedStation, CanBeRemoved = CanBeRemoved, Health = Health, Comfort = Comfort, Resources = Resources }; } internal static PieceEntry FromDefinition(string prefab, PieceDefinition definition, string? pieceTable = null) { PieceComponentDefinition piece = definition.Piece; return new PieceEntry { Piece = prefab, Override = true, Remove = false, Name = piece?.Name, Description = piece?.Description, PieceTable = pieceTable, Category = FormatReferenceCategory(prefab, piece?.Category), SortOrder = 100, NeedStation = piece?.NeedStation, CanBeRemoved = piece?.CanBeRemoved, Health = piece?.Health, Comfort = piece?.Comfort, Resources = piece?.Resources, SapCollector = definition.SapCollector, Beehive = definition.Beehive, Fermenter = definition.Fermenter, CookingStation = definition.CookingStation, Smelter = definition.Smelter, Container = definition.Container, StationExtension = definition.StationExtension, CraftingStation = definition.CraftingStation }; } } internal sealed class PieceReferenceEntry { public string Piece { get; set; } = ""; public bool? CanBeRemoved { get; set; } public float? Health { get; set; } public string? Comfort { get; set; } public List? Resources { get; set; } public string? SapCollector { get; set; } public string? Beehive { get; set; } public FermenterDefinition? Fermenter { get; set; } public CookingStationDefinition? CookingStation { get; set; } public SmelterDefinition? Smelter { get; set; } public string? Container { get; set; } public string? StationExtension { get; set; } public CraftingStationReferenceDefinition? CraftingStation { get; set; } internal static PieceReferenceEntry From(string prefab, PieceDefinition definition) { return ReferenceValue.ClonePruned(new PieceReferenceEntry { Piece = prefab, CanBeRemoved = definition.Piece?.CanBeRemoved, Health = definition.Piece?.Health, Comfort = FormatReferenceComfort(definition.Piece?.Comfort), Resources = PieceResourceDefinition.ToReference(definition.Piece?.Resources), SapCollector = definition.SapCollector, Beehive = definition.Beehive, Fermenter = ((definition.Fermenter != null) ? ReferenceValue.ClonePruned(definition.Fermenter) : null), CookingStation = ((definition.CookingStation != null) ? ReferenceValue.ClonePruned(definition.CookingStation) : null), Smelter = ((definition.Smelter != null) ? ReferenceValue.ClonePruned(definition.Smelter) : null), Container = definition.Container, StationExtension = definition.StationExtension, CraftingStation = ((definition.CraftingStation != null) ? ReferenceValue.ClonePruned(new CraftingStationReferenceDefinition { DiscoveryRange = definition.CraftingStation.DiscoveryRange, BuildRange = definition.CraftingStation.BuildRange, CraftRequiresRoof = definition.CraftingStation.CraftRequiresRoof, CraftRequiresFire = definition.CraftingStation.CraftRequiresFire }) : null) }) ?? new PieceReferenceEntry { Piece = prefab }; } private static string? FormatReferenceComfort(string? value) { string[] array = DataForgeValue.SplitTuple(value); if (array.Length != 2 || !array[1].Equals("None", StringComparison.OrdinalIgnoreCase)) { return value; } return array[0]; } } internal sealed class PieceDefinition { public PieceComponentDefinition? Piece { get; set; } public string? SapCollector { get; set; } public string? Beehive { get; set; } public FermenterDefinition? Fermenter { get; set; } public CookingStationDefinition? CookingStation { get; set; } public SmelterDefinition? Smelter { get; set; } public string? Container { get; set; } public string? StationExtension { get; set; } public CraftingStationComponentDefinition? CraftingStation { get; set; } internal static PieceDefinition From(PieceEntry entry) { return new PieceDefinition { Piece = entry.ToPieceComponentDefinition(), SapCollector = entry.SapCollector, Beehive = entry.Beehive, Fermenter = entry.Fermenter, CookingStation = entry.CookingStation, Smelter = entry.Smelter, Container = entry.Container, StationExtension = entry.StationExtension, CraftingStation = entry.CraftingStation }; } internal static PieceDefinition From(Piece piece) { return new PieceDefinition { Piece = PieceComponentDefinition.From(piece), SapCollector = FormatSapCollector(((Component)piece).GetComponent()), Beehive = FormatBeehive(((Component)piece).GetComponent()), Fermenter = FermenterDefinition.From(((Component)piece).GetComponent()), CookingStation = CookingStationDefinition.From(((Component)piece).GetComponent()), Smelter = SmelterDefinition.From(((Component)piece).GetComponent()), Container = FormatContainer(((Component)piece).GetComponent()), StationExtension = FormatStationExtension(((Component)piece).GetComponents()), CraftingStation = CraftingStationComponentDefinition.From(((Component)piece).GetComponent()) }; } } internal sealed class PieceComponentDefinition { public string? Name { get; set; } public string? Description { get; set; } public string? Category { get; set; } public string? NeedStation { get; set; } public bool? CanBeRemoved { get; set; } public float? Health { get; set; } public string? Comfort { get; set; } public List? Resources { get; set; } internal static PieceComponentDefinition From(Piece piece) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) WearNTear component = ((Component)piece).GetComponent(); PieceComponentDefinition pieceComponentDefinition = new PieceComponentDefinition(); pieceComponentDefinition.Name = piece.m_name; pieceComponentDefinition.Description = piece.m_description; pieceComponentDefinition.Category = FormatPieceCategory(piece); pieceComponentDefinition.NeedStation = FormatCraftingStation(piece.m_craftingStation); pieceComponentDefinition.CanBeRemoved = piece.m_canBeRemoved; pieceComponentDefinition.Health = (((Object)(object)component != (Object)null) ? new float?(component.m_health) : ((float?)null)); pieceComponentDefinition.Comfort = FormatTuple(piece.m_comfort, piece.m_comfortGroup); pieceComponentDefinition.Resources = PieceResourceDefinition.From(piece.m_resources); return pieceComponentDefinition; } } internal sealed class PieceResourceDefinition : Dictionary { internal static List From(Requirement[] requirements) { List list = new List(); if (requirements == null) { return list; } foreach (Requirement val in requirements) { if (!((Object)(object)val?.m_resItem == (Object)null)) { PieceResourceDefinition pieceResourceDefinition = new PieceResourceDefinition(); pieceResourceDefinition[GetPrefabName(((Component)val.m_resItem).gameObject)] = string.Join(", ", Math.Max(0, val.m_amount).ToString(CultureInfo.InvariantCulture), val.m_recover.ToString().ToLowerInvariant()); list.Add(pieceResourceDefinition); } } return list; } internal static List? ToReference(List? resources) { if (resources == null) { return null; } List list = new List(); foreach (PieceResourceDefinition resource in resources) { foreach (KeyValuePair item in resource) { string[] parts = DataForgeValue.SplitTuple(item.Value); int num = Math.Max(0, GetIntPart(parts, 0, 1)); bool boolPart = GetBoolPart(parts, 1, defaultValue: true); PieceResourceDefinition pieceResourceDefinition = new PieceResourceDefinition(); pieceResourceDefinition[item.Key] = (boolPart ? num.ToString(CultureInfo.InvariantCulture) : FormatTuple(num, false)); list.Add(pieceResourceDefinition); } } if (list.Count <= 0) { return null; } return list; } } private sealed class PieceOrderItem { public GameObject Prefab { get; private set; } public PieceCategory Category { get; private set; } public int SortOrder { get; private set; } public int OriginalIndex { get; private set; } internal static PieceOrderItem From(GameObject piece, int originalIndex, IReadOnlyDictionary sortOrders) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) Piece component = piece.GetComponent(); string prefabName = GetPrefabName(piece); int value; return new PieceOrderItem { Prefab = piece, Category = (PieceCategory)(((Object)(object)component != (Object)null) ? ((int)component.m_category) : 0), SortOrder = (sortOrders.TryGetValue(prefabName, out value) ? value : 100), OriginalIndex = originalIndex }; } } private sealed class PieceTableAssignment { internal string PieceTable { get; } internal string LogContext { get; } internal PieceTableAssignment(string pieceTable, string logContext) { PieceTable = pieceTable; LogContext = logContext; } } private sealed class PieceTableMembershipSnapshot { private readonly Dictionary> TableNamesByPiece; internal PieceTableMembershipSnapshot(Dictionary> tableNamesByPiece) { TableNamesByPiece = tableNamesByPiece; } internal List GetTableNames(string prefabName) { if (!TableNamesByPiece.TryGetValue(NormalizePrefabName(prefabName), out List value)) { return new List(); } return value; } internal bool IsOnlyInIgnoredTables(string prefabName) { List tableNames = GetTableNames(prefabName); if (tableNames.Count > 0) { return tableNames.All(IsIgnoredPieceTableName); } return false; } } private sealed class ReferenceComparer : IEqualityComparer where T : class { internal static readonly ReferenceComparer Instance = new ReferenceComparer(); public bool Equals(T? x, T? y) { return x == y; } public int GetHashCode(T obj) { return RuntimeHelpers.GetHashCode(obj); } } internal sealed class FermenterDefinition { public float? Duration { get; set; } public List? Conversions { get; set; } internal static FermenterDefinition? From(Fermenter? fermenter) { if (!((Object)(object)fermenter == (Object)null)) { return new FermenterDefinition { Duration = fermenter.m_fermentationDuration, Conversions = FermenterConversionDefinition.From(fermenter.m_conversion) }; } return null; } } internal sealed class FermenterConversionDefinition : Dictionary { internal static List From(List conversions) { List list = new List(); if (conversions == null) { return list; } foreach (ItemConversion conversion in conversions) { if (!((Object)(object)conversion?.m_from == (Object)null) && !((Object)(object)conversion.m_to == (Object)null)) { FermenterConversionDefinition fermenterConversionDefinition = new FermenterConversionDefinition(); fermenterConversionDefinition[GetItemName(conversion.m_from)] = FormatTuple(GetItemName(conversion.m_to), conversion.m_producedItems); list.Add(fermenterConversionDefinition); } } return list; } } internal sealed class CookingStationDefinition { public string? Fuel { get; set; } public List? Conversions { get; set; } internal static CookingStationDefinition? From(CookingStation? cookingStation) { if (!((Object)(object)cookingStation == (Object)null)) { CookingStationDefinition cookingStationDefinition = new CookingStationDefinition(); cookingStationDefinition.Fuel = FormatTuple(GetItemName(cookingStation.m_fuelItem), cookingStation.m_requireFire, cookingStation.m_maxFuel, cookingStation.m_secPerFuel); cookingStationDefinition.Conversions = CookingStationConversionDefinition.From(cookingStation.m_conversion); return cookingStationDefinition; } return null; } } internal sealed class CookingStationConversionDefinition : Dictionary { internal static List From(List conversions) { List list = new List(); if (conversions == null) { return list; } foreach (ItemConversion conversion in conversions) { if (!((Object)(object)conversion?.m_from == (Object)null) && !((Object)(object)conversion.m_to == (Object)null)) { CookingStationConversionDefinition cookingStationConversionDefinition = new CookingStationConversionDefinition(); cookingStationConversionDefinition[GetItemName(conversion.m_from)] = FormatTuple(GetItemName(conversion.m_to), conversion.m_cookTime); list.Add(cookingStationConversionDefinition); } } return list; } } internal sealed class SmelterDefinition { public string? Input { get; set; } public string? Output { get; set; } public bool? RequiresRoof { get; set; } public List? Conversions { get; set; } internal static SmelterDefinition? From(Smelter? smelter) { if (!((Object)(object)smelter == (Object)null)) { SmelterDefinition smelterDefinition = new SmelterDefinition(); smelterDefinition.Input = FormatTuple(GetItemName(smelter.m_fuelItem), smelter.m_maxFuel, smelter.m_maxOre); smelterDefinition.Output = FormatTuple(smelter.m_fuelPerProduct, smelter.m_secPerProduct); smelterDefinition.RequiresRoof = smelter.m_requiresRoof; smelterDefinition.Conversions = SmelterConversionDefinition.From(smelter.m_conversion); return smelterDefinition; } return null; } } internal sealed class SmelterConversionDefinition : Dictionary { internal static List From(List conversions) { List list = new List(); if (conversions == null) { return list; } foreach (ItemConversion conversion in conversions) { if (!((Object)(object)conversion?.m_from == (Object)null) && !((Object)(object)conversion.m_to == (Object)null)) { SmelterConversionDefinition smelterConversionDefinition = new SmelterConversionDefinition(); smelterConversionDefinition[GetItemName(conversion.m_from)] = GetItemName(conversion.m_to); list.Add(smelterConversionDefinition); } } return list; } } internal sealed class CraftingStationReferenceDefinition { public float? DiscoveryRange { get; set; } public string? BuildRange { get; set; } public bool? CraftRequiresRoof { get; set; } public bool? CraftRequiresFire { get; set; } } internal sealed class CraftingStationComponentDefinition { public string? Name { get; set; } public float? DiscoveryRange { get; set; } public string? BuildRange { get; set; } public bool? CraftRequiresRoof { get; set; } public bool? CraftRequiresFire { get; set; } public bool? ShowBasicRecipes { get; set; } public float? UseDistance { get; set; } public int? UseAnimation { get; set; } public string? CraftingSkill { get; set; } internal static CraftingStationComponentDefinition? From(CraftingStation? craftingStation) { if (!((Object)(object)craftingStation == (Object)null)) { CraftingStationComponentDefinition craftingStationComponentDefinition = new CraftingStationComponentDefinition(); craftingStationComponentDefinition.Name = craftingStation.m_name; craftingStationComponentDefinition.DiscoveryRange = craftingStation.m_discoverRange; craftingStationComponentDefinition.BuildRange = FormatTuple(craftingStation.m_rangeBuild, craftingStation.m_extraRangePerLevel); craftingStationComponentDefinition.CraftRequiresRoof = craftingStation.m_craftRequireRoof; craftingStationComponentDefinition.CraftRequiresFire = craftingStation.m_craftRequireFire; craftingStationComponentDefinition.ShowBasicRecipes = craftingStation.m_showBasicRecipies; craftingStationComponentDefinition.UseDistance = craftingStation.m_useDistance; craftingStationComponentDefinition.UseAnimation = craftingStation.m_useAnimation; craftingStationComponentDefinition.CraftingSkill = ((object)Unsafe.As(ref craftingStation.m_craftingSkill)/*cast due to .constrained prefix*/).ToString(); return craftingStationComponentDefinition; } return null; } } private const string DomainName = "pieces"; private const string ReferenceFileName = "pieces.reference.yml"; private const string FullScaffoldFileName = "pieces.full.yml"; private const string SyncedPayloadKey = "pieces"; private const long ReloadDelayTicks = 10000000L; private const int DefaultPieceSortOrder = 100; private const string ReferenceStateKey = "pieces"; private const string ReferenceLogicVersion = "2026-06-24-piece-reference-state-v2"; private static readonly HashSet IgnoredCategoryNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "Feasts", "Food", "Meads" }; private static readonly HashSet IgnoredPieceTableNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "Feaster", "FeasterPieceTable", "_FeasterPieceTable", "ServingTray" }; private static readonly object StateLock = new object(); private static readonly Dictionary Baselines = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary> PieceTableOrderBaselines = new Dictionary>(ReferenceComparer.Instance); private static readonly HashSet ManagedStationExtensionInstanceIds = new HashSet(); private static readonly HashSet RuntimeAppliedPieceKeys = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary PieceTableAliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Hammer"] = "_HammerPieceTable", ["Hoe"] = "_HoePieceTable", ["Cultivator"] = "_CultivatorPieceTable", ["ServingTray"] = "_FeasterPieceTable" }; private static Dictionary? JotunnPieceCategoryNames; private static Dictionary? JotunnPieceCategoryValues; private static bool JotunnPieceCategoryMapLoaded; private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); private static readonly ISerializer SparseSerializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull).DisableAliases() .Build(); private static readonly ISerializer FullSerializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).DisableAliases().Build(); private static List ActiveEntries = new List(); private static Dictionary> ActiveRuntimeEntriesByPiece = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static Dictionary ActiveEntrySignaturesByPiece = new Dictionary(StringComparer.OrdinalIgnoreCase); private static HashSet? PendingChangedPieceKeys; private static bool HasPendingScopedApply; private static bool ForceNextFullApply = true; private static CustomSyncedValue? SyncedPayload; private static string? LastAppliedSyncedPayload; private static FileSystemWatcher? Watcher; private static DataForgeFileWatcher.DebouncedAction? ReloadDebouncer; private static bool GameDataReady; private static bool ObjectDbReady; private static bool PieceTablesReady; private static bool RuntimeStateWasApplied; private static bool PieceTableSortWasApplied; private static bool PieceTableMembershipWasApplied; private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge"); internal static void Initialize(ConfigSync configSync) { SyncedPayload = new CustomSyncedValue(configSync, "pieces", ""); SyncedPayload.ValueChanged += OnSyncedPayloadChanged; } internal static void Dispose() { if (SyncedPayload != null) { SyncedPayload.ValueChanged -= OnSyncedPayloadChanged; } Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } internal static void SetupFileWatcher() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { Watcher?.Dispose(); Watcher = null; ReloadDebouncer?.Dispose(); ReloadDebouncer = null; } else { EnsureConfigDirectoryAndDefaultOverride(); Watcher?.Dispose(); ReloadDebouncer?.Dispose(); ReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadYamlValues); Watcher = DataForgeFileWatcher.Create(ConfigDirectory, "*.*", includeSubdirectories: false, ReadYamlValues); } } internal static void ReloadFromDiskAndSync() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { ApplySyncedPayload(SyncedPayload?.Value ?? ""); return; } EnsureConfigDirectoryAndDefaultOverride(); List list = LoadEntriesFromDisk(); lock (StateLock) { SetActiveEntries(list); } PublishPayload(SerializeEntries(list)); ApplyCurrentConfiguration(); } internal static void ApplyCurrentConfiguration() { if (!GameDataReady || !ObjectDbReady || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null) { return; } bool flag = HasActiveRuntimeDefinitions(); Dictionary activeSortOrders = GetActiveSortOrders(); Dictionary activePieceTableAssignments = GetActivePieceTableAssignments(); HashSet activeRemovedPieces = GetActiveRemovedPieces(); HashSet hashSet; lock (StateLock) { hashSet = ConsumePendingChangedPieceKeys(); } if (hashSet != null && hashSet.Count == 0) { return; } bool flag2 = activeSortOrders.Count > 0; bool flag3 = activePieceTableAssignments.Count > 0; bool flag4 = activeRemovedPieces.Count > 0; if (!flag && !flag2 && !flag3 && !flag4 && !RuntimeStateWasApplied && !PieceTableSortWasApplied && !PieceTableMembershipWasApplied) { return; } int num; object obj; if (!flag) { num = (RuntimeStateWasApplied ? 1 : 0); if (num == 0) { obj = null; goto IL_00d6; } } else { num = 1; } obj = GetRuntimeApplyKeys(hashSet, flag); goto IL_00d6; IL_00d6: HashSet hashSet2 = (HashSet)obj; if (num != 0) { CaptureBaselinesForPiecesIfNeeded(hashSet2); ApplyToPrefabDefinitions(hashSet2); } ApplyPieceTableStructure(activePieceTableAssignments, activeSortOrders, activeRemovedPieces); if (num != 0) { ApplyToLoadedInstances(hashSet2); InvalidateCraftingStationExtensionCaches(); RefreshLocalBuildPieces(); } RuntimeStateWasApplied = flag; RuntimeAppliedPieceKeys.Clear(); if (flag) { foreach (string runtimeApplyKey in GetRuntimeApplyKeys(null, flag)) { RuntimeAppliedPieceKeys.Add(runtimeApplyKey); } } PieceTableSortWasApplied = flag2; PieceTableMembershipWasApplied = flag3 || flag4; } private static bool ShouldSkipRemoteClientBaselineWork() { if (!DataForgePlugin.IsRemoteServerClient || RuntimeStateWasApplied || PieceTableSortWasApplied || PieceTableMembershipWasApplied) { return false; } lock (StateLock) { return ActiveEntries.Count == 0; } } internal static void OnGameDataReady() { if (!((Object)(object)ZNetScene.instance == (Object)null)) { GameDataReady = true; if (!ShouldSkipRemoteClientBaselineWork()) { WriteGeneratedArtifacts(); ApplyCurrentConfiguration(); } } } internal static void OnObjectDBReady() { if (!((Object)(object)ObjectDB.instance == (Object)null)) { ObjectDbReady = true; if (!ShouldSkipRemoteClientBaselineWork()) { WriteGeneratedArtifacts(); ApplyCurrentConfiguration(); } } } internal static void OnWorldShutdown() { GameDataReady = false; ObjectDbReady = false; PieceTablesReady = false; RuntimeStateWasApplied = false; RuntimeAppliedPieceKeys.Clear(); PieceTableSortWasApplied = false; PieceTableMembershipWasApplied = false; } internal static void OnPieceTablesReady() { if (!((Object)(object)ZNetScene.instance == (Object)null)) { GameDataReady = true; PieceTablesReady = true; if (!ShouldSkipRemoteClientBaselineWork()) { WriteGeneratedArtifacts(); ApplyCurrentConfiguration(); } } } private static void ReadYamlValues(object sender, FileSystemEventArgs e) { if (ShouldReloadForFileEvent(e)) { ReloadDebouncer?.Schedule(); } } private static void ReloadYamlValues() { try { DataForgePlugin.Log.LogDebug((object)"Reloading piece YAML files..."); ReloadFromDiskAndSync(); DataForgePlugin.Log.LogInfo((object)"Piece YAML reload complete."); } catch (Exception arg) { DataForgePlugin.Log.LogError((object)$"Error reloading piece YAML files: {arg}"); } } private static bool ShouldReloadForFileEvent(FileSystemEventArgs e) { if (!DataForgePlugin.UsesLocalAuthorityFiles) { return false; } if (IsOverrideFile(e.FullPath)) { return true; } if (e is RenamedEventArgs e2) { return IsOverrideFile(e2.OldFullPath); } return false; } private static void OnSyncedPayloadChanged() { if (!DataForgePlugin.UsesLocalAuthorityFiles) { string payload = SyncedPayload?.Value ?? ""; DataForgeProfiler.Profile(string.Format("{0}.ApplySyncedPayload chars={1}", "pieces", payload.Length), delegate { ApplySyncedPayload(payload); }); } } private static void ApplySyncedPayload(string payload) { if (!string.Equals(LastAppliedSyncedPayload, payload, StringComparison.Ordinal)) { LastAppliedSyncedPayload = payload; List activeEntries = DeserializeEntries(payload, "synced piece payload"); lock (StateLock) { SetActiveEntries(activeEntries); } ApplyCurrentConfiguration(); } } private static void SetActiveEntries(List entries) { Dictionary dictionary = BuildEntrySignaturesByPiece(entries); if (!ForceNextFullApply) { PendingChangedPieceKeys = GetChangedKeys(ActiveEntrySignaturesByPiece, dictionary); HasPendingScopedApply = true; } ActiveEntries = entries; ActiveRuntimeEntriesByPiece = BuildActiveRuntimeEntriesByPiece(entries); ActiveEntrySignaturesByPiece = dictionary; } private static HashSet? ConsumePendingChangedPieceKeys() { if (ForceNextFullApply) { ForceNextFullApply = false; PendingChangedPieceKeys = null; HasPendingScopedApply = false; return null; } if (!HasPendingScopedApply) { return null; } HashSet? result = PendingChangedPieceKeys ?? new HashSet(StringComparer.OrdinalIgnoreCase); PendingChangedPieceKeys = null; HasPendingScopedApply = false; return result; } private static Dictionary BuildEntrySignaturesByPiece(List entries) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (PieceEntry entry in entries) { if (!string.IsNullOrWhiteSpace(entry.Piece)) { if (!dictionary.TryGetValue(entry.Piece, out var value)) { value = new List(); dictionary[entry.Piece] = value; } value.Add(entry); } } Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair> item in dictionary) { dictionary2[item.Key] = SparseSerializer.Serialize(item.Value); } return dictionary2; } private static HashSet GetChangedKeys(Dictionary oldSignatures, Dictionary newSignatures) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair oldSignature in oldSignatures) { if (!newSignatures.TryGetValue(oldSignature.Key, out string value) || !string.Equals(oldSignature.Value, value, StringComparison.Ordinal)) { hashSet.Add(oldSignature.Key); } } foreach (KeyValuePair newSignature in newSignatures) { if (!oldSignatures.TryGetValue(newSignature.Key, out string value2) || !string.Equals(value2, newSignature.Value, StringComparison.Ordinal)) { hashSet.Add(newSignature.Key); } } return hashSet; } private static Dictionary> BuildActiveRuntimeEntriesByPiece(List entries) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (PieceEntry entry in entries) { if (entry.Override && !entry.Remove && entry.HasRuntimeDefinition && !string.IsNullOrWhiteSpace(entry.Piece)) { if (!dictionary.TryGetValue(entry.Piece, out var value)) { value = new List(); dictionary[entry.Piece] = value; } value.Add(entry); } } return dictionary; } private static void PublishPayload(string payload) { DataForgeSync.PublishPayload(SyncedPayload, "pieces", payload); } private static List LoadEntriesFromDisk() { return DataForgeOverrideFiles.LoadEntries(GetOverrideFiles(), DeserializeEntries); } private static List DeserializeEntries(string yaml, string source) { if (string.IsNullOrWhiteSpace(yaml)) { return new List(); } try { return NormalizeEntries(Deserializer.Deserialize>(yaml), source); } catch (Exception ex) { DataForgePlugin.Log.LogError((object)("Failed to parse " + source + ": " + ex.Message)); return new List(); } } private static List NormalizeEntries(List? entries, string source) { List list = new List(); if (entries == null) { return list; } int num = 0; foreach (PieceEntry entry in entries) { num++; string text = DataForgeLogContext.FormatSource(source, num); if (string.IsNullOrWhiteSpace(entry.Piece)) { DataForgeLogContext.Warning(text + ": Skipping piece entry without piece."); continue; } entry.Piece = NormalizePrefabName(entry.Piece); entry.SetLogContext(text + " piece=" + entry.Piece); if (IsIgnoredCategoryName(entry.Category)) { entry.Category = null; } if (IsIgnoredPieceTableName(entry.PieceTable)) { entry.PieceTable = null; } list.Add(entry); } return list; } private static string SerializeEntries(List entries) { return SparseSerializer.Serialize(entries); } private static IEnumerable GetOverrideFiles() { return DataForgeOverrideFiles.GetOverrideFiles(ConfigDirectory, IsOverrideFile); } private static bool IsOverrideFile(string path) { string extension = Path.GetExtension(path); if (!extension.Equals(".yml", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".yaml", StringComparison.OrdinalIgnoreCase)) { return false; } string fileName = Path.GetFileName(path); if (fileName.Equals("pieces.reference.yml", StringComparison.OrdinalIgnoreCase) || fileName.Equals("pieces.full.yml", StringComparison.OrdinalIgnoreCase)) { return false; } return fileName.StartsWith("pieces", StringComparison.OrdinalIgnoreCase); } private static void EnsureConfigDirectoryAndDefaultOverride() { DataForgeOverrideFiles.EnsureDefaultOverride(ConfigDirectory, "pieces.yml", GetOverrideFiles, DefaultOverrideTemplate); } private static string DefaultOverrideTemplate() { return string.Join(Environment.NewLine, "# DataForge piece overrides.", "# Copy entries from pieces.reference.yml, or run `dataforge:full piece` to generate pieces.full.yml for exhaustive field examples.", "# You can also create additional override files like pieces_asdf.yml; DataForge loads pieces.yml and pieces_*.yml together.", "# Omitted fields keep the current piece value. Values below are common defaults or examples.", "#", "# Schema:", "# - piece: wood_wall # required; piece prefab id.", "# override: true # default true; false skips this entire piece entry, including remove.", "# remove: false # default false; true hides this piece from build tables without deleting placed pieces.", "# name: $piece_woodwall # Piece.m_name localization token or text.", "# description: $piece_woodwall_desc # Piece.m_description localization token or text.", "# pieceTable: Hammer # build tool/table to show this piece in. Hammer is the reference default and is omitted there.", "# category: Building # Piece.PieceCategory enum; Feasts, Food, and Meads are ignored by DataForge.", "# sortOrder: 100 # lower appears earlier in the same build tab; omitted keeps original order.", "# needStation: None # station prefab/name needed to build; None clears the station requirement.", "# canBeRemoved: true # false prevents removing the placed piece with a hammer.", "# health: 100 # max structural health when the prefab is damageable.", "# comfort: 0, None # comfort amount, comfort group: None, Fire, Bed, Banner, Chair, Table, Carpet.", "# resources:", "# - Wood: 2 # item: amount; add ', false' to disable build resource recovery.", "# sapCollector: Sap, 60, 10 # produced item, seconds per unit, max stored units.", "# beehive: 1200, 4 # seconds per produced honey, max stored honey.", "# fermenter:", "# duration: 2400 # fermentation duration in seconds.", "# conversions:", "# - MeadBaseHealthMedium: MeadHealthMedium, 6 # from item: to item, produced amount.", "# cookingStation:", "# fuel: Wood, true, 10, 60 # fuel item prefab, require external fire, maxFuel, seconds per fuel. None clears fuel.", "# conversions:", "# - BreadDough: Bread, 50 # from item: to item, cook time seconds.", "# smelter:", "# input: Coal, 20, 10 # fuel item prefab; None clears it, maxFuel, maxOre.", "# output: 2, 30 # fuelPerProduct, seconds per product.", "# requiresRoof: true # true requires roof cover.", "# conversions:", "# - CopperOre: Copper # from item: to item.", "# container: 10, 4 # width, height.", "# stationExtension: forge, 5 # target crafting station prefab/name, max station distance.", "# craftingStation:", "# name: $piece_forge # CraftingStation.m_name localization token or text.", "# discoveryRange: 4 # station discovery range.", "# buildRange: 20, 0 # base build range, extra range per extension level.", "# craftRequiresRoof: false # true requires roof to use the station.", "# craftRequiresFire: false # true requires fire to use the station.", "# showBasicRecipes: true # CraftingStation.m_showBasicRecipies.", "# useDistance: 2 # interaction distance.", "# useAnimation: 2 # player crafting animation id.", "# craftingSkill: Crafting # Skills.SkillType used for craft speed/bonus/raise.", "#", "# Example:", "# - piece: wood_wall", "# health: 250", "# resources:", "# - Wood: 4") + Environment.NewLine; } private static void CaptureAllBaselinesIfNeeded() { if (!ObjectDbReady || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null) { return; } int num = 0; foreach (var (prefabName, piece) in GetPrefabPieces()) { if (CaptureBaseline(prefabName, piece)) { num++; } } if (num > 0) { DataForgePlugin.Log.LogInfo((object)$"Captured {num} piece prefab baselines. Tracking {Baselines.Count} total."); } } private static void CaptureBaselinesForPiecesIfNeeded(IEnumerable? prefabNames) { if (!ObjectDbReady || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || prefabNames == null) { return; } int num = 0; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); Piece piece = default(Piece); foreach (string prefabName in prefabNames) { string text = NormalizePrefabName(prefabName); if (text.Length != 0 && hashSet.Add(text) && !Baselines.ContainsKey(text)) { GameObject val = ResolvePiecePrefab(text); if (!((Object)(object)val == (Object)null) && val.TryGetComponent(ref piece) && CaptureBaseline(GetPrefabName(val), piece)) { num++; } } } if (num > 0) { DataForgePlugin.Log.LogInfo((object)$"Captured {num} targeted piece prefab baselines. Tracking {Baselines.Count} total."); } } private static bool CaptureBaseline(string prefabName, Piece piece) { if (string.IsNullOrWhiteSpace(prefabName) || Baselines.ContainsKey(prefabName)) { return false; } Baselines[prefabName] = PieceDefinition.From(piece); return true; } private static IEnumerable<(string PrefabName, Piece Piece)> GetPrefabPieces() { if ((Object)(object)ZNetScene.instance == (Object)null) { yield break; } HashSet seen = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if ((Object)(object)prefab == (Object)null) { continue; } Piece component = prefab.GetComponent(); if (IsManagedPiece(prefab, component)) { string prefabName = GetPrefabName(prefab); if (seen.Add(prefabName)) { yield return (PrefabName: prefabName, Piece: component); } } } } private static void ApplyToPrefabDefinitions(HashSet? pieceKeys = null) { foreach (var item in (pieceKeys == null) ? GetPrefabPieces() : GetPrefabPieces(pieceKeys)) { var (prefabName, _) = item; ApplyConfiguredState(((Component)item.Piece).gameObject, prefabName, adjustHealthZdo: false); } } private static IEnumerable<(string PrefabName, Piece Piece)> GetPrefabPieces(IEnumerable prefabNames) { HashSet seen = new HashSet(StringComparer.OrdinalIgnoreCase); Piece item = default(Piece); foreach (string prefabName in prefabNames) { if (seen.Add(prefabName)) { GameObject val = ResolvePiecePrefab(prefabName); if ((Object)(object)val != (Object)null && val.TryGetComponent(ref item)) { yield return (PrefabName: GetPrefabName(val), Piece: item); } } } } private static void ApplyToLoadedInstances(HashSet? pieceKeys = null) { //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) foreach (Piece item in Piece.s_allPieces.ToList()) { if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).gameObject == (Object)null) { continue; } Scene scene = ((Component)item).gameObject.scene; if (((Scene)(ref scene)).IsValid() && IsManagedPiece(((Component)item).gameObject, item)) { string prefabName = GetPrefabName(((Component)item).gameObject); if (pieceKeys == null || pieceKeys.Contains(prefabName)) { ApplyConfiguredState(((Component)item).gameObject, prefabName, adjustHealthZdo: true); } } } } private static bool IsManagedPiece(GameObject gameObject, Piece? piece = null) { if ((Object)(object)gameObject != (Object)null && (Object)(object)(piece ?? gameObject.GetComponent()) != (Object)null) { return (Object)(object)gameObject.GetComponent() != (Object)null; } return false; } private static void ApplyConfiguredState(GameObject gameObject, string prefabName, bool adjustHealthZdo) { bool flag = false; if (Baselines.TryGetValue(prefabName, out PieceDefinition value)) { flag = value.StationExtension != null; ApplyDefinition(gameObject, value, adjustHealthZdo); } if (!DataForgePlugin.PieceOverridesEnabled) { if (!flag) { RemoveManagedStationExtensionIfPresent(gameObject); } return; } List value2; lock (StateLock) { ActiveRuntimeEntriesByPiece.TryGetValue(prefabName, out value2); } if (value2 == null) { if (!flag) { RemoveManagedStationExtensionIfPresent(gameObject); } return; } foreach (PieceEntry item in value2) { PieceDefinition pieceDefinition = PieceDefinition.From(item); flag = flag || pieceDefinition.StationExtension != null; using (DataForgeLogContext.Push(item.LogContext)) { ApplyDefinition(gameObject, pieceDefinition, adjustHealthZdo); } } if (!flag) { RemoveManagedStationExtensionIfPresent(gameObject); } } private static bool HasActiveRuntimeDefinitions() { if (!DataForgePlugin.PieceOverridesEnabled) { return false; } lock (StateLock) { return ActiveRuntimeEntriesByPiece.Count > 0; } } private static HashSet GetRuntimeApplyKeys(HashSet? changedPieceKeys, bool hasActiveRuntimeDefinitions) { if (changedPieceKeys != null) { return new HashSet(changedPieceKeys, StringComparer.OrdinalIgnoreCase); } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (hasActiveRuntimeDefinitions) { lock (StateLock) { foreach (string key in ActiveRuntimeEntriesByPiece.Keys) { hashSet.Add(key); } } } if (RuntimeStateWasApplied) { foreach (string runtimeAppliedPieceKey in RuntimeAppliedPieceKeys) { hashSet.Add(runtimeAppliedPieceKey); } } return hashSet; } private static Dictionary GetActiveSortOrders() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!DataForgePlugin.PieceOverridesEnabled) { return dictionary; } lock (StateLock) { foreach (PieceEntry activeEntry in ActiveEntries) { if (activeEntry.Override && !activeEntry.Remove && activeEntry.SortOrder.HasValue) { dictionary[activeEntry.Piece] = activeEntry.SortOrder.Value; } } return dictionary; } } private static Dictionary GetActivePieceTableAssignments() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!DataForgePlugin.PieceOverridesEnabled) { return dictionary; } lock (StateLock) { foreach (PieceEntry activeEntry in ActiveEntries) { string pieceTable = activeEntry.PieceTable; if (activeEntry.Override && !activeEntry.Remove && !string.IsNullOrWhiteSpace(pieceTable) && !IsIgnoredPieceTableName(pieceTable)) { dictionary[activeEntry.Piece] = new PieceTableAssignment(pieceTable.Trim(), activeEntry.LogContext); } } return dictionary; } } private static HashSet GetActiveRemovedPieces() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (!DataForgePlugin.PieceOverridesEnabled) { return hashSet; } lock (StateLock) { foreach (PieceEntry activeEntry in ActiveEntries) { if (activeEntry.Override && activeEntry.Remove && !string.IsNullOrWhiteSpace(activeEntry.Piece)) { hashSet.Add(activeEntry.Piece); } } return hashSet; } } private static void ApplyDefinition(GameObject gameObject, PieceDefinition definition, bool adjustHealthZdo) { Piece component = gameObject.GetComponent(); if ((Object)(object)component != (Object)null && definition.Piece != null) { ApplyPieceDefinition(component, definition.Piece, adjustHealthZdo); } ApplySupportedComponentDefinitions(gameObject, definition); } private static void ApplyPieceDefinition(Piece piece, PieceComponentDefinition definition, bool adjustHealthZdo) { DataForgeValue.Copy(definition.Name, delegate(string value) { piece.m_name = value; Door component = ((Component)piece).GetComponent(); if ((Object)(object)component != (Object)null) { component.m_name = value; } }); DataForgeValue.Copy(definition.Description, delegate(string value) { piece.m_description = value; }); ApplyCategory(piece, definition.Category); ApplyCraftingStation(piece, definition.NeedStation); DataForgeValue.Copy(definition.CanBeRemoved, delegate(bool value) { piece.m_canBeRemoved = value; }); ApplyPieceHealth(piece, definition.Health, adjustHealthZdo); ApplyComfort(piece, definition.Comfort); ApplyResources(piece, definition.Resources); } private static void ApplyCategory(Piece piece, string? categoryName) { //IL_0054: 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_0074: Unknown result type (might be due to invalid IL or missing references) string text = categoryName?.Trim() ?? ""; if (text.Length == 0 || IsIgnoredCategoryName(text)) { return; } if (!TryResolvePieceCategory(text, out var category)) { DataForgeLogContext.Warning(GetPrefabName(((Component)piece).gameObject) + " has unknown piece category '" + text + "'."); return; } piece.m_category = category; foreach (PieceTable item in GetPieceTablesContaining(((Component)piece).gameObject)) { EnsurePieceTableCategory(item, category); } } private static void ApplyCraftingStation(Piece piece, string? value) { if (value == null) { return; } string text = value.Trim(); string prefabName = GetPrefabName(((Component)piece).gameObject); if (text.Length == 0) { return; } if (DataForgeValue.IsNone(text)) { piece.m_craftingStation = null; return; } CraftingStation val = ResolveCraftingStation(text); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(prefabName + " has unknown craftingStation '" + text + "'."); } else { piece.m_craftingStation = val; } } private static void ApplyResources(Piece piece, List? resources) { //IL_007e: 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_008b: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown if (resources == null) { return; } List list = new List(); foreach (PieceResourceDefinition resource in resources) { foreach (KeyValuePair item2 in resource) { ItemDrop val = ResolveItemDrop(item2.Key); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(GetPrefabName(((Component)piece).gameObject) + " has unknown build resource '" + item2.Key + "'."); continue; } string[] parts = DataForgeValue.SplitTuple(item2.Value); Requirement item = new Requirement { m_resItem = val, m_amount = Math.Max(0, GetIntPart(parts, 0, 1)), m_amountPerLevel = 0, m_recover = GetBoolPart(parts, 1, defaultValue: true) }; list.Add(item); } } piece.m_resources = list.ToArray(); } private static void ApplyComfort(Piece piece, string? value) { //IL_0063: 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) if (string.IsNullOrWhiteSpace(value)) { return; } bool wasComfortPiece = piece.m_comfort > 0; string[] array = DataForgeValue.SplitTuple(value); CopyIntPart(array, 0, delegate(int parsed) { piece.m_comfort = Math.Max(0, parsed); }); if (array.Length > 1 && array[1].Length > 0) { if (Enum.TryParse(array[1], ignoreCase: true, out ComfortGroup result)) { piece.m_comfortGroup = result; } else { DataForgeLogContext.Warning(GetPrefabName(((Component)piece).gameObject) + " has unknown comfort group '" + array[1] + "'. Expected: None, Fire, Bed, Banner, Chair, Table, Carpet."); } } UpdateComfortPieceRegistration(piece, wasComfortPiece); } private static void UpdateComfortPieceRegistration(Piece piece, bool wasComfortPiece) { if (piece.m_comfort > 0) { Piece.s_allComfortPieces.Add(piece); } else if (wasComfortPiece) { Piece.s_allComfortPieces.Remove(piece); } } private static void ApplyPieceHealth(Piece piece, float? health, bool adjustHealthZdo) { if (!health.HasValue) { return; } WearNTear component = ((Component)piece).GetComponent(); if (!((Object)(object)component == (Object)null)) { if (health.Value >= 0f) { ApplyHealth(component, health.Value, adjustHealthZdo); } else { DataForgeLogContext.Warning(GetPrefabName(((Component)piece).gameObject) + " has invalid negative health; keeping previous value."); } } } private static void ApplySupportedComponentDefinitions(GameObject gameObject, PieceDefinition definition) { if (definition.SapCollector != null) { SapCollector component = gameObject.GetComponent(); if ((Object)(object)component != (Object)null) { ApplySapCollectorDefinition(component, definition.SapCollector); } } if (definition.Beehive != null) { Beehive component2 = gameObject.GetComponent(); if ((Object)(object)component2 != (Object)null) { ApplyBeehiveDefinition(component2, definition.Beehive); } } if (definition.Fermenter != null) { Fermenter component3 = gameObject.GetComponent(); if ((Object)(object)component3 != (Object)null) { ApplyFermenterDefinition(component3, definition.Fermenter); } } if (definition.CookingStation != null) { CookingStation component4 = gameObject.GetComponent(); if ((Object)(object)component4 != (Object)null) { ApplyCookingStationDefinition(component4, definition.CookingStation); } } if (definition.Smelter != null) { Smelter component5 = gameObject.GetComponent(); if ((Object)(object)component5 != (Object)null) { ApplySmelterDefinition(component5, definition.Smelter); } } if (definition.Container != null) { Container component6 = gameObject.GetComponent(); if ((Object)(object)component6 != (Object)null) { ApplyContainerDefinition(component6, definition.Container); } } if (definition.StationExtension != null) { ApplyStationExtensionDefinition(gameObject, definition.StationExtension); } if (definition.CraftingStation != null) { CraftingStation component7 = gameObject.GetComponent(); if ((Object)(object)component7 != (Object)null) { ApplyCraftingStationComponentDefinition(component7, definition.CraftingStation); } } } private static void ApplySapCollectorDefinition(SapCollector sapCollector, string definition) { if (string.IsNullOrWhiteSpace(definition)) { return; } string[] array = DataForgeValue.SplitTuple(definition); if (array.Length != 0 && array[0].Length > 0) { ItemDrop val = ResolveItemDrop(array[0]); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(GetPrefabName(((Component)sapCollector).gameObject) + " has unknown sapCollector production item '" + array[0] + "'."); } else { sapCollector.m_spawnItem = val; } } CopyFloatPart(array, 1, delegate(float parsed) { sapCollector.m_secPerUnit = Math.Max(0f, parsed); }); CopyIntPart(array, 2, delegate(int parsed) { sapCollector.m_maxLevel = Math.Max(0, parsed); }); } private static void ApplyBeehiveDefinition(Beehive beehive, string definition) { if (!string.IsNullOrWhiteSpace(definition)) { string[] parts = DataForgeValue.SplitTuple(definition); CopyFloatPart(parts, 0, delegate(float parsed) { beehive.m_secPerUnit = Math.Max(0f, parsed); }); CopyIntPart(parts, 1, delegate(int parsed) { beehive.m_maxHoney = Math.Max(0, parsed); }); } } private static void ApplyFermenterDefinition(Fermenter fermenter, FermenterDefinition definition) { DataForgeValue.Copy(definition.Duration, delegate(float value) { fermenter.m_fermentationDuration = Math.Max(0f, value); }); if (definition.Conversions != null) { fermenter.m_conversion = BuildFermenterConversions(fermenter, definition.Conversions); } } private static List BuildFermenterConversions(Fermenter fermenter, List definitions) { //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_00f5: 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_0117: Expected O, but got Unknown List list = new List(); string prefabName = GetPrefabName(((Component)fermenter).gameObject); foreach (FermenterConversionDefinition definition in definitions) { foreach (KeyValuePair item in definition) { string[] array = DataForgeValue.SplitTuple(item.Value); if (array.Length == 0 || array[0].Length == 0) { DataForgeLogContext.Warning(prefabName + " has fermenter conversion '" + item.Key + "' without output item."); continue; } ItemDrop val = ResolveItemDrop(item.Key); ItemDrop val2 = ResolveItemDrop(array[0]); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { DataForgeLogContext.Warning(prefabName + " has unknown fermenter conversion '" + item.Key + ": " + item.Value + "'."); } else { list.Add(new ItemConversion { m_from = val, m_to = val2, m_producedItems = Math.Max(1, GetIntPart(array, 1, 4)) }); } } } return list; } private static void ApplyCookingStationDefinition(CookingStation cookingStation, CookingStationDefinition definition) { if (!string.IsNullOrWhiteSpace(definition.Fuel)) { string[] array = DataForgeValue.SplitTuple(definition.Fuel); if (array.Length != 0 && array[0].Length > 0) { if (DataForgeValue.IsNone(array[0])) { cookingStation.m_fuelItem = null; cookingStation.m_useFuel = false; } else { ItemDrop val = ResolveItemDrop(array[0]); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(GetPrefabName(((Component)cookingStation).gameObject) + " has unknown cookingStation fuel item '" + array[0] + "'."); } else { cookingStation.m_fuelItem = val; cookingStation.m_useFuel = true; } } } CopyBoolPart(array, 1, delegate(bool parsed) { cookingStation.m_requireFire = parsed; }); CopyIntPart(array, 2, delegate(int parsed) { cookingStation.m_maxFuel = Math.Max(0, parsed); }); CopyIntPart(array, 3, delegate(int parsed) { cookingStation.m_secPerFuel = Math.Max(0, parsed); }); } if (definition.Conversions != null) { cookingStation.m_conversion = BuildCookingStationConversions(cookingStation, definition.Conversions); } } private static List BuildCookingStationConversions(CookingStation cookingStation, List definitions) { //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_00f5: 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_011f: Expected O, but got Unknown List list = new List(); string prefabName = GetPrefabName(((Component)cookingStation).gameObject); foreach (CookingStationConversionDefinition definition in definitions) { foreach (KeyValuePair item in definition) { string[] array = DataForgeValue.SplitTuple(item.Value); if (array.Length == 0 || array[0].Length == 0) { DataForgeLogContext.Warning(prefabName + " has cookingStation conversion '" + item.Key + "' without output item."); continue; } ItemDrop val = ResolveItemDrop(item.Key); ItemDrop val2 = ResolveItemDrop(array[0]); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { DataForgeLogContext.Warning(prefabName + " has unknown cookingStation conversion '" + item.Key + ": " + item.Value + "'."); } else { list.Add(new ItemConversion { m_from = val, m_to = val2, m_cookTime = Math.Max(0f, GetFloatPart(array, 1, 10f)) }); } } } return list; } private static void ApplySmelterDefinition(Smelter smelter, SmelterDefinition definition) { if (!string.IsNullOrWhiteSpace(definition.Input)) { string[] array = DataForgeValue.SplitTuple(definition.Input); if (array.Length != 0 && array[0].Length > 0) { string text = array[0]; if (DataForgeValue.IsNone(text)) { smelter.m_fuelItem = null; } else { ItemDrop val = ResolveItemDrop(text); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(GetPrefabName(((Component)smelter).gameObject) + " has unknown smelter fuel item '" + text + "'."); } else { smelter.m_fuelItem = val; } } } CopyIntPart(array, 1, delegate(int parsed) { smelter.m_maxFuel = Math.Max(0, parsed); }); CopyIntPart(array, 2, delegate(int parsed) { smelter.m_maxOre = Math.Max(0, parsed); }); } if (!string.IsNullOrWhiteSpace(definition.Output)) { string[] parts = DataForgeValue.SplitTuple(definition.Output); CopyIntPart(parts, 0, delegate(int parsed) { smelter.m_fuelPerProduct = Math.Max(0, parsed); }); CopyFloatPart(parts, 1, delegate(float parsed) { smelter.m_secPerProduct = Math.Max(0f, parsed); }); } DataForgeValue.Copy(definition.RequiresRoof, delegate(bool value) { smelter.m_requiresRoof = value; }); if (definition.Conversions != null) { smelter.m_conversion = BuildSmelterConversions(smelter, definition.Conversions); } } private static List BuildSmelterConversions(Smelter smelter, List definitions) { //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown List list = new List(); string prefabName = GetPrefabName(((Component)smelter).gameObject); foreach (SmelterConversionDefinition definition in definitions) { foreach (KeyValuePair item in definition) { string text = item.Value.Trim(); if (text.Length == 0) { DataForgeLogContext.Warning(prefabName + " has smelter conversion '" + item.Key + "' without output item."); continue; } ItemDrop val = ResolveItemDrop(item.Key); ItemDrop val2 = ResolveItemDrop(text); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { DataForgeLogContext.Warning(prefabName + " has unknown smelter conversion '" + item.Key + ": " + item.Value + "'."); } else { list.Add(new ItemConversion { m_from = val, m_to = val2 }); } } } return list; } private static void ApplyContainerDefinition(Container container, string definition) { if (!string.IsNullOrWhiteSpace(definition)) { string[] parts = DataForgeValue.SplitTuple(definition); int requestedWidth = Math.Max(1, container.m_width); int requestedHeight = Math.Max(1, container.m_height); if (TryGetIntPart(parts, 0, out var parsed)) { requestedWidth = Math.Max(1, parsed); } if (TryGetIntPart(parts, 1, out var parsed2)) { requestedHeight = Math.Max(1, parsed2); } ApplyContainerSize(container, requestedWidth, requestedHeight); } } private static void ApplyContainerSize(Container container, int requestedWidth, int requestedHeight) { requestedWidth = Math.Max(1, requestedWidth); requestedHeight = Math.Max(1, requestedHeight); Inventory inventory = container.GetInventory(); if (inventory == null) { container.m_width = requestedWidth; container.m_height = requestedHeight; return; } int num = Math.Max(1, inventory.GetWidth()); int num2 = Math.Max(1, inventory.GetHeight()); int num3 = requestedWidth; int num4 = requestedHeight; bool flag = requestedWidth < num; bool flag2 = requestedHeight < num2; bool flag3 = false; if ((flag || flag2) && (container.IsInUse() || inventory.NrOfItems() > 0)) { if (flag) { num3 = num; } if (flag2) { num4 = num2; } flag3 = true; } container.m_width = num3; container.m_height = num4; inventory.m_width = num3; inventory.m_height = num4; if (flag3) { DataForgePlugin.Log.LogDebug((object)string.Format("{0} container resize requested {1}x{2}, applied {3}x{4} because loaded container is {5}.", GetPrefabName(((Component)container).gameObject), requestedWidth, requestedHeight, num3, num4, container.IsInUse() ? "open" : "not empty")); } } private static void ApplyStationExtensionDefinition(GameObject gameObject, string definition) { string prefabName = GetPrefabName(gameObject); StationExtension val = gameObject.GetComponent(); if ((Object)(object)val == (Object)null) { string stationExtensionStation = GetStationExtensionStation(definition); if (string.IsNullOrWhiteSpace(stationExtensionStation) || DataForgeValue.IsNone(stationExtensionStation)) { DataForgeLogContext.Warning(prefabName + " stationExtension needs a station when adding a new StationExtension component."); return; } val = gameObject.AddComponent(); ManagedStationExtensionInstanceIds.Add(((Object)gameObject).GetInstanceID()); val.m_piece = gameObject.GetComponent(); val.m_continousConnection = false; val.m_stack = false; } ApplyStationExtensionDefinition(val, definition); } private static void ApplyStationExtensionDefinition(StationExtension extension, string definition) { string[] array = DataForgeValue.SplitTuple(definition); if (array.Length != 0 && array[0].Length > 0) { if (DataForgeValue.IsNone(array[0])) { DataForgeLogContext.Warning(GetPrefabName(((Component)extension).gameObject) + " stationExtension cannot use None because Valheim requires a non-null crafting station."); return; } CraftingStation val = ResolveCraftingStation(array[0]); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning(GetPrefabName(((Component)extension).gameObject) + " has unknown stationExtension station '" + array[0] + "'."); } else { extension.m_craftingStation = val; } } CopyFloatPart(array, 1, delegate(float value) { extension.m_maxStationDistance = Math.Max(0f, value); }); } private static void RemoveManagedStationExtensionIfPresent(GameObject gameObject) { if (!ManagedStationExtensionInstanceIds.Remove(((Object)gameObject).GetInstanceID())) { return; } StationExtension component = gameObject.GetComponent(); if ((Object)(object)component == (Object)null) { return; } StationExtension.m_allExtensions.Remove(component); try { Object.DestroyImmediate((Object)(object)component); } catch (Exception ex) { DataForgePlugin.Log.LogDebug((object)("Could not immediately remove managed StationExtension from '" + GetPrefabName(gameObject) + "': " + ex.Message)); Object.Destroy((Object)(object)component); } } private static void InvalidateCraftingStationExtensionCaches() { CraftingStation[] array = Resources.FindObjectsOfTypeAll(); foreach (CraftingStation val in array) { if (!((Object)(object)val == (Object)null)) { val.m_updateExtensionTimer = 2f; val.m_attachedExtensions?.Clear(); } } } private static string GetStationExtensionStation(string definition) { string[] array = DataForgeValue.SplitTuple(definition); if (array.Length == 0) { return ""; } return array[0]; } private static void ApplyCraftingStationComponentDefinition(CraftingStation craftingStation, CraftingStationComponentDefinition definition) { //IL_010e: 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) DataForgeValue.Copy(definition.Name, delegate(string value) { craftingStation.m_name = value; }); DataForgeValue.Copy(definition.DiscoveryRange, delegate(float value) { craftingStation.m_discoverRange = Math.Max(0f, value); }); if (!string.IsNullOrWhiteSpace(definition.BuildRange)) { string[] parts = DataForgeValue.SplitTuple(definition.BuildRange); CopyFloatPart(parts, 0, delegate(float value) { craftingStation.m_rangeBuild = Math.Max(0f, value); }); CopyFloatPart(parts, 1, delegate(float value) { craftingStation.m_extraRangePerLevel = Math.Max(0f, value); }); } DataForgeValue.Copy(definition.CraftRequiresRoof, delegate(bool value) { craftingStation.m_craftRequireRoof = value; }); DataForgeValue.Copy(definition.CraftRequiresFire, delegate(bool value) { craftingStation.m_craftRequireFire = value; }); DataForgeValue.Copy(definition.ShowBasicRecipes, delegate(bool value) { craftingStation.m_showBasicRecipies = value; }); DataForgeValue.Copy(definition.UseDistance, delegate(float value) { craftingStation.m_useDistance = Math.Max(0f, value); }); DataForgeValue.Copy(definition.UseAnimation, delegate(int value) { craftingStation.m_useAnimation = Math.Max(0, value); }); if (!string.IsNullOrWhiteSpace(definition.CraftingSkill)) { if (Enum.TryParse(definition.CraftingSkill, ignoreCase: true, out SkillType result)) { craftingStation.m_craftingSkill = result; } else { DataForgeLogContext.Warning(GetPrefabName(((Component)craftingStation).gameObject) + " has unknown craftingStation craftingSkill '" + definition.CraftingSkill + "'."); } } craftingStation.m_updateExtensionTimer = 2f; } private static void CopyBoolPart(string[] parts, int index, Action assign) { if (index < parts.Length && parts[index].Length > 0 && bool.TryParse(parts[index], out var result)) { assign(result); } } private static void CopyIntPart(string[] parts, int index, Action assign) { if (TryGetIntPart(parts, index, out var parsed)) { assign(parsed); } } private static bool TryGetIntPart(string[] parts, int index, out int parsed) { parsed = 0; if (index < parts.Length && parts[index].Length > 0) { return int.TryParse(parts[index], NumberStyles.Integer, CultureInfo.InvariantCulture, out parsed); } return false; } private static void CopyFloatPart(string[] parts, int index, Action assign) { if (index < parts.Length && parts[index].Length > 0 && float.TryParse(parts[index], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { assign(result); } } private static int GetIntPart(string[] parts, int index, int defaultValue) { if (index >= parts.Length || !int.TryParse(parts[index], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return defaultValue; } return result; } private static float GetFloatPart(string[] parts, int index, float defaultValue) { if (index >= parts.Length || !float.TryParse(parts[index], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return defaultValue; } return result; } private static bool GetBoolPart(string[] parts, int index, bool defaultValue) { if (index >= parts.Length || !bool.TryParse(parts[index], out var result)) { return defaultValue; } return result; } private static ItemDrop? ResolveItemDrop(string prefabName) { if ((Object)(object)ObjectDB.instance != (Object)null) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(prefabName); ItemDrop result = default(ItemDrop); if ((Object)(object)itemPrefab != (Object)null && itemPrefab.TryGetComponent(ref result)) { return result; } } if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(prefabName); ItemDrop result2 = default(ItemDrop); if ((Object)(object)prefab != (Object)null && prefab.TryGetComponent(ref result2)) { return result2; } } return null; } private static CraftingStation? ResolveCraftingStation(string stationName) { if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(stationName); CraftingStation result = default(CraftingStation); if ((Object)(object)prefab != (Object)null && prefab.TryGetComponent(ref result)) { return result; } } foreach (var prefabPiece in GetPrefabPieces()) { Piece item = prefabPiece.Piece; CraftingStation component = ((Component)item).GetComponent(); if (!((Object)(object)component == (Object)null) && (((Object)item).name.Equals(stationName, StringComparison.OrdinalIgnoreCase) || ((Object)((Component)item).gameObject).name.Equals(stationName, StringComparison.OrdinalIgnoreCase) || ((Object)component).name.Equals(stationName, StringComparison.OrdinalIgnoreCase) || component.m_name.Equals(stationName, StringComparison.OrdinalIgnoreCase))) { return component; } } return null; } private static IEnumerable GetAllPieceTables(bool includeIgnored = false) { HashSet seen = new HashSet(); foreach (var (pieceTableName, val) in GetBuildPieceOwnerTables()) { if ((Object)(object)val != (Object)null && (includeIgnored || !IsIgnoredPieceTableName(pieceTableName)) && (includeIgnored || !IsIgnoredPieceTableName(((Object)val).name)) && seen.Add(val)) { yield return val; } } PieceTable[] array = Resources.FindObjectsOfTypeAll(); foreach (PieceTable val2 in array) { if ((Object)(object)val2 != (Object)null && (includeIgnored || !IsIgnoredPieceTableName(((Object)val2).name)) && seen.Add(val2)) { yield return val2; } } } private static IEnumerable GetPieceTablesContaining(GameObject piecePrefab, bool includeIgnored = false) { foreach (PieceTable allPieceTable in GetAllPieceTables(includeIgnored)) { if (allPieceTable.m_pieces != null && allPieceTable.m_pieces.Contains(piecePrefab)) { yield return allPieceTable; } } } private static void EnsurePieceTableCategory(PieceTable pieceTable, PieceCategory category) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) PieceTableCategoryGuard.EnsureCategory(pieceTable, category); } private static void CapturePieceTableOrderBaselinesIfNeeded(IEnumerable pieceTables) { if (!PieceTablesReady) { return; } foreach (PieceTable pieceTable in pieceTables) { CapturePieceTableOrderBaseline(pieceTable); } } private static void CapturePieceTableOrderBaseline(PieceTable pieceTable) { if (Object.op_Implicit((Object)(object)pieceTable) && pieceTable.m_pieces != null && !PieceTableOrderBaselines.ContainsKey(pieceTable)) { PieceTableCategoryGuard.Normalize(pieceTable); PieceTableOrderBaselines[pieceTable] = pieceTable.m_pieces.Where((GameObject piece) => (Object)(object)piece != (Object)null).ToList(); } } private static void ApplyPieceTableStructure(IReadOnlyDictionary pieceTableAssignments, IReadOnlyDictionary sortOrders, IReadOnlyCollection removedPieces) { if (!PieceTablesReady || (pieceTableAssignments.Count <= 0 && removedPieces.Count <= 0 && sortOrders.Count <= 0 && !PieceTableMembershipWasApplied && !PieceTableSortWasApplied)) { return; } HashSet affectedPieceTables = GetAffectedPieceTables(pieceTableAssignments, sortOrders, removedPieces, PieceTableMembershipWasApplied || PieceTableSortWasApplied); if (affectedPieceTables.Count == 0) { return; } CapturePieceTableOrderBaselinesIfNeeded(affectedPieceTables); RestorePieceTableMemberships(affectedPieceTables); if (pieceTableAssignments.Count > 0) { ApplyPieceTableAssignments(pieceTableAssignments, affectedPieceTables); } if (removedPieces.Count > 0) { ApplyPieceTableRemovals(removedPieces, affectedPieceTables); } if (sortOrders.Count == 0) { RefreshLocalBuildPieces(); return; } foreach (PieceTable item in affectedPieceTables) { ApplyPieceTableSortOrder(item, sortOrders); } RefreshLocalBuildPieces(); } private static HashSet GetAffectedPieceTables(IReadOnlyDictionary pieceTableAssignments, IReadOnlyDictionary sortOrders, IReadOnlyCollection removedPieces, bool includePreviouslyTouchedTables) { HashSet hashSet = new HashSet(ReferenceComparer.Instance); if (includePreviouslyTouchedTables) { foreach (PieceTable key in PieceTableOrderBaselines.Keys) { AddPieceTableIfValid(hashSet, key); } } foreach (KeyValuePair pieceTableAssignment in pieceTableAssignments) { GameObject val = ResolvePiecePrefab(pieceTableAssignment.Key); if ((Object)(object)val != (Object)null) { foreach (PieceTable item in GetPieceTablesContaining(val)) { AddPieceTableIfValid(hashSet, item); } } PieceTable pieceTable = ResolvePieceTable(pieceTableAssignment.Value.PieceTable); AddPieceTableIfValid(hashSet, pieceTable); } foreach (string item2 in removedPieces.Concat(sortOrders.Keys)) { GameObject val2 = ResolvePiecePrefab(item2); if ((Object)(object)val2 == (Object)null) { continue; } foreach (PieceTable item3 in GetPieceTablesContaining(val2)) { AddPieceTableIfValid(hashSet, item3); } } return hashSet; } private static void AddPieceTableIfValid(HashSet pieceTables, PieceTable? pieceTable) { if ((Object)(object)pieceTable != (Object)null && !IsIgnoredPieceTableName(((Object)pieceTable).name)) { pieceTables.Add(pieceTable); } } private static void ApplyPieceTableRemovals(IReadOnlyCollection removedPieces, IReadOnlyCollection affectedTables) { foreach (string removedPiece in removedPieces) { foreach (PieceTable affectedTable in affectedTables) { if (Object.op_Implicit((Object)(object)affectedTable) && affectedTable.m_pieces != null) { RemovePieceByPrefabName(affectedTable, removedPiece); PieceTableCategoryGuard.Normalize(affectedTable); } } } } private static void RestorePieceTableMemberships(IReadOnlyCollection affectedTables) { HashSet capturedBaselinePieces = GetCapturedBaselinePieces(affectedTables); foreach (PieceTable affectedTable in affectedTables) { if (!Object.op_Implicit((Object)(object)affectedTable) || affectedTable.m_pieces == null) { continue; } if (!PieceTableOrderBaselines.TryGetValue(affectedTable, out List value)) { CapturePieceTableOrderBaseline(affectedTable); PieceTableOrderBaselines.TryGetValue(affectedTable, out value); } if (value == null) { PieceTableCategoryGuard.Normalize(affectedTable); continue; } List pieces = new List(); HashSet seen = new HashSet(ReferenceComparer.Instance); foreach (GameObject item in value) { AddPieceIfValid(pieces, seen, item); } foreach (GameObject piece in affectedTable.m_pieces) { if (!((Object)(object)piece == (Object)null) && !capturedBaselinePieces.Contains(piece)) { AddPieceIfValid(pieces, seen, piece); } } affectedTable.m_pieces = pieces; PieceTableCategoryGuard.Normalize(affectedTable); } } private static void ApplyPieceTableAssignments(IReadOnlyDictionary assignments, IReadOnlyCollection affectedTables) { foreach (KeyValuePair item in assignments.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.OrdinalIgnoreCase)) { using (DataForgeLogContext.Push(item.Value.LogContext)) { GameObject val = ResolvePiecePrefab(item.Key); if ((Object)(object)val == (Object)null) { DataForgeLogContext.Warning("Could not move piece '" + item.Key + "': piece prefab was not found."); continue; } PieceTable val2 = ResolvePieceTable(item.Value.PieceTable); if ((Object)(object)val2 == (Object)null) { DataForgeLogContext.Warning("Could not move piece '" + item.Key + "': pieceTable '" + item.Value.PieceTable + "' was not found."); } else { MovePiecePrefabToTable(val, val2, affectedTables); } } } } private static void MovePiecePrefabToTable(GameObject prefab, PieceTable target, IReadOnlyCollection affectedTables) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) string prefabName = GetPrefabName(prefab); foreach (PieceTable affectedTable in affectedTables) { if (Object.op_Implicit((Object)(object)affectedTable) && affectedTable.m_pieces != null && affectedTable != target) { RemovePieceByPrefabName(affectedTable, prefabName); PieceTableCategoryGuard.Normalize(affectedTable); } } if (target.m_pieces == null) { target.m_pieces = new List(); } if (!ContainsPieceByPrefabName(target, prefabName)) { target.m_pieces.Add(prefab); } Piece component = prefab.GetComponent(); if ((Object)(object)component != (Object)null) { EnsurePieceTableCategory(target, component.m_category); } PieceTableCategoryGuard.Normalize(target); } private static void RemovePieceByPrefabName(PieceTable pieceTable, string prefabName) { if (pieceTable.m_pieces == null) { return; } for (int num = pieceTable.m_pieces.Count - 1; num >= 0; num--) { GameObject val = pieceTable.m_pieces[num]; if ((Object)(object)val == (Object)null || GetPrefabName(val).Equals(prefabName, StringComparison.OrdinalIgnoreCase)) { pieceTable.m_pieces.RemoveAt(num); } } } private static bool ContainsPieceByPrefabName(PieceTable pieceTable, string prefabName) { if (pieceTable.m_pieces != null) { return pieceTable.m_pieces.Any((GameObject piece) => (Object)(object)piece != (Object)null && GetPrefabName(piece).Equals(prefabName, StringComparison.OrdinalIgnoreCase)); } return false; } private static HashSet GetCapturedBaselinePieces(IEnumerable affectedTables) { HashSet hashSet = new HashSet(ReferenceComparer.Instance); foreach (PieceTable affectedTable in affectedTables) { if (!PieceTableOrderBaselines.TryGetValue(affectedTable, out List value)) { continue; } foreach (GameObject item in value) { if ((Object)(object)item != (Object)null) { hashSet.Add(item); } } } return hashSet; } private static void AddPieceIfValid(List pieces, HashSet seen, GameObject? piece) { if ((Object)(object)piece != (Object)null && seen.Add(piece)) { pieces.Add(piece); } } private static void ApplyPieceTableSortOrder(PieceTable pieceTable, IReadOnlyDictionary sortOrders) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)pieceTable) || pieceTable.m_pieces == null) { return; } List list = (from item in GetPieceTableBaselineOrderedPieces(pieceTable).Select((GameObject piece, int index) => PieceOrderItem.From(piece, index, sortOrders)) where (Object)(object)item.Prefab != (Object)null select item).ToList(); if (list.Count == 0) { return; } Dictionary> dictionary = (from item in list group item by item.Category).ToDictionary((IGrouping group) => group.Key, (IGrouping group) => new Queue(from item in @group orderby item.SortOrder, item.OriginalIndex select item)); List list2 = new List(list.Count); foreach (PieceOrderItem item in list) { list2.Add(dictionary[item.Category].Dequeue().Prefab); } pieceTable.m_pieces = list2; PieceTableCategoryGuard.Normalize(pieceTable); } private static List GetPieceTableBaselineOrderedPieces(PieceTable pieceTable) { CapturePieceTableOrderBaseline(pieceTable); if (pieceTable.m_pieces == null) { return new List(); } List list = pieceTable.m_pieces.Where((GameObject piece) => (Object)(object)piece != (Object)null).ToList(); if (!PieceTableOrderBaselines.TryGetValue(pieceTable, out List value)) { return list; } Dictionary baselineIndex = new Dictionary(ReferenceComparer.Instance); for (int num = 0; num < value.Count; num++) { GameObject val = value[num]; if ((Object)(object)val != (Object)null && !baselineIndex.ContainsKey(val)) { baselineIndex[val] = num; } } int value2; return (from item in list.Select((GameObject piece, int index) => new { Piece = piece, CurrentIndex = index, HasBaselineIndex = baselineIndex.TryGetValue(piece, out value2), BaselineIndex = value2 }) orderby (!item.HasBaselineIndex) ? 1 : 0, (!item.HasBaselineIndex) ? item.CurrentIndex : item.BaselineIndex select item.Piece).ToList(); } private static string? FormatReferenceCategory(string prefabName, string? fallback) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) GameObject val = ResolvePiecePrefab(prefabName); Piece val2 = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)val2 != (Object)null) { return NullIfIgnoredCategory(FormatPieceCategory(val2)); } string text = fallback?.Trim() ?? ""; if (text.Length > 0 && TryResolvePieceCategory(text, out var category)) { return NullIfIgnoredCategory(FormatPieceCategory(category, null, text)); } return NullIfIgnoredCategory(fallback); } private static string? NullIfIgnoredCategory(string? categoryName) { if (!IsIgnoredCategoryName(categoryName)) { return categoryName; } return null; } private static bool IsIgnoredCategoryName(string? categoryName) { string text = categoryName?.Trim() ?? ""; if (text.Length > 0) { return IgnoredCategoryNames.Contains(text.TrimStart(new char[1] { '$' })); } return false; } private static bool IsIgnoredPieceTableName(string? pieceTableName) { string text = pieceTableName?.Trim() ?? ""; if (text.Length == 0) { return false; } if (!IgnoredPieceTableNames.Contains(text)) { return IgnoredPieceTableNames.Contains(NormalizePieceTableIdentifier(text)); } return true; } private static string FormatPieceCategory(Piece piece) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return FormatPieceCategory(piece.m_category, ((Component)piece).gameObject, ((object)Unsafe.As(ref piece.m_category)/*cast due to .constrained prefix*/).ToString()); } private unsafe static string FormatPieceCategory(PieceCategory category, GameObject? piecePrefab, string fallback) { //IL_002a: 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) if (!int.TryParse(((object)(*(PieceCategory*)(&category))/*cast due to .constrained prefix*/).ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var _)) { return ((object)(*(PieceCategory*)(&category))/*cast due to .constrained prefix*/).ToString(); } if (TryGetCategoryDisplayName(category, piecePrefab, out string displayName) && !IsNumericCategoryName(displayName)) { return displayName; } if (TryGetJotunnPieceCategoryDisplayName(category, out displayName)) { return displayName; } return fallback; } private static bool TryResolvePieceCategory(string categoryName, out PieceCategory category) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected I4, but got Unknown if (Enum.TryParse(categoryName, ignoreCase: true, out category)) { return true; } if (TryResolveJotunnPieceCategory(categoryName, out category)) { return true; } string input = categoryName.Trim(); foreach (PieceTable allPieceTable in GetAllPieceTables()) { List list = (Object.op_Implicit((Object)(object)allPieceTable) ? allPieceTable.m_categories : null); List list2 = (Object.op_Implicit((Object)(object)allPieceTable) ? allPieceTable.m_categoryLabels : null); if (list == null || list2 == null) { continue; } int num = Math.Min(list.Count, list2.Count); for (int i = 0; i < num; i++) { if (CategoryLabelMatches(input, list2[i])) { category = (PieceCategory)(int)list[i]; return true; } } } return false; } private static bool TryGetJotunnPieceCategoryDisplayName(PieceCategory category, out string displayName) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) EnsureJotunnPieceCategoryMap(); if (JotunnPieceCategoryNames != null && JotunnPieceCategoryNames.TryGetValue(category, out string value) && !string.IsNullOrWhiteSpace(value) && !IsNumericCategoryName(value)) { displayName = value; return true; } displayName = ""; return false; } private static bool TryResolveJotunnPieceCategory(string categoryName, out PieceCategory category) { EnsureJotunnPieceCategoryMap(); string key = categoryName.Trim(); if (JotunnPieceCategoryValues != null && JotunnPieceCategoryValues.TryGetValue(key, out category)) { return true; } category = (PieceCategory)0; return false; } private static void EnsureJotunnPieceCategoryMap() { //IL_00d8: 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_00fa: 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) if (JotunnPieceCategoryMapLoaded) { return; } JotunnPieceCategoryMapLoaded = true; JotunnPieceCategoryNames = new Dictionary(); JotunnPieceCategoryValues = new Dictionary(StringComparer.OrdinalIgnoreCase); Type? type = FindLoadedType("Jotunn.Managers.PieceManager"); object obj = type?.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (!(type?.GetMethod("GetPieceCategoriesMap", Type.EmptyTypes)?.Invoke(obj, null) is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { Type type2 = item.GetType(); object obj2 = type2.GetProperty("Key")?.GetValue(item); object obj3 = type2.GetProperty("Value")?.GetValue(item); if (obj2 is PieceCategory val && obj3 is string text && !string.IsNullOrWhiteSpace(text)) { JotunnPieceCategoryNames[val] = text; JotunnPieceCategoryValues[text] = val; } } } private static Type? FindLoadedType(string fullName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(fullName, throwOnError: false); if (type != null) { return type; } } return null; } private static bool IsNumericCategoryName(string? categoryName) { int result; return int.TryParse(categoryName?.Trim() ?? "", NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } private static bool TryGetCategoryDisplayName(PieceCategory category, GameObject? piecePrefab, out string displayName) { //IL_0053: 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) if ((Object)(object)piecePrefab != (Object)null) { foreach (PieceTable item in GetPieceTablesContaining(piecePrefab)) { if (TryGetCategoryDisplayNameFromTable(item, category, out displayName)) { return true; } } } foreach (PieceTable allPieceTable in GetAllPieceTables()) { if (TryGetCategoryDisplayNameFromTable(allPieceTable, category, out displayName)) { return true; } } displayName = ""; return false; } private static bool TryGetCategoryDisplayNameFromTable(PieceTable pieceTable, PieceCategory category, out string displayName) { //IL_004b: 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) displayName = ""; List list = (Object.op_Implicit((Object)(object)pieceTable) ? pieceTable.m_categories : null); List list2 = (Object.op_Implicit((Object)(object)pieceTable) ? pieceTable.m_categoryLabels : null); if (list == null || list2 == null) { return false; } int num = Math.Min(list.Count, list2.Count); for (int i = 0; i < num; i++) { if (list[i] == category && TryFormatCategoryLabel(list2[i], out displayName)) { return true; } } return false; } private static bool CategoryLabelMatches(string input, string? label) { string text = label?.Trim() ?? ""; if (text.Length == 0) { return false; } if (input.Equals(text, StringComparison.OrdinalIgnoreCase) || input.Equals(text.TrimStart(new char[1] { '$' }), StringComparison.OrdinalIgnoreCase)) { return true; } if (TryFormatCategoryLabel(text, out string displayName)) { return input.Equals(displayName, StringComparison.OrdinalIgnoreCase); } return false; } private static bool TryFormatCategoryLabel(string? label, out string displayName) { displayName = ""; string text = label?.Trim() ?? ""; if (text.Length == 0) { return false; } if (!text.StartsWith("$", StringComparison.Ordinal)) { displayName = text; return true; } string text2 = ((Localization.instance != null) ? Localization.instance.Localize(text).Trim() : ""); if (text2.Length > 0 && !text2.Equals(text, StringComparison.OrdinalIgnoreCase)) { displayName = text2; return true; } return TryFormatKnownCategoryToken(text, out displayName); } private static bool TryFormatKnownCategoryToken(string token, out string displayName) { displayName = ""; string text = token.TrimStart(new char[1] { '$' }); string[] array = new string[2] { "piecemanager_cat_", "jotunn_cat_" }; foreach (string text2 in array) { if (text.StartsWith(text2, StringComparison.OrdinalIgnoreCase)) { string value = text.Substring(text2.Length); displayName = ToDisplayName(value); return displayName.Length > 0; } } return false; } private static string ToDisplayName(string value) { string[] source = (from part in value.Split(new char[3] { '_', '-', ' ' }, StringSplitOptions.RemoveEmptyEntries) select part.Trim() into part where part.Length > 0 select part).ToArray(); return string.Join(" ", source.Select(Capitalize)); } private static string Capitalize(string value) { if (value.Length == 0) { return value; } if (value.Length != 1) { return char.ToUpperInvariant(value[0]) + value.Substring(1); } return value.ToUpperInvariant(); } private static GameObject? ResolvePiecePrefab(string prefabName) { string text = NormalizePrefabName(prefabName); if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(text); if ((Object)(object)prefab != (Object)null && IsManagedPiece(prefab)) { return prefab; } } foreach (var (text2, val) in GetPrefabPieces()) { if (text2.Equals(text, StringComparison.OrdinalIgnoreCase)) { return ((Component)val).gameObject; } } return null; } private static string? GetReferencePieceTableName(string prefabName) { return BuildPieceTableMembershipSnapshot().GetTableNames(prefabName).FirstOrDefault((string name) => !IsDefaultReferencePieceTableName(name) && !IsIgnoredPieceTableName(name)); } private static string? GetFullScaffoldPieceTableName(string prefabName) { return GetFullScaffoldPieceTableName(prefabName, BuildPieceTableMembershipSnapshot()); } private static bool ShouldGeneratePieceEntry(string prefabName) { return ShouldGeneratePieceEntry(prefabName, BuildPieceTableMembershipSnapshot()); } private static string? GetFullScaffoldPieceTableName(string prefabName, PieceTableMembershipSnapshot pieceTableMembership) { return (from name in pieceTableMembership.GetTableNames(prefabName) where !IsIgnoredPieceTableName(name) select name).FirstOrDefault(); } private static bool ShouldGeneratePieceEntry(string prefabName, PieceTableMembershipSnapshot pieceTableMembership) { return !pieceTableMembership.IsOnlyInIgnoredTables(prefabName); } private static bool IsOnlyInIgnoredPieceTables(string prefabName) { return BuildPieceTableMembershipSnapshot().IsOnlyInIgnoredTables(prefabName); } private static PieceTableMembershipSnapshot BuildPieceTableMembershipSnapshot() { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (PieceTable allPieceTable in GetAllPieceTables(includeIgnored: true)) { if (!Object.op_Implicit((Object)(object)allPieceTable) || allPieceTable.m_pieces == null) { continue; } string friendlyPieceTableName = GetFriendlyPieceTableName(allPieceTable); if (string.IsNullOrWhiteSpace(friendlyPieceTableName)) { continue; } foreach (GameObject piece in allPieceTable.m_pieces) { if (!((Object)(object)piece == (Object)null)) { string prefabName = GetPrefabName(piece); if (!dictionary.TryGetValue(prefabName, out var value)) { value = (dictionary[prefabName] = new List()); } if (!value.Contains(friendlyPieceTableName, StringComparer.OrdinalIgnoreCase)) { value.Add(friendlyPieceTableName); } } } } return new PieceTableMembershipSnapshot(dictionary); } private static string GetFriendlyPieceTableName(PieceTable pieceTable) { string pieceTableOwnerItemName = GetPieceTableOwnerItemName(pieceTable); if (!string.IsNullOrWhiteSpace(pieceTableOwnerItemName)) { return pieceTableOwnerItemName; } string text = NormalizePrefabName(((Object)pieceTable).name); foreach (KeyValuePair pieceTableAlias in PieceTableAliases) { if (pieceTableAlias.Value.Equals(text, StringComparison.OrdinalIgnoreCase)) { return pieceTableAlias.Key; } } foreach (var (text2, val) in GetNamedPieceTables()) { if (pieceTable == val && !string.IsNullOrWhiteSpace(text2)) { return NormalizePieceTableAlias(text2); } } return NormalizePieceTableAlias(text); } private static string? GetPieceTableOwnerItemName(PieceTable pieceTable) { foreach (var buildPieceOwnerTable in GetBuildPieceOwnerTables()) { var (text, _) = buildPieceOwnerTable; if (buildPieceOwnerTable.Table == pieceTable && text.Length > 0) { return text; } } return null; } private static string NormalizePieceTableAlias(string pieceTableName) { string text = NormalizePrefabName(pieceTableName); foreach (KeyValuePair pieceTableAlias in PieceTableAliases) { if (pieceTableAlias.Key.Equals(text, StringComparison.OrdinalIgnoreCase) || pieceTableAlias.Value.Equals(text, StringComparison.OrdinalIgnoreCase)) { return pieceTableAlias.Key; } } return text; } private static bool IsDefaultReferencePieceTableName(string pieceTableName) { return NormalizePieceTableIdentifier(pieceTableName).Equals("_HammerPieceTable", StringComparison.OrdinalIgnoreCase); } private static PieceTable? ResolvePieceTable(string pieceTableName) { if (string.IsNullOrWhiteSpace(pieceTableName)) { return null; } if (IsIgnoredPieceTableName(pieceTableName)) { return null; } string value = NormalizePieceTableIdentifier(pieceTableName); foreach (var (text, result) in GetNamedPieceTables()) { if (text.Equals(value, StringComparison.OrdinalIgnoreCase) || text.Equals(pieceTableName.Trim(), StringComparison.OrdinalIgnoreCase)) { return result; } } return null; } private static string NormalizePieceTableIdentifier(string pieceTableName) { string text = NormalizePrefabName(pieceTableName); if (!PieceTableAliases.TryGetValue(text, out string value)) { return text; } return value; } private static IEnumerable<(string Name, PieceTable Table)> GetNamedPieceTables() { HashSet seenAliases = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var (itemName, pieceTable) in GetBuildPieceOwnerTables()) { foreach (var item5 in GetPieceTableNamesForItem(itemName, pieceTable)) { string item = item5.Name; PieceTable item2 = item5.Table; string item3 = $"{RuntimeHelpers.GetHashCode(item2)}:{item}"; if (seenAliases.Add(item3)) { yield return (Name: item, Table: item2); } } } PieceTable[] array = Resources.FindObjectsOfTypeAll(); foreach (PieceTable val in array) { if ((Object)(object)val == (Object)null) { continue; } string text = NormalizePrefabName(((Object)val).name); if (text.Length != 0) { string item4 = $"{RuntimeHelpers.GetHashCode(val)}:{text}"; if (seenAliases.Add(item4)) { yield return (Name: text, Table: val); } } } } private static IEnumerable<(string Name, PieceTable Table)> GetBuildPieceOwnerTables() { HashSet seen = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (GameObject knownItemPrefab in GetKnownItemPrefabs()) { PieceTable val = knownItemPrefab.GetComponent()?.m_itemData?.m_shared?.m_buildPieces; string text = NormalizePrefabName(((Object)knownItemPrefab).name); if (!((Object)(object)val == (Object)null) && text.Length != 0) { string item = $"{RuntimeHelpers.GetHashCode(val)}:{text}"; if (seen.Add(item)) { yield return (Name: text, Table: val); } } } if (ItemDrop.s_instances == null) { yield break; } foreach (ItemDrop s_instance in ItemDrop.s_instances) { if ((Object)(object)s_instance == (Object)null) { continue; } PieceTable val2 = s_instance.m_itemData?.m_shared?.m_buildPieces; if ((Object)(object)val2 == (Object)null) { continue; } string text2 = (((Object)(object)s_instance.m_itemData?.m_dropPrefab != (Object)null) ? NormalizePrefabName(((Object)s_instance.m_itemData.m_dropPrefab).name) : GetPrefabName(((Component)s_instance).gameObject)); if (text2.Length != 0) { string item2 = $"{RuntimeHelpers.GetHashCode(val2)}:{text2}"; if (seen.Add(item2)) { yield return (Name: text2, Table: val2); } } } } private static IEnumerable GetKnownItemPrefabs() { HashSet seen = new HashSet(ReferenceComparer.Instance); if ((Object)(object)ObjectDB.instance != (Object)null) { foreach (GameObject item in ObjectDB.instance.m_items) { if ((Object)(object)item != (Object)null && (Object)(object)item.GetComponent() != (Object)null && seen.Add(item)) { yield return item; } } } if ((Object)(object)ZNetScene.instance == (Object)null) { yield break; } foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent() != (Object)null && seen.Add(prefab)) { yield return prefab; } } } private static IEnumerable<(string Name, PieceTable Table)> GetPieceTableNamesForItem(string itemName, PieceTable pieceTable) { if (itemName.Length > 0) { yield return (Name: itemName, Table: pieceTable); } string tableName = NormalizePrefabName(((Object)pieceTable).name); if (tableName.Length > 0) { yield return (Name: tableName, Table: pieceTable); } foreach (KeyValuePair alias in PieceTableAliases) { if (alias.Key.Equals(itemName, StringComparison.OrdinalIgnoreCase) || alias.Value.Equals(tableName, StringComparison.OrdinalIgnoreCase)) { yield return (Name: alias.Key, Table: pieceTable); yield return (Name: alias.Value, Table: pieceTable); } } } private static void RefreshLocalBuildPieces() { if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Player.m_localPlayer.m_buildPieces == (Object)null)) { PieceTableCategoryGuard.Normalize(Player.m_localPlayer.m_buildPieces); Player.m_localPlayer.UpdateAvailablePiecesList(); } } private static void ApplyHealth(WearNTear wearNTear, float maxHealth, bool adjustHealthZdo) { float health = wearNTear.m_health; wearNTear.m_health = maxHealth; if (adjustHealthZdo && !(health <= 0f)) { ZNetView component = ((Component)wearNTear).GetComponent(); if (!((Object)(object)component == (Object)null) && component.GetZDO() != null && component.IsOwner()) { ZDO zDO = component.GetZDO(); float num = Mathf.Clamp01(zDO.GetFloat(ZDOVars.s_health, health) / health); zDO.Set(ZDOVars.s_health, Mathf.Clamp(maxHealth * num, 0f, maxHealth)); } } } private static void WriteGeneratedArtifacts() { if (DataForgePlugin.UsesLocalAuthorityFiles) { WriteReferenceArtifact(); } } internal static bool TryWriteFullScaffoldConfigurationFile(out string path, out string error) { path = Path.Combine(ConfigDirectory, "pieces.full.yml"); return GeneratedArtifactWriter.TryWriteFullScaffoldIfReady(path, "pieces", CanBuildGeneratedArtifacts(), "pieces game data is not ready yet.", delegate { EnsureConfigDirectoryAndDefaultOverride(); CaptureAllBaselinesIfNeeded(); PieceTableMembershipSnapshot pieceTableMembership = BuildPieceTableMembershipSnapshot(); var entries = (from pair in Baselines where ShouldGeneratePieceEntry(pair.Key, pieceTableMembership) select new { Entry = PieceEntry.FromDefinition(pair.Key, pair.Value, GetFullScaffoldPieceTableName(pair.Key, pieceTableMembership)), OwnerKey = pair.Key, SortKey = DataForgeResourceMap.BuildSortKey(GetPieceGroupSortRank(pair.Value), DataForgeResourceMap.GetResourceTierSortValue(pair.Value.Piece?.Resources?.SelectMany((PieceResourceDefinition resource) => resource.Keys) ?? Array.Empty()), pair.Key) }).OrderBy(pair => pair.SortKey, StringComparer.OrdinalIgnoreCase).ToList(); return GeneratedArtifactWriter.GeneratedHeader("pieces", "pieces.yml", "full scaffold") + DataForgeReferenceSections.SerializeReferenceSections(entries, entry => entry.SortKey, entry => DataForgeOwnerResolver.GetPrefabOwnerName(entry.OwnerKey), entry => entry.Entry, FullSerializer); }, out error); } private static void WriteReferenceArtifact() { if (CanBuildGeneratedArtifacts()) { EnsureConfigDirectoryAndDefaultOverride(); CaptureAllBaselinesIfNeeded(); string sourceSignature = ComputeReferenceSourceSignature(); string text = Path.Combine(ConfigDirectory, "pieces.reference.yml"); if (!ShouldSkipReferenceUpdate(text, sourceSignature) && (GeneratedArtifactWriter.WriteReferenceIfReady(Baselines.Count > 0, ConfigDirectory, "pieces.reference.yml", "pieces", "pieces.yml", BuildReferenceArtifactContent) || File.Exists(text))) { RecordReferenceUpdateState(text, sourceSignature); } } } private static string BuildReferenceArtifactContent() { PieceTableMembershipSnapshot pieceTableMembership = BuildPieceTableMembershipSnapshot(); return DataForgeReferenceSections.SerializeReferenceSections((from pair in Baselines where ShouldGeneratePieceEntry(pair.Key, pieceTableMembership) select new { Entry = PieceReferenceEntry.From(pair.Key, pair.Value), SortKey = DataForgeResourceMap.BuildSortKey(GetPieceGroupSortRank(pair.Value), DataForgeResourceMap.GetResourceTierSortValue(pair.Value.Piece?.Resources?.SelectMany((PieceResourceDefinition resource) => resource.Keys) ?? Array.Empty()), pair.Key) }).ToList(), entry => entry.SortKey, entry => DataForgeOwnerResolver.GetPrefabOwnerName(entry.Entry.Piece), entry => entry.Entry, SparseSerializer); } private static bool CanBuildGeneratedArtifacts() { if (ObjectDbReady && (Object)(object)ZNetScene.instance != (Object)null) { return (Object)(object)ObjectDB.instance != (Object)null; } return false; } private static string ComputeReferenceSourceSignature() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("2026-06-24-piece-reference-state-v2"); stringBuilder.AppendLine(BuildFileStamp(Path.Combine(ConfigDirectory, "z_resourcemap.txt"))); foreach (var item2 in GetPrefabPieces().OrderBy<(string, Piece), string>(((string PrefabName, Piece Piece) pair) => pair.PrefabName, StringComparer.OrdinalIgnoreCase)) { string item = item2.Item1; stringBuilder.AppendLine(item); } return ComputeStableHash(stringBuilder.ToString()); } private static bool ShouldSkipReferenceUpdate(string referencePath, string sourceSignature) { return DataForgeReferenceState.ShouldSkip("pieces", referencePath, sourceSignature, "2026-06-24-piece-reference-state-v2"); } private static void RecordReferenceUpdateState(string referencePath, string sourceSignature) { DataForgeReferenceState.Record("pieces", referencePath, sourceSignature, "2026-06-24-piece-reference-state-v2"); } private static string BuildFileStamp(string path) { return DataForgeReferenceState.BuildFileStamp(path); } private static string ComputeStableHash(string value) { return DataForgeReferenceState.ComputeStableHash(value); } private static int GetPieceGroupSortRank(PieceDefinition definition) { if (definition.CraftingStation != null) { return 0; } if (!string.IsNullOrWhiteSpace(definition.StationExtension)) { return 1; } if (definition.Smelter != null || definition.CookingStation != null || definition.Fermenter != null || !string.IsNullOrWhiteSpace(definition.Beehive) || !string.IsNullOrWhiteSpace(definition.SapCollector)) { return 2; } if (!string.IsNullOrWhiteSpace(definition.Container)) { return 3; } if (HasComfort(definition.Piece?.Comfort)) { return 4; } PieceComponentDefinition? piece = definition.Piece; if ((piece != null && piece.Health.HasValue) || definition.Piece?.Resources != null) { return 5; } return 6; } private static bool HasComfort(string? comfort) { string[] array = DataForgeValue.SplitTuple(comfort); if (array.Length != 0 && int.TryParse(array[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result > 0; } return false; } private static string GetPrefabName(GameObject gameObject) { return NormalizePrefabName(((Object)gameObject).name); } private static string NormalizePrefabName(string prefabName) { return prefabName.Replace("(Clone)", "").Trim(); } private static string? FormatSapCollector(SapCollector? sapCollector) { if (!((Object)(object)sapCollector == (Object)null)) { return FormatTuple(GetItemName(sapCollector.m_spawnItem), sapCollector.m_secPerUnit, sapCollector.m_maxLevel); } return null; } private static string? FormatBeehive(Beehive? beehive) { if (!((Object)(object)beehive == (Object)null)) { return FormatTuple(beehive.m_secPerUnit, beehive.m_maxHoney); } return null; } private static string? FormatContainer(Container? container) { if (!((Object)(object)container == (Object)null)) { return FormatTuple(container.m_width, container.m_height); } return null; } private static string? FormatStationExtension(StationExtension[] extensions) { if (extensions == null || extensions.Length == 0) { return null; } StationExtension val = ((IEnumerable)extensions).FirstOrDefault((Func)((StationExtension extension) => (Object)(object)extension != (Object)null)); if (!((Object)(object)val == (Object)null)) { return FormatTuple(((Object)(object)val.m_craftingStation != (Object)null) ? GetPrefabName(((Component)val.m_craftingStation).gameObject) : "None", val.m_maxStationDistance); } return null; } private static string FormatCraftingStation(CraftingStation? craftingStation) { if (!((Object)(object)craftingStation != (Object)null)) { return "None"; } return GetPrefabName(((Component)craftingStation).gameObject); } private static string GetItemName(ItemDrop? itemDrop) { if (!((Object)(object)itemDrop != (Object)null)) { return "None"; } return GetPrefabName(((Component)itemDrop).gameObject); } private static string FormatTuple(params object[] values) { return string.Join(", ", values.Select(FormatTupleValue)); } private static string FormatTupleValue(object value) { if (!(value is float num)) { if (!(value is double num2)) { if (!(value is int num3)) { if (!(value is bool flag)) { if (value is IFormattable formattable) { return formattable.ToString(null, CultureInfo.InvariantCulture); } return value.ToString() ?? ""; } return flag.ToString().ToLowerInvariant(); } return num3.ToString(CultureInfo.InvariantCulture); } return num2.ToString("0.###", CultureInfo.InvariantCulture); } return num.ToString("0.###", CultureInfo.InvariantCulture); } } } 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; } } }