Decompiled source of DataForge v1.2.0

DataForge.dll

Decompiled a day 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 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.2.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace 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 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 bool WatcherResetPending;

		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;

		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;
			WatcherResetPending = false;
			lock (StateLock)
			{
				ActivePayload = CreateEmptyPayload();
				LastParsedPayload = null;
			}
		}

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

		private static void SetupFileWatcher()
		{
			Watcher?.Dispose();
			Watcher = null;
			ReloadDebouncer?.Dispose();
			ReloadDebouncer = null;
			WatcherResetPending = false;
			if (HasLocalAuthority)
			{
				EnsureConfigDirectoryAndDefaultOverride();
				ReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadYamlValues);
				CreateLocalizationWatcher();
			}
		}

		private static void CreateLocalizationWatcher()
		{
			Directory.CreateDirectory(LocalizationDirectory);
			FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(LocalizationDirectory, "*.*");
			fileSystemWatcher.IncludeSubdirectories = false;
			fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
			fileSystemWatcher.Changed += ReadYamlValues;
			fileSystemWatcher.Created += ReadYamlValues;
			fileSystemWatcher.Deleted += ReadYamlValues;
			fileSystemWatcher.Renamed += delegate(object sender, RenamedEventArgs args)
			{
				ReadYamlValues(sender, args);
			};
			fileSystemWatcher.Error += OnWatcherError;
			fileSystemWatcher.EnableRaisingEvents = true;
			Watcher = fileSystemWatcher;
		}

		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 instance = Localization.m_instance;
			if (instance != null)
			{
				ApplyCurrentLocalization(instance, instance.GetSelectedLanguage());
			}
		}

		internal static void ApplyCurrentLocalization(Localization localization, string? language)
		{
			if (!IsLiveLocalization(localization))
			{
				return;
			}
			string text = NormalizeLanguage(language);
			if (AppliedLocalization != localization || !AppliedLanguage.Equals(text, StringComparison.OrdinalIgnoreCase))
			{
				RestoreAppliedTranslations();
				AppliedLocalization = localization;
				AppliedLanguage = text;
			}
			Dictionary<string, string> dictionary;
			lock (StateLock)
			{
				dictionary = BuildTranslationsForLanguage(ActivePayload, text);
			}
			bool flag = RestoreRemovedTranslations(localization, dictionary.Keys);
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				flag |= ApplyTranslation(localization, item.Key, item.Value);
			}
			if (flag || dictionary.Count > 0)
			{
				localization.m_cache.EvictAll();
			}
		}

		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 && Localization.m_instance != null)
			{
				return localization == Localization.m_instance;
			}
			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}");
			}
			finally
			{
				if (WatcherResetPending && HasLocalAuthority)
				{
					WatcherResetPending = false;
					try
					{
						Watcher?.Dispose();
						Watcher = null;
						CreateLocalizationWatcher();
					}
					catch (Exception arg2)
					{
						DataForgePlugin.Log.LogError((object)$"Failed to rebuild the localization file watcher: {arg2}");
					}
				}
			}
		}

		private static void OnWatcherError(object sender, ErrorEventArgs e)
		{
			if (HasLocalAuthority)
			{
				DataForgePlugin.Log.LogWarning((object)("Localization file watcher lost events and will be rebuilt: " + e.GetException().Message));
				WatcherResetPending = true;
				ReloadDebouncer?.Schedule();
			}
		}

		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)
				{
					EnsureSourceOfTruthFileMode();
				}
				else
				{
					NotifyLocalizationChanged();
				}
			}
			else
			{
				Watcher?.Dispose();
				Watcher = null;
				ReloadDebouncer?.Dispose();
				ReloadDebouncer = null;
				WatcherResetPending = false;
				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 = localization.m_translations.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);
			localization.m_translations[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;
			}
			bool flag = false;
			string[] array = AppliedTranslations.Keys.ToArray();
			foreach (string token in array)
			{
				flag |= RestoreTranslationIfOwned(localization, token);
			}
			if (flag)
			{
				localization.m_cache.EvictAll();
			}
			ClearAppliedTranslationState();
		}

		private static bool RestoreTranslationIfOwned(Localization localization, string token)
		{
			if (!AppliedTranslations.TryGetValue(token, out TranslationLease value) || !localization.m_translations.TryGetValue(token, out var value2) || !string.Equals(value2, value.LastAppliedValue, StringComparison.Ordinal))
			{
				return false;
			}
			if (value.OriginalExisted)
			{
				localization.m_translations[token] = value.OriginalValue ?? "";
			}
			else
			{
				localization.m_translations.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 (Localization.m_instance == 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.2.0")]
	[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.2.0";

		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.2.0",
			MinimumRequiredVersion = "1.2.0",
			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_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Expected O, but got Unknown
			//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Expected O, but got Unknown
			bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			_serverConfigLocked = ConfigEntry("1 - General", "Lock Configuration", Toggle.On, "If on, the configuration is locked and can be changed by server admins only.", synchronizedSetting: true, 1000);
			ConfigSync.AddLockingConfigEntry<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);
			};
			_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);
			};
			_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);
			};
			_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);
			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()
		{
			SaveWithRespectToConfigSet();
			_watcher?.Dispose();
			_watcher = null;
			_configReloadDebouncer?.Dispose();
			_configReloadDebouncer = null;
			DataForgeRuntimeCleanup.RunOnce();
			LocalizationOverrideManager.Dispose();
			StatusEffectOverrideManager.Dispose();
			ItemOverrideManager.Dispose();
			RecipeOverrideManager.Dispose();
			PieceOverrideManager.Dispose();
			VneiRefreshManager.Dispose();
			ConfigSync.SourceOfTruthChanged -= OnSourceOfTruthChanged;
			_harmony.UnpatchSelf();
		}

		private static void OnSourceOfTruthChanged(bool isSourceOfTruth)
		{
			if (isSourceOfTruth)
			{
				EnsureSourceOfTruthFileMode();
				return;
			}
			_sourceOfTruthFileModeReady = false;
			StatusEffectOverrideManager.SetupFileWatcher();
			ItemOverrideManager.SetupFileWatcher();
			RecipeOverrideManager.SetupFileWatcher();
			PieceOverrideManager.SetupFileWatcher();
		}

		internal static void EnsureSourceOfTruthFileMode()
		{
			if (UsesLocalAuthorityFiles && !_sourceOfTruthFileModeReady)
			{
				_sourceOfTruthFileModeReady = true;
				LocalizationOverrideManager.EnsureSourceOfTruthFileMode();
				StatusEffectOverrideManager.SetupFileWatcher();
				ItemOverrideManager.SetupFileWatcher();
				RecipeOverrideManager.SetupFileWatcher();
				PieceOverrideManager.SetupFileWatcher();
				StatusEffectOverrideManager.ReloadFromDiskAndSync();
				ItemOverrideManager.ReloadFromDiskAndSync();
				RecipeOverrideManager.ReloadFromDiskAndSync();
				PieceOverrideManager.ReloadFromDiskAndSync();
			}
		}

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

		private static void ClampMaxStoredFireplaceFuel()
		{
			int num = Math.Min(9999, Math.Max(0, _maxStoredFireplaceFuel.Value));
			if (_maxStoredFireplaceFuel.Value != num)
			{
				_maxStoredFireplaceFuel.Value = num;
			}
		}

		private void SetupWatcher()
		{
			_watcher?.Dispose();
			_configReloadDebouncer?.Dispose();
			_configReloadDebouncer = DataForgeFileWatcher.CreateDebouncedAction(10000000L, ReloadConfigValues);
			_watcher = DataForgeFileWatcher.Create(Paths.ConfigPath, ConfigFileName, includeSubdirectories: false, ReadConfigValues);
		}

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

		private void ReloadConfigValues()
		{
			lock (_reloadLock)
			{
				if (!File.Exists(ConfigFileFullPath))
				{
					Log.LogWarning((object)"Config file does not exist. Skipping reload.");
					return;
				}
				try
				{
					string b = ReadFileTextIfExists(ConfigFileFullPath);
					if (string.Equals(_lastConfigFileText, b, StringComparison.Ordinal))
					{
						Log.LogDebug((object)"Skipping configuration reload because the config file content did not change.");
						return;
					}
					Log.LogDebug((object)"Reloading configuration...");
					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;
			if (reload)
			{
				((BaseUnityPlugin)this).Config.Reload();
			}
			else
			{
				((BaseUnityPlugin)this).Config.Save();
			}
			if (saveOnConfigSet)
			{
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		private static string? ReadFileTextIfExists(string path)
		{
			try
			{
				return File.Exists(path) ? File.ReadAllText(path) : null;
			}
			catch (IOException)
			{
				return null;
			}
			catch (UnauthorizedAccessException)
			{
				return null;
			}
		}

		private ConfigEntry<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
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__WriteFullScaffoldFiles;

			public static ConsoleOptionsFetcher <1>__GetFullTabOptions;
		}

		private const string WriteFullCommandName = "dataforge:full";

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

		private static bool _registered;

		internal static void Register()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			if (!_registered)
			{
				_registered = true;
				object obj = <>O.<0>__WriteFullScaffoldFiles;
				if (obj == null)
				{
					ConsoleEvent val = WriteFullScaffoldFiles;
					<>O.<0>__WriteFullScaffoldFiles = val;
					obj = (object)val;
				}
				object obj2 = <>O.<1>__GetFullTabOptions;
				if (obj2 == null)
				{
					ConsoleOptionsFetcher val2 = GetFullTabOptions;
					<>O.<1>__GetFullTabOptions = val2;
					obj2 = (object)val2;
				}
				new ConsoleCommand("dataforge:full", "Write DataForge full scaffold YAML files with explicit defaults. Usage: dataforge:full [item|recipe|effect|piece|all]", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false);
			}
		}

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

		private static void WriteFullScaffoldFiles(ConsoleEventArgs args)
		{
			if (!TryParseScope(args, out var includeItem, out var includeRecipe, out var includeEffect, out var includePiece))
			{
				return;
			}
			if (includeItem)
			{
				if (ItemOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path, out string error))
				{
					Terminal context = args.Context;
					if (context != null)
					{
						context.AddString("Wrote item full scaffold to " + path);
					}
				}
				else
				{
					Terminal context2 = args.Context;
					if (context2 != null)
					{
						context2.AddString(error);
					}
				}
			}
			if (includeRecipe)
			{
				if (RecipeOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path2, out string error2))
				{
					Terminal context3 = args.Context;
					if (context3 != null)
					{
						context3.AddString("Wrote recipe full scaffold to " + path2);
					}
				}
				else
				{
					Terminal context4 = args.Context;
					if (context4 != null)
					{
						context4.AddString(error2);
					}
				}
			}
			if (includeEffect)
			{
				if (StatusEffectOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path3, out string error3))
				{
					Terminal context5 = args.Context;
					if (context5 != null)
					{
						context5.AddString("Wrote effect full scaffold to " + path3);
					}
				}
				else
				{
					Terminal context6 = args.Context;
					if (context6 != null)
					{
						context6.AddString(error3);
					}
				}
			}
			if (!includePiece)
			{
				return;
			}
			if (PieceOverrideManager.TryWriteFullScaffoldConfigurationFile(out string path4, out string error4))
			{
				Terminal context7 = args.Context;
				if (context7 != null)
				{
					context7.AddString("Wrote piece full scaffold to " + path4);
				}
			}
			else
			{
				Terminal context8 = args.Context;
				if (context8 != null)
				{
					context8.AddString(error4);
				}
			}
		}

		private static bool TryParseScope(ConsoleEventArgs args, out bool includeItem, out bool includeRecipe, out bool includeEffect, out bool includePiece)
		{
			string text = ((args.Length >= 2) ? (args[1] ?? "").Trim().ToLowerInvariant() : "all");
			if (text.Length == 0)
			{
				text = "all";
			}
			switch (text)
			{
			case "all":
				includeItem = true;
				includeRecipe = true;
				includeEffect = true;
				includePiece = true;
				return true;
			case "items":
			case "item":
				includeItem = true;
				includeRecipe = false;
				includeEffect = false;
				includePiece = false;
				return true;
			case "recipe":
			case "recipes":
				includeItem = false;
				includeRecipe = true;
				includeEffect = false;
				includePiece = false;
				return true;
			case "effect":
			case "effects":
				includeItem = false;
				includeRecipe = false;
				includeEffect = true;
				includePiece = false;
				return true;
			case "piece":
			case "pieces":
				includeItem = false;
				includeRecipe = false;
				includeEffect = false;
				includePiece = true;
				return true;
			default:
			{
				includeItem = false;
				includeRecipe = false;
				includeEffect = false;
				includePiece = false;
				Terminal context = args.Context;
				if (context != null)
				{
					context.AddString("Syntax: dataforge:full [item|recipe|effect|piece|all]");
				}
				return false;
			}
			}
		}
	}
	internal static class DataForgeLogContext
	{
		private sealed class PopWhenDisposed : IDisposable
		{
			private readonly string? previous;

			private bool disposed;

			internal PopWhenDisposed(string? previous)
			{
				this.previous = previous;
			}

			public void Dispose()
			{
				if (!disposed)
				{
					CurrentContext = previous;
					disposed = true;
				}
			}
		}

		[ThreadStatic]
		private static string? CurrentContext;

		internal static IDisposable Push(string? context)
		{
			string currentContext = CurrentContext;
			CurrentContext = (string.IsNullOrWhiteSpace(context) ? currentContext : context);
			return new PopWhenDisposed(currentContext);
		}

		internal static string FormatSource(string source, int entryIndex)
		{
			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 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)
		{
			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.SortKey, StringComparer.OrdinalIgnoreCase).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)
				{
					string value = CollapseScalarBlockListsToInlineLists(serializer.Serialize(new TOutput[1] { getOutput(item2.Entry) }).TrimEnd('\r', '\n'));
					stringBuilder.AppendLine(value);
				}
				flag = true;
			}
			if (!flag)
			{
				return "[]" + Environment.NewLine;
			}
			return stringBuilder.ToString();
		}

		private static void AppendSectionHeaderComment(StringBuilder builder, string ownerName)
		{
			builder.Append("# ===== ");
			builder.Append(string.IsNullOrWhiteSpace(ownerName) ? "Unknown / Untracked" : ownerName.Trim());
			builder.AppendLine(" =====");
		}

		private static int GetOwnerSortBucket(string ownerName)
		{
			if (string.Equals(ownerName, "Valheim", StringComparison.OrdinalIgnoreCase))
			{
				return 0;
			}
			if (!string.Equals(ownerName, "Unknown / Untracked", StringComparison.OrdinalIgnoreCase))
			{
				return 1;
			}
			return 2;
		}

		private static string CollapseScalarBlockListsToInlineLists(string yaml)
		{
			if (string.IsNullOrWhiteSpace(yaml) || yaml.IndexOf("- ", StringComparison.Ordinal) < 0)
			{
				return yaml;
			}
			string[] array = yaml.Replace("\r\n", "\n").Split(new char[1] { '\n' });
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < array.Length; i++)
			{
				if (TryCollapseScalarBlockList(array, ref i, out string collapsedLine))
				{
					stringBuilder.AppendLine(collapsedLine);
				}
				else
				{
					stringBuilder.AppendLine(array[i]);
				}
			}
			return stringBuilder.ToString().TrimEnd('\r', '\n');
		}

		private static bool TryCollapseScalarBlockList(string[] lines, ref int index, out string collapsedLine)
		{
			collapsedLine = "";
			string text = lines[index];
			int num = text.IndexOf(':');
			if (num < 0 || num != text.Length - 1)
			{
				return false;
			}
			int num2 = index + 1;
			if (num2 >= lines.Length)
			{
				return false;
			}
			int firstNonWhitespaceIndex = GetFirstNonWhitespaceIndex(text);
			int firstNonWhitespaceIndex2 = GetFirstNonWhitespaceIndex(lines[num2]);
			if (firstNonWhitespaceIndex < 0 || firstNonWhitespaceIndex2 <= firstNonWhitespaceIndex || !lines[num2].TrimStart(Array.Empty<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)
		{
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			AddIfNew(normalizedName);
			int num = normalizedName.IndexOf(':');
			if (num > 0)
			{
				AddIfNew(normalizedName.Substring(0, num));
			}
			foreach (string item in seen)
			{
				yield return item;
			}
			void AddIfNew(string candidate)
			{
				string text = NormalizeName(candidate);
				if (text.Length > 0)
				{
					seen.Add(text);
				}
			}
		}

		private static string NormalizeName(string? name)
		{
			return (name ?? "").Replace("(Clone)", "").Trim();
		}
	}
	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 sealed class PluginResourceSnapshot
		{
			public string OwnerName { get; set; } = "";

			public string PluginName { get; set; } = "";

			public string PluginGuid { get; set; } = "";

			public string AssemblyName { get; set; } = "";

			public string[] ResourceNames { get; set; } = Array.Empty<string>();
		}

		private static readonly object Sync = new object();

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

		private static string _loadedSignature = "";

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

		private static void EnsureMappingsLoaded()
		{
			string text = BuildSignature();
			if (string.Equals(text, _loadedSignature, StringComparison.Ordinal))
			{
				return;
			}
			lock (Sync)
			{
				if (string.Equals(text, _loadedSignature, StringComparison.Ordinal))
				{
					return;
				}
				AssetOwners.Clear();
				List<PluginResourceSnapshot> pluginResources = GetPluginResources();
				foreach (AssetBundle allLoadedAssetBundle in AssetBundle.GetAllLoadedAssetBundles())
				{
					string text2 = ((Object)allLoadedAssetBundle).name ?? "";
					if (text2.Length == 0)
					{
						continue;
					}
					string value = ResolveOwnerName(text2, pluginResources);
					if (string.IsNullOrWhiteSpace(value))
					{
						continue;
					}
					string[] allAssetNames = allLoadedAssetBundle.GetAllAssetNames();
					foreach (string text3 in allAssetNames)
					{
						if (text3.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase) || text3.EndsWith(".asset", StringComparison.OrdinalIgnoreCase))
						{
							string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text3);
							if (!string.IsNullOrWhiteSpace(fileNameWithoutExtension))
							{
								AssetOwners[fileNameWithoutExtension] = value;
							}
						}
					}
				}
				_loadedSignature = text;
				DataForgePlugin.Log.LogDebug((object)$"Tracked {AssetOwners.Count} mod asset owner mapping(s) for reference sections.");
			}
		}

		private static IEnumerable<string> EnumerateLookupCandidates(string assetName)
		{
			string normalizedName = (assetName ?? "").Replace("(Clone)", "").Trim();
			if (normalizedName.Length != 0)
			{
				yield return normalizedName;
				int num = normalizedName.IndexOf(':');
				if (num > 0)
				{
					yield return normalizedName.Substring(0, num);
				}
			}
		}

		private static List<PluginResourceSnapshot> GetPluginResources()
		{
			return (from plugin in Chainloader.PluginInfos.Values.Select(delegate(PluginInfo pluginInfo)
				{
					string text = (pluginInfo.Metadata.Name ?? "").Trim();
					string text2 = (pluginInfo.Metadata.GUID ?? "").Trim();
					string assemblyName = "";
					string[] resourceNames = Array.Empty<string>();
					try
					{
						assemblyName = ((object)pluginInfo.Instance)?.GetType().Assembly.GetName().Name ?? "";
						resourceNames = ((object)pluginInfo.Instance)?.GetType().Assembly.GetManifestResourceNames() ?? Array.Empty<string>();
					}
					catch
					{
					}
					return new PluginResourceSnapshot
					{
						OwnerName = ((text.Length > 0) ? text : text2),
						PluginName = text,
						PluginGuid = text2,
						AssemblyName = assemblyName,
						ResourceNames = resourceNames
					};
				})
				where plugin.OwnerName.Length > 0
				select plugin).ToList();
		}

		private static string ResolveOwnerName(string bundleName, List<PluginResourceSnapshot> plugins)
		{
			PluginResourceSnapshot pluginResourceSnapshot = plugins.FirstOrDefault((PluginResourceSnapshot plugin) => plugin.ResourceNames.Any((string resourceName) => resourceName.EndsWith(bundleName, StringComparison.OrdinalIgnoreCase)));
			if (pluginResourceSnapshot != null)
			{
				return pluginResourceSnapshot.OwnerName;
			}
			string normalizedBundleName = NormalizeToken(Path.GetFileNameWithoutExtension(bundleName));
			if (normalizedBundleName.Length == 0)
			{
				return "";
			}
			return plugins.FirstOrDefault(delegate(PluginResourceSnapshot plugin)
			{
				string pluginToken = NormalizeToken(plugin.PluginName);
				string pluginToken2 = NormalizeToken(plugin.PluginGuid);
				string pluginToken3 = NormalizeToken(plugin.AssemblyName);
				return IsTokenMatch(normalizedBundleName, pluginToken) || IsTokenMatch(normalizedBundleName, pluginToken2) || IsTokenMatch(normalizedBundleName, pluginToken3);
			})?.OwnerName ?? "";
		}

		private static bool IsTokenMatch(string bundleName, string pluginToken)
		{
			if (pluginToken.Length > 0)
			{
				if (bundleName.IndexOf(pluginToken, StringComparison.OrdinalIgnoreCase) < 0)
				{
					return pluginToken.IndexOf(bundleName, StringComparison.OrdinalIgnoreCase) >= 0;
				}
				return true;
			}
			return false;
		}

		private static string NormalizeToken(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (char c in value)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}

		private static string BuildSignature()
		{
			IEnumerable<string> values = (from bundle in AssetBundle.GetAllLoadedAssetBundles()
				select ((Object)bundle).name ?? "" into name
				where name.Length > 0
				select name).OrderBy<string, string>((string name) => name, StringComparer.OrdinalIgnoreCase);
			IEnumerable<string> values2 = Chainloader.PluginInfos.Values.Select(delegate(PluginInfo pluginInfo)
			{
				string text = pluginInfo.Metadata.Name ?? "";
				string text2 = pluginInfo.Metadata.GUID ?? "";
				string text3 = "";
				try
				{
					text3 = ((object)pluginInfo.Instance)?.GetType().Assembly.GetName().Name ?? "";
				}
				catch
				{
				}
				return text2 + ":" + text + ":" + text3;
			}).OrderBy<string, string>((string token) => token, StringComparer.OrdinalIgnoreCase);
			return string.Join("|", values) + "||" + string.Join("|", values2);
		}
	}
	internal readonly struct DataForgeItemSortGroup
	{
		internal int BigGroupRank { get; }

		internal int SubGroupRank { get; }

		internal int DetailRank { get; }

		internal DataForgeItemSortGroup(int bigGroupRank, int subGroupRank, int detailRank)
		{
			BigGroupRank = bigGroupRank;
			SubGroupRank = subGroupRank;
			DetailRank = detailRank;
		}
	}
	internal static class DataForgeItemSortClassifier
	{
		private const int Melee = 0;

		private const int Ranged = 1;

		private const int Magic = 2;

		private const int Equipment = 3;

		private const int Food = 4;

		private const int Consumable = 5;

		private const int Meadbase = 6;

		private const int Misc = 7;

		private const int Unknown = 8;

		internal static DataForgeItemSortGroup Classify(string? itemName, SharedData? shared)
		{
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_022a: Invalid comparison between Unknown and I4
			if (shared == null)
			{
				return ClassifyFallback(itemName);
			}
			if (MatchElementalMagic(shared))
			{
				return Group(2, 0);
			}
			if (MatchBloodMagic(shared))
			{
				return Group(2, 1);
			}
			if (MatchBow(itemName, shared))
			{
				return Group(1, 0);
			}
			if (MatchArrow(itemName, shared))
			{
				return Group(1, 1);
			}
			if (MatchCrossbow(itemName, shared))
			{
				return Group(1, 2);
			}
			if (MatchBolt(itemName, shared))
			{
				return Group(1, 3);
			}
			if (MatchAmmo(shared))
			{
				return Group(1, 4);
			}
			if (MatchBomb(shared))
			{
				return Group(1, 5);
			}
			if (MatchSword(itemName, shared))
			{
				return Group(0, 0, SwordDetail(itemName, shared));
			}
			if (MatchAxe(itemName, shared))
			{
				return Group(0, 1, AxeDetail(itemName, shared));
			}
			if (MatchClub(itemName, shared))
			{
				return Group(0, 2, ClubDetail(itemName, shared));
			}
			if (MatchKnife(itemName, shared))
			{
				return Group(0, 3);
			}
			if (MatchSpear(itemName, shared))
			{
				return Group(0, 4);
			}
			if (MatchPolearm(itemName, shared))
			{
				return Group(0, 5);
			}
			if (MatchFists(itemName, shared))
			{
				return Group(0, 6);
			}
			if (MatchShield(itemName, shared))
			{
				return Group(0, 7, ShieldDetail(itemName));
			}
			if (MatchPickaxe(itemName, shared))
			{
				return Group(0, 8);
			}
			if (MatchTool(itemName, shared))
			{
				return Group(0, 9);
			}
			if (MatchHelmet(shared))
			{
				return Group(3, 0);
			}
			if (MatchChest(shared))
			{
				return Group(3, 1);
			}
			if (MatchLegs(shared))
			{
				return Group(3, 2);
			}
			if (MatchCape(shared))
			{
				return Group(3, 3);
			}
			if (MatchUtility(shared))
			{
				return Group(3, 4);
			}
			if (MatchTrinket(shared))
			{
				return Group(3, 5);
			}
			if (MatchFeast(itemName))
			{
				return Group(4, 3);
			}
			if (MatchNativeFood(shared, out var subGroupRank))
			{
				return Group(4, subGroupRank);
			}
			if (MatchMeadbase(itemName, shared))
			{
				return Group(6, 0);
			}
			if (MatchMead(itemName, shared))
			{
				return Group(5, 0);
			}
			if (MatchPotion(itemName, shared))
			{
				return Group(5, 1);
			}
			if ((int)shared.m_itemType == 13)
			{
				return Group(7, 0);
			}
			if (shared.m_value > 0)
			{
				return Group(7, 1);
			}
			return ClassifyFallback(itemName);
		}

		private static DataForgeItemSortGroup ClassifyFallback(string? itemName)
		{
			if (HasToken(itemName, "staff", "magic", "elemental"))
			{
				return Group(2, 0);
			}
			if (HasToken(itemName, "bloodmagic"))
			{
				return Group(2, 1);
			}
			if (HasToken(itemName, "bow") && !HasToken(itemName, "crossbow"))
			{
				return Group(1, 0);
			}
			if (HasToken(itemName, "arrow"))
			{
				return Group(1, 1);
			}
			if (HasToken(itemName, "crossbow", "arbalest"))
			{
				return Group(1, 2);
			}
			if (HasToken(itemName, "bolt"))
			{
				return Group(1, 3);
			}
			if (HasToken(itemName, "bomb"))
			{
				return Group(1, 5);
			}
			if (HasToken(itemName, "sword"))
			{
				return Group(0, 0, SwordDetail(itemName, null));
			}
			if (HasToken(itemName, "axe", "battleaxe"))
			{
				return Group(0, 1, AxeDetail(itemName, null));
			}
			if (HasToken(itemName, "club", "mace", "sledge"))
			{
				return Group(0, 2, ClubDetail(itemName, null));
			}
			if (HasToken(itemName, "knife"))
			{
				return Group(0, 3);
			}
			if (HasToken(itemName, "spear"))
			{
				return Group(0, 4);
			}
			if (HasToken(itemName, "atgeir", "polearm"))
			{
				return Group(0, 5);
			}
			if (HasToken(itemName, "fist"))
			{
				return Group(0, 6);
			}
			if (HasToken(itemName, "shield", "buckler"))
			{
				return Group(0, 7, ShieldDetail(itemName));
			}
			if (HasToken(itemName, "pickaxe"))
			{
				return Group(0, 8);
			}
			if (HasToken(itemName, "hammer", "hoe", "cultivator", "torch", "fishingrod", "tankard"))
			{
				return Group(0, 9);
			}
			if (HasToken(itemName, "helmet", "hood", "helm"))
			{
				return Group(3, 0);
			}
			if (HasToken(itemName, "chest", "cuirass", "tunic", "robe"))
			{
				return Group(3, 1);
			}
			if (HasToken(itemName, "legs", "leg", "pants", "greaves"))
			{
				return Group(3, 2);
			}
			if (HasToken(itemName, "cape", "shoulder"))
			{
				return Group(3, 3);
			}
			if (HasToken(itemName, "trinket"))
			{
				return Group(3, 5);
			}
			if (HasToken(itemName, "feast"))
			{
				return Group(4, 3);
			}
			if (HasToken(itemName, "meadbase"))
			{
				return Group(6, 0);
			}
			if (HasToken(itemName, "mead"))
			{
				return Group(5, 0);
			}
			if (HasToken(itemName, "potion"))
			{
				return Group(5, 1);
			}
			if (HasToken(itemName, "trophy"))
			{
				return Group(7, 0);
			}
			return Group(8, 0);
		}

		private static bool MatchSword(string? itemName, SharedData shared)
		{
			if (!IsSkillAttack(shared, (SkillType)1))
			{
				return HasToken(itemName, shared, "sword");
			}
			return true;
		}

		private static bool MatchAxe(string? itemName, SharedData shared)
		{
			if (!IsSkillAttack(shared, (SkillType)7))
			{
				return HasToken(itemName, shared, "axe", "battleaxe");
			}
			return true;
		}

		private static bool MatchClub(string? itemName, SharedData shared)
		{
			if (!IsSkillAttack(shared, (SkillType)3))
			{
				return HasToken(itemName, shared, "club", "mace", "sledge");
			}
			return true;
		}

		private static bool MatchKnife(string? itemName, SharedData shared)
		{
			if (!IsSkillAttack(shared, (SkillType)2))
			{
				return HasToken(itemName, shared, "knife");
			}
			return true;
		}

		private static bool MatchSpear(string? itemName, SharedData shared)
		{
			if (!IsSkillAttack(shared, (SkillType)5))
			{
				return HasToken(itemName, shared, "spear");
			}
			return true;
		}

		private static bool MatchPolearm(string? itemName, SharedData shared)
		{
			if (!IsSkillAttack(shared, (SkillType)4) && !IsItemTypeOrAttach(shared, (ItemType)20))
			{
				return HasToken(itemName, shared, "atgeir", "polearm");
			}
			return true;
		}

		private static bool MatchFists(string? itemName, SharedData shared)
		{
			if (!IsSkillAttack(shared, (SkillType)11))
			{
				return HasToken(itemName, shared, "fist");
			}
			return true;
		}

		private static bool MatchShield(string? itemName, SharedData shared)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			if (!IsItemTypeOrAttach(shared, (ItemType)5) && (int)shared.m_skillType != 6)
			{
				return HasToken(itemName, shared, "shield", "buckler");
			}
			return true;
		}

		private static bool MatchPickaxe(string? itemName, SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			if ((int)shared.m_skillType != 12)
			{
				return HasToken(itemName, shared, "pickaxe");
			}
			return true;
		}

		private static bool MatchTool(string? itemName, SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Invalid comparison between Unknown and I4
			if ((int)shared.m_itemType != 19 && !IsItemTypeOrAttach(shared, (ItemType)15) && (int)shared.m_skillType != 104 && (int)shared.m_skillType != 106)
			{
				return HasToken(itemName, shared, "hammer", "hoe", "cultivator", "torch", "fishingrod", "tankard");
			}
			return true;
		}

		private static bool MatchBow(string? itemName, SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Invalid comparison between Unknown and I4
			if ((int)shared.m_skillType != 14 && (int)shared.m_skillType != 9 && (int)shared.m_skillType != 10)
			{
				if ((int)shared.m_itemType != 4 && !IsSkillAttack(shared, (SkillType)8))
				{
					if (HasToken(itemName, shared, "bow"))
					{
						return !HasToken(itemName, shared, "crossbow");
					}
					return false;
				}
				return true;
			}
			return false;
		}

		private static bool MatchCrossbow(string? itemName, SharedData shared)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Invalid comparison between Unknown and I4
			if (!MatchAmmo(shared) && ((int)shared.m_skillType == 14 || HasToken(itemName, shared, "crossbow", "arbalest")))
			{
				if ((int)shared.m_itemType != 4 && !HasAttackAnimation(shared))
				{
					return HasToken(itemName, shared, "crossbow", "arbalest");
				}
				return true;
			}
			return false;
		}

		private static bool MatchArrow(string? itemName, SharedData shared)
		{
			if (!HasAttackAnimation(shared) && (string.Equals(shared.m_ammoType, "$ammo_arrows", StringComparison.Ordinal) || HasToken(itemName, shared, "arrow")))
			{
				return GetTotalDamage(shared) > 0f;
			}
			return false;
		}

		private static bool MatchBolt(string? itemName, SharedData shared)
		{
			if (!HasAttackAnimation(shared) && (string.Equals(shared.m_ammoType, "$ammo_bolts", StringComparison.Ordinal) || HasToken(itemName, shared, "bolt")))
			{
				return GetTotalDamage(shared) > 0f;
			}
			return false;
		}

		private static bool MatchAmmo(SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			if ((int)shared.m_itemType != 9)
			{
				return (int)shared.m_itemType == 23;
			}
			return true;
		}

		private static bool MatchBomb(SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			if ((int)shared.m_itemType == 3 && (int)shared.m_animationState == 0)
			{
				Attack attack = shared.m_attack;
				if (attack != null && (int)attack.m_attackType == 2)
				{
					return string.Equals(shared.m_attack?.m_attackAnimation, "throw_bomb", StringComparison.Ordinal);
				}
			}
			return false;
		}

		private static bool MatchElementalMagic(SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			return (int)shared.m_skillType == 9;
		}

		private static bool MatchBloodMagic(SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			return (int)shared.m_skillType == 10;
		}

		private static bool MatchHelmet(SharedData shared)
		{
			return IsItemTypeOrAttach(shared, (ItemType)6);
		}

		private static bool MatchChest(SharedData shared)
		{
			return IsItemTypeOrAttach(shared, (ItemType)7);
		}

		private static bool MatchLegs(SharedData shared)
		{
			return IsItemTypeOrAttach(shared, (ItemType)11);
		}

		private static bool MatchCape(SharedData shared)
		{
			return IsItemTypeOrAttach(shared, (ItemType)17);
		}

		private static bool MatchUtility(SharedData shared)
		{
			return IsItemTypeOrAttach(shared, (ItemType)18);
		}

		private static bool MatchTrinket(SharedData shared)
		{
			return IsItemTypeOrAttach(shared, (ItemType)24);
		}

		private static bool MatchNativeFood(SharedData shared, out int subGroupRank)
		{
			SharedData foodSharedData = GetFoodSharedData(shared);
			subGroupRank = 0;
			if (!HasFoodCarrier(shared) || (foodSharedData.m_food <= 0f && foodSharedData.m_foodStamina <= 0f && foodSharedData.m_foodEitr <= 0f))
			{
				return false;
			}
			if (foodSharedData.m_food >= foodSharedData.m_foodStamina && foodSharedData.m_food >= foodSharedData.m_foodEitr)
			{
				subGroupRank = 0;
				return true;
			}
			if (foodSharedData.m_foodStamina >= foodSharedData.m_food && foodSharedData.m_foodStamina >= foodSharedData.m_foodEitr)
			{
				subGroupRank = 1;
				return true;
			}
			subGroupRank = 2;
			return true;
		}

		private static bool HasFoodCarrier(SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			if ((int)shared.m_itemType != 2)
			{
				if ((int)shared.m_itemType == 1)
				{
					return (Object)(object)shared.m_appendToolTip != (Object)null;
				}
				return false;
			}
			return true;
		}

		private static SharedData GetFoodSharedData(SharedData shared)
		{
			return shared.m_appendToolTip?.m_itemData?.m_shared ?? shared;
		}

		private static bool MatchFeast(string? itemName)
		{
			if (HasToken(itemName, "feast"))
			{
				return HasToken(itemName, "material");
			}
			return false;
		}

		private static bool MatchMeadbase(string? itemName, SharedData shared)
		{
			return HasToken(itemName, shared, "meadbase");
		}

		private static bool MatchMead(string? itemName, SharedData shared)
		{
			if (!MatchMeadbase(itemName, shared))
			{
				return HasToken(itemName, shared, "mead");
			}
			return false;
		}

		private static bool MatchPotion(string? itemName, SharedData shared)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Invalid comparison between Unknown and I4
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Invalid comparison between Unknown and I4
			if (!MatchMead(itemName, shared) && !MatchMeadbase(itemName, shared) && !MatchNativeFood(shared, out var _))
			{
				if (!HasToken(itemName, shared, "potion"))
				{
					if ((int)shared.m_itemType == 1 || (int)shared.m_itemType == 2)
					{
						return (Object)(object)shared.m_consumeStatusEffect != (Object)null;
					}
					return false;
				}
				return true;
			}
			return false;
		}

		private static int SwordDetail(string? itemName, SharedData? shared)
		{
			if (IsTwoHanded(shared) || HasToken(itemName, "greatsword", "twohandedsword", "2hsword"))
			{
				return 1;
			}
			return 0;
		}

		private static int AxeDetail(string? itemName, SharedData? shared)
		{
			if (IsBattleaxe(itemName, shared))
			{
				return 2;
			}
			if (!IsTwoHanded(shared) && !HasToken(itemName, "twohandedaxe", "2haxe", "dualaxe"))
			{
				return 0;
			}
			return 1;
		}

		private static bool IsBattleaxe(string? itemName, SharedData? shared)
		{
			if (HasToken(itemName, "dualaxe"))
			{
				return false;
			}
			if (HasToken(itemName, "battleaxe"))
			{
				return true;
			}
			string value = shared?.m_attack?.m_attackAnimation ?? "";
			string text = shared?.m_secondaryAttack?.m_attackAnimation ?? "";
			if (IsTwoHanded(shared) && string.Equals(text, "battleaxe_secondary", StringComparison.OrdinalIgnoreCase) && !ContainsIgnoreCase(value, "dualaxe"))
			{
				if (!ContainsIgnoreCase(value, "battleaxe"))
				{
					return ContainsIgnoreCase(text, "battleaxe");
				}
				return true;
			}
			return false;
		}

		private static int ClubDetail(string? itemName, SharedData? shared)
		{
			if (!IsTwoHanded(shared) && !HasToken(itemName, "sledge", "twohandedclub", "2hclub"))
			{
				return 0;
			}
			return 1;
		}

		private static bool IsTwoHanded(SharedData? shared)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Invalid comparison between Unknown and I4
			if (shared == null || (int)shared.m_itemType != 14)
			{
				if (shared == null)
				{
					return false;
				}
				return (int)shared.m_itemType == 22;
			}
			return true;
		}

		private static int ShieldDetail(string? itemName)
		{
			if (HasToken(itemName, "buckler"))
			{
				return 0;
			}
			if (!HasToken(itemName, "tower"))
			{
				return 1;
			}
			return 2;
		}

		private static bool IsSkillAttack(SharedData shared, SkillType skillType)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			if (shared.m_skillType == skillType && HasAttackAnimation(shared))
			{
				return GetTotalDamage(shared) > 0f;
			}
			return false;
		}

		private static bool HasAttackAnimation(SharedData shared)
		{
			return !string.IsNullOrEmpty(shared.m_attack?.m_attackAnimation);
		}

		private static float GetTotalDamage(SharedData shared)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			DamageTypes damages = shared.m_damages;
			return damages.m_blunt + damages.m_slash + damages.m_pierce + damages.m_chop + damages.m_pickaxe + damages.m_fire + damages.m_frost + damages.m_lightning + damages.m_poison + damages.m_spirit;
		}

		private static bool IsItemTypeOrAttach(SharedData shared, ItemType itemType)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (shared.m_itemType != itemType)
			{
				return shared.m_attachOverride == itemType;
			}
			return true;
		}

		private static bool HasToken(string? itemName, SharedData? shared, params string[] tokens)
		{
			string[] array = new string[3]
			{
				itemName,
				shared?.m_name,
				StripLocalizationToken(shared?.m_name)
			};
			for (int i = 0; i < array.Length; i++)
			{
				if (HasToken(array[i], tokens))
				{
					return true;
				}
			}
			return false;
		}

		private static bool HasToken(string? value, params string[] tokens)
		{
			string text = DataForgeResourceMap.NormalizeResourceToken(value);
			if (text.Length == 0)
			{
				return false;
			}
			for (int i = 0; i < tokens.Length; i++)
			{
				string text2 = DataForgeResourceMap.NormalizeResourceToken(tokens[i]);
				if (text2.Length > 0 && text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}

		private static bool ContainsIgnoreCase(string value, string token)
		{
			return value.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static string StripLocalizationToken(string? value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "";
			}
			string text = value.Trim();
			if (!text.StartsWith("$item_", StringComparison.OrdinalIgnoreCase))
			{
				return text.TrimStart(new char[1] { '$' });
			}
			return text.Substring("$item_".Length);
		}

		private static DataForgeItemSortGroup Group(int bigGroupRank, int subGroupRank, int detailRank = 0)
		{
			return new DataForgeItemSortGroup(bigGroupRank, subGroupRank, detailRank);
		}
	}
	internal static class DataForgeResourceMap
	{
		private readonly struct RecipeLookupEntry
		{
			internal Recipe Recipe { get; }

			internal int? Tier { get; }

			internal RecipeLookupEntry(Recipe recipe, int? tier)
			{
				Recipe = recipe;
				Tier = tier;
			}
		}

		private const string ResourceMapFileName = "z_resourcemap.txt";

		private const int UnknownTierSortValue = 999999;

		private static readonly object Sync = new object();

		private static Dictionary<string, int> ResourceTierByToken = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, ItemDrop?> ItemDropCache = new Dictionary<string, ItemDrop>(StringComparer.OrdinalIgnoreCase);

		private static Dictionary<string, RecipeLookupEntry>? RecipeOutputLookup;

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

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

		private static DateTime LoadedWriteTimeUtc = DateTime.MinValue;

		private static bool Loaded;

		private static ObjectDB? CachedObjectDb;

		private static int CachedObjectDbItemCount = -1;

		private static int CachedObjectDbRecipeCount = -1;

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

		private static string ResourceMapPath => Path.Combine(ConfigDirectory, "z_resourcemap.txt");

		internal static string BuildSortKey(int groupRank, int tierSortValue, string name)
		{
			return string.Join("|", Math.Max(0, groupRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, tierSortValue).ToString("D6", CultureInfo.InvariantCulture), name ?? "");
		}

		internal static string BuildItemSortKey(string? itemName, int tierSortValue, string name)
		{
			DataForgeItemSortGroup itemSortGroup = GetItemSortGroup(itemName);
			return string.Join("|", Math.Max(0, itemSortGroup.BigGroupRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, itemSortGroup.SubGroupRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, itemSortGroup.DetailRank).ToString("D3", CultureInfo.InvariantCulture), Math.Max(0, tierSortValue).ToString("D6", CultureInfo.InvariantCulture), name ?? "");
		}

		private static DataForgeItemSortGroup GetItemSortGroup(string? itemName)
		{
			EnsureObjectDbCacheFresh();
			string text = CleanPrefabName(itemName);
			if (text.Length > 0 && ItemSortGroupCache.TryGetValue(text, out var value))
			{
				return value;
			}
			DataForgeItemSortGroup dataForgeItemSortGroup = DataForgeItemSortClassifier.Classify(itemName, ResolveItemDrop(itemName)?.m_itemData?.m_shared);
			if (text.Length > 0)
			{
				ItemSortGroupCache[text] = dataForgeItemSortGroup;
			}
			return dataForgeItemSortGroup;
		}

		internal static int GetItemTierSortValue(string? itemName)
		{
			EnsureObjectDbCacheFresh();
			string text = CleanPrefabName(itemName);
			if (text.Length > 0 && ItemTierSortValueCache.TryGetValue(text, out var value))
			{
				return value;
			}
			int? knownTierForItem = GetKnownTierForItem(itemName);
			int? knownTierForRecipe = GetKnownTierForRecipe(ResolveRecipeForItem(itemName));
			int num = SortValue(MaxTier(knownTierForItem, knownTierForRecipe));
			if (text.Length > 0)
			{
				ItemTierSortValueCache[text] = num;
			}
			return num;
		}

		internal static int GetResourceTierSortValue(IEnumerable<string?> resourceNames)