Decompiled source of DataForge v1.3.5

DataForge.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Timers;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using ModAssetOwnership;
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.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("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.3.5")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.5.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;
		}
	}
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace ModAssetOwnership
{
	internal sealed class AssetOwner
	{
		internal readonly string Guid;

		internal readonly string Name;

		internal readonly string AssemblyName;

		internal readonly string[] Resources;

		internal AssetOwner(string guid, string name, string assemblyName, string[] resources)
		{
			Guid = guid;
			Name = (string.IsNullOrWhiteSpace(name) ? guid : name);
			AssemblyName = assemblyName;
			Resources = resources;
		}
	}
	internal static class AssetOwnerMatching
	{
		internal static AssetOwner? Resolve(string bundleName, IReadOnlyList<AssetOwner> plugins)
		{
			if (string.IsNullOrWhiteSpace(bundleName))
			{
				return null;
			}
			AssetOwner assetOwner = null;
			bool flag = false;
			foreach (AssetOwner plugin in plugins)
			{
				if (string.IsNullOrWhiteSpace(plugin.Guid))
				{
					continue;
				}
				bool flag2 = false;
				string[] resources = plugin.Resources;
				foreach (string text in resources)
				{
					if (text.Equals(bundleName, StringComparison.OrdinalIgnoreCase) || text.EndsWith("." + bundleName, StringComparison.OrdinalIgnoreCase))
					{
						flag2 = true;
						break;
					}
				}
				if (flag2)
				{
					flag = true;
					if (assetOwner != null && !assetOwner.Guid.Equals(plugin.Guid, StringComparison.OrdinalIgnoreCase))
					{
						return null;
					}
					assetOwner = plugin;
				}
			}
			if (flag)
			{
				return assetOwner;
			}
			string text2 = Normalize(Path.GetFileNameWithoutExtension(bundleName));
			if (text2.Length == 0)
			{
				return null;
			}
			foreach (AssetOwner plugin2 in plugins)
			{
				if (!string.IsNullOrWhiteSpace(plugin2.Guid) && (!(text2 != Normalize(plugin2.Name)) || !(text2 != Normalize(plugin2.Guid)) || !(text2 != Normalize(plugin2.AssemblyName))))
				{
					if (assetOwner != null && !assetOwner.Guid.Equals(plugin2.Guid, StringComparison.OrdinalIgnoreCase))
					{
						return null;
					}
					assetOwner = plugin2;
				}
			}
			return assetOwner;
		}

		internal static void Add(Dictionary<string, AssetOwner> owners, HashSet<string> ambiguous, string name, AssetOwner owner)
		{
			if (!ambiguous.Contains(name))
			{
				if (owners.TryGetValue(name, out AssetOwner value) && !value.Guid.Equals(owner.Guid, StringComparison.OrdinalIgnoreCase))
				{
					owners.Remove(name);
					ambiguous.Add(name);
				}
				else
				{
					owners[name] = owner;
				}
			}
		}

		private static string Normalize(string value)
		{
			StringBuilder stringBuilder = new StringBuilder();
			string text = value ?? "";
			foreach (char c in text)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}
	}
}
namespace DataForge
{
	internal static class LocalizationOverrideManager
	{
		private sealed class TranslationLease
		{
			internal bool OriginalExisted { get; }

			internal string? OriginalValue { get; }

			internal string LastAppliedValue { get; set; } = "";

			internal TranslationLease(bool originalExisted, string? originalValue)
			{
				OriginalExisted = originalExisted;
				OriginalValue = originalValue;
			}
		}

		internal sealed class LocalizationPayload
		{
			public int Version { get; set; }

			public Dictionary<string, Dictionary<string, string>>? Languages { get; set; }
		}

		private static readonly FieldInfo LocalizationInstanceField = AccessTools.Field(typeof(Localization), "m_instance");

		private static readonly FieldRef<Localization, Dictionary<string, string>> Translations = AccessTools.FieldRefAccess<Localization, Dictionary<string, string>>("m_translations");

		private static readonly FieldRef<Localization, LRUCache<string>> TranslationCache = AccessTools.FieldRefAccess<Localization, LRUCache<string>>("m_cache");

		private const string DomainName = "localization";

		private const string DefaultLanguageFileName = "English.yml";

		private const string KoreanLanguageFileName = "Korean.yml";

		private const string SyncedPayloadKey = "localization";

		private const long ReloadDelayTicks = 10000000L;

		private const int PayloadVersion = 1;

		private const int MaxPayloadBytes = 2097152;

		private const int MaxLanguageCount = 32;

		private const int MaxLanguageNameLength = 64;

		private const int MaxTokensPerLanguage = 8192;

		private const int MaxTokenLength = 128;

		private const int MaxTextLength = 4096;

		private static readonly (string Token, string Text)[] BuiltInEnglishTranslations = new(string, string)[6]
		{
			("$df_se_tooltip_attack_damage", "{0} attack damage: <color=orange>x{1}%</color>"),
			("$df_se_tooltip_raise_skill", "{0} skill XP: <color=orange>{1}</color>"),
			("$df_se_tooltip_max_health", "Max health: <color=orange>{0}</color>"),
			("$df_se_tooltip_max_stamina", "Max stamina: <color=orange>{0}</color>"),
			("$df_se_tooltip_max_eitr", "Max eitr: <color=orange>{0}</color>"),
			("$df_skill_all", "All")
		};

		private static readonly (string Token, string Text)[] BuiltInKoreanTranslations = new(string, string)[6]
		{
			("$df_se_tooltip_attack_damage", "{0} 공격 피해: <color=orange>x{1}%</color>"),
			("$df_se_tooltip_raise_skill", "{0} 기술 경험치: <color=orange>{1}</color>"),
			("$df_se_tooltip_max_health", "최대 체력: <color=orange>{0}</color>"),
			("$df_se_tooltip_max_stamina", "최대 스태미나: <color=orange>{0}</color>"),
			("$df_se_tooltip_max_eitr", "최대 에이트르: <color=orange>{0}</color>"),
			("$df_skill_all", "전체")
		};

		private static readonly object StateLock = new object();

		private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).WithDuplicateKeyChecking().Build();

		private static readonly ISerializer Serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).DisableAliases().Build();

		private static LocalizationPayload ActivePayload = CreateEmptyPayload();

		private static ConfigSync? ConfigSyncInstance;

		private static CustomSyncedValue<string>? SyncedPayload;

		private static FileSystemWatcher? Watcher;

		private static DataForgeFileWatcher.DebouncedAction? ReloadDebouncer;

		private static string? LastParsedPayload;

		private static bool LocalFileModeReady;

		private static readonly Dictionary<string, TranslationLease> AppliedTranslations = new Dictionary<string, TranslationLease>(StringComparer.Ordinal);

		private static Localization? AppliedLocalization;

		private static string AppliedLanguage = "";

		private static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "DataForge");

		private static string LocalizationDirectory => Path.Combine(ConfigDirectory, "localization");

		private static bool HasLocalAuthority => ConfigSyncInstance?.IsSourceOfTruth ?? false;

		private static Localization LocalizationInstance()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			return (Localization)LocalizationInstanceField.GetValue(null);
		}

		internal static void Initialize(ConfigSync configSync)
		{
			ConfigSyncInstance = configSync;
			SyncedPayload = new CustomSyncedValue<string>(configSync, "localization", "", 100);
			SyncedPayload.ValueChanged += OnSyncedPayloadChanged;
			configSync.SourceOfTruthChanged += OnSourceOfTruthChanged;
			EnsureSourceOfTruthFileMode();
		}

		internal static void Dispose()
		{
			RestoreAppliedTranslations();
			if (SyncedPayload != null)
			{
				SyncedPayload.ValueChanged -= OnSyncedPayloadChanged;
				SyncedPayload = null;
			}
			if (ConfigSyncInstance != null)
			{
				ConfigSyncInstance.SourceOfTruthChanged -= OnSourceOfTruthChanged;
				ConfigSyncInstance = null;
			}
			Watcher?.Dispose();
			Watcher = null;
			ReloadDebouncer?.Dispose();
			ReloadDebouncer = null;
			LocalFileModeReady = false;
			lock (StateLock)
			{
				ActivePayload = CreateEmptyPayload();
				LastParsedPayload = null;
			}
		}

		internal static bool EnsureSourceOfTruthFileMode()
		{
			if (!HasLocalAuthority)
			{
				return false;
			}
			if (LocalFileModeReady)
			{
				return true;
			}
			LocalFileModeReady = true;
			try
			{
				SetupFileWatcher();
				DataForgeFileWatcher.CancelPendingRecreate("localization");
				if (!ReloadFromDiskAndSync())
				{
					NotifyLocalizationChanged();
					ReloadDebouncer?.Schedule();
				}
				return true;
			}
			catch (Exception arg)
			{
				LocalFileModeReady = false;
				Watcher?.Dispose();
				Watcher = null;
				ReloadDebouncer?.Dispose();
				ReloadDebouncer = null;
				DataForgePlugin.Log.LogError((object)$"Failed to initialize server localization files: {arg}");
				return false;
			}
		}

		private static void SetupFileWatcher()
		{
			Watcher?.Dispose();
			Watcher = null;
			ReloadDebouncer?.Dispose();
			ReloadDebouncer = null;
			if (HasLocalAuthority)
			{
				EnsureConfigDirectoryAndDefaultOverride();
				ReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadYamlValues);
				Watcher = DataForgeFileWatcher.Create(LocalizationDirectory, "*.*", includeSubdirectories: false, ReadYamlValues, OnWatcherError);
			}
		}

		private static bool ReloadFromDiskAndSync()
		{
			if (!HasLocalAuthority)
			{
				return false;
			}
			EnsureConfigDirectoryAndDefaultOverride();
			if (!TryLoadPayloadFromDisk(out LocalizationPayload payload))
			{
				return false;
			}
			if (!TrySerializeAndVerifyPayload(payload, out string serialized, out LocalizationPayload verified))
			{
				return false;
			}
			lock (StateLock)
			{
				ActivePayload = verified;
				LastParsedPayload = serialized;
			}
			PublishPayload(serialized);
			ApplyCurrentLocalization();
			NotifyLocalizationChanged();
			return true;
		}

		internal static void ApplyCurrentLocalization()
		{
			Localization val = LocalizationInstance();
			if (val != null)
			{
				ApplyCurrentLocalization(val, val.GetSelectedLanguage());
			}
		}

		internal static void ApplyCurrentLocalization(Localization localization, string? language)
		{
			if (!IsLiveLocalization(localization))
			{
				return;
			}
			string text = NormalizeLanguage(language);
			bool flag = AppliedLocalization != localization || !AppliedLanguage.Equals(text, StringComparison.OrdinalIgnoreCase);
			Dictionary<string, string> dictionary;
			lock (StateLock)
			{
				dictionary = BuildTranslationsForLanguage(ActivePayload, text);
			}
			if (!flag && DataForgeApi.GetState(DataForgeDomain.Localization).IsReady && !NeedsTranslationApply(localization, dictionary))
			{
				return;
			}
			using DataForgeApplyScope dataForgeApplyScope = DataForgeApi.BeginApply(DataForgeDomain.Localization, HasLocalAuthority, from token in AppliedTranslations.Keys.Concat(dictionary.Keys)
				select "$" + token, flag, text);
			if (flag)
			{
				RestoreAppliedTranslations();
				AppliedLocalization = localization;
				AppliedLanguage = text;
			}
			bool flag2 = RestoreRemovedTranslations(localization, dictionary.Keys);
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				flag2 |= ApplyTranslation(localization, item.Key, item.Value);
			}
			if (flag2 || dictionary.Count > 0)
			{
				TranslationCache.Invoke(localization).EvictAll();
			}
			dataForgeApplyScope.Complete(dictionary.Keys.Select((string token) => "$" + token));
		}

		private static bool NeedsTranslationApply(Localization localization, Dictionary<string, string> translations)
		{
			if (AppliedTranslations.Count != translations.Count)
			{
				return true;
			}
			foreach (KeyValuePair<string, string> translation in translations)
			{
				if (!AppliedTranslations.TryGetValue(translation.Key, out TranslationLease value) || !string.Equals(value.LastAppliedValue, translation.Value, StringComparison.Ordinal) || !Translations.Invoke(localization).TryGetValue(translation.Key, out var value2) || !string.Equals(value2, translation.Value, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		internal static void BeforeLanguageSetup(Localization localization)
		{
			if (IsLiveLocalization(localization))
			{
				RestoreAppliedTranslations(localization);
			}
		}

		internal static void OnWorldShutdown()
		{
			ConfigSync? configSyncInstance = ConfigSyncInstance;
			if (configSyncInstance != null && !configSyncInstance.IsSourceOfTruth)
			{
				LocalFileModeReady = false;
				ClearActivePayloadAndRestore();
			}
			else
			{
				RestoreAppliedTranslations();
			}
		}

		private static bool IsLiveLocalization(Localization localization)
		{
			if (localization != null && LocalizationInstance() != null)
			{
				return localization == LocalizationInstance();
			}
			return false;
		}

		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...");
				if (ReloadFromDiskAndSync())
				{
					DataForgePlugin.Log.LogInfo((object)"Localization YAML reload complete.");
				}
			}
			catch (Exception arg)
			{
				DataForgePlugin.Log.LogError((object)$"Error reloading localization YAML files: {arg}");
			}
		}

		private static void OnWatcherError(object sender, ErrorEventArgs e)
		{
			if (HasLocalAuthority)
			{
				DataForgePlugin.Log.LogWarning((object)("Localization file watcher lost events; scheduling a full reload: " + e.GetException().Message));
				if (!DataForgeFileWatcher.TryRecreate("localization", delegate
				{
					SetupFileWatcher();
					LocalFileModeReady = true;
					ReloadDebouncer?.Schedule();
				}))
				{
					LocalFileModeReady = false;
					ReloadYamlValues();
				}
			}
		}

		private static bool ShouldReloadForFileEvent(FileSystemEventArgs e)
		{
			if (!HasLocalAuthority)
			{
				return false;
			}
			if (IsLocalizationFile(e.FullPath))
			{
				return true;
			}
			if (e is RenamedEventArgs e2)
			{
				return IsLocalizationFile(e2.OldFullPath);
			}
			return false;
		}

		private static void OnSyncedPayloadChanged()
		{
			if (!HasLocalAuthority)
			{
				ApplySyncedPayload(SyncedPayload?.Value ?? "");
			}
		}

		private static void OnSourceOfTruthChanged(bool isSourceOfTruth)
		{
			LocalFileModeReady = false;
			if (isSourceOfTruth)
			{
				ClearActivePayloadAndRestore();
				if (!HasLocalAuthority)
				{
					NotifyLocalizationChanged();
				}
			}
			else
			{
				Watcher?.Dispose();
				Watcher = null;
				ReloadDebouncer?.Dispose();
				ReloadDebouncer = null;
				ClearActivePayloadAndRestore();
				NotifyLocalizationChanged();
			}
		}

		private static void ApplySyncedPayload(string payload)
		{
			if (!string.Equals(LastParsedPayload, payload, StringComparison.Ordinal))
			{
				if (!TryDeserializePayload(payload, "synced localization payload", out LocalizationPayload localizationPayload))
				{
					return;
				}
				lock (StateLock)
				{
					ActivePayload = localizationPayload;
					LastParsedPayload = payload;
				}
			}
			ApplyCurrentLocalization();
			NotifyLocalizationChanged();
		}

		private static void PublishPayload(string payload)
		{
			DataForgeSync.PublishPayload(SyncedPayload, "localization", payload);
		}

		private static LocalizationPayload LoadPayloadFromDisk()
		{
			LocalizationPayload localizationPayload = CreateEmptyPayload();
			if (!Directory.Exists(LocalizationDirectory))
			{
				return localizationPayload;
			}
			string[] array = Directory.GetFiles(LocalizationDirectory, "*.yml").Concat(Directory.GetFiles(LocalizationDirectory, "*.yaml")).OrderBy<string, string>((string path) => path, StringComparer.OrdinalIgnoreCase)
				.ToArray();
			if (array.Length > 32)
			{
				throw new InvalidDataException($"Localization contains {array.Length} language files; the limit is {32}.");
			}
			long num = 0L;
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string[] array2 = array;
			foreach (string obj in array2)
			{
				FileInfo fileInfo = new FileInfo(obj);
				num += fileInfo.Length;
				if (fileInfo.Length > 2097152 || num > 2097152)
				{
					throw new InvalidDataException($"Localization files exceed the {2097152}-byte safety limit.");
				}
				string text = Path.GetFileNameWithoutExtension(obj).Trim();
				if (text.Length == 0 || text.Length > 64)
				{
					throw new InvalidDataException($"Localization language names must contain from 1 to {64} characters.");
				}
				if (!hashSet.Add(text))
				{
					throw new InvalidDataException("Localization has more than one file for language '" + text + "' when compared case-insensitively.");
				}
				Dictionary<string, string> value = LoadTranslationMap(obj, text + " localization");
				localizationPayload.Languages[text] = value;
			}
			return localizationPayload;
		}

		private static bool TryLoadPayloadFromDisk(out LocalizationPayload payload)
		{
			try
			{
				payload = LoadPayloadFromDisk();
				return true;
			}
			catch (Exception ex)
			{
				DataForgePlugin.Log.LogError((object)("Localization reload failed; keeping the last-known-good configuration. " + ex.Message));
				payload = CreateEmptyPayload();
				return false;
			}
		}

		private static Dictionary<string, string> LoadTranslationMap(string path, string source)
		{
			if (!File.Exists(path))
			{
				return new Dictionary<string, string>(StringComparer.Ordinal);
			}
			if (new FileInfo(path).Length > 2097152)
			{
				throw new InvalidDataException($"{source} exceeds the {2097152}-byte safety limit.");
			}
			string text = File.ReadAllText(path);
			if (string.IsNullOrWhiteSpace(text))
			{
				return new Dictionary<string, string>(StringComparer.Ordinal);
			}
			try
			{
				YamlStream yamlStream = new YamlStream();
				using StringReader input = new StringReader(text);
				yamlStream.Load(input);
				if (yamlStream.Documents.Count == 0)
				{
					return new Dictionary<string, string>(StringComparer.Ordinal);
				}
				if (yamlStream.Documents.Count != 1)
				{
					throw new FormatException(source + " must contain exactly one YAML document.");
				}
				if (!(yamlStream.Documents[0].RootNode is YamlMappingNode yamlMappingNode))
				{
					throw new FormatException(source + " must be a flat token-to-text mapping.");
				}
				if (yamlMappingNode.Children.Count > 8192)
				{
					throw new FormatException($"{source} contains {yamlMappingNode.Children.Count} tokens; the limit is {8192}.");
				}
				Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
				HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				foreach (KeyValuePair<YamlNode, YamlNode> child in yamlMappingNode.Children)
				{
					if (!TryNormalizeToken(((child.Key as YamlScalarNode) ?? throw new FormatException(source + " has an invalid token: token keys must be scalar strings.")).Value, out string token, out string error))
					{
						throw new FormatException(source + " has an invalid token: " + error);
					}
					if (!hashSet.Add(token))
					{
						throw new FormatException(source + " has duplicate normalized token '" + token + "' when compared case-insensitively.");
					}
					if (!(child.Value is YamlScalarNode yamlScalarNode) || string.IsNullOrEmpty(yamlScalarNode.Value))
					{
						throw new FormatException(source + " token '" + token + "' must have a non-empty scalar string value.");
					}
					string value = yamlScalarNode.Value;
					if (value.Length > 4096)
					{
						throw new FormatException($"{source} token '{token}' exceeds the {4096}-character text limit.");
					}
					dictionary[token] = value;
				}
				return dictionary;
			}
			catch (Exception ex)
			{
				throw new InvalidDataException("Failed to parse " + source + " from '" + path + "': " + ex.Message, ex);
			}
		}

		private static bool TrySerializeAndVerifyPayload(LocalizationPayload payload, out string serialized, out LocalizationPayload verified)
		{
			serialized = "";
			verified = CreateEmptyPayload();
			try
			{
				serialized = Serializer.Serialize(payload);
				if (Encoding.UTF8.GetByteCount(serialized) > 2097152)
				{
					DataForgePlugin.Log.LogError((object)$"Localization payload exceeds the {2097152}-byte safety limit; keeping the last-known-good configuration.");
					return false;
				}
			}
			catch (Exception ex)
			{
				DataForgePlugin.Log.LogError((object)("Failed to serialize localization payload: " + ex.Message));
				return false;
			}
			return TryDeserializePayload(serialized, "local localization payload round-trip", out verified);
		}

		private static bool TryDeserializePayload(string payload, string source, out LocalizationPayload localizationPayload)
		{
			localizationPayload = CreateEmptyPayload();
			if (string.IsNullOrWhiteSpace(payload))
			{
				DataForgePlugin.Log.LogError((object)(source + " was rejected because the payload is empty."));
				return false;
			}
			if (Encoding.UTF8.GetByteCount(payload) > 2097152)
			{
				DataForgePlugin.Log.LogError((object)$"{source} was rejected because it exceeds the {2097152}-byte safety limit.");
				return false;
			}
			try
			{
				LocalizationPayload payload2 = Deserializer.Deserialize<LocalizationPayload>(payload);
				localizationPayload = NormalizePayload(payload2, source);
				return true;
			}
			catch (Exception ex)
			{
				DataForgePlugin.Log.LogError((object)(source + " was rejected; keeping the last-known-good configuration. " + ex.Message));
				return false;
			}
		}

		private static LocalizationPayload NormalizePayload(LocalizationPayload? payload, string source)
		{
			if (payload == null || payload.Version != 1 || payload.Languages == null)
			{
				string arg = ((payload == null) ? "missing" : payload.Version.ToString());
				throw new InvalidDataException($"{source} must have version {1} and a languages mapping; received version {arg}.");
			}
			if (payload.Languages.Count > 32)
			{
				throw new InvalidDataException($"{source} contains {payload.Languages.Count} languages; the limit is {32}.");
			}
			LocalizationPayload localizationPayload = CreateEmptyPayload();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<string, Dictionary<string, string>> language in payload.Languages)
			{
				string text = language.Key?.Trim() ?? "";
				if (text.Length == 0 || text.Length > 64)
				{
					throw new InvalidDataException($"{source} language names must contain from 1 to {64} characters.");
				}
				if (!hashSet.Add(text))
				{
					throw new InvalidDataException(source + " has duplicate language '" + text + "' when compared case-insensitively.");
				}
				if (language.Value == null || language.Value.Count > 8192)
				{
					string text2 = ((language.Value == null) ? "null" : language.Value.Count.ToString());
					throw new InvalidDataException($"{source} language '{text}' has {text2} tokens; the limit is {8192}.");
				}
				Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
				HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				foreach (KeyValuePair<string, string> item in language.Value)
				{
					if (!TryNormalizeToken(item.Key, out string token, out string error))
					{
						throw new InvalidDataException(source + " language '" + text + "' has an invalid token: " + error);
					}
					if (!hashSet2.Add(token))
					{
						throw new InvalidDataException(source + " language '" + text + "' has duplicate normalized token '" + token + "' when compared case-insensitively.");
					}
					if (string.IsNullOrEmpty(item.Value) || item.Value.Length > 4096)
					{
						throw new InvalidDataException($"{source} language '{text}' token '{token}' must be non-empty and no longer than {4096} characters.");
					}
					dictionary[token] = item.Value;
				}
				localizationPayload.Languages[text] = dictionary;
			}
			return localizationPayload;
		}

		private static Dictionary<string, string> BuildTranslationsForLanguage(LocalizationPayload payload, string language)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			if (payload.Languages != null && payload.Languages.TryGetValue("English", out Dictionary<string, string> value))
			{
				MergeTranslations(dictionary, value);
			}
			if (!language.Equals("English", StringComparison.OrdinalIgnoreCase) && payload.Languages != null && payload.Languages.TryGetValue(language, out Dictionary<string, string> value2))
			{
				MergeTranslations(dictionary, value2);
			}
			return dictionary;
		}

		private static void MergeTranslations(Dictionary<string, string> target, Dictionary<string, string> source)
		{
			foreach (KeyValuePair<string, string> item in source)
			{
				target[item.Key] = item.Value;
			}
		}

		private static bool ApplyTranslation(Localization localization, string token, string text)
		{
			string value;
			bool flag = Translations.Invoke(localization).TryGetValue(token, out value);
			if (!AppliedTranslations.TryGetValue(token, out TranslationLease value2) || !flag || !string.Equals(value, value2.LastAppliedValue, StringComparison.Ordinal))
			{
				value2 = new TranslationLease(flag, value);
				AppliedTranslations[token] = value2;
			}
			bool result = !flag || !string.Equals(value, text, StringComparison.Ordinal);
			Translations.Invoke(localization)[token] = text;
			value2.LastAppliedValue = text;
			return result;
		}

		private static bool RestoreRemovedTranslations(Localization localization, IEnumerable<string> currentTokens)
		{
			HashSet<string> current = new HashSet<string>(currentTokens, StringComparer.Ordinal);
			bool flag = false;
			string[] array = AppliedTranslations.Keys.Where((string token) => !current.Contains(token)).ToArray();
			foreach (string text in array)
			{
				flag |= RestoreTranslationIfOwned(localization, text);
				AppliedTranslations.Remove(text);
			}
			return flag;
		}

		private static void RestoreAppliedTranslations()
		{
			RestoreAppliedTranslations(AppliedLocalization);
		}

		private static void RestoreAppliedTranslations(Localization? localization)
		{
			if (localization == null)
			{
				ClearAppliedTranslationState();
				return;
			}
			using DataForgeApplyScope dataForgeApplyScope = ((!DataForgeWorldLifecycle.IsShuttingDown && IsLiveLocalization(localization) && AppliedTranslations.Count > 0) ? DataForgeApi.BeginApply(DataForgeDomain.Localization, HasLocalAuthority, AppliedTranslations.Keys.Select((string text) => "$" + text), fullRefresh: false, AppliedLanguage) : null);
			bool flag = false;
			string[] array = AppliedTranslations.Keys.ToArray();
			foreach (string token in array)
			{
				flag |= RestoreTranslationIfOwned(localization, token);
			}
			if (flag)
			{
				TranslationCache.Invoke(localization).EvictAll();
			}
			ClearAppliedTranslationState();
			dataForgeApplyScope?.Complete(Array.Empty<string>());
		}

		private static bool RestoreTranslationIfOwned(Localization localization, string token)
		{
			if (!AppliedTranslations.TryGetValue(token, out TranslationLease value) || !Translations.Invoke(localization).TryGetValue(token, out var value2) || !string.Equals(value2, value.LastAppliedValue, StringComparison.Ordinal))
			{
				return false;
			}
			if (value.OriginalExisted)
			{
				Translations.Invoke(localization)[token] = value.OriginalValue ?? "";
			}
			else
			{
				Translations.Invoke(localization).Remove(token);
			}
			return true;
		}

		private static void ClearAppliedTranslationState()
		{
			AppliedLocalization = null;
			AppliedLanguage = "";
			AppliedTranslations.Clear();
		}

		private static void ClearActivePayloadAndRestore()
		{
			lock (StateLock)
			{
				ActivePayload = CreateEmptyPayload();
				LastParsedPayload = null;
			}
			RestoreAppliedTranslations();
		}

		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());
			}
			EnsureBuiltInTranslations(path, (IReadOnlyCollection<(string Token, string Text)>)(object)BuiltInEnglishTranslations, "# Built-in DataForge tooltip tokens. You can edit these texts.");
			string path2 = Path.Combine(LocalizationDirectory, "Korean.yml");
			if (!File.Exists(path2))
			{
				File.WriteAllText(path2, DefaultKoreanLocalizationTemplate());
			}
			EnsureBuiltInTranslations(path2, (IReadOnlyCollection<(string Token, string Text)>)(object)BuiltInKoreanTranslations, "# DataForge 기본 툴팁 토큰입니다. 원하는 문구로 수정할 수 있습니다.");
		}

		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.\"", "", "# Built-in DataForge tooltip tokens. You can edit these texts.", FormatBuiltInTranslationLines(BuiltInEnglishTranslations), "#", "# Example item override:", "# - item: MeadHealthtest", "#   cloneFrom: MeadHealthMajor", "#   name: $df_item_meadhealthtest", "#   description: Direct text override without localization", "");
		}

		private static string FormatBuiltInTranslationLines(IEnumerable<(string Token, string Text)> translations)
		{
			return string.Join(Environment.NewLine, translations.Select<(string, string), string>(((string Token, string Text) entry) => entry.Token + ": " + QuoteYaml(entry.Text)));
		}

		private static string DefaultKoreanLocalizationTemplate()
		{
			return string.Join(Environment.NewLine, "# DataForge 서버 동기화 localization 파일입니다.", "#", "# 이 폴더에는 Valheim 언어 이름을 파일명으로 사용합니다:", "# English.yml, Korean.yml, Turkish.yml, German.yml 등.", "#", "# English.yml은 기본 fallback 파일입니다. 클라이언트 언어가 한국어이면", "# DataForge는 English.yml을 먼저 적용한 뒤 Korean.yml을 덮어씁니다.", "#", "# override 필드에 $df_item_meadhealthtest 같은 토큰을 넣으면 이 파일의 번역을 사용합니다.", "# $ 토큰을 쓰지 않고 필드에 직접 텍스트를 넣어도 그대로 표시됩니다.", "#", "# 예시 localization 항목:", "# $df_item_meadhealthtest: \"테스트 아이템\"", "# $df_item_meadhealthtest_description: \"대형 체력 벌꿀주를 복제한 테스트 아이템입니다.\"", "", "# DataForge 기본 툴팁 토큰입니다. 원하는 문구로 수정할 수 있습니다.", FormatBuiltInTranslationLines(BuiltInKoreanTranslations), "#", "# 예시 item override:", "# - item: MeadHealthtest", "#   cloneFrom: MeadHealthMajor", "#   name: $df_item_meadhealthtest", "#   description: localization 토큰 없이 직접 입력한 설명", "");
		}

		private static void EnsureBuiltInTranslations(string path, IReadOnlyCollection<(string Token, string Text)> translations, string header)
		{
			Dictionary<string, string> dictionary;
			string text;
			try
			{
				dictionary = LoadTranslationMap(path, "built-in localization file '" + Path.GetFileName(path) + "'");
				text = File.ReadAllText(path);
			}
			catch
			{
				return;
			}
			HashSet<string> existingTokens = new HashSet<string>(dictionary.Keys, StringComparer.OrdinalIgnoreCase);
			string token;
			string error;
			List<(string, string)> list = translations.Where<(string, string)>(((string Token, string Text) entry) => TryNormalizeToken(entry.Token, out token, out error) && !existingTokens.Contains(token)).ToList();
			if (list.Count == 0)
			{
				return;
			}
			using StreamWriter streamWriter = File.AppendText(path);
			if (text.Length > 0 && !text.EndsWith(Environment.NewLine, StringComparison.Ordinal))
			{
				streamWriter.WriteLine();
			}
			streamWriter.WriteLine();
			streamWriter.WriteLine(header);
			foreach (var (text2, value) in list)
			{
				streamWriter.WriteLine(text2 + ": " + QuoteYaml(value));
			}
		}

		private static string QuoteYaml(string value)
		{
			return "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
		}

		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);
			return string.Equals(b: Path.GetFullPath(LocalizationDirectory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), a: Path.GetDirectoryName(fullPath)?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), comparisonType: StringComparison.OrdinalIgnoreCase);
		}

		private static bool TryNormalizeToken(string? value, out string token, out string error)
		{
			token = value?.Trim() ?? "";
			error = "";
			if (token.StartsWith("$", StringComparison.Ordinal))
			{
				token = token.Substring(1);
			}
			if (token.Length == 0 || token.Length > 128)
			{
				error = $"token names must contain from 1 to {128} characters after an optional leading '$'.";
				return false;
			}
			if (token.IndexOf('$') >= 0 || token.Any((char character) => char.IsControl(character) || " (){}[]+-!?/\\&%,.:-=<>\r\n\t".IndexOf(character) >= 0))
			{
				error = "token '" + token + "' contains a character that terminates Valheim localization tokens.";
				return false;
			}
			return true;
		}

		private static string NormalizeLanguage(string? language)
		{
			string text = language?.Trim() ?? "";
			if (text.Length != 0)
			{
				return text;
			}
			return "English";
		}

		private static void NotifyLocalizationChanged()
		{
			if (LocalizationInstance() == null || Localization.OnLanguageChange == null)
			{
				return;
			}
			Delegate[] invocationList = Localization.OnLanguageChange.GetInvocationList();
			foreach (Delegate obj in invocationList)
			{
				try
				{
					((Action)obj)();
				}
				catch (Exception ex)
				{
					DataForgePlugin.Log.LogWarning((object)("A localized UI subscriber failed after a DataForge localization update: " + ex.Message));
				}
			}
		}

		private static LocalizationPayload CreateEmptyPayload()
		{
			return new LocalizationPayload
			{
				Version = 1,
				Languages = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase)
			};
		}
	}
	[HarmonyPatch(typeof(FejdStartup), "SetupGui")]
	internal static class DataForgeLocalizationFejdStartupPatch
	{
		[HarmonyPriority(0)]
		private static void Postfix()
		{
			try
			{
				LocalizationOverrideManager.ApplyCurrentLocalization();
			}
			catch (Exception ex)
			{
				DataForgePlugin.Log.LogWarning((object)("Failed to apply DataForge localization to the startup UI: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Localization), "SetupLanguage")]
	internal static class DataForgeLocalizationSetupLanguagePatch
	{
		[HarmonyPriority(800)]
		private static void Prefix(Localization __instance)
		{
			try
			{
				LocalizationOverrideManager.BeforeLanguageSetup(__instance);
			}
			catch (Exception ex)
			{
				DataForgePlugin.Log.LogWarning((object)("Failed to prepare DataForge localization before a language change: " + ex.Message));
			}
		}

		[HarmonyPriority(0)]
		private static void Postfix(Localization __instance, string language)
		{
			try
			{
				LocalizationOverrideManager.ApplyCurrentLocalization(__instance, language);
			}
			catch (Exception ex)
			{
				DataForgePlugin.Log.LogWarning((object)("Failed to apply DataForge localization for '" + language + "': " + ex.Message));
			}
		}
	}
	[BepInPlugin("sighsorry.DataForge", "DataForge", "1.3.5")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class DataForgePlugin : BaseUnityPlugin
	{
		public enum Toggle
		{
			On = 1,
			Off = 0
		}

		public enum UpgradeMaterialScalingMode
		{
			Vanilla,
			Flat,
			Reduced
		}

		private sealed class ConfigurationManagerAttributes
		{
			[UsedImplicitly]
			public int? Order;

			[UsedImplicitly]
			public bool? Browsable;

			[UsedImplicitly]
			public string? Category;

			[UsedImplicitly]
			public Action<ConfigEntryBase>? CustomDrawer;
		}

		internal const string ModName = "DataForge";

		internal const string ModVersion = "1.3.5";

		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.3.5",
			MinimumRequiredVersion = "1.3.5",
			ModRequired = true
		};

		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 bool _configReloadInProgress;

		private static bool _sourceOfTruthFileModeReady;

		internal static readonly ManualLogSource Log = Logger.CreateLogSource("DataForge");

		private const long ReloadDelayTicks = 10000000L;

		private const int MaxStoredFireplaceFuelLimit = 9999;

		private static ConfigEntry<Toggle> _serverConfigLocked = null;

		private static ConfigEntry<Toggle> _enableItemOverrides = null;

		private static ConfigEntry<Toggle> _enableRecipeOverrides = null;

		private static ConfigEntry<Toggle> _enableStatusEffectOverrides = null;

		private static ConfigEntry<Toggle> _enablePieceOverrides = null;

		private static ConfigEntry<int> _stackableStackMultiplier = null;

		private static ConfigEntry<float> _itemWeightMultiplier = null;

		private static ConfigEntry<UpgradeMaterialScalingMode> _upgradeMaterialScaling = null;

		private static ConfigEntry<Toggle> _showPieceComfortInHammer = null;

		private static ConfigEntry<Toggle> _highlightStationExtensionsInHammer = null;

		private static ConfigEntry<Toggle> _ignoreStationExtensionSpacing = null;

		private static ConfigEntry<int> _maxStoredFireplaceFuel = 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 UpgradeMaterialScalingMode UpgradeMaterialScaling => _upgradeMaterialScaling.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));

		public void Awake()
		{
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Expected O, but got Unknown
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Expected O, but got Unknown
			DataForgeApi.SetWarningSink(delegate(string message)
			{
				Log.LogWarning((object)message);
			});
			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<Toggle>(_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
			{
				ApplyConfigChange(ItemOverrideManager.ApplyCurrentConfiguration);
				DataForgeIconSync.ScheduleManifestRefresh();
			};
			_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
			{
				ApplyConfigChange(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
			{
				ApplyConfigChange(StatusEffectOverrideManager.ApplyCurrentConfiguration);
				DataForgeIconSync.ScheduleManifestRefresh();
			};
			_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
			{
				ApplyConfigChange(PieceOverrideManager.ApplyCurrentConfiguration);
				DataForgeIconSync.ScheduleManifestRefresh();
			};
			_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<int>(1, 10), Array.Empty<object>()), synchronizedSetting: true, 500);
			_stackableStackMultiplier.SettingChanged += delegate
			{
				ApplyConfigChange(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<float>(0f, 2f), Array.Empty<object>()), synchronizedSetting: true, 400);
			_itemWeightMultiplier.SettingChanged += delegate
			{
				ApplyConfigChange(ItemOverrideManager.ApplyCurrentConfiguration);
			};
			_upgradeMaterialScaling = ConfigEntry("2 - Misc", "Upgrade Material Scaling", UpgradeMaterialScalingMode.Vanilla, "Controls standard per-level upgrade material costs globally. Vanilla uses amount x (target quality - 1), Flat always uses the configured upgrade amount, and Reduced uses ceil(amount x target quality / 2). Exact-quality requirements are unchanged.", synchronizedSetting: true, 350);
			_upgradeMaterialScaling.SettingChanged += delegate
			{
				ApplyConfigChange(RecipeOverrideManager.RefreshComputedRequirementState);
			};
			_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();
			LocalizationOverrideManager.Initialize(ConfigSync);
			DataForgeIconSync.Initialize(ConfigSync);
			StatusEffectOverrideManager.Initialize(ConfigSync);
			ItemOverrideManager.Initialize(ConfigSync);
			RecipeOverrideManager.Initialize(ConfigSync);
			PieceOverrideManager.Initialize(ConfigSync);
			VneiRefreshManager.Initialize();
			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()
		{
			DataForgeLifecycleStep.Run("file watcher recovery cleanup", DataForgeFileWatcher.CancelPendingRecreates);
			DataForgeLifecycleStep.Run("configuration save", delegate
			{
				SaveWithRespectToConfigSet();
			});
			DataForgeLifecycleStep.Run("configuration watcher cleanup", delegate
			{
				try
				{
					_watcher?.Dispose();
				}
				finally
				{
					_watcher = null;
				}
			});
			DataForgeLifecycleStep.Run("configuration reload cleanup", delegate
			{
				try
				{
					_configReloadDebouncer?.Dispose();
				}
				finally
				{
					_configReloadDebouncer = null;
				}
			});
			DataForgeLifecycleStep.Run("runtime cleanup", delegate
			{
				DataForgeRuntimeCleanup.RunOnce();
			});
			DataForgeLifecycleStep.Run("icon sync dispose", DataForgeIconSync.Dispose);
			DataForgeLifecycleStep.Run("localization dispose", LocalizationOverrideManager.Dispose);
			DataForgeLifecycleStep.Run("status-effect dispose", StatusEffectOverrideManager.Dispose);
			DataForgeLifecycleStep.Run("item dispose", ItemOverrideManager.Dispose);
			DataForgeLifecycleStep.Run("recipe dispose", RecipeOverrideManager.Dispose);
			DataForgeLifecycleStep.Run("piece dispose", PieceOverrideManager.Dispose);
			DataForgeLifecycleStep.Run("VNEI refresh dispose", VneiRefreshManager.Dispose);
			DataForgeLifecycleStep.Run("source-of-truth event cleanup", delegate
			{
				ConfigSync.SourceOfTruthChanged -= OnSourceOfTruthChanged;
			});
			DataForgeLifecycleStep.Run("Harmony cleanup", (Action)_harmony.UnpatchSelf);
			DataForgeApi.Shutdown();
		}

		private static void OnSourceOfTruthChanged(bool isSourceOfTruth)
		{
			DataForgeIconSync.OnSourceOfTruthChanged();
			_sourceOfTruthFileModeReady = false;
			if (isSourceOfTruth)
			{
				EnsureSourceOfTruthFileMode();
				return;
			}
			CancelDomainWatcherRecovery();
			DataForgeLifecycleStep.Run("status-effect local-file cleanup", StatusEffectOverrideManager.SetupFileWatcher);
			DataForgeLifecycleStep.Run("item local-file cleanup", ItemOverrideManager.SetupFileWatcher);
			DataForgeLifecycleStep.Run("recipe local-file cleanup", RecipeOverrideManager.SetupFileWatcher);
			DataForgeLifecycleStep.Run("piece local-file cleanup", PieceOverrideManager.SetupFileWatcher);
		}

		internal static void EnsureSourceOfTruthFileMode()
		{
			if (!UsesLocalAuthorityFiles)
			{
				return;
			}
			DataForgeLifecycleStep.Run("localization source-of-truth setup", delegate
			{
				LocalizationOverrideManager.EnsureSourceOfTruthFileMode();
			});
			if (!_sourceOfTruthFileModeReady)
			{
				bool flag = DataForgeLifecycleStep.Run("status-effect source-of-truth watcher setup", StatusEffectOverrideManager.SetupFileWatcher);
				int num = 1 & (flag ? 1 : 0);
				if (flag)
				{
					DataForgeFileWatcher.CancelPendingRecreate("status-effect");
				}
				int num2 = num & (DataForgeLifecycleStep.Run("status-effect source-of-truth reload", StatusEffectOverrideManager.ReloadFromDiskAndSync) ? 1 : 0);
				bool flag2 = DataForgeLifecycleStep.Run("item source-of-truth watcher setup", ItemOverrideManager.SetupFileWatcher);
				int num3 = num2 & (flag2 ? 1 : 0);
				if (flag2)
				{
					DataForgeFileWatcher.CancelPendingRecreate("item");
				}
				int num4 = num3 & (DataForgeLifecycleStep.Run("item source-of-truth reload", ItemOverrideManager.ReloadFromDiskAndSync) ? 1 : 0);
				bool flag3 = DataForgeLifecycleStep.Run("recipe source-of-truth watcher setup", RecipeOverrideManager.SetupFileWatcher);
				int num5 = num4 & (flag3 ? 1 : 0);
				if (flag3)
				{
					DataForgeFileWatcher.CancelPendingRecreate("recipe");
				}
				int num6 = num5 & (DataForgeLifecycleStep.Run("recipe source-of-truth reload", RecipeOverrideManager.ReloadFromDiskAndSync) ? 1 : 0);
				bool flag4 = DataForgeLifecycleStep.Run("piece source-of-truth watcher setup", PieceOverrideManager.SetupFileWatcher);
				int num7 = num6 & (flag4 ? 1 : 0);
				if (flag4)
				{
					DataForgeFileWatcher.CancelPendingRecreate("piece");
				}
				_sourceOfTruthFileModeReady = (byte)((uint)num7 & (DataForgeLifecycleStep.Run("piece source-of-truth reload", PieceOverrideManager.ReloadFromDiskAndSync) ? 1u : 0u)) != 0;
			}
		}

		private static void CancelDomainWatcherRecovery()
		{
			DataForgeFileWatcher.CancelPendingRecreate("localization");
			DataForgeFileWatcher.CancelPendingRecreate("status-effect");
			DataForgeFileWatcher.CancelPendingRecreate("item");
			DataForgeFileWatcher.CancelPendingRecreate("recipe");
			DataForgeFileWatcher.CancelPendingRecreate("piece");
		}

		private void Update()
		{
			DataForgeIconSync.Update();
			VneiPrefabCleanupGuard.TryPatchVneiIndexAll(_harmony);
			DataForgeApi.DispatchPending();
		}

		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, OnConfigWatcherError);
		}

		private void ReadConfigValues(object sender, FileSystemEventArgs e)
		{
			_configReloadDebouncer?.Schedule();
		}

		private void OnConfigWatcherError(object sender, ErrorEventArgs e)
		{
			Log.LogWarning((object)("Configuration file watcher lost events; scheduling a full reload: " + e.GetException().Message));
			if (!DataForgeFileWatcher.TryRecreate("configuration", delegate
			{
				SetupWatcher();
				_configReloadDebouncer?.Schedule();
			}))
			{
				ReloadConfigValues();
			}
		}

		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...");
					bool itemOverridesEnabled = ItemOverridesEnabled;
					bool recipeOverridesEnabled = RecipeOverridesEnabled;
					bool statusEffectOverridesEnabled = StatusEffectOverridesEnabled;
					bool pieceOverridesEnabled = PieceOverridesEnabled;
					int stackableStackMultiplier = StackableStackMultiplier;
					float itemWeightMultiplier = ItemWeightMultiplier;
					UpgradeMaterialScalingMode upgradeMaterialScaling = UpgradeMaterialScaling;
					_configReloadInProgress = true;
					try
					{
						SaveWithRespectToConfigSet(reload: true);
					}
					finally
					{
						_configReloadInProgress = false;
					}
					_lastConfigFileText = ReadFileTextIfExists(ConfigFileFullPath);
					if (statusEffectOverridesEnabled != StatusEffectOverridesEnabled)
					{
						StatusEffectOverrideManager.ApplyCurrentConfiguration();
					}
					if (itemOverridesEnabled != ItemOverridesEnabled || stackableStackMultiplier != StackableStackMultiplier || Math.Abs(itemWeightMultiplier - ItemWeightMultiplier) > 0.0001f)
					{
						ItemOverrideManager.ApplyCurrentConfiguration();
					}
					if (recipeOverridesEnabled != RecipeOverridesEnabled)
					{
						RecipeOverrideManager.ApplyCurrentConfiguration();
					}
					else if (upgradeMaterialScaling != UpgradeMaterialScaling)
					{
						RecipeOverrideManager.RefreshComputedRequirementState();
					}
					if (pieceOverridesEnabled != PieceOverridesEnabled)
					{
						PieceOverrideManager.ApplyCurrentConfiguration();
					}
					Log.LogInfo((object)"Configuration reload complete.");
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"Error reloading configuration: {arg}");
				}
			}
		}

		private void ApplyConfigChange(Action apply)
		{
			if (!_configReloadInProgress)
			{
				apply();
			}
		}

		private void SaveWithRespectToConfigSet(bool reload = false)
		{
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			try
			{
				if (reload)
				{
					((BaseUnityPlugin)this).Config.Reload();
				}
				else
				{
					((BaseUnityPlugin)this).Config.Save();
				}
			}
			finally
			{
				((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<T> ConfigEntry<T>(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<object>();
			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<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val);
			ConfigSync.AddConfigEntry<T>(val2).SynchronizedConfig = synchronizedSetting;
			return val2;
		}

		private ConfigEntry<T> ConfigEntry<T>(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<object>()), 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
	{
		private delegate bool TryRegenerateReference(out string path, out bool changed, out string error);

		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__WriteFullScaffoldFiles;

			public static ConsoleOptionsFetcher <1>__GetFullTabOptions;

			public static ConsoleEvent <2>__WriteReferenceFiles;

			public static ConsoleOptionsFetcher <3>__GetReferenceTabOptions;

			public static TryRegenerateReference <4>__TryRegenerateReferenceFile;

			public static TryRegenerateReference <5>__TryRegenerateReferenceFile;

			public static TryRegenerateReference <6>__TryRegenerateReferenceFile;

			public static TryRegenerateReference <7>__TryRegenerateReferenceFile;

			public static TryRegenerateReference <8>__TryRegeneratePieceCategoryReferenceFile;

			public static TryRegenerateReference <9>__TryRegenerateReferenceFile;
		}

		private const string WriteFullCommandName = "dataforge:full";

		private const string WriteReferenceCommandName = "dataforge:refer";

		private static readonly List<string> FullTabOptions = new List<string> { "item", "recipe", "effect", "piece", "all" };

		private static readonly List<string> ReferenceTabOptions = new List<string> { "item", "recipe", "effect", "piece", "pieceCategory", "material", "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_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: 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, true, false, false, false, (ConsoleOptionsFetcher)obj2, false, true, true);
				object obj3 = <>O.<2>__WriteReferenceFiles;
				if (obj3 == null)
				{
					ConsoleEvent val3 = WriteReferenceFiles;
					<>O.<2>__WriteReferenceFiles = val3;
					obj3 = (object)val3;
				}
				object obj4 = <>O.<3>__GetReferenceTabOptions;
				if (obj4 == null)
				{
					ConsoleOptionsFetcher val4 = GetReferenceTabOptions;
					<>O.<3>__GetReferenceTabOptions = val4;
					obj4 = (object)val4;
				}
				new ConsoleCommand("dataforge:refer", "Regenerate DataForge compact reference files. Usage: dataforge:refer [item|recipe|effect|piece|pieceCategory|material|all]", (ConsoleEvent)obj3, false, false, true, false, false, false, (ConsoleOptionsFetcher)obj4, false, true, true);
			}
		}

		private static List<string> GetFullTabOptions()
		{
			return FullTabOptions;
		}

		private static List<string> GetReferenceTabOptions()
		{
			return ReferenceTabOptions;
		}

		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 void WriteReferenceFiles(ConsoleEventArgs args)
		{
			string text = ((args.Length >= 2) ? (args[1] ?? "").Trim().ToLowerInvariant() : "all");
			if (text.Length == 0)
			{
				text = "all";
			}
			switch (text)
			{
			case "all":
				WriteReferenceResult(args, "item", ItemOverrideManager.TryRegenerateReferenceFile);
				WriteReferenceResult(args, "recipe", RecipeOverrideManager.TryRegenerateReferenceFile);
				WriteReferenceResult(args, "effect", StatusEffectOverrideManager.TryRegenerateReferenceFile);
				WriteReferenceResult(args, "piece", PieceOverrideManager.TryRegenerateReferenceFile);
				WriteReferenceResult(args, "piece category", PieceOverrideManager.TryRegeneratePieceCategoryReferenceFile);
				WriteReferenceResult(args, "material", MaterialReferenceWriter.TryRegenerateReferenceFile);
				break;
			case "items":
			case "item":
				WriteReferenceResult(args, "item", ItemOverrideManager.TryRegenerateReferenceFile);
				break;
			case "recipe":
			case "recipes":
				WriteReferenceResult(args, "recipe", RecipeOverrideManager.TryRegenerateReferenceFile);
				break;
			case "effect":
			case "effects":
				WriteReferenceResult(args, "effect", StatusEffectOverrideManager.TryRegenerateReferenceFile);
				break;
			case "piece":
			case "pieces":
				WriteReferenceResult(args, "piece", PieceOverrideManager.TryRegenerateReferenceFile);
				break;
			case "category":
			case "piececategory":
			case "piece-category":
			case "categories":
				WriteReferenceResult(args, "piece category", PieceOverrideManager.TryRegeneratePieceCategoryReferenceFile);
				break;
			case "material":
			case "materials":
				WriteReferenceResult(args, "material", MaterialReferenceWriter.TryRegenerateReferenceFile);
				break;
			default:
			{
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString("Syntax: dataforge:refer [item|recipe|effect|piece|pieceCategory|material|all]");
				}
				break;
			}
			}
		}

		private static void WriteReferenceResult(ConsoleEventArgs args, string label, TryRegenerateReference regenerate)
		{
			if (!regenerate(out var path, out var changed, out var error))
			{
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString(error);
				}
			}
			else
			{
				Terminal context2 = args.Context;
				if (context2 != null)
				{
					context2.AddString(changed ? ("Wrote " + label + " reference to " + path) : (label + " reference is already up to date at " + path));
				}
			}
		}

		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)
		{
			return FormatSource(source, entryIndex, null);
		}

		internal static string FormatSource(string source, int entryIndex, long? lineNumber)
		{
			string displaySource = GetDisplaySource(source);
			if (!lineNumber.HasValue || lineNumber.GetValueOrDefault() <= 0 || !IsLocalAuthorityFile(source))
			{
				return $"{displaySource}#{entryIndex}";
			}
			return $"{displaySource}:{lineNumber.Value} (#{entryIndex})";
		}

		internal static string FormatSourceLine(string source, long lineNumber)
		{
			string displaySource = GetDisplaySource(source);
			if (lineNumber <= 0 || !IsLocalAuthorityFile(source))
			{
				return displaySource;
			}
			return $"{displaySource}:{lineNumber}";
		}

		internal static IReadOnlyList<long> GetLocalTopLevelEntryLines(string yaml, string source)
		{
			if (!IsLocalAuthorityFile(source) || string.IsNullOrWhiteSpace(yaml))
			{
				return Array.Empty<long>();
			}
			YamlStream yamlStream = new YamlStream();
			using StringReader input = new StringReader(yaml);
			yamlStream.Load(input);
			if (yamlStream.Documents.Count == 0 || !(yamlStream.Documents[0].RootNode is YamlSequenceNode yamlSequenceNode))
			{
				return Array.Empty<long>();
			}
			List<long> list = new List<long>(yamlSequenceNode.Children.Count);
			foreach (YamlNode child in yamlSequenceNode.Children)
			{
				list.Add(child.Start.Line);
			}
			return list;
		}

		internal static long? GetEntryLine(IReadOnlyList<long> lines, int entryIndex)
		{
			int num = entryIndex - 1;
			if (num < 0 || num >= lines.Count)
			{
				return null;
			}
			return lines[num];
		}

		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;
		}

		private static bool IsLocalAuthorityFile(string source)
		{
			if (DataForgePlugin.UsesLocalAuthorityFiles && !string.IsNullOrWhiteSpace(source))
			{
				return File.Exists(source);
			}
			return false;
		}

		private static string GetDisplaySource(string source)
		{
			string text = source?.Trim() ?? "";
			string text2 = ((text.Length == 0) ? "unknown source" : Path.GetFileName(text));
			if (text2.Length != 0)
			{
				return text2;
			}
			return text;
		}
	}
	internal sealed class DataForgeIconManifestEntry
	{
		internal string LogicalName { get; }

		internal string Hash { get; }

		internal int ByteLength { get; }

		internal int Width { get; }

		internal int Height { get; }

		internal long PixelCount => (long)Width * (long)Height;

		internal DataForgeIconManifestEntry(string logicalName, string hash, int byteLength, int width, int height)
		{
			LogicalName = logicalName;
			Hash = hash;
			ByteLength = byteLength;
			Width = width;
			Height = height;
		}
	}
	internal sealed class DataForgeIconManifest
	{
		internal string Revision { get; }

		internal IReadOnlyList<DataForgeIconManifestEntry> Entries { get; }

		internal int UniqueContentCount { get; }

		internal long TotalBytes { get; }

		internal long TotalPixels { get; }

		internal DataForgeIconManifest(string revision, List<DataForgeIconManifestEntry> entries, int uniqueContentCount, long totalBytes, long totalPixels)
		{
			Revision = revision;
			Entries = new ReadOnlyCollection<DataForgeIconManifestEntry>(entries.ToArray());
			UniqueContentCount = uniqueContentCount;
			TotalBytes = totalBytes;
			TotalPixels = totalPixels;
		}
	}
	internal static class DataForgeIconProtocol
	{
		private readonly struct ContentDescription : IEquatable<ContentDescription>
		{
			private int ByteLength { get; }

			private int Width { get; }

			private int Height { get; }

			internal ContentDescription(int byteLength, int width, int height)
			{
				ByteLength = byteLength;
				Width = width;
				Height = height;
			}

			public bool Equals(ContentDescription other)
			{
				if (ByteLength == other.ByteLength && Width == other.Width)
				{
					return Height == other.Height;
				}
				return false;
			}
		}

		internal const int ProtocolVersion = 1;

		internal const int MaxIconBytes = 524288;

		internal const int MaxIconDimension = 1024;

		internal const long MaxIconPixels = 1048576L;

		internal const int MaxTotalBytes = 2097152;

		internal const long MaxTotalPixels = 16777216L;

		internal const int MaxIconCount = 128;

		internal const int MaxLogicalNameUtf8Bytes = 240;

		internal const int MaxManifestCharacters = 65536;

		private const int MinimumPngBytes = 33;

		private const string ManifestMagic = "DATAFORGE_ICONS_V1";

		private const string RevisionPrefix = "revision=";

		private static readonly byte[] PngSignature = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 };

		private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		internal static bool TryNormalizeLogicalName(string? value, out string normalized, out string error)
		{
			normalized = string.Empty;
			error = string.Empty;
			if (string.IsNullOrWhiteSpace(value))
			{
				error = "Icon name is empty.";
				return false;
			}
			string text = value.Trim();
			if (text.Length > 240)
			{
				error = "Icon name '" + value + "' is too long.";
				return false;
			}
			string text2;
			try
			{
				text2 = text.Normalize(NormalizationForm.FormC).Replace('\\', '/');
				if (StrictUtf8.GetByteCount(text2) > 240)
				{
					error = $"Icon name '{value}' exceeds the {240}-byte UTF-8 limit.";
					return false;
				}
			}
			catch (ArgumentException)
			{
				error = "Icon name '" + value + "' contains invalid Unicode text.";
				return false;
			}
			if (text2[0] == '/' || (text2.Length >= 2 && text2[1] == ':'))
			{
				error = "Icon name '" + value + "' must be relative.";
				return false;
			}
			string text3 = text2;
			for (int i = 0; i < text3.Length; i++)
			{
				if (char.IsControl(text3[i]))
				{
					error = "Icon name '" + value + "' contains a control character.";
					return false;
				}
			}
			string[] array = text2.Split(new char[1] { '/' });
			foreach (string text4 in array)
			{
				if (text4.Length == 0 || text4 == "." || text4 == "..")
				{
					error = "Icon name '" + value + "' contains an empty or dot path segment.";
					return false;
				}
				if (!string.Equals(text4, text4.Trim(), StringComparison.Ordinal) || text4.EndsWith(".", StringComparison.Ordinal))
				{
					error = "Icon name '" + value + "' contains a path segment with unsafe trailing characters.";
					return false;
				}
				if (text4.IndexOfAny(new char[7] { '<', '>', ':', '"', '|', '?', '*' }) >= 0)
				{
					error = "Icon name '" + value + "' contains a character that is invalid in a file name.";
					return false;
				}
			}
			string text5 = array[^1];
			int num = text5.LastIndexOf('.');
			if (num < 0)
			{
				array[^1] = text5 + ".png";
			}
			else
			{
				if (num == 0 || !text5.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
				{
					error = "Icon name '" + value + "' must use the .png extension.";
					return false;
				}
				array[^1] = text5.Substring(0, text5.Length - 4) + ".png";
			}
			normalized = string.Join("/", array);
			if (StrictUtf8.GetByteCount(normalized) > 240)
			{
				normalized = string.Empty;
				error = $"Icon name '{value}' exceeds the {240}-byte UTF-8 limit after normalization.";
				return false;
			}
			return true;
		}

		internal static string ComputeSha256(byte[] bytes)
		{
			if (bytes == null)
			{
				throw new ArgumentNullException("bytes");
			}
			using SHA256 sHA = SHA256.Create();
			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();
		}

		internal static bool IsValidSha256(string? value)
		{
			if (value == null || value.Length != 64)
			{
				return false;
			}
			foreach (char c in value)
			{
				if ((c < '0' || c > '9') && (c < 'a' || c > 'f'))
				{
					return false;
				}
			}
			return true;
		}

		internal static bool TryReadPngInfo(byte[]? bytes, out int width, out int height, out string error)
		{
			width = 0;
			height = 0;
			error = string.Empty;
			if (bytes == null || bytes.Length < 33)
			{
				error = "PNG data is shorter than its signature and IHDR chunk.";
				return false;
			}
			if (bytes.Length > 524288)
			{
				error = $"PNG data exceeds the {524288}-byte per-icon limit.";
				return false;
			}
			for (int i = 0; i < PngSignature.Length; i++)
			{
				if (bytes[i] != PngSignature[i])
				{
					error = "PNG signature is invalid.";
					return false;
				}
			}
			if (ReadUInt32BigEndian(bytes, 8) != 13 || bytes[12] != 73 || bytes[13] != 72 || bytes[14] != 68 || bytes[15] != 82)
			{
				error = "PNG does not start with a 13-byte IHDR chunk.";
				return false;
			}
			uint num = ReadUInt32BigEndian(bytes, 16);
			uint num2 = ReadUInt32BigEndian(bytes, 20);
			if (num == 0 || num2 == 0 || num > 1024 || num2 > 1024)
			{
				error = $"PNG dimensions must be between 1 and {1024} pixels.";
				return false;
			}
			if ((long)num * (long)num2 > 1048576)
			{
				error = $"PNG exceeds the {1048576L}-pixel per-icon limit.";
				return false;
			}
			width = (int)num;
			height = (int)num2;
			return true;
		}

		internal static bool TryCreateManifest(IEnumerable<DataForgeIconManifestEntry>? entries, out DataForgeIconManifest manifest, out string error)
		{
			manifest = null;
			error = string.Empty;
			if (entries == null)
			{
				error = "Icon manifest entries are missing.";
				return false;
			}
			List<DataForgeIconManifestEntry> list = new List<DataForgeIconManifestEntry>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			Dictionary<string, ContentDescription> dictionary = new Dictionary<string, ContentDescription>(StringComparer.Ordinal);
			long num = 0L;
			long num2 = 0L;
			foreach (DataForgeIconManifestEntry entry in entries)
			{
				if (entry == null)
				{
					error = "Icon manifest contains a null entry.";
					return false;
				}
				if (list.Count >= 128)
				{
					error = $"Icon manifest exceeds the {128}-icon limit.";
					return false;
				}
				if (!TryNormalizeLogicalName(entry.LogicalName, out string normalized, out error))
				{
					return false;
				}
				if (!hashSet.Add(normalized))
				{
					error = "Icon manifest contains the duplicate logical name '" + normalized + "'.";
					return false;
				}
				if (!IsValidSha256(entry.Hash))
				{
					error = "Icon '" + normalized + "' has an invalid lowercase SHA-256 hash.";
					return false;
				}
				if (!TryValidateManifestDimensions(normalized, entry.ByteLength, entry.Width, entry.Height, out error))
				{
					return false;
				}
				ContentDescription contentDescription = new ContentDescription(entry.ByteLength, entry.Width, entry.Height);
				if (dictionary.TryGetValue(entry.Hash, out var value))
				{
					if (!value.Equals(contentDescription))
					{
						error = "Icons sharing hash '" + entry.Hash + "' disagree on size or dimensions.";
						return false;
					}
				}
				else
				{
					dictionary.Add(entry.Hash, contentDescription);
					num += entry.ByteLength;
					num2 += entry.PixelCount;
					if (num > 2097152)
					{
						error = $"Icon manifest exceeds the {2097152}-byte aggregate limit.";
						return false;
					}
					if (num2 > 16777216)
					{
						error = $"Icon manifest exceeds the {16777216L}-pixel aggregate limit.";
						return false;
					}
				}
				list.Add(new DataForgeIconManifestEntry(normalized, entry.Hash, entry.ByteLength, entry.Width, entry.Height));
			}
			list.Sort((DataForgeIconManifestEntry left, DataForgeIconManifestEntry right) => StringComparer.Ordinal.Compare(left.LogicalName, right.LogicalName));
			string s = BuildCanonicalBody(list);
			string revision = ComputeSha256(StrictUtf8.GetBytes(s));
			manifest = new DataForgeIconManifest(revision, list, dictionary.Count, num, num2);
			return true;
		}

		internal static string SerializeManifest(IEnumerable<DataForgeIconManifestEntry> entries)
		{
			if (!TryCreateManifest(entries, out DataForgeIconManifest manifest, out string error))
			{
				throw new ArgumentException(error, "entries");
			}
			return SerializeManifest(manifest);
		}

		internal static string SerializeManifest(DataForgeIconManifest manifest)
		{
			if (manifest == null)
			{
				throw new ArgumentNullException("manifest");
			}
			string text = BuildCanonicalBody(manifest.Entries);
			return "DATAFORGE_ICONS_V1\nrevision=" + manifest.Revision + "\n" + text;
		}

		internal static bool TryParseManifest(string? payload, out DataForgeIconManifest manifest, out string error)
		{
			manifest = null;
			error = string.Empty;
			if (payload == null)
			{
				error = "Icon manifest payload is missing.";
				return false;
			}
			if (payload.Length > 65536)
			{
				error = $"Icon manifest exceeds the {65536}-character limit.";
				return false;
			}
			string text = payload.Replace("\r\n", "\n");
			if (text.IndexOf('\r') >= 0)
			{
				error = "Icon manifest contains an invalid line ending.";
				return false;
			}
			int num = text.IndexOf('\n');
			int num2 = ((num < 0) ? (-1) : text.IndexOf('\n', num + 1));
			if (num < 0 || num2 < 0 || !string.Equals(text.Substring(0, num), "DATAFORGE_ICONS_V1", StringComparison.Ordinal))
			{
				error = "Icon manifest header is invalid.";
				return false;
			}
			string text2 = text.Substring(num + 1, num2 - num - 1);
			if (!text2.StartsWith("revision=", StringComparison.Ordinal))
			{
				error = "Icon manifest revision line is invalid.";
				return false;
			}
			string text3 = text2.Substring("revision=".Length);
			if (!IsValidSha256(text3))
			{
				error = "Icon manifest revision is not a lowercase SHA-256 hash.";
				return false;
			}
			string text4 = text.Substring(num2 + 1);
			if (text4.EndsWith("\n", StringComparison.Ordinal))
			{
				error = "Icon manifest has a non-canonical trailing line.";
				return false;
			}
			string[] array = ((text4.Length == 0) ? Array.Empty<string>() : text4.Split(new char[1] { '\n' }));
			if (array.Length > 128)
			{
				error = $"Icon manifest exceeds the {128}-icon limit.";
				return false;
			}
			List<DataForgeIconManifestEntry> list = new List<DataForgeIconManifestEntry>(array.Length);
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				string[] array3 = array2[i].Split(new char[1] { '|' });
				if (array3.Length != 5 || array3[0].Length == 0)
				{
					error = "Icon manifest contains a malformed entry line.";
					return false;
				}
				string text5;
				try
				{
					byte[] bytes = Convert.FromBase64String(array3[0]);
					text5 = StrictUtf8.GetString(bytes);
					if (!string.Equals(Convert.ToBase64String(StrictUtf8.GetBytes(text5)), array3[0], StringComparison.Ordinal))
					{
						error = "Icon manifest contains a non-canonical encoded name.";
						return false;
					}
				}
				catch (FormatException)
				{
					error = "Icon manifest contains an invalid base64 name.";
					return false;
				}
				catch (DecoderFallbackException)
				{
					error = "Icon manifest contains a name that is not valid UTF-8.";
					return false;
				}
				if (!TryParsePositiveInt(array3[2], out var value) || !TryParsePositiveInt(array3[3], out var value2) || !TryParsePositiveInt(array3[4], out var value3))
				{
					error = "Icon '" + text5 + "' has malformed numeric metadata.";
					return false;
				}
				list.Add(new DataForgeIconManifestEntry(text5, array3[1], value, value2, value3));
			}
			if (!TryCreateManifest(list, out DataForgeIconManifest manifest2, out error))
			{
				return false;
			}
			string b = BuildCanonicalBody(manifest2.Entries);
			if (!string.Equals(text4, b, StringComparison.Ordinal))
			{
				error = "Icon manifest entries are not in canonical form or order.";
				return false;
			}
			if (!string.Equals(text3, manifest2.Revision, StringComparison.Ordinal))
			{
				error = "Icon manifest revision does not match its canonical body.";
				return false;
			}
			manifest = manifest2;
			return true;
		}

		private static bool TryValidateManifestDimensions(string logicalName, int byteLength, int width, int height, out string error)
		{
			error = string.Empty;
			if (byteLength < 33 || byteLength > 524288)
			{
				error = "Icon '" + logicalName + "' has a byte length outside the permitted range.";
				return false;
			}
			if (width <= 0 || height <= 0 || width > 1024 || height > 1024)
			{
				error = "Icon '" + logicalName + "' has dimensions outside the permitted range.";
				return false;
			}
			if ((long)width * (long)height > 1048576)
			{
				error = "Icon '" + logicalName + "' exceeds the per-icon pixel limit.";
				return false;
			}
			return true;
		}

		private static bool TryParsePositiveInt(string text, out int value)
		{
			if (int.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out value) && value > 0)
			{
				return string.Equals(value.ToString(CultureInfo.InvariantCulture), text, StringComparison.Ordinal);
			}
			return false;
		}

		private static string BuildCanonicalBody(IReadOnlyList<DataForgeIconManifestEntry> entries)
		{
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < entries.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append('\n');
				}
				DataForgeIconManifestEntry dataForgeIconManifestEntry = entries[i];
				stringBuilder.Append(Convert.ToBase64String(StrictUtf8.GetBytes(dataForgeIconManifestEntry.LogicalName)));
				stringBuilder.Append('|');
				stringBuilder.Append(dataForgeIconManifestEntry.Hash);
				stringBuilder.Append('|');
				stringBuilder.Append(dataForgeIconManifestEntry.ByteLength.ToString(CultureInfo.InvariantCulture));
				stringBuilder.Append('|');
				stringBuilder.Append(dataForgeIconManifestEntry.Width.ToString(CultureInfo.InvariantCulture));
				stringBuilder.Append('|');
				stringBuilder.Append(dataForgeIconManifestEntry.Height.ToString(CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}

		private static uint ReadUInt32BigEndian(byte[] bytes, int offset)
		{
			return (uint)((bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]);
		}
	}
	internal static class DataForgeReferenceSections
	{
		private sealed class GroupedEntry<TSource>
		{
			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<TSource, TOutput>(IEnumerable<TSource> entries, Func<TSource, string> getSortKey, Func<TSource, string> getOwnerName, Func<TSource, TOutput> getOutput, ISerializer serializer, Func<TSource, IEnumerable<string>>? getComments = null)
		{
			DataForgeAssetOwnerCatalog.PrepareForReferenceGeneration();
			List<IGrouping<string, GroupedEntry<TSource>>> list = (from entry in entries.Select(delegate(TSource entry)
				{
					string text = (getOwnerName(entry) ?? "").Trim();
					return new GroupedEntry<TSource>
					{
						Entry = entry,
						SortKey = (getSortKey(entry) ?? "").Trim(),
						OwnerName = ((text.Length > 0) ? text : "Unknown / Untracked")
					};
				})
				orderby GetOwnerSortBucket(entry.OwnerName)
				select entry).ThenBy<GroupedEntry<TSource>, string>((GroupedEntry<TSource> entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase).ThenBy<GroupedEntry<TSource>, string>((GroupedEntry<TSource> entry) => entry.OwnerName, StringComparer.Ordinal).ThenBy<GroupedEntry<TSource>, string>((GroupedEntry<TSource> entry) => entry.SortKey, StringComparer.OrdinalIgnoreCase)
				.ThenBy<GroupedEntry<TSource>, string>((GroupedEntry<TSource> entry) => entry.SortKey, StringComparer.Ordinal)
				.GroupBy<GroupedEntry<TSource>, string>((GroupedEntry<TSource> entry) => entry.OwnerName, StringComparer.OrdinalIgnoreCase)
				.ToList();
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			foreach (IGrouping<string, GroupedEntry<TSource>> item in list)
			{
				if (flag)
				{
					stringBuilder.AppendLine();
				}
				AppendSectionHeaderComment(stringBuilder, item.Key);
				foreach (GroupedEntry<TSource> item2 in item)
				{
					if (getComments != null)
					{
						AppendEntryComments(stringBuilder, getComments(item2.Entry));
					}
					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 void AppendEntryComments(StringBuilder builder, IEnumerable<string> comments)
		{
			foreach (string comment in comments)
			{
				string[] array = comment.Replace("\r\n", "\n").Replace('\r', '\n').Replace('\u0085', '\n')
					.Replace('\u2028', '\n')
					.Replace('\u2029', '\n')
					.Split(new char[1] { '\n' });
				foreach (string value in array)
				{
					builder.Append("# ").AppendLine(value);
				}
			}
		}

		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<char>()).StartsWith("- ", StringComparison.Ordinal))
			{
				return false;
			}
			List<string> list = new List<string>();
			int i;
			for (i = num2; i < lines.Length; i++)
			{
				string text2 = lines[i];
				if (GetFirstNonWhitespaceIndex(text2) != firstNonWhitespaceIndex2 || !text2.TrimStart(Array.Empty<char>()).StartsWith("- ", StringComparison.Ordinal))
				{
					break;
				}
				string text3 = text2.TrimStart(Array.Empty<char>()).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<string> EnumerateLookupCandidates(string normalizedName)
		{
			yield return normalizedName;
			int num = normalizedName.IndexOf(':');
			if (num > 0)
			{
				string text = NormalizeName(normalizedName.Substring(0, num));
				if (text.Length > 0 && !text.Equals(normalizedName, StringComparison.OrdinalIgnoreCase))
				{
					yield return text;
				}
			}
		}

		private static string NormalizeName(string? name)
		{
			return (name ?? "").Replace("(Clone)", "").Trim();
		}
	}
	internal static class DataForgeVanillaAssetCatalog
	{
		private enum CatalogState
		{
			Uninitialized,
			Loaded,
			Unavailable
		}

		private static readonly object Sync = new object();

		private static readonly HashSet<string> PrefabNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> AssetNames = new HashSet<string>(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 static readonly object Sync = new object();

		private static readonly Dictionary<string, AssetOwner> AssetOwners = new Dictionary<string, AssetOwner>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> AmbiguousAssetNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static string _loadedSignature = "";

		private static bool _mappingsInitialized;

		internal static void PrepareForReferenceGeneration()
		{
			EnsureMappingsLoaded();
		}

		internal static string GetOwnerName(string assetName)
		{
			if (!_mappingsInitialized)
			{
				EnsureMappingsLoaded();
			}
			foreach (string item in EnumerateLookupCandidates(assetName))
			{
				if (AssetOwners.TryGetValue(item, out AssetOwner value) && !string.IsNullOrWhiteSpace(value.Guid))
				{
					return value.Name;
				}
			}
			return "Unknown / Untracked";
		}

		private static void EnsureMappingsLoaded()
		{
			List<AssetOwner> pluginResources = GetPluginResources();
			AssetBundle[] array = AssetBundle.GetAllLoadedAssetBundles().ToArray();
			string text = BuildSignature(array, pluginResources);
			if (_mappingsInitialized && string.Equals(text, _loadedSignature, StringComparison.Ordinal))
			{
				return;
			}
			lock (Sync)
			{
				if (_mappingsInitialized && string.Equals(text, _loadedSignature, StringComparison.Ordinal))
				{
					return;
				}
				AssetOwners.Clear();
				AmbiguousAssetNames.Clear();
				foreach (AssetBundle item in array.OrderBy<AssetBundle, string>((AssetBundle bundle) => ((Object)bundle).name ?? "", StringComparer.OrdinalIgnoreCase).ThenBy<AssetBundle, string>((AssetBundle bundle) => ((Object)bundle).name ?? "", StringComparer.Ordinal))
				{
					string text2 = ((Object)item).name ?? "";
					if (text2.Length == 0)
					{
						continue;
					}
					AssetOwner assetOwner = AssetOwnerMatching.Resolve