Decompiled source of SULFUR Content Lib v0.1.0

SULFUR Content Lib.dll

Decompiled an hour ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using HarmonyLib;
using I2.Loc;
using PerfectRandom.Sulfur.Core;
using PerfectRandom.Sulfur.Core.DataStorage;
using PerfectRandom.Sulfur.Core.DevTools;
using PerfectRandom.Sulfur.Core.Items;
using PerfectRandom.Sulfur.Core.Stats;
using PerfectRandom.Sulfur.Core.UI;
using PerfectRandom.Sulfur.Core.UI.Inventory;
using PerfectRandom.Sulfur.Core.Units;
using Ryuka.Sulfur.ContentLib.Bootstrap;
using Ryuka.Sulfur.ContentLib.Integration;
using Ryuka.Sulfur.ContentLib.Patches;
using Ryuka.Sulfur.ContentLib.Registry;
using Ryuka.Sulfur.ContentLib.Saves;
using Ryuka.Sulfur.ContentLib.Texts;
using SULFURTogether.Api;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Ryuka")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+c560c9a5908d1987fd81732cacdd7bbc8ec2e2ff")]
[assembly: AssemblyProduct("SULFUR Content Lib")]
[assembly: AssemblyTitle("SULFUR Content Lib")]
[assembly: InternalsVisibleTo("ContentLib.Tests")]
[assembly: AssemblyVersion("0.1.0.0")]
namespace Ryuka.Sulfur.ContentLib
{
	public static class ContentManifest
	{
		public static bool IsSealed => LibraryState.Declarations.Phase == RegistryPhase.Sealed;

		public static bool IsFaulted => LibraryState.IsFaulted;

		public static event Action Sealed
		{
			add
			{
				LibraryState.SealedSignal.Add(value);
			}
			remove
			{
				LibraryState.SealedSignal.Remove(value);
			}
		}

		public static bool TryGetItem(string key, out ItemDefinition definition)
		{
			definition = null;
			ContentTables tables = LibraryState.Tables;
			if (key != null && tables != null)
			{
				return tables.ItemsByKey.TryGetValue(key, out definition);
			}
			return false;
		}

		public static bool TryGetEnchantment(string key, out EnchantmentDefinition definition)
		{
			definition = null;
			ContentTables tables = LibraryState.Tables;
			if (key != null && tables != null)
			{
				return tables.EnchantmentsByKey.TryGetValue(key, out definition);
			}
			return false;
		}

		public static bool TryGetRecipe(string key, out RecipeData recipe)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			recipe = default(RecipeData);
			if (key == null || !LibraryState.Recipes.TryGetValue(key, out recipe))
			{
				return false;
			}
			if (recipe.itemsNeeded != null)
			{
				recipe.itemsNeeded = new List<IngredientDefinition>(recipe.itemsNeeded);
			}
			return true;
		}

		public static bool TryGetKey(ItemId id, out string key)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			key = null;
			return LibraryState.Tables?.LiveItemKeys.TryGetValue(id.value, out key) ?? false;
		}

		public static bool TryGetKey(EnchantmentDefinition enchantment, out string key)
		{
			key = null;
			return LibraryState.Tables?.TryGetEnchantmentKey(enchantment, out key) ?? false;
		}
	}
	public static class ContentRegistry
	{
		public static bool DeclareItem(string key, Func<ItemDefinition> factory)
		{
			if (LibraryState.Declarations.TryDeclareItem(key, factory, out var error))
			{
				return true;
			}
			LibraryLog.Source.LogError((object)("Item '" + key + "' was not declared: " + error + "."));
			return false;
		}

		public static bool DeclareEnchantment(string key, string appliedByItemKey, Func<EnchantmentDefinition> factory)
		{
			if (LibraryState.Declarations.TryDeclareEnchantment(key, appliedByItemKey, factory, out var error))
			{
				return true;
			}
			LibraryLog.Source.LogError((object)("Enchantment '" + key + "' was not declared: " + error + "."));
			return false;
		}

		public static bool DeclareRecipe(string key, Func<RecipeData> factory)
		{
			return DeclareRecipe(key, factory, knownFromStart: false);
		}

		public static bool DeclareRecipe(string key, Func<RecipeData> factory, bool knownFromStart)
		{
			if (LibraryState.Declarations.TryDeclareRecipe(key, factory, knownFromStart, out var error))
			{
				return true;
			}
			LibraryLog.Source.LogError((object)("Recipe '" + key + "' was not declared: " + error + "."));
			return false;
		}
	}
	public static class ContentTexts
	{
		public static int LoadFrom(BaseUnityPlugin plugin)
		{
			if ((Object)(object)plugin == (Object)null || plugin.Info == null || plugin.Info.Metadata == null)
			{
				LibraryLog.Source.LogError((object)"ContentTexts.LoadFrom: the plugin is null or not loaded by BepInEx.");
				return 0;
			}
			return LoadFrom(plugin, plugin.Info.Metadata.GUID);
		}

		public static int LoadFrom(BaseUnityPlugin plugin, string owner)
		{
			if ((Object)(object)plugin == (Object)null || plugin.Info == null || string.IsNullOrEmpty(owner))
			{
				LibraryLog.Source.LogError((object)"ContentTexts.LoadFrom: the plugin or the owner is missing.");
				return 0;
			}
			string text = (string.IsNullOrEmpty(plugin.Info.Location) ? null : Path.GetDirectoryName(plugin.Info.Location));
			List<string> usedDirectories;
			Dictionary<string, Dictionary<string, string>> dictionary = LangFiles.Read(text, TextStore.IsContentFileKey, delegate(string problem)
			{
				LibraryLog.Source.LogWarning((object)("Texts of '" + owner + "': " + problem + "."));
			}, out usedDirectories);
			int num = 0;
			foreach (KeyValuePair<string, Dictionary<string, string>> item in dictionary)
			{
				LibraryState.Texts.MergeFile(owner, item.Key, item.Value);
				num += item.Value.Count;
			}
			if (num == 0)
			{
				LibraryLog.Source.LogWarning((object)("Texts of '" + owner + "': no 'item.*' or 'enchantment.*' entry in any <language code>.json in or next to '" + text + "'."));
			}
			else
			{
				LibraryLog.Source.LogInfo((object)string.Format("Texts of '{0}': {1} text(s) in {2} language(s) from '{3}'.", owner, num, dictionary.Count, string.Join("', '", usedDirectories)));
			}
			ContentTextPublisher.TextsChanged(fromFile: true);
			return num;
		}

		public static bool SetItemName(string key, string languageCode, string text)
		{
			return Set(key, languageCode, TextStore.ItemTerm(key ?? string.Empty, TextKind.Name), text, "SetItemName");
		}

		public static bool SetItemDescription(string key, string languageCode, string text)
		{
			return Set(key, languageCode, TextStore.ItemTerm(key ?? string.Empty, TextKind.Description), text, "SetItemDescription");
		}

		public static bool SetItemFlavor(string key, string languageCode, string text)
		{
			return Set(key, languageCode, TextStore.ItemTerm(key ?? string.Empty, TextKind.Flavor), text, "SetItemFlavor");
		}

		public static bool SetEnchantmentName(string key, string languageCode, string text)
		{
			return Set(key, languageCode, TextStore.EnchantmentTerm(key ?? string.Empty), text, "SetEnchantmentName");
		}

		private static bool Set(string key, string languageCode, string term, string text, string method)
		{
			if (!ContentKey.TryValidate(key, out var error))
			{
				LibraryLog.Source.LogError((object)("ContentTexts." + method + ": '" + key + "' is not a content key: " + error + "."));
				return false;
			}
			if (string.IsNullOrEmpty(languageCode))
			{
				LibraryLog.Source.LogError((object)("ContentTexts." + method + ": no language code for '" + key + "'."));
				return false;
			}
			LibraryState.Texts.SetDirect(term, languageCode, text);
			ContentTextPublisher.TextsChanged(fromFile: false);
			return true;
		}
	}
	public static class ItemTemplates
	{
		private static readonly Dictionary<string, Sprite> Sprites = new Dictionary<string, Sprite>(StringComparer.OrdinalIgnoreCase);

		private static MethodInfo _loadImage;

		public static T Clone<T>(T template) where T : ItemDefinition
		{
			if ((Object)(object)template == (Object)null)
			{
				throw new ArgumentNullException("template");
			}
			T val = Object.Instantiate<T>(template);
			((Object)(object)val).hideFlags = (HideFlags)61;
			return val;
		}

		public static Sprite LoadSprite(BaseUnityPlugin plugin, string fileName)
		{
			if ((Object)(object)plugin == (Object)null)
			{
				throw new ArgumentNullException("plugin");
			}
			string text;
			List<string> list;
			try
			{
				string location = plugin.Info.Location;
				text = (string.IsNullOrEmpty(location) ? null : Path.GetDirectoryName(location));
				list = SpriteCandidates(text, fileName, out var error);
				if (list == null)
				{
					LibraryLog.Source.LogError((object)("ItemTemplates.LoadSprite: '" + fileName + "' cannot be used: " + error + "."));
					return null;
				}
			}
			catch (Exception ex)
			{
				LibraryLog.Source.LogError((object)("ItemTemplates.LoadSprite: '" + fileName + "' cannot be used: " + ex.Message));
				return null;
			}
			foreach (string item in list)
			{
				if (Sprites.TryGetValue(item, out var value) && (Object)(object)value != (Object)null)
				{
					return value;
				}
				if (File.Exists(item))
				{
					Sprite val = ReadSprite(item);
					if ((Object)(object)val != (Object)null)
					{
						Sprites[item] = val;
					}
					return val;
				}
			}
			LibraryLog.Source.LogError((object)("ItemTemplates.LoadSprite: no file '" + fileName + "' in '" + text + "'."));
			return null;
		}

		internal static List<string> SpriteCandidates(string pluginDirectory, string fileName, out string error)
		{
			if (string.IsNullOrEmpty(pluginDirectory))
			{
				error = "the plugin has no folder";
				return null;
			}
			if (string.IsNullOrWhiteSpace(fileName))
			{
				error = "the file name is empty";
				return null;
			}
			if (Path.IsPathRooted(fileName) || fileName.IndexOf(':') >= 0)
			{
				error = "give a path relative to your plugin's folder";
				return null;
			}
			string[] array = fileName.Split('/', '\\');
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				if (array2[i] == "..")
				{
					error = "the path must stay inside your plugin's folder";
					return null;
				}
			}
			error = null;
			List<string> list = new List<string> { Path.Combine(pluginDirectory, Path.Combine(array)) };
			if (array.Length > 1)
			{
				list.Add(Path.Combine(pluginDirectory, array[^1]));
			}
			return list;
		}

		private static bool LoadImage(Texture2D texture, byte[] bytes)
		{
			if (_loadImage == null)
			{
				_loadImage = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule")?.GetMethod("LoadImage", new Type[3]
				{
					typeof(Texture2D),
					typeof(byte[]),
					typeof(bool)
				});
				if (_loadImage == null)
				{
					throw new MissingMethodException("UnityEngine.ImageConversion.LoadImage was not found");
				}
			}
			return (bool)_loadImage.Invoke(null, new object[3] { texture, bytes, true });
		}

		private static Sprite ReadSprite(string path)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = null;
			try
			{
				byte[] bytes = File.ReadAllBytes(path);
				val = new Texture2D(2, 2, (TextureFormat)4, false);
				if (!LoadImage(val, bytes))
				{
					Object.Destroy((Object)(object)val);
					LibraryLog.Source.LogError((object)("ItemTemplates.LoadSprite: '" + path + "' is not a PNG or JPG image."));
					return null;
				}
				((Object)val).name = Path.GetFileNameWithoutExtension(path);
				((Texture)val).filterMode = (FilterMode)1;
				((Texture)val).wrapMode = (TextureWrapMode)1;
				((Object)val).hideFlags = (HideFlags)61;
				Sprite obj = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
				((Object)obj).name = ((Object)val).name;
				((Object)obj).hideFlags = (HideFlags)61;
				return obj;
			}
			catch (Exception ex)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
				LibraryLog.Source.LogError((object)("ItemTemplates.LoadSprite: could not read '" + path + "': " + (ex.InnerException ?? ex).Message));
				return null;
			}
		}
	}
	[BepInPlugin("ryuka.sulfur.contentlib", "SULFUR Content Lib", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class ContentLibPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "ryuka.sulfur.contentlib";

		public const string PluginName = "SULFUR Content Lib";

		public const string PluginVersion = "0.1.0";

		internal static ContentLibPlugin Instance { get; private set; }

		private void Awake()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			Instance = this;
			Harmony val = new Harmony("ryuka.sulfur.contentlib");
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(ContentLibPlugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0)
				{
					try
					{
						val.CreateClassProcessor(type).Patch();
					}
					catch (Exception ex)
					{
						LibraryState.Fault("patch " + type.Name + " could not be applied: " + ex.Message);
					}
				}
			}
			DevSpawnMenuPatch.Install(val);
			RecipeKnownPatch.Install(val);
			SessionRequirementRegistration.TryRegister();
			LibraryLog.Source.LogInfo((object)"SULFUR Content Lib 0.1.0 loaded.");
		}
	}
	internal static class KnownMods
	{
		internal const string CustomItemFrameworkHarmonyId = "com.customitemframework";

		internal const string SulfurTogetherGuid = "com.ryuka.sulfur.together";
	}
	internal static class LibraryLog
	{
		internal static readonly ManualLogSource Source = Logger.CreateLogSource("SULFUR Content Lib");
	}
	internal static class LibraryState
	{
		internal static readonly DeclarationSet Declarations = new DeclarationSet();

		internal static readonly TextStore Texts = new TextStore();

		internal static readonly OnceSignal SealedSignal = new OnceSignal(delegate(Exception ex)
		{
			LibraryLog.Source.LogError((object)$"A ContentManifest.Sealed handler threw: {ex}");
		});

		internal static ContentTables Tables { get; private set; }

		internal static bool IsFaulted { get; private set; }

		internal static string FaultReason { get; private set; }

		internal static IReadOnlyDictionary<string, RecipeData> Recipes { get; private set; } = new Dictionary<string, RecipeData>();

		internal static HashSet<string> RecipesKnownFromStart { get; private set; } = new HashSet<string>(StringComparer.Ordinal);

		internal static void PublishRecipes(Dictionary<string, RecipeData> recipes, HashSet<string> knownFromStart)
		{
			Recipes = recipes;
			RecipesKnownFromStart = knownFromStart;
		}

		internal static void PublishTables(ContentTables tables)
		{
			if (!IsFaulted)
			{
				Tables = tables;
			}
		}

		internal static void Fault(string reason)
		{
			if (IsFaulted)
			{
				LibraryLog.Source.LogError((object)("Further integrity fault: " + reason));
				return;
			}
			IsFaulted = true;
			FaultReason = reason;
			LibraryLog.Source.LogError((object)("INTEGRITY FAULT: " + reason + ". Saves that contain library items will not be written this session, so nothing on disk is changed."));
		}
	}
}
namespace Ryuka.Sulfur.ContentLib.Texts
{
	internal static class ContentTextPublisher
	{
		internal readonly struct OriginalDescription
		{
			internal string Description { get; }

			internal bool HasCustomDescription { get; }

			internal OriginalDescription(string description, bool hasCustomDescription)
			{
				Description = description;
				HasCustomDescription = hasCustomDescription;
			}
		}

		private sealed class ByReference : IEqualityComparer<ItemDefinition>
		{
			internal static readonly ByReference Instance = new ByReference();

			public bool Equals(ItemDefinition x, ItemDefinition y)
			{
				return x == y;
			}

			public int GetHashCode(ItemDefinition obj)
			{
				return RuntimeHelpers.GetHashCode(obj);
			}
		}

		private static readonly Dictionary<ItemDefinition, OriginalDescription> Originals = new Dictionary<ItemDefinition, OriginalDescription>(ByReference.Instance);

		private static bool _started;

		internal static void Start(MonoBehaviour host)
		{
			if (LibraryState.Tables != null)
			{
				_started = true;
				ReportUnmatched();
				GameLocalization.Publish(BuildTerms, host);
			}
		}

		internal static void TextsChanged(bool fromFile)
		{
			if (_started)
			{
				if (fromFile)
				{
					ReportUnmatched();
				}
				GameLocalization.Refresh();
			}
		}

		private static Dictionary<string, Dictionary<string, string>> BuildTerms(string currentLanguageCode)
		{
			ContentTables tables = LibraryState.Tables;
			Dictionary<string, Dictionary<string, string>> dictionary = LibraryState.Texts.Collect(tables.ItemsByKey.Keys, tables.EnchantmentsByKey.Keys);
			foreach (KeyValuePair<string, ItemDefinition> item in tables.ItemsByKey)
			{
				if (item.Value != null)
				{
					AddDefault(dictionary, TextStore.ItemTerm(item.Key, TextKind.Name), item.Value.displayName);
					dictionary.TryGetValue(TextStore.ItemTerm(item.Key, TextKind.Description), out var value);
					ApplyDescription(item.Value, (value == null) ? null : TextStore.Resolve(value, currentLanguageCode));
				}
			}
			foreach (KeyValuePair<string, EnchantmentDefinition> item2 in tables.EnchantmentsByKey)
			{
				if (item2.Value != null)
				{
					AddDefault(dictionary, TextStore.EnchantmentTerm(item2.Key), item2.Value.enchantmentName);
				}
			}
			return dictionary;
		}

		internal static void AddDefault(Dictionary<string, Dictionary<string, string>> terms, string term, string text)
		{
			if (!string.IsNullOrEmpty(text))
			{
				if (!terms.TryGetValue(term, out var value))
				{
					value = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
					terms.Add(term, value);
				}
				if (!value.ContainsKey("en"))
				{
					value.Add("en", text);
				}
			}
		}

		internal static OriginalDescription DescriptionFields(OriginalDescription original, bool isWeapon, bool isMelee, string text)
		{
			if (string.IsNullOrEmpty(text) || (isWeapon && isMelee))
			{
				return original;
			}
			if (!isWeapon)
			{
				return new OriginalDescription(original.Description, hasCustomDescription: true);
			}
			return new OriginalDescription(string.IsNullOrEmpty(original.Description) ? text : original.Description, original.HasCustomDescription);
		}

		private static void ApplyDescription(ItemDefinition item, string text)
		{
			if (!Originals.TryGetValue(item, out var value))
			{
				if (string.IsNullOrEmpty(text))
				{
					return;
				}
				value = new OriginalDescription(item.description, item.hasCustomDescription);
				Originals.Add(item, value);
			}
			WeaponSO val = (WeaponSO)(object)((item is WeaponSO) ? item : null);
			OriginalDescription originalDescription = DescriptionFields(value, val != null, val != null && val.IsMelee, text);
			item.description = originalDescription.Description;
			item.hasCustomDescription = originalDescription.HasCustomDescription;
		}

		private static void ReportUnmatched()
		{
			DeclarationSet declarations = LibraryState.Declarations;
			List<string> list = new List<string>();
			foreach (ItemDeclaration item in declarations.Items)
			{
				list.Add(item.Key);
			}
			List<string> list2 = new List<string>();
			foreach (EnchantmentDeclaration enchantment in declarations.Enchantments)
			{
				list2.Add(enchantment.Key);
			}
			List<string> list3 = LibraryState.Texts.UnmatchedFileKeys(list, list2);
			if (list3.Count > 0)
			{
				string arg = string.Join("; ", list3.GetRange(0, Math.Min(20, list3.Count)));
				LibraryLog.Source.LogWarning((object)($"{list3.Count} text(s) in language files name no declared item or enchantment and are not used: {arg}" + ((list3.Count > 20) ? "; ..." : string.Empty) + ". The name is the part of the content key after the colon."));
			}
		}
	}
	internal static class GameLocalization
	{
		private const MissingTranslationAction LeaveNull = (MissingTranslationAction)4;

		private static LanguageSourceData _source;

		private static OnLanguageChange _handler;

		private static Func<string, Dictionary<string, Dictionary<string, string>>> _terms;

		private static MonoBehaviour _host;

		private static bool _refreshQueued;

		internal static void Publish(Func<string, Dictionary<string, Dictionary<string, string>>> terms, MonoBehaviour host)
		{
			_terms = terms;
			_host = host;
			Rebuild();
			HookLanguageChange();
		}

		internal static void Refresh()
		{
			if (_terms != null && !_refreshQueued)
			{
				if ((Object)(object)_host == (Object)null || !((Behaviour)_host).isActiveAndEnabled)
				{
					Rebuild();
					return;
				}
				_refreshQueued = true;
				_host.StartCoroutine(RebuildNextFrame());
			}
		}

		private static IEnumerator RebuildNextFrame()
		{
			yield return null;
			_refreshQueued = false;
			Rebuild();
		}

		private static void Rebuild()
		{
			try
			{
				int count;
				LanguageSourceData val = Build(out count);
				if (_source != null)
				{
					LocalizationManager.RemoveSource(_source);
					_source = null;
				}
				if (val != null)
				{
					LocalizationManager.AddSource(val);
					_source = val;
					LocalizationManager.LocalizeAll(true);
					LibraryLog.Source.LogInfo((object)($"Texts published: {count} term(s) in {val.mLanguages.Count} language(s); " + "game language '" + LocalizationManager.CurrentLanguage + "' (" + LocalizationManager.CurrentLanguageCode + ")."));
				}
			}
			catch (Exception arg)
			{
				LibraryLog.Source.LogError((object)$"Could not publish item and enchantment texts; what was published before stays: {arg}");
			}
		}

		private static LanguageSourceData Build(out int count)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			count = 0;
			Dictionary<string, Dictionary<string, string>> dictionary = _terms(LocalizationManager.CurrentLanguageCode);
			if (dictionary.Count == 0)
			{
				return null;
			}
			List<LanguageData> list = GameLanguages();
			if (list.Count == 0)
			{
				LibraryLog.Source.LogWarning((object)"The game lists no language yet; item and enchantment texts are not published until the language changes.");
				return null;
			}
			LanguageSourceData val = new LanguageSourceData
			{
				mLanguages = list,
				mTerms = new List<TermData>(),
				OnMissingTranslation = (MissingTranslationAction)4
			};
			foreach (KeyValuePair<string, Dictionary<string, string>> item in dictionary)
			{
				TermData val2 = val.AddTerm(item.Key, (eTermType)0, true);
				val2.Languages = new string[list.Count];
				val2.Flags = new byte[list.Count];
				for (int i = 0; i < list.Count; i++)
				{
					val2.Languages[i] = TextStore.Resolve(item.Value, list[i].Code) ?? string.Empty;
				}
				count++;
			}
			val.UpdateDictionary(true);
			return val;
		}

		private static List<LanguageData> GameLanguages()
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected O, but got Unknown
			List<LanguageData> list = new List<LanguageData>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (LanguageSourceData source in LocalizationManager.Sources)
			{
				if (source == null || source == _source || source.mLanguages == null)
				{
					continue;
				}
				foreach (LanguageData mLanguage in source.mLanguages)
				{
					if (mLanguage != null && !string.IsNullOrEmpty(mLanguage.Name) && hashSet.Add(mLanguage.Name))
					{
						list.Add(new LanguageData
						{
							Name = mLanguage.Name,
							Code = mLanguage.Code,
							Flags = mLanguage.Flags
						});
					}
				}
			}
			return list;
		}

		private static void HookLanguageChange()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			if (_handler == null)
			{
				AsyncAssetLoading instance = StaticInstance<AsyncAssetLoading>.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					_handler = new OnLanguageChange(Rebuild);
					instance.onLanguageChange = (OnLanguageChange)Delegate.Combine((Delegate?)(object)_handler, (Delegate?)(object)instance.onLanguageChange);
				}
			}
		}
	}
	internal static class LangFiles
	{
		[DataContract]
		private sealed class LangEntry
		{
			[DataMember(Name = "key")]
			public string Key { get; set; }

			[DataMember(Name = "value")]
			public string Value { get; set; }
		}

		[DataContract]
		private sealed class LangFile
		{
			[DataMember(Name = "entries")]
			public LangEntry[] Entries { get; set; }
		}

		private static readonly HashSet<string> KnownCodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
		{
			"en", "sv", "fr", "it", "de", "es", "pt", "ru", "pl", "ja",
			"ko", "zh-CN", "tr", "zh-TW", "ar", "zh"
		};

		internal static IEnumerable<string> SearchDirectories(string pluginDirectory)
		{
			if (!string.IsNullOrEmpty(pluginDirectory))
			{
				yield return Path.Combine(pluginDirectory, "lang");
				yield return pluginDirectory;
				string directoryName = Path.GetDirectoryName(pluginDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
				if (!string.IsNullOrEmpty(directoryName))
				{
					yield return Path.Combine(directoryName, "lang");
				}
			}
		}

		internal static Dictionary<string, Dictionary<string, string>> Read(string pluginDirectory, Func<string, bool> wanted, Action<string> problem, out List<string> usedDirectories)
		{
			Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
			usedDirectories = new List<string>();
			foreach (string item in SearchDirectories(pluginDirectory))
			{
				bool flag = false;
				foreach (KeyValuePair<string, Dictionary<string, string>> item2 in ReadDirectory(item, wanted, problem))
				{
					if (!dictionary.TryGetValue(item2.Key, out var value))
					{
						value = new Dictionary<string, string>(StringComparer.Ordinal);
						dictionary.Add(item2.Key, value);
					}
					foreach (KeyValuePair<string, string> item3 in item2.Value)
					{
						if (!value.ContainsKey(item3.Key))
						{
							value.Add(item3.Key, item3.Value);
							flag = true;
						}
					}
				}
				if (flag)
				{
					usedDirectories.Add(item);
				}
			}
			return dictionary;
		}

		internal static bool LooksLikeLanguageCode(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			int num = name.IndexOf('-');
			string text = ((num < 0) ? name : name.Substring(0, num));
			if (text.Length < 2 || text.Length > 3 || !AllLetters(text, digitsToo: false))
			{
				return false;
			}
			if (num < 0)
			{
				return true;
			}
			string text2 = name.Substring(num + 1);
			if (text2.Length >= 2 && text2.Length <= 4)
			{
				return AllLetters(text2, digitsToo: true);
			}
			return false;
		}

		private static bool AllLetters(string text, bool digitsToo)
		{
			foreach (char c in text)
			{
				if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (!digitsToo || c < '0' || c > '9'))
				{
					return false;
				}
			}
			return true;
		}

		private static Dictionary<string, Dictionary<string, string>> ReadDirectory(string directory, Func<string, bool> wanted, Action<string> problem)
		{
			Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
			if (!Directory.Exists(directory))
			{
				return dictionary;
			}
			string[] files;
			try
			{
				files = Directory.GetFiles(directory, "*.json", SearchOption.TopDirectoryOnly);
			}
			catch (Exception ex)
			{
				problem("could not list '" + directory + "': " + ex.Message);
				return dictionary;
			}
			Array.Sort(files, (IComparer<string>?)StringComparer.OrdinalIgnoreCase);
			string[] array = files;
			foreach (string text in array)
			{
				string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
				if (LooksLikeLanguageCode(fileNameWithoutExtension))
				{
					Dictionary<string, string> dictionary2 = ReadFile(text, wanted, KnownCodes.Contains(fileNameWithoutExtension) ? problem : ((Action<string>)delegate
					{
					}));
					if (dictionary2.Count > 0)
					{
						dictionary[fileNameWithoutExtension] = dictionary2;
					}
				}
			}
			return dictionary;
		}

		private static Dictionary<string, string> ReadFile(string file, Func<string, bool> wanted, Action<string> problem)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			try
			{
				byte[] array = File.ReadAllBytes(file);
				int num = ((array.Length >= 3 && array[0] == 239 && array[1] == 187 && array[2] == 191) ? 3 : 0);
				using MemoryStream memoryStream = new MemoryStream(array, num, array.Length - num, writable: false);
				if (((XmlObjectSerializer)new DataContractJsonSerializer(typeof(LangFile))).ReadObject((Stream)memoryStream) is LangFile { Entries: not null, Entries: var entries })
				{
					foreach (LangEntry langEntry in entries)
					{
						if (langEntry != null && langEntry.Key != null && !string.IsNullOrEmpty(langEntry.Value) && wanted(langEntry.Key))
						{
							dictionary[langEntry.Key] = langEntry.Value;
						}
					}
				}
			}
			catch (Exception ex)
			{
				problem("could not read '" + file + "': " + ex.Message);
			}
			return dictionary;
		}
	}
	internal enum TextKind
	{
		Name,
		Description,
		Flavor
	}
	internal sealed class TextStore
	{
		internal const string ItemTermPrefix = "Items/";

		internal const string EnchantmentTermPrefix = "EnchantmentDefinitions/";

		internal const string DescriptionSuffix = "_description";

		internal const string FlavorSuffix = "_flavor";

		internal const string EnglishCode = "en";

		private static readonly TextKind[] ItemKinds = new TextKind[3]
		{
			TextKind.Name,
			TextKind.Description,
			TextKind.Flavor
		};

		private readonly Dictionary<string, Dictionary<string, Dictionary<string, string>>> _files = new Dictionary<string, Dictionary<string, Dictionary<string, string>>>(StringComparer.Ordinal);

		private readonly Dictionary<string, Dictionary<string, string>> _direct = new Dictionary<string, Dictionary<string, string>>(StringComparer.Ordinal);

		internal static string ItemTerm(string key, TextKind kind)
		{
			return kind switch
			{
				TextKind.Description => "Items/" + key + "_description", 
				TextKind.Flavor => "Items/" + key + "_flavor", 
				_ => "Items/" + key, 
			};
		}

		internal static string EnchantmentTerm(string key)
		{
			return "EnchantmentDefinitions/" + key;
		}

		internal static string ItemFileKey(string name, TextKind kind)
		{
			return kind switch
			{
				TextKind.Description => "item." + name + ".description", 
				TextKind.Flavor => "item." + name + ".flavor", 
				_ => "item." + name + ".name", 
			};
		}

		internal static string EnchantmentFileKey(string name)
		{
			return "enchantment." + name + ".name";
		}

		internal static bool IsContentFileKey(string fileKey)
		{
			if (fileKey != null)
			{
				if (!fileKey.StartsWith("item.", StringComparison.Ordinal))
				{
					return fileKey.StartsWith("enchantment.", StringComparison.Ordinal);
				}
				return true;
			}
			return false;
		}

		internal static bool EndsWithReservedSuffix(string key)
		{
			if (!key.EndsWith("_description", StringComparison.OrdinalIgnoreCase))
			{
				return key.EndsWith("_flavor", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		internal static IReadOnlyList<string> FallbackChain(string code)
		{
			if (string.IsNullOrEmpty(code))
			{
				return new string[1] { "en" };
			}
			if (string.Equals(code, "zh", StringComparison.OrdinalIgnoreCase) || string.Equals(code, "zh-TW", StringComparison.OrdinalIgnoreCase))
			{
				return new string[4] { "zh-TW", "zh-CN", "zh", "en" };
			}
			List<string> list = new List<string> { code };
			int num = code.IndexOf('-');
			if (num > 0)
			{
				list.Add(code.Substring(0, num));
			}
			if (!list.Contains("en"))
			{
				list.Add("en");
			}
			return list;
		}

		internal void MergeFile(string owner, string code, IReadOnlyDictionary<string, string> entries)
		{
			if (!_files.TryGetValue(owner, out var value))
			{
				value = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
				_files.Add(owner, value);
			}
			if (!value.TryGetValue(code, out var value2))
			{
				value2 = new Dictionary<string, string>(StringComparer.Ordinal);
				value.Add(code, value2);
			}
			foreach (KeyValuePair<string, string> entry in entries)
			{
				value2[entry.Key] = entry.Value;
			}
		}

		internal void SetDirect(string term, string code, string text)
		{
			if (!_direct.TryGetValue(term, out var value))
			{
				if (string.IsNullOrEmpty(text))
				{
					return;
				}
				value = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				_direct.Add(term, value);
			}
			if (string.IsNullOrEmpty(text))
			{
				value.Remove(code);
			}
			else
			{
				value[code] = text;
			}
		}

		internal Dictionary<string, Dictionary<string, string>> Collect(IEnumerable<string> itemKeys, IEnumerable<string> enchantmentKeys)
		{
			Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(StringComparer.Ordinal);
			foreach (string itemKey in itemKeys)
			{
				TextKind[] itemKinds = ItemKinds;
				foreach (TextKind kind in itemKinds)
				{
					AddTerm(dictionary, ItemTerm(itemKey, kind), itemKey, (string name) => ItemFileKey(name, kind));
				}
			}
			foreach (string enchantmentKey in enchantmentKeys)
			{
				AddTerm(dictionary, EnchantmentTerm(enchantmentKey), enchantmentKey, EnchantmentFileKey);
			}
			return dictionary;
		}

		internal List<string> UnmatchedFileKeys(IEnumerable<string> itemKeys, IEnumerable<string> enchantmentKeys)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (string itemKey in itemKeys)
			{
				if (TrySplit(itemKey, out var owner, out var name))
				{
					TextKind[] itemKinds = ItemKinds;
					foreach (TextKind kind in itemKinds)
					{
						hashSet.Add(owner + ": " + ItemFileKey(name, kind));
					}
				}
			}
			foreach (string enchantmentKey in enchantmentKeys)
			{
				if (TrySplit(enchantmentKey, out var owner2, out var name2))
				{
					hashSet.Add(owner2 + ": " + EnchantmentFileKey(name2));
				}
			}
			SortedSet<string> sortedSet = new SortedSet<string>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, Dictionary<string, Dictionary<string, string>>> file in _files)
			{
				foreach (Dictionary<string, string> value in file.Value.Values)
				{
					foreach (string key in value.Keys)
					{
						string item = file.Key + ": " + key;
						if (!hashSet.Contains(item))
						{
							sortedSet.Add(item);
						}
					}
				}
			}
			return new List<string>(sortedSet);
		}

		internal static string Resolve(IReadOnlyDictionary<string, string> byCode, string code)
		{
			foreach (string item in FallbackChain(code))
			{
				if (TryGetIgnoreCase(byCode, item, out var text) && !string.IsNullOrEmpty(text))
				{
					return text;
				}
			}
			return null;
		}

		private static bool TrySplit(string key, out string owner, out string name)
		{
			int num = key.IndexOf(':');
			if (num <= 0 || num == key.Length - 1)
			{
				owner = null;
				name = null;
				return false;
			}
			owner = key.Substring(0, num);
			name = key.Substring(num + 1);
			return true;
		}

		private static bool TryGetIgnoreCase(IReadOnlyDictionary<string, string> byCode, string code, out string text)
		{
			if (byCode.TryGetValue(code, out text))
			{
				return true;
			}
			foreach (KeyValuePair<string, string> item in byCode)
			{
				if (string.Equals(item.Key, code, StringComparison.OrdinalIgnoreCase))
				{
					text = item.Value;
					return true;
				}
			}
			text = null;
			return false;
		}

		private void AddTerm(Dictionary<string, Dictionary<string, string>> terms, string term, string key, Func<string, string> fileKey)
		{
			if (!terms.TryGetValue(term, out var value))
			{
				value = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			}
			if (TrySplit(key, out var owner, out var name) && _files.TryGetValue(owner, out var value2))
			{
				string key2 = fileKey(name);
				foreach (KeyValuePair<string, Dictionary<string, string>> item in value2)
				{
					if (item.Value.TryGetValue(key2, out var value3) && !string.IsNullOrEmpty(value3))
					{
						value[item.Key] = value3;
					}
				}
			}
			if (_direct.TryGetValue(term, out var value4))
			{
				foreach (KeyValuePair<string, string> item2 in value4)
				{
					value[item2.Key] = item2.Value;
				}
			}
			if (value.Count > 0)
			{
				terms[term] = value;
			}
		}
	}
}
namespace Ryuka.Sulfur.ContentLib.Saves
{
	internal static class InsuredHold
	{
		internal sealed class Swap
		{
			private readonly SulfurSaveState _state;

			private readonly List<InventoryData> _insured;

			private readonly SerializableDictionary<string, SerializableList<InventoryData>> _stash;

			internal bool HadHold { get; }

			internal SerializableList<InventoryData> OriginalHold { get; }

			internal Swap(SulfurSaveState state, List<InventoryData> insured, SerializableDictionary<string, SerializableList<InventoryData>> stash)
			{
				_state = state;
				_insured = insured;
				_stash = stash;
				HadHold = ((Dictionary<string, SerializableList<InventoryData>>)(object)stash)?.TryGetValue("contentlib|insured", out SerializableList<InventoryData> _) ?? false;
				OriginalHold = (HadHold ? ((Dictionary<string, SerializableList<InventoryData>>)(object)stash)["contentlib|insured"] : null);
			}

			internal void End()
			{
				_state.INSURED_ITEMS = _insured;
				if (_stash == null)
				{
					_state.STASH = null;
					return;
				}
				_state.STASH = _stash;
				if (HadHold)
				{
					((Dictionary<string, SerializableList<InventoryData>>)(object)_stash)["contentlib|insured"] = OriginalHold;
				}
				else
				{
					((Dictionary<string, SerializableList<InventoryData>>)(object)_stash).Remove("contentlib|insured");
				}
			}
		}

		internal const string StashKey = "contentlib|insured";

		internal static Swap Begin(SulfurSaveState state)
		{
			List<InventoryData> iNSURED_ITEMS = state.INSURED_ITEMS;
			if (iNSURED_ITEMS == null)
			{
				return null;
			}
			List<InventoryData> list = null;
			foreach (InventoryData item in iNSURED_ITEMS)
			{
				if (IsLibraryHost(item))
				{
					(list ?? (list = new List<InventoryData>())).Add(item);
				}
			}
			if (list == null)
			{
				return null;
			}
			List<InventoryData> list2 = new List<InventoryData>(iNSURED_ITEMS.Count - list.Count);
			foreach (InventoryData item2 in iNSURED_ITEMS)
			{
				if (!IsLibraryHost(item2))
				{
					list2.Add(item2);
				}
			}
			Swap swap = new Swap(state, iNSURED_ITEMS, state.STASH);
			List<InventoryData> list3 = new List<InventoryData>();
			if (swap.HadHold && swap.OriginalHold?.list != null)
			{
				list3.AddRange(swap.OriginalHold.list);
			}
			list3.AddRange(list);
			if (state.STASH == null)
			{
				state.STASH = new SerializableDictionary<string, SerializableList<InventoryData>>();
			}
			((Dictionary<string, SerializableList<InventoryData>>)(object)state.STASH)["contentlib|insured"] = new SerializableList<InventoryData>(list3);
			state.INSURED_ITEMS = list2;
			return swap;
		}

		internal static int PutBack(SulfurSaveState state, List<InventoryData> strays)
		{
			if (state.STASH == null || !((Dictionary<string, SerializableList<InventoryData>>)(object)state.STASH).TryGetValue("contentlib|insured", out SerializableList<InventoryData> value))
			{
				return 0;
			}
			int num = 0;
			if (value?.list != null && value.list.Count > 0)
			{
				List<InventoryData> list = ((state.INSURED_ITEMS == null) ? new List<InventoryData>(value.list.Count) : new List<InventoryData>(state.INSURED_ITEMS));
				foreach (InventoryData item in value.list)
				{
					if (item != null)
					{
						if (item.id.value == ItemId.None.value)
						{
							strays.Add(item);
							continue;
						}
						list.Add(item);
						num++;
					}
				}
				state.INSURED_ITEMS = list;
			}
			((Dictionary<string, SerializableList<InventoryData>>)(object)state.STASH).Remove("contentlib|insured");
			return num;
		}

		private static bool IsLibraryHost(InventoryData item)
		{
			if (item != null)
			{
				return Numbering.IsItemNumber(item.id.value);
			}
			return false;
		}
	}
	internal enum SaveStateStatus
	{
		PassedThrough,
		Normalized,
		Blocked
	}
	internal static class SaveStateTracker
	{
		private sealed class Entry
		{
			internal SaveStateStatus Status;

			internal string Reason;

			internal int BlockedWrites;

			internal bool Alerted;
		}

		private static readonly ConditionalWeakTable<SulfurSaveState, Entry> States = new ConditionalWeakTable<SulfurSaveState, Entry>();

		internal static void Set(SulfurSaveState state, SaveStateStatus status, string reason = null)
		{
			Entry orCreateValue = States.GetOrCreateValue(state);
			orCreateValue.Status = status;
			orCreateValue.Reason = reason;
		}

		internal static bool TryGet(SulfurSaveState state, out SaveStateStatus status, out string reason)
		{
			if (States.TryGetValue(state, out var value))
			{
				status = value.Status;
				reason = value.Reason;
				return true;
			}
			status = SaveStateStatus.PassedThrough;
			reason = null;
			return false;
		}

		internal static int CountBlockedWrite(SulfurSaveState state)
		{
			return ++States.GetOrCreateValue(state).BlockedWrites;
		}

		internal static bool WasAlerted(SulfurSaveState state)
		{
			if (States.TryGetValue(state, out var value))
			{
				return value.Alerted;
			}
			return false;
		}

		internal static void MarkAlerted(SulfurSaveState state)
		{
			States.GetOrCreateValue(state).Alerted = true;
		}
	}
	internal sealed class NormalizingEnumerator : IEnumerator<SulfurSaveState>, IDisposable, IEnumerator
	{
		private readonly IEnumerator<SulfurSaveState> _inner;

		private readonly int _slot;

		private readonly bool _backup;

		private SulfurSaveState _handled;

		public SulfurSaveState Current { get; private set; }

		object IEnumerator.Current => Current;

		internal NormalizingEnumerator(IEnumerator<SulfurSaveState> inner, int slot, bool backup)
		{
			_inner = inner;
			_slot = slot;
			_backup = backup;
		}

		public bool MoveNext()
		{
			if (!_inner.MoveNext())
			{
				return false;
			}
			SulfurSaveState current = _inner.Current;
			if (current != null && current != _handled)
			{
				_handled = current;
				SaveGuard.OnRead(current, _slot, _backup);
			}
			Current = current;
			return true;
		}

		public void Reset()
		{
			_inner.Reset();
			_handled = null;
			Current = null;
		}

		public void Dispose()
		{
			_inner.Dispose();
		}
	}
	internal enum WriteDecision
	{
		Pass,
		Stamp,
		Refuse
	}
	internal static class SaveGuard
	{
		internal static void OnRead(SulfurSaveState state, int slot, bool backup)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((int)state.loadResult != 0)
				{
					return;
				}
				string text = (backup ? $"slot {slot} backup" : $"slot {slot}");
				if (LibraryState.IsFaulted)
				{
					if (SaveNormalizer.ContainsLibraryNumbers(state))
					{
						Block(state, text + " holds library items and the library is faulted (" + LibraryState.FaultReason + ")");
					}
					else
					{
						SaveStateTracker.Set(state, SaveStateStatus.PassedThrough);
					}
					return;
				}
				if (LibraryState.Declarations.Phase != RegistryPhase.Sealed)
				{
					SaveStateTracker.Set(state, SaveStateStatus.PassedThrough);
					return;
				}
				NormalizeReport normalizeReport = SaveNormalizer.Normalize(state, LibraryState.Tables.LiveItemNumbers, LibraryState.Tables.LiveEnchantmentKeyByItemKey);
				SaveStateTracker.Set(state, SaveStateStatus.Normalized);
				if (normalizeReport.Changed)
				{
					LibraryLog.Source.LogInfo((object)$"Read {text}: {normalizeReport}.");
				}
			}
			catch (Exception ex)
			{
				Block(state, string.Format("slot {0}{1} could not be normalized: {2}", slot, backup ? " backup" : string.Empty, ex.Message));
				LibraryLog.Source.LogDebug((object)ex.ToString());
			}
		}

		internal static bool AllowWrite(SulfurSaveState state, Action<SaveResults> onCompleted, out InsuredHold.Swap swap)
		{
			swap = null;
			try
			{
				if (state == null)
				{
					return true;
				}
				SaveStateStatus status;
				string reason;
				bool flag = SaveStateTracker.TryGet(state, out status, out reason);
				string reason2;
				switch (DecideWrite(state, LibraryState.Declarations.Phase == RegistryPhase.Sealed, LibraryState.IsFaulted, LibraryState.FaultReason, flag ? new SaveStateStatus?(status) : ((SaveStateStatus?)null), reason, state == SulfurSaveState.active, LibraryState.Tables?.LiveItemNumbers, out reason2))
				{
				case WriteDecision.Stamp:
					if (state.PLAYER_RECORDS == null)
					{
						state.PLAYER_RECORDS = new SerializableDictionary<string, int>();
					}
					SaveMap.Stamp((IDictionary<string, int>)state.PLAYER_RECORDS, LibraryState.Tables.LiveItemNumbers);
					swap = InsuredHold.Begin(state);
					return true;
				case WriteDecision.Refuse:
					return Refuse(state, reason2, onCompleted);
				default:
					return true;
				}
			}
			catch (Exception ex)
			{
				return Refuse(state, "the write check threw " + ex.Message, onCompleted);
			}
		}

		internal static void AfterWrite(InsuredHold.Swap swap)
		{
			if (swap == null)
			{
				return;
			}
			try
			{
				swap.End();
			}
			catch (Exception arg)
			{
				LibraryLog.Source.LogError((object)$"Could not put the insured items back after a save write: {arg}");
			}
		}

		internal static WriteDecision DecideWrite(SulfurSaveState state, bool isSealed, bool isFaulted, string faultReason, SaveStateStatus? status, string blockedReason, bool isActive, IReadOnlyDictionary<string, ushort> live, out string reason)
		{
			reason = null;
			if (status == SaveStateStatus.Blocked)
			{
				if (!isActive)
				{
					return WriteDecision.Pass;
				}
				reason = blockedReason;
				return WriteDecision.Refuse;
			}
			if (!isSealed && !isFaulted)
			{
				return WriteDecision.Pass;
			}
			bool flag = SaveNormalizer.ContainsLibraryNumbers(state);
			if (isFaulted)
			{
				if (!flag)
				{
					return WriteDecision.Pass;
				}
				reason = "it holds library items and the library is faulted (" + faultReason + ")";
				return WriteDecision.Refuse;
			}
			if (status == SaveStateStatus.PassedThrough)
			{
				if (!flag)
				{
					return WriteDecision.Pass;
				}
				reason = "it was read before the library was ready and now holds library items";
				return WriteDecision.Refuse;
			}
			if (!status.HasValue && SaveMap.HasAnyRecord((IDictionary<string, int>)state.PLAYER_RECORDS) && !MapEquals(SaveMap.Read((IDictionary<string, int>)state.PLAYER_RECORDS), live))
			{
				reason = "it was not read through the game's save loader and carries a content map from another run";
				return WriteDecision.Refuse;
			}
			return WriteDecision.Stamp;
		}

		private static bool MapEquals(SavedMap saved, IReadOnlyDictionary<string, ushort> live)
		{
			if (saved.Status != SavedMapStatus.Valid || live == null || saved.KeyByNumber.Count != live.Count)
			{
				return false;
			}
			foreach (KeyValuePair<string, ushort> item in live)
			{
				if (!saved.KeyByNumber.TryGetValue(item.Value, out var value) || value != item.Key)
				{
					return false;
				}
			}
			return true;
		}

		private static void Block(SulfurSaveState state, string reason)
		{
			SaveStateTracker.Set(state, SaveStateStatus.Blocked, reason);
			LibraryLog.Source.LogError((object)("SAVE PROTECTED: " + reason + ". The game will run on the save as it is on disk, and nothing will be written to it this session, so no item is lost or changed."));
		}

		private static bool Refuse(SulfurSaveState state, string reason, Action<SaveResults> onCompleted)
		{
			int num = SaveStateTracker.CountBlockedWrite(state);
			if (num == 1 || num % 20 == 0)
			{
				LibraryLog.Source.LogError((object)$"Save write for slot {state.slotIndex} blocked ({num} so far): {reason}. Progress in this session is not saved.");
			}
			if (!SaveStateTracker.WasAlerted(state) && Alert("SULFUR Content Lib: this session is NOT being saved (" + reason + "). See the BepInEx log."))
			{
				SaveStateTracker.MarkAlerted(state);
			}
			try
			{
				onCompleted?.Invoke((SaveResults)2);
			}
			catch (Exception arg)
			{
				LibraryLog.Source.LogError((object)$"A save completion callback threw: {arg}");
			}
			return false;
		}

		private static bool Alert(string text)
		{
			try
			{
				UIManager instance = StaticInstance<UIManager>.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return false;
				}
				instance.ShowSmallAlert(text, 10f, (UISounds)0);
				return true;
			}
			catch (Exception ex)
			{
				LibraryLog.Source.LogDebug((object)("Could not show the alert: " + ex.Message));
				return false;
			}
		}
	}
	internal enum SavedMapStatus
	{
		Absent,
		Valid,
		Invalid
	}
	internal sealed class SavedMap
	{
		internal SavedMapStatus Status { get; }

		internal IReadOnlyDictionary<ushort, string> KeyByNumber { get; }

		internal string Problem { get; }

		private SavedMap(SavedMapStatus status, Dictionary<ushort, string> keyByNumber, string problem)
		{
			Status = status;
			KeyByNumber = keyByNumber;
			Problem = problem;
		}

		internal static SavedMap Absent()
		{
			return new SavedMap(SavedMapStatus.Absent, new Dictionary<ushort, string>(), null);
		}

		internal static SavedMap Invalid(string problem)
		{
			return new SavedMap(SavedMapStatus.Invalid, new Dictionary<ushort, string>(), problem);
		}

		internal static SavedMap Valid(Dictionary<ushort, string> keyByNumber)
		{
			return new SavedMap(SavedMapStatus.Valid, keyByNumber, null);
		}
	}
	internal static class SaveMap
	{
		internal const string RecordPrefix = "contentlib|";

		internal const string ItemRecordPrefix = "contentlib|v1|item|";

		internal const string ChecksumRecord = "contentlib|v1|checksum";

		internal const string ExtensionRecordPrefix = "contentlib|v1|ext|";

		internal static bool IsExtensionRecord(string recordName)
		{
			return recordName?.StartsWith("contentlib|v1|ext|", StringComparison.Ordinal) ?? false;
		}

		internal static bool IsLibraryRecord(string recordName)
		{
			return recordName?.StartsWith("contentlib|", StringComparison.Ordinal) ?? false;
		}

		internal static bool HasAnyRecord(IDictionary<string, int> records)
		{
			if (records == null)
			{
				return false;
			}
			foreach (string key in records.Keys)
			{
				if (IsLibraryRecord(key))
				{
					return true;
				}
			}
			return false;
		}

		internal static SavedMap Read(IDictionary<string, int> records)
		{
			if (!HasAnyRecord(records))
			{
				return SavedMap.Absent();
			}
			Dictionary<ushort, string> dictionary = new Dictionary<ushort, string>();
			List<KeyValuePair<string, ushort>> list = new List<KeyValuePair<string, ushort>>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			int? num = null;
			foreach (KeyValuePair<string, int> record in records)
			{
				if (!IsLibraryRecord(record.Key) || IsExtensionRecord(record.Key))
				{
					continue;
				}
				if (string.Equals(record.Key, "contentlib|v1|checksum", StringComparison.Ordinal))
				{
					num = record.Value;
					continue;
				}
				if (!record.Key.StartsWith("contentlib|v1|item|", StringComparison.Ordinal))
				{
					return SavedMap.Invalid("unknown record '" + record.Key + "' (another version of the library wrote this save?)");
				}
				string text = record.Key.Substring("contentlib|v1|item|".Length);
				if (!ContentKey.TryValidate(text, out var error))
				{
					return SavedMap.Invalid("record '" + record.Key + "' has an invalid key: " + error);
				}
				if (!hashSet.Add(text))
				{
					return SavedMap.Invalid("two records name keys that differ only by case ('" + text + "')");
				}
				if (!Numbering.IsItemNumber(record.Value))
				{
					return SavedMap.Invalid($"record '{record.Key}' holds {record.Value}, outside {32768}-{39999}");
				}
				ushort num2 = (ushort)record.Value;
				if (dictionary.TryGetValue(num2, out var value))
				{
					return SavedMap.Invalid($"number {num2} is mapped to both '{value}' and '{text}'");
				}
				dictionary.Add(num2, text);
				list.Add(new KeyValuePair<string, ushort>(text, num2));
			}
			if (!num.HasValue)
			{
				return SavedMap.Invalid("the checksum record is missing");
			}
			int num3 = Checksum(list);
			if (num.Value != num3)
			{
				return SavedMap.Invalid($"the checksum is {num.Value}, the records give {num3}");
			}
			return SavedMap.Valid(dictionary);
		}

		internal static void Stamp(IDictionary<string, int> records, IReadOnlyDictionary<string, ushort> liveNumbers)
		{
			List<string> list = new List<string>();
			foreach (string key in records.Keys)
			{
				if (IsLibraryRecord(key) && !IsExtensionRecord(key))
				{
					list.Add(key);
				}
			}
			foreach (string item in list)
			{
				records.Remove(item);
			}
			foreach (KeyValuePair<string, ushort> liveNumber in liveNumbers)
			{
				records["contentlib|v1|item|" + liveNumber.Key] = liveNumber.Value;
			}
			records["contentlib|v1|checksum"] = Checksum(liveNumbers);
		}

		internal static int Checksum(IEnumerable<KeyValuePair<string, ushort>> entries)
		{
			return (int)Fnv1a.Hash(ContentMapText.Utf8(entries));
		}
	}
	internal static class QuarantineFormat
	{
		internal const string StashKey = "contentlib|quarantine";

		internal const string KeyPrefix = "contentlib|v1|key|";

		internal const string UnidentifiedPrefix = "contentlib|v1|unidentified|";

		internal static string ForKey(string key)
		{
			return "contentlib|v1|key|" + key;
		}

		internal static ItemAttributeCollectionData NoAttributes()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			return new ItemAttributeCollectionData
			{
				itemAttributes = Array.Empty<ItemAttributeData>()
			};
		}

		internal static string ForUnidentified(int number)
		{
			return "contentlib|v1|unidentified|" + number.ToString(CultureInfo.InvariantCulture);
		}

		internal static bool TryGetKey(InventoryData entry, out string key)
		{
			key = null;
			if (entry == null || ((ItemId)(ref entry.id)).IsValid || entry.identifier == null || !entry.identifier.StartsWith("contentlib|v1|key|", StringComparison.Ordinal))
			{
				return false;
			}
			string text = entry.identifier.Substring("contentlib|v1|key|".Length);
			if (!ContentKey.TryValidate(text, out var _))
			{
				return false;
			}
			key = text;
			return true;
		}
	}
	internal enum ResolutionKind
	{
		NotLibrary,
		Mapped,
		Missing,
		Unidentified
	}
	internal readonly struct Resolution
	{
		internal ResolutionKind Kind { get; }

		internal ushort Original { get; }

		internal ushort Number { get; }

		internal string Key { get; }

		internal bool Lost
		{
			get
			{
				if (Kind != ResolutionKind.Missing)
				{
					return Kind == ResolutionKind.Unidentified;
				}
				return true;
			}
		}

		internal string QuarantineIdentifier
		{
			get
			{
				if (Kind != ResolutionKind.Unidentified)
				{
					return QuarantineFormat.ForKey(Key);
				}
				return QuarantineFormat.ForUnidentified(Original);
			}
		}

		internal Resolution(ResolutionKind kind, ushort original, ushort number, string key)
		{
			Kind = kind;
			Original = original;
			Number = number;
			Key = key;
		}
	}
	internal sealed class NumberResolver
	{
		private readonly SavedMap _map;

		private readonly IReadOnlyDictionary<string, ushort> _live;

		internal NumberResolver(SavedMap map, IReadOnlyDictionary<string, ushort> live)
		{
			if (map.Status == SavedMapStatus.Invalid)
			{
				throw new ArgumentException("An invalid map cannot resolve numbers.", "map");
			}
			_map = map;
			_live = live;
		}

		internal Resolution Resolve(ItemId id)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			ushort value = id.value;
			if (!Numbering.IsItemNumber(value))
			{
				return new Resolution(ResolutionKind.NotLibrary, value, value, null);
			}
			if (!_map.KeyByNumber.TryGetValue(value, out var value2))
			{
				return new Resolution(ResolutionKind.Unidentified, value, 0, null);
			}
			if (!_live.TryGetValue(value2, out var value3))
			{
				return new Resolution(ResolutionKind.Missing, value, 0, value2);
			}
			return new Resolution(ResolutionKind.Mapped, value, value3, value2);
		}
	}
	internal sealed class NormalizeReport
	{
		internal int Remapped;

		internal int Quarantined;

		internal int Detached;

		internal int Unbuilt;

		internal int Dropped;

		internal int QuestsRemoved;

		internal int InsuredReturned;

		internal SavedMapStatus MapStatus;

		internal bool Changed => Remapped + Quarantined + Detached + Unbuilt + Dropped + QuestsRemoved > 0;

		public override string ToString()
		{
			return $"saved map {MapStatus}; {Remapped} id(s) remapped, {Quarantined} item(s) and {Detached} attached item(s) " + $"quarantined, {Unbuilt} enchantment(s) that were not built taken off and their item(s) returned, " + $"{Dropped} dropped, {QuestsRemoved} quest(s) removed, {InsuredReturned} insured item(s) put back";
		}
	}
	internal sealed class SaveNormalizeException : Exception
	{
		internal SaveNormalizeException(string message)
			: base(message)
		{
		}
	}
	internal static class SaveNormalizer
	{
		private sealed class Plan
		{
			private readonly NumberResolver _resolver;

			private readonly List<Action> _commits = new List<Action>();

			private readonly IReadOnlyDictionary<string, string> _links;

			internal NormalizeReport Report { get; } = new NormalizeReport();

			internal List<InventoryData> NewQuarantine { get; } = new List<InventoryData>();

			internal Plan(NumberResolver resolver, IReadOnlyDictionary<string, string> links)
			{
				_resolver = resolver;
				_links = links;
			}

			internal void Commit()
			{
				foreach (Action commit in _commits)
				{
					commit();
				}
			}

			internal void PlayerList(List<InventoryData> list, Action<List<InventoryData>> assign)
			{
				//IL_004d: Unknown result type (might be due to invalid IL or missing references)
				if (list == null)
				{
					return;
				}
				List<InventoryData> kept = new List<InventoryData>(list.Count);
				bool flag = false;
				foreach (InventoryData item in list)
				{
					if (item == null)
					{
						kept.Add(null);
						continue;
					}
					Resolution resolution = _resolver.Resolve(item.id);
					if (resolution.Lost)
					{
						Quarantine(item, resolution);
						flag = true;
					}
					else
					{
						KeepItem(item, resolution, playerOwned: true);
						kept.Add(item);
					}
				}
				if (flag)
				{
					_commits.Add(delegate
					{
						assign(kept);
					});
				}
			}

			internal void ShopList(List<InventoryData> list, Action<List<InventoryData>> assign)
			{
				//IL_004d: Unknown result type (might be due to invalid IL or missing references)
				if (list == null)
				{
					return;
				}
				List<InventoryData> kept = new List<InventoryData>(list.Count);
				bool flag = false;
				foreach (InventoryData item in list)
				{
					if (item == null)
					{
						kept.Add(null);
						continue;
					}
					Resolution resolution = _resolver.Resolve(item.id);
					if (resolution.Lost)
					{
						Report.Dropped++;
						flag = true;
					}
					else
					{
						KeepItem(item, resolution, playerOwned: false);
						kept.Add(item);
					}
				}
				if (flag)
				{
					_commits.Add(delegate
					{
						assign(kept);
					});
				}
			}

			internal void Sacrificed(List<ItemId> list, Action<List<ItemId>> assign)
			{
				//IL_0032: Unknown result type (might be due to invalid IL or missing references)
				//IL_0037: Unknown result type (might be due to invalid IL or missing references)
				//IL_003e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0060: Unknown result type (might be due to invalid IL or missing references)
				//IL_0075: Unknown result type (might be due to invalid IL or missing references)
				//IL_0087: Unknown result type (might be due to invalid IL or missing references)
				//IL_009b: Unknown result type (might be due to invalid IL or missing references)
				if (list == null)
				{
					return;
				}
				List<ItemId> kept = new List<ItemId>(list.Count);
				bool flag = false;
				foreach (ItemId item in list)
				{
					Resolution resolution = _resolver.Resolve(item);
					switch (resolution.Kind)
					{
					case ResolutionKind.NotLibrary:
						kept.Add(item);
						break;
					case ResolutionKind.Mapped:
						kept.Add(new ItemId(resolution.Number));
						flag |= resolution.Number != item.value;
						if (resolution.Number != item.value)
						{
							Report.Remapped++;
						}
						break;
					default:
						Report.Dropped++;
						flag = true;
						break;
					}
				}
				if (flag)
				{
					_commits.Add(delegate
					{
						assign(kept);
					});
				}
			}

			internal void PrinterCounters(SerializableDictionary<ItemId, int> counters, Action<SerializableDictionary<ItemId, int>> assign)
			{
				//IL_003a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0062: Unknown result type (might be due to invalid IL or missing references)
				//IL_0082: Unknown result type (might be due to invalid IL or missing references)
				//IL_009c: Unknown result type (might be due to invalid IL or missing references)
				if (counters == null)
				{
					return;
				}
				SerializableDictionary<ItemId, int> rebuilt = new SerializableDictionary<ItemId, int>();
				bool flag = false;
				foreach (KeyValuePair<ItemId, int> item in (Dictionary<ItemId, int>)(object)counters)
				{
					Resolution resolution = _resolver.Resolve(item.Key);
					switch (resolution.Kind)
					{
					case ResolutionKind.NotLibrary:
						((Dictionary<ItemId, int>)(object)rebuilt).Add(item.Key, item.Value);
						break;
					case ResolutionKind.Mapped:
						((Dictionary<ItemId, int>)(object)rebuilt).Add(new ItemId(resolution.Number), item.Value);
						if (resolution.Number != item.Key.value)
						{
							Report.Remapped++;
							flag = true;
						}
						break;
					default:
						Report.Dropped++;
						flag = true;
						break;
					}
				}
				if (flag)
				{
					_commits.Add(delegate
					{
						assign(rebuilt);
					});
				}
			}

			internal void Quests(SerializableDictionary<string, SerializableList<QuestRequirement>> quests, Action<SerializableDictionary<string, SerializableList<QuestRequirement>>> assign)
			{
				//IL_0078: Unknown result type (might be due to invalid IL or missing references)
				if (quests == null)
				{
					return;
				}
				SerializableDictionary<string, SerializableList<QuestRequirement>> kept = new SerializableDictionary<string, SerializableList<QuestRequirement>>();
				bool flag = false;
				foreach (KeyValuePair<string, SerializableList<QuestRequirement>> item in (Dictionary<string, SerializableList<QuestRequirement>>)(object)quests)
				{
					List<QuestRequirement> list = item.Value?.list;
					bool flag2 = false;
					List<KeyValuePair<QuestRequirement, ushort>> list2 = new List<KeyValuePair<QuestRequirement, ushort>>();
					if (list != null)
					{
						foreach (QuestRequirement item2 in list)
						{
							if (item2 != null)
							{
								Resolution resolution = _resolver.Resolve(item2.ItemId);
								if (resolution.Lost)
								{
									flag2 = true;
									break;
								}
								if (resolution.Kind == ResolutionKind.Mapped && resolution.Number != item2.ItemId.value)
								{
									list2.Add(new KeyValuePair<QuestRequirement, ushort>(item2, resolution.Number));
								}
							}
						}
					}
					if (flag2)
					{
						Report.QuestsRemoved++;
						flag = true;
						continue;
					}
					((Dictionary<string, SerializableList<QuestRequirement>>)(object)kept).Add(item.Key, item.Value);
					foreach (KeyValuePair<QuestRequirement, ushort> item3 in list2)
					{
						QuestRequirement target = item3.Key;
						ushort number = item3.Value;
						_commits.Add(delegate
						{
							//IL_000c: Unknown result type (might be due to invalid IL or missing references)
							//IL_0011: Unknown result type (might be due to invalid IL or missing references)
							target.ItemId = new ItemId(number);
						});
						Report.Remapped++;
					}
				}
				if (flag)
				{
					_commits.Add(delegate
					{
						assign(kept);
					});
				}
			}

			private void KeepItem(InventoryData item, Resolution resolution, bool playerOwned)
			{
				//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
				//IL_0099: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
				bool changed;
				ItemId[] attachments = Children(item.attachmentIds, playerOwned, detachLibraryChildren: false, enchantments: false, out changed);
				bool changed2;
				ItemId[] enchantments = Children(item.enchantmentIds, playerOwned, detachLibraryChildren: false, enchantments: true, out changed2);
				bool flag = resolution.Kind == ResolutionKind.Mapped && resolution.Number != item.id.value;
				if (flag)
				{
					Report.Remapped++;
				}
				if (flag || changed || changed2)
				{
					ItemId newId = (ItemId)(flag ? new ItemId(resolution.Number) : item.id);
					_commits.Add(delegate
					{
						//IL_0007: Unknown result type (might be due to invalid IL or missing references)
						//IL_000c: Unknown result type (might be due to invalid IL or missing references)
						item.id = newId;
						item.attachmentIds = attachments;
						item.enchantmentIds = enchantments;
					});
				}
			}

			private void Quarantine(InventoryData item, Resolution resolution)
			{
				bool changed;
				ItemId[] attachments = Children(item.attachmentIds, playerOwned: true, detachLibraryChildren: true, enchantments: false, out changed);
				ItemId[] enchantments = Children(item.enchantmentIds, playerOwned: true, detachLibraryChildren: true, enchantments: true, out changed);
				string identifier = resolution.QuarantineIdentifier;
				_commits.Add(delegate
				{
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					//IL_000b: Unknown result type (might be due to invalid IL or missing references)
					item.id = ItemId.None;
					item.identifier = identifier;
					item.attachments = null;
					item.enchantments = null;
					item.attachmentIds = attachments;
					item.enchantmentIds = enchantments;
					item.equipped = false;
				});
				NewQuarantine.Add(item);
				Report.Quarantined++;
			}

			private ItemId[] Children(ItemId[] ids, bool playerOwned, bool detachLibraryChildren, bool enchantments, out bool changed)
			{
				//IL_0021: Unknown result type (might be due to invalid IL or missing references)
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				//IL_003f: Unknown result type (might be due to invalid IL or missing references)
				//IL_008b: Unknown result type (might be due to invalid IL or missing references)
				//IL_009c: Unknown result type (might be due to invalid IL or missing references)
				changed = false;
				if (ids == null || ids.Length == 0)
				{
					return ids;
				}
				List<ItemId> list = new List<ItemId>(ids.Length);
				foreach (ItemId val in ids)
				{
					Resolution resolution = _resolver.Resolve(val);
					if (resolution.Kind == ResolutionKind.NotLibrary)
					{
						list.Add(val);
						continue;
					}
					bool flag = enchantments && resolution.Kind == ResolutionKind.Mapped && !_links.ContainsKey(resolution.Key);
					if (resolution.Kind == ResolutionKind.Mapped && !detachLibraryChildren && !flag)
					{
						list.Add(new ItemId(resolution.Number));
						if (resolution.Number != val.value)
						{
							Report.Remapped++;
							changed = true;
						}
						continue;
					}
					changed = true;
					if (playerOwned)
					{
						NewQuarantine.Add(DetachedEntry(resolution));
						if (flag)
						{
							Report.Unbuilt++;
						}
						else
						{
							Report.Detached++;
						}
					}
					else
					{
						Report.Dropped++;
					}
				}
				if (!changed)
				{
					return ids;
				}
				return list.ToArray();
			}

			private static InventoryData DetachedEntry(Resolution resolution)
			{
				//IL_0000: 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_0023: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Expected O, but got Unknown
				return new InventoryData(ItemId.None, 0, 0, 1, 0, (CaliberTypes)0, QuarantineFormat.NoAttributes(), Array.Empty<ItemId>(), Array.Empty<ItemId>(), 0, 1, 1, false, false)
				{
					identifier = resolution.QuarantineIdentifier
				};
			}
		}

		internal static NormalizeReport Normalize(SulfurSaveState state, IReadOnlyDictionary<string, ushort> live, IReadOnlyDictionary<string, string> liveEnchantmentKeyByItemKey)
		{
			if (liveEnchantmentKeyByItemKey == null)
			{
				throw new ArgumentNullException("liveEnchantmentKeyByItemKey");
			}
			SavedMap savedMap = SaveMap.Read((IDictionary<string, int>)state.PLAYER_RECORDS);
			if (savedMap.Status == SavedMapStatus.Invalid)
			{
				throw new SaveNormalizeException("the save's content map is invalid: " + savedMap.Problem);
			}
			Plan plan = new Plan(new NumberResolver(savedMap, live), liveEnchantmentKeyByItemKey);
			plan.Report.MapStatus = savedMap.Status;
			plan.PlayerList(state.INVENTORY, delegate(List<InventoryData> list)
			{
				state.INVENTORY = list;
			});
			plan.PlayerList(state.INSURED_ITEMS, delegate(List<InventoryData> list)
			{
				state.INSURED_ITEMS = list;
			});
			SerializableList<InventoryData> val = null;
			if (state.STASH != null)
			{
				foreach (KeyValuePair<string, SerializableList<InventoryData>> item in (Dictionary<string, SerializableList<InventoryData>>)(object)state.STASH)
				{
					if (string.Equals(item.Key, "contentlib|quarantine", StringComparison.Ordinal))
					{
						val = item.Value;
						continue;
					}
					SerializableList<InventoryData> holder = item.Value;
					if (holder != null)
					{
						plan.PlayerList(holder.list, delegate(List<InventoryData> list)
						{
							holder.list = list;
						});
					}
				}
			}
			if (state.SHOPKEEPER_INVENTORY != null)
			{
				foreach (ShopKeeperInventoryData shop in ((Dictionary<string, ShopKeeperInventoryData>)(object)state.SHOPKEEPER_INVENTORY).Values)
				{
					if (shop != null)
					{
						plan.ShopList(shop.items, delegate(List<InventoryData> list)
						{
							shop.items = list;
						});
						plan.ShopList(shop.itemsHidden, delegate(List<InventoryData> list)
						{
							shop.itemsHidden = list;
						});
					}
				}
			}
			plan.Sacrificed(state.SACRIFICED_WEAPONS, delegate(List<ItemId> list)
			{
				state.SACRIFICED_WEAPONS = list;
			});
			plan.PrinterCounters(state.WEAPON_PRINTER_COUNTER, delegate(SerializableDictionary<ItemId, int> counters)
			{
				state.WEAPON_PRINTER_COUNTER = counters;
			});
			plan.Quests(state.QUESTGIVER_CURRENT_QUEST, delegate(SerializableDictionary<string, SerializableList<QuestRequirement>> quests)
			{
				state.QUESTGIVER_CURRENT_QUEST = quests;
			});
			plan.Commit();
			plan.Report.InsuredReturned = InsuredHold.PutBack(state, plan.NewQuarantine);
			if (plan.NewQuarantine.Count > 0)
			{
				if (state.STASH == null)
				{
					state.STASH = new SerializableDictionary<string, SerializableList<InventoryData>>();
				}
				if (val?.list != null)
				{
					val.list.AddRange(plan.NewQuarantine);
				}
				else
				{
					((Dictionary<string, SerializableList<InventoryData>>)(object)state.STASH)["contentlib|quarantine"] = new SerializableList<InventoryData>(plan.NewQuarantine);
				}
			}
			if (state.PLAYER_RECORDS == null)
			{
				state.PLAYER_RECORDS = new SerializableDictionary<string, int>();
			}
			SaveMap.Stamp((IDictionary<string, int>)state.PLAYER_RECORDS, live);
			return plan.Report;
		}

		internal static bool ContainsLibraryNumbers(SulfurSaveState state)
		{
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			if (AnyInList(state.INVENTORY) || AnyInList(state.INSURED_ITEMS))
			{
				return true;
			}
			if (state.STASH != null)
			{
				foreach (SerializableList<InventoryData> value in ((Dictionary<string, SerializableList<InventoryData>>)(object)state.STASH).Values)
				{
					if (value != null && AnyInList(value.list))
					{
						return true;
					}
				}
			}
			if (state.SHOPKEEPER_INVENTORY != null)
			{
				foreach (ShopKeeperInventoryData value2 in ((Dictionary<string, ShopKeeperInventoryData>)(object)state.SHOPKEEPER_INVENTORY).Values)
				{
					if (value2 != null && (AnyInList(value2.items) || AnyInList(value2.itemsHidden)))
					{
						return true;
					}
				}
			}
			if (state.SACRIFICED_WEAPONS != null)
			{
				foreach (ItemId sACRIFICED_WEAPON in state.SACRIFICED_WEAPONS)
				{
					if (Numbering.IsItemNumber(sACRIFICED_WEAPON.value))
					{
						return true;
					}
				}
			}
			if (state.WEAPON_PRINTER_COUNTER != null)
			{
				foreach (ItemId key in ((Dictionary<ItemId, int>)(object)state.WEAPON_PRINTER_COUNTER).Keys)
				{
					if (Numbering.IsItemNumber(key.value))
					{
						return true;
					}
				}
			}
			if (state.QUESTGIVER_CURRENT_QUEST != null)
			{
				foreach (SerializableList<QuestRequirement> value3 in ((Dictionary<string, SerializableList<QuestRequirement>>)(object)state.QUESTGIVER_CURRENT_QUEST).Values)
				{
					if (value3?.list == null)
					{
						continue;
					}
					foreach (QuestRequirement item in value3.list)
					{
						if (item != null && Numbering.IsItemNumber(item.ItemId.value))
						{
							return true;
						}
					}
				}
			}
			return false;
		}

		private static bool AnyInList(List<InventoryData> list)
		{
			if (list == null)
			{
				return false;
			}
			foreach (InventoryData item in list)
			{
				if (item != null && (Numbering.IsItemNumber(item.id.value) || AnyInArray(item.attachmentIds) || AnyInArray(item.enchantmentIds)))
				{
					return true;
				}
			}
			return false;
		}

		private static bool AnyInArray(ItemId[] ids)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if (ids == null)
			{
				return false;
			}
			for (int i = 0; i < ids.Length; i++)
			{
				if (Numbering.IsItemNumber(ids[i].value))
				{
					return true;
				}
			}
			return false;
		}
	}
	internal static class StashRestore
	{
		internal const string MainStashKey = "Stash";

		internal static List<KeyValuePair<InventoryData, string>> SelectRestorable(List<InventoryData> quarantine, IReadOnlyDictionary<string, ushort> live)
		{
			List<KeyValuePair<InventoryData, string>> list = new List<KeyValuePair<InventoryData, string>>();
			if (quarantine == null)
			{
				return list;
			}
			foreach (InventoryData item in quarantine)
			{
				if (QuarantineFormat.TryGetKey(item, out var key) && live.ContainsKey(key))
				{
					list.Add(new KeyValuePair<InventoryData, string>(item, key));
				}
			}
			return list;
		}

		internal static void Run()
		{
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			ContentTables tables = LibraryState.Tables;
			SulfurSaveState active = SulfurSaveState.active;
			if (tables == null || LibraryState.IsFaulted || active?.STASH == null || !SaveStateTracker.TryGet(active, out var status, out var _) || status != SaveStateStatus.Normalized || !((Dictionary<string, SerializableList<InventoryData>>)(object)active.STASH).TryGetValue("contentlib|quarantine", out SerializableList<InventoryData> value) || value?.list == null)
			{
				return;
			}
			List<KeyValuePair<InventoryData, string>> list = SelectRestorable(value.list, tables.LiveItemNumbers);
			if (list.Count == 0)
			{
				return;
			}
			UIManager instance = StaticInstance<UIManager>.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				InventoryUI inventoryUI = instance.InventoryUI;
				obj = ((inventoryUI != null) ? inventoryUI.GetStashItemGrid(string.Empty) : null);
			}
			ItemGrid val = (ItemGrid)obj;
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			int num = 0;
			int num2 = 0;
			foreach (KeyValuePair<InventoryData, string> item in list)
			{
				ItemDefinition val2 = tables.ItemsByKey[item.Value];
				if (val2.deprecated || val2.unlocksCheckpointWhenBroughtToChurch)
				{
					continue;
				}
				Vector2Int inventorySize = val2.inventorySize;
				Vector2Int possibleSpace = val.GetPossibleSpace(inventorySize, false, false, false);
				if (((Vector2Int)(ref possibleSpace)).x < 0 || ((Vector2Int)(ref possibleSpace)).y < 0)
				{
					num2++;
				}
				else if (TryPlace(val, item.Key, val2, tables.LiveItemNumbers[item.Value], possibleSpace, inventorySize))
				{
					value.list.Remove(item.Key);
					if (!((Dictionary<string, SerializableList<InventoryData>>)(object)active.STASH).TryGetValue("Stash", out SerializableList<InventoryData> value2) || value2?.list == null)
					{
						value2 = new SerializableList<InventoryData>(new List<InventoryData>());
						((Dictionary<string, SerializableList<InventoryData>>)(object)active.STASH)["Stash"] = value2;
					}
					value2.list.Add(item.Key);
					num++;
				}
			}
			if (num > 0 || num2 > 0)
			{
				LibraryLog.Source.LogInfo((object)($"Quarantine: restored {num} item(s) to the stash; {num2} did not fit and stay quarantined; " + $"{value.list.Count} entr(y/ies) remain."));
			}
		}

		private static bool TryPlace(ItemGrid grid, InventoryData entry, ItemDefinition definition, ushort number, Vector2Int position, Vector2Int size)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			ItemId oldId = entry.id;
			string oldIdentifier = entry.identifier;
			int[] oldPosition = entry.position;
			bool oldEquipped = entry.equipped;
			bool oldRotated = entry.rotated;
			int oldX = entry.xSize;
			int oldY = entry.ySize;
			string[] oldAttachments = entry.attachments;
			string[] oldEnchantments = entry.enchantments;
			ItemAttributeCollectionData oldAttributes = entry.attributes;
			entry.id = new ItemId(number);
			entry.identifier = null;
			entry.attachments = null;
			entry.enchantments = null;
			entry.position = new int[2]
			{
				((Vector2Int)(ref position)).x,
				((Vector2Int)(ref position)).y
			};
			entry.equipped = false;
			entry.rotated = false;
			entry.xSize = ((Vector2Int)(ref size)).x;
			entry.ySize = ((Vector2Int)(ref size)).y;
			if (entry.attributes?.itemAttributes == null)
			{
				entry.attributes = QuarantineFormat.NoAttributes();
			}
			try
			{
				grid.AddItem(entry);
			}
			catch (Exception arg)
			{
				LibraryLog.Source.LogError((object)$"Quarantine: restoring '{((Object)definition).name}' threw; it stays quarantined: {arg}");
				Revert();
				return false;
			}
			GridSlot slotAt = grid.GetSlotAt(((Vector2Int)(ref position)).x, ((Vector2Int)(ref position)).y);
			if (slotAt == null || (Object)(object)slotAt.ItemInSlot == (Object)null || slotAt.ItemInSlot.itemDefinition != definition)
			{
				LibraryLog.Source.LogError((object)($"Quarantine: '{((Object)definition).name}' was handed to the game but is not in the stash grid at {position}; " + "the game may have dropped it on the floor."));
			}
			return true;
			void Revert()
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				entry.id = oldId;
				entry.identifier = oldIdentifier;
				entry.position = oldPosition;
				entry.equipped = oldEquipped;
				entry.rotated = oldRotated;
				entry.xSize = oldX;
				entry.ySize = oldY;
				entry.attachments = oldAttachments;
				entry.enchantments = oldEnchantments;
				entry.attributes = oldAttributes;
			}
		}
	}
}
namespace Ryuka.Sulfur.ContentLib.Registry
{
	internal static class ContentDigest
	{
		internal const string VersionPrefix = "v1.";

		private const int HashBytesKept = 20;

		private static ContentTables _cachedFor;

		private static string _cached;

		internal static string Compute(IEnumerable<KeyValuePair<string, ushort>> liveItemNumbers, IEnumerable<KeyValuePair<string, string>> liveEnchantmentKeyByItemKey)
		{
			if (liveItemNumbers == null)
			{
				throw new ArgumentNullException("liveItemNumbers");
			}
			if (liveEnchantmentKeyByItemKey == null)
			{
				throw new ArgumentNullException("liveEnchantmentKeyByItemKey");
			}
			byte[] array3;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				byte[] array = ContentMapText.Utf8(liveItemNumbers);
				memoryStream.Write(array, 0, array.Length);
				byte[] array2 = LinksUtf8(liveEnchantmentKeyByItemKey);
				memoryStream.Write(array2, 0, array2.Length);
				using SHA256 sHA = SHA256.Create();
				array3 = sHA.ComputeHash(memoryStream.ToArray());
			}
			StringBuilder stringBuilder = new StringBuilder("v1.", "v1.".Length + 40);
			for (int i = 0; i < 20; i++)
			{
				stringBuilder.Append("0123456789abcdef"[array3[i] >> 4]).Append("0123456789abcdef"[array3[i] & 0xF]);
			}
			return stringBuilder.ToString();
		}

		internal static string For(RegistryPhase phase, bool isFaulted, ContentTables tables)
		{
			return For(phase, isFaulted, tables, Hash);
		}

		private static string Hash(ContentTables tables)
		{
			return Compute(tables.LiveItemNumbers, tables.LiveEnchantmentKeyByItemKey);
		}

		internal static string Current()
		{
			return For(LibraryState.Declarations.Phase, LibraryState.IsFaulted, LibraryState.Tables, ComputeOnce);
		}

		private static string For(RegistryPhase phase, bool isFaulted, ContentTables tables, Func<ContentTables, string> hash)
		{
			if (isFaulted)
			{
				throw new InvalidOperationException("the library is in the faulted state; its item ids are not trusted");
			}
			if (phase != RegistryPhase.Sealed)
			{
				throw new InvalidOperationException("the registry is not sealed yet; item numbers are not final");
			}
			if (tables == null)
			{
				throw new InvalidOperationException("the content tables were not published");
			}
			return hash(tables);
		}

		private static string ComputeOnce(ContentTables tables)
		{
			if (tables != _cachedFor)
			{
				_cached = Hash(tables);
				_cachedFor = tables;
			}
			return _cached;
		}

		private static byte[] LinksUtf8(IEnumerable<KeyValuePair<string, string>> links)
		{
			List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(links);
			list.Sort((KeyValuePair<string, string> a, KeyValuePair<string, string> b) => string.CompareOrdinal(a.Key, b.Key));
			StringBuilder stringBuilder = new StringBuilder("links\n");
			stringBuilder.Append(list.Count.ToString(CultureInfo.InvariantCulture)).Append('\n');
			foreach (KeyValuePair<string, string> item in list)
			{
				stringBuilder.Append(item.Key).Append('>').Append(item.Value)
					.Append('\n');
			}
			return Encoding.UTF8.GetBytes(stringBuilder.ToString());
		}
	}
	internal static class ContentKey
	{
		internal const int MaxLength = 128;

		internal const char Separator = ':';

		internal static bool TryValidate(string key, out string error)
		{
			if (key == null)
			{
				error = "the key is null";
				return false;
			}
			if (key.Length == 0)
			{
				error = "the key is empty";
				return false;
			}
			if (key.Length > 128)
			{
				error = $"the key is {key.Length} characters long; the limit is {128}";
				return false;
			}
			int num = -1;
			for (int i = 0; i < key.Length; i++)
			{
				char c = key[i];
				if (c == ':')
				{
					if (num >= 0)
					{
						error = "the key has more than one ':'";
						return false;
					}
					num = i;
				}
				else if (!IsAllowed(c))
				{
					error = $"the key contains '{c}' at position {i}; only A-Z, a-z, 0-9, '.', '_' and '-' are allowed";
					return false;
				}
			}
			if (num < 0)
			{
				error = "the key has no ':' between the owner GUID and the name";
				return false;
			}
			if (num == 0)
			{
				error = "the owner GUID before ':' is empty";
				return false;
			}
			if (num == key.Length - 1)
			{
				error = "the name after ':' is empty";
				return false;
			}
			error = null;
			return true;
		}

		private static bool IsAllowed(char c)
		{
			if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '.' && c != '_')
			{
				return c == '-';
			}
			return true;
		}
	}
	internal static class ContentMapText
	{
		internal static byte[] Utf8(IEnumerable<KeyValuePair<string, ushort>> entries)
		{
			List<KeyValuePair<string, ushort>> list = new List<KeyValuePair<string, ushort>>(entries);
			list.Sort((KeyValuePair<string, ushort> a, KeyValuePair<string, ushort> b) => string.CompareOrdinal(a.Key, b.Key));
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(list.Count.ToString(CultureInfo.InvariantCulture)).Append('\n');
			foreach (KeyValuePair<string, ushort> item in list)
			{
				stringBuilder.Append(item.Key).Append('=').Append(item.Value.ToString(CultureInfo.InvariantCulture))
					.Append('\n');
			}
			return Encoding.UTF8.GetBytes(stringBuilder.ToString());
		}
	}
	internal sealed class ContentTables
	{
		private sealed class ByReference : IEqualityComparer<EnchantmentDefinition>
		{
			internal static readonly ByReference Instance = new ByReference();

			public bool Equals(EnchantmentDefinition x, EnchantmentDefinition y)
			{
				return x == y;
			}

			public int GetHashCode(EnchantmentDefinition obj)
			{
				return RuntimeHelpers.GetHashCode(obj);
			}
		}

		private readonly ItemDefinition[] _itemsBySlot = (ItemDefinition[])(object)new ItemDefinition[7232];

		private readonly EnchantmentDefinition[] _enchantmentsBySlot;

		private readonly Dictionary<string, ushort> _liveItemNumbers = new Dictionary<string, ushort>(StringComparer.Ordinal);

		private readonly Dictionary<ushort, string> _liveItemKeys = new Dictionary<ushort, string>();

		private readonly Dictionary<string, ItemDefinition> _itemsByKey = new Dictionary<string, ItemDefinition>(StringComparer.Ordinal);

		private readonly Dictionary<string, EnchantmentDefinition> _enchantmentsByKey = new Dictionary<string, EnchantmentDefinition>(StringComparer.Ordinal);

		private readonly Dictionary<string, ushort> _enchantmentNumbers = new Dictionary<string, ushort>(StringComparer.Ordinal);

		private readonly Dictionary<string, string> _liveEnchantmentKeyByItemKey = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly Dictionary<EnchantmentDefinition, string> _enchantmentKeys = new Dictionary<EnchantmentDefinition, string>(ByReference.Instance);

		internal int EnchantmentWindowStart { get; }

		internal IReadOnlyDictionary<string, ushort> LiveItemNumbers => _liveItemNumbers;

		internal IReadOnlyDictionary<ushort, string> LiveItemKeys => _liveItemKeys;

		internal IReadOnlyDictionary<string, ItemDefinition> ItemsByKey => _itemsByKey;

		internal IReadOnlyDictionary<string, EnchantmentDefinition> EnchantmentsByKey => _enchantmentsByKey;

		internal IReadOnlyDictionary<string, ushort> EnchantmentNumbers => _enchantmentNumbers;

		internal IReadOnlyDictionary<string, string> LiveEnchantmentKeyByItemKey => _liveEnchantmentKeyByItemKey;

		internal ContentTables(int enchantmentWindowSize)
		{
			if (enchantmentWindowSize < 0 || enchantmentWindowSize > 499)
			{
				throw new ArgumentOutOfRangeException("enchantmentWindowSize");
			}
			EnchantmentWindowStart = Numbering.EnchantmentWindowStart(enchantmentWindowSize);
			_enchantmentsBySlot = (EnchantmentDefinition[])(object)new EnchantmentDefinition[enchantmentWindowSize];
		}

		internal bool OwnsEnchantmentNumber(int value)
		{
			if (value >= EnchantmentWindowStart)
			{
				return value < 500;
			}
			return false;
		}

		internal ItemDefinition ResolveItem(int value)
		{
			return _itemsBySlot[value - 32768];
		}

		internal EnchantmentDefinition ResolveEnchantment(int value)
		{
			return _enchantmentsBySlot[value - EnchantmentWindowStart];
		}

		internal void AddItem(string key, ushort number, ItemDefinition definition)
		{
			if (!Numbering.IsItemNumber(number))
			{
				throw new ArgumentOutOfRangeException("number");
			}
			int num = number - 32768;
			if (_itemsBySlot[num] != null || _liveItemKeys.ContainsKey(number) || _liveItemNumbers.ContainsKey(key))
			{
				throw new InvalidOperationException($"Item number {number} or key '{key}' is already live.");
			}
			_itemsBySlot[num] = definition;
			_liveItemNumbers.Add(key, number);
			_liveItemKeys.Add(number, key);
			_itemsByKey.Add(key, definition);
		}

		internal void AddEnchantment(string key, string appliedByItemKey, ushort number, EnchantmentDefinition definition)
		{
			if (!OwnsEnchantmentNumber(number))
			{
				throw new ArgumentOutOfRangeException("number");
			}
			int num = number - EnchantmentWindowStart;
			if (_enchantmentsBySlot[num] != null || _enchantmentsByKey.ContainsKey(key))
			{
				throw new InvalidOperationException($"Enchantment number {number} or key '{key}' is already live.");
			}
			if (appliedByItemKey == null || !_liveItemNumbers.ContainsKey(appliedByItemKey))
			{
				throw new InvalidOperationException("Enchantment '" + key + "' names an item that is not live: '" + appliedByItemKey + "'.");
			}
			if (_liveEnchantmentKeyByItemKey.ContainsKey(appliedByItemKey))
			{
				throw new InvalidOperationException("Item '" + appliedByItemKey + "' already applies an enchantment.");
			}
			_enchantmentsBySlot[num] = definition;
			_enchantmentsByKey.Add(key, definition);
			_enchantmentNumbers.Add(key, number);
			_liveEnchantmentKeyByItemKey.Add(appliedByItemKey, key);
			if (definition != null)
			{
				_enchantmentKeys.Add(definition, key);
			}
		}

		internal bool TryGetEnchantmentKey(EnchantmentDefinition definition, out string key)
		{
			key = null;
			if (definition != null)
			{
				return _enchantmentKeys.TryGetValue(definition, out key);
			}
			return false;
		}
	}
	internal enum RegistryPhase
	{
		Declaring,
		Bootstrapping,
		Sealed
	}
	internal sealed class ItemDeclaration
	{
		internal string Key { get; }

		internal Func<ItemDefinition> Factory { get; }

		internal ItemDeclaration(string key, Func<ItemDefinition> factory)
		{
			Key = key;
			Factory = factory;
		}
	}
	internal sealed class EnchantmentDeclaration
	{
		internal string Key { get; }

		internal string AppliedByItemKey { get; }

		internal Func<EnchantmentDefinition> Factory { get; }

		internal EnchantmentDeclaration(string key, string appliedByItemKey, Func<EnchantmentDefinition> factory)
		{
			Key = key;
			AppliedByItemKey = appliedByItemKey;
			Factory = factory;
		}
	}
	internal sealed class RecipeDeclaration
	{
		internal string Key { get; }

		internal bool KnownFromStart { get; }

		internal Func<RecipeData> Factory { get; }

		internal RecipeDeclaration(string key, Func<RecipeData> factory, bool knownFromStart)
		{
			Key = key;
			Factory = factory;
			KnownFromStart = knownFromStart;
		}
	}
	internal sealed class DeclarationSet
	{
		private readonly Dictionary<string, ItemDeclaration> _items = new Dictionary<string, ItemDeclaration>(StringComparer.Ordinal);

		private readonly Dictionary<string, EnchantmentDeclaration> _enchantments = new Dictionary<string, EnchantmentDeclaration>(StringComparer.Ordinal);

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

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

		private readonly Dictionary<string, RecipeDeclaration> _recipes = new Dictionary<string, RecipeDeclaration>(StringComparer.Ordinal);

		internal RegistryPhase Phase { get; private set; }

		internal IEnumerable<RecipeDeclaration> Recipes => _recipes.Values;

		internal int RecipeCount => _recipes.Count;

		internal IEnumerable<ItemDeclaration> Items => _items.Values;

		internal IEnumerable<EnchantmentDeclaration> Enchantments => _enchantments.Values;

		internal int ItemCount => _items.Count;

		internal int EnchantmentCount => _enchantments.Count;

		internal bool TryGetItem(string key, out ItemDeclaration declaration)
		{
			return _items.TryGetValue(key, out declaration);
		}

		internal bool TryDeclareItem(string key, Func<ItemDefinition> factory, out string error)
		{
			if (!CheckCommon(key, factory, out error))
			{
				return false;
			}
			if (TextStore.EndsWithReservedSuffix(key))
			{
				error = "an item key cannot end with '_description' or '_flavor'; the game uses those names for another item's texts";
				return false;
			}
			if (_enchantmentByAppliedBy.TryGetValue(key, out var value) && !HasExactAppliedBy(key))
			{
				error = "enchantment '" + value + "' names its item with different letter case; keys that differ only by case are one key";
				return false;
			}
			_items.Add(key, new ItemDeclaration(key, factory));
			_keysByFoldedCase.Add(key, key);
			return true;
		}

		internal bool TryDeclareEnchantment(string key, string appliedByItemKey, Func<EnchantmentDefinition> factory, out string error)
		{
			if (!CheckCommon(key, factory, out error))
			{
				return false;
			}
			if (_enchantments.Count >= 499)
			{
				error = $"{499} enchantments are already declared; no more ids exist below {500}";
				return false;
			}
			if (_enchantmentByAppliedBy.TryGetValue(key, out var value))
			{
				error = "the key is already named as the item that applies enchantment '" + value + "'";
				return false;
			}
			if (!ContentKey.TryValidate(appliedByItemKey, out var error2))
			{
				error = "the applying item key is invalid: " + error2;
				return false;
			}
			if (string.Equals(key, appliedByItemKey, StringComparison.OrdinalIgnoreCase))
			{
				error = "an enchantment cannot be applied by its own key";
				return false;
			}
			if (_enchantmentByAppliedBy.TryGetValue(appliedByItemKey, out var value2))
			{
				error = "item '" + appliedByItemKey + "' already applies enchantment '" + value2 + "'; one item applies one enchantment";
				return false;
			}
			if (_keysByFoldedCase.TryGetValue(appliedByItemKey, out var value3))
			{
				if (_enchantments.ContainsKey(value3))
				{
					error = "'" + appliedByItemKey + "' is an enchantment, not an item";
					return false;
				}
				if (_recipes.ContainsKey(value3))
				{
					error = "'" + appliedByItemKey + "' is a recipe, not an item";
					return false;
				}
				if (!string.Equals(value3, appliedByItemKey, StringComparison.Ordinal))
				{
					error = "the applying item key differs only by case from the declared item '" + value3 + "'";
					return false;
				}
			}
			_enchantments.Add(key, new EnchantmentDeclaration(key, appliedByItemKey, factory));
			_keysByFoldedCase.Add(key, key);
			_enchantmentByAppliedBy.Add(appliedByItemKey, key);
			return true;
		}

		internal bool TryDeclareRecipe(string key, Func<RecipeData> factory, out string error)
		{
			return TryDeclareRecipe(key, factory, knownFromStart: false, out error);
		}

		internal bool TryDeclareRecipe(string key, Func<RecipeData> factory, bool knownFromStart, out string error)
		{
			if (!CheckCommon(key, factory, out error))
			{
				return false;
			}
			if (_enchantmentByAppliedBy.TryGetValue(key, out var value))
			{
				error = "the key is already named as the item that applies enchantment '" + value + "'";
				return false;
			}
			_recipes.Add(key, new RecipeDeclaration(key, factory, knownFromStart));
			_keysByFoldedCase.Add(key, key);
			return true;
		}

		private bool HasExactAppliedBy(string itemKey)
		{
			foreach (EnchantmentDeclaration value in _enchantments.Values)
			{
				if (string.Equals(value.AppliedByItemKey, itemKey, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		internal bool TryBeginBootstrap()
		{
			if (Phase != RegistryPhase.Declaring)
			{
				return false;
			}
			Phase = RegistryPhase.Bootstrapping;
			return true;
		}

		internal void Seal()
		{
			if (Phase != RegistryPhase.Bootstrapping)
			{
				throw new InvalidOperationException($"Cannot seal from phase {Phase}.");
			}
			Phase = RegistryPhase.Sealed;
		}

		private bool CheckCommon(string key, Delegate factory, out string error)
		{
			if (Phase != RegistryPhase.Declaring)
			{
				error = ((Phase == RegistryPhase.Sealed) ? "the registry is sealed; declare content in your plugin's Awake" : "the bootstrap has already started; declare content in your plugin's Awake");
				return false;
			}
			if (!ContentKey.TryValidate(key, out error))
			{
				return false;
			}
			if ((object)factory == null)
			{
				error = "the factory is null";
				return false;
			}
			if (_keysByFoldedCase.TryGetValue(key, out var value))
			{
				error = (string.Equals(value, key, StringComparison.Ordinal) ? "the key is already declared" : ("the key differs only by case from the declared key '" + value + "'"));
				return false;
			}
			error = null;
			return true;
		}
	}
	internal static class Fnv1a
	{
		private const uint OffsetBasis = 2166136261u;

		private const uint Prime = 16777619u;

		internal static uint Hash(string text)
		{
			return Hash(Encoding.UTF8.GetBytes(text));
		}

		internal static uint Hash(byte[] bytes)
		{
			uint num = 2166136261u;
			for (int i = 0; i < bytes.Length; i++)
			{
				num ^= bytes[i];
				num *= 16777619;
			}
			return num;
		}
	}
	internal static class Numbering
	{
		internal const int ItemFirst = 32768;

		internal const int ItemEndExclusive = 40000;

		internal const int ItemCapacity = 7232;

		internal const int EnchantmentEndExclusive = 500;

		internal const int EnchantmentMaxCapacity = 499;

		internal static bool IsItemNumber(int value)
		{
			if (value >= 32768)
			{
				return value < 40000;
			}
			return false;
		}

		internal static int EnchantmentWindowStart(int count)
		{
			return 500 - count;
		}

		internal static Dictionary<string, ushort> NumberItems(IEnumerable<string> keys, out List<string> overflow)
		{
			List<string> list = SortDistinct(keys);
			Dictionary<string, ushort> dictionary = new Dictionary<string, ushort>(list.Count, StringComparer.Ordinal);
			bool[] array = new bool[7232];
			overflow = new List<string>();
			foreach (string item in list)
			{
				if (dictionary.Count == 7232)
				{
					overflow.Add(item);
					continue;
				}
				int num = (int)(Fnv1a.Hash(item) % 7232);
				while (array[num])
				{
					num = ((num + 1 != 7232) ? (num + 1) : 0);
				}
				array[num] = true;
				dictionary.Add(item, (ushort)(32768 + num));
			}
			return dictionary;
		}

		internal static Dictionary<string, ushort> NumberEnchantments(IEnumerable<string> keys)
		{
			List<string> list = SortDistinct(keys);
			if (list.Count > 499)
			{
				throw new ArgumentException($"{list.Count} enchantments cannot fit below {500}.", "keys");
			}
			int num = EnchantmentWindowStart(list.Count);
			Dictionary<string, ushort> dictionary = new Dictionary<string, ushort>(list.Count, StringComparer.Ordinal);
			for (int i = 0; i < list.Count; i++)
			{
				dictionary.Add(list[i], (ushort)(num + i));
			}
			return dictionary;
		}

		private static List<string> SortDistinct(IEnumerable<string> keys)
		{
			List<stri