Decompiled source of Almanac v0.1.24

plugins/Almanac/Almanac.Core.dll

Decompiled 16 hours ago
using System;
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.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using ExtraPotions.Almanac.Attributes;
using ExtraPotions.Almanac.Editors;
using ExtraPotions.Almanac.Index;
using ExtraPotions.Almanac.Values;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ExtraPotions")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (C) 2026 expDARE (ExtraPotions Meadworks)")]
[assembly: AssemblyFileVersion("0.1.24.0")]
[assembly: AssemblyInformationalVersion("0.1.24+fatal: not a git repository (or any of the parent directories): .git")]
[assembly: AssemblyProduct("Almanac.Core")]
[assembly: AssemblyTitle("Almanac.Core")]
[assembly: AssemblyVersion("0.1.24.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ExtraPotions.Almanac
{
	public static class AlmanacCore
	{
		public const string Guid = "com.meadworks.almanac";

		public const string Name = "Almanac";

		public const string Version = "0.1.24";

		public const string Family = "Runestone";

		public const string DataFolder = "Meadworks/almanac";
	}
}
namespace ExtraPotions.Almanac.Writing
{
	public enum WriteVerdict
	{
		Write,
		NoChange,
		RefusedInvalid,
		RefusedLocked
	}
	public enum SaveAction
	{
		None,
		AutoSaveByFile,
		MarkDirty,
		SaveSuppressed,
		OwnedBySyncSystem,
		SaveEntry
	}
	public enum HandlerOutcome
	{
		Keep,
		RestorePrevious,
		MarkRejected
	}
	public sealed class WriteRequest
	{
		public bool Editable { get; set; }

		public string? LockReason { get; set; }

		public bool ValueValid { get; set; }

		public string? Error { get; set; }

		public bool SameAsCurrent { get; set; }

		public bool ConnectedAsClient { get; set; }

		public bool FileHasSyncedEntries { get; set; }

		public bool EntrySynced { get; set; }

		public bool SaveOnConfigSet { get; set; }

		public bool PersistOwnValue { get; set; }
	}
	public sealed class WriteDecision
	{
		public WriteVerdict Verdict { get; }

		public SaveAction Save { get; }

		public string? Reason { get; }

		internal WriteDecision(WriteVerdict verdict, SaveAction save, string? reason)
		{
			Verdict = verdict;
			Save = save;
			Reason = reason;
		}

		public override string ToString()
		{
			return Verdict.ToString() + " / " + Save.ToString() + ((Reason == null) ? "" : (" (" + Reason + ")"));
		}
	}
	public static class EntryWritePolicy
	{
		public static WriteDecision Decide(WriteRequest request)
		{
			if (!request.ValueValid)
			{
				return new WriteDecision(WriteVerdict.RefusedInvalid, SaveAction.None, request.Error ?? "Invalid value");
			}
			if (!request.Editable)
			{
				return new WriteDecision(WriteVerdict.RefusedLocked, SaveAction.None, request.LockReason ?? "Locked");
			}
			if (request.SameAsCurrent)
			{
				return new WriteDecision(WriteVerdict.NoChange, SaveAction.None, null);
			}
			SaveAction save = ((request.ConnectedAsClient && request.EntrySynced) ? SaveAction.OwnedBySyncSystem : (request.PersistOwnValue ? SaveAction.SaveEntry : (request.SaveOnConfigSet ? SaveAction.AutoSaveByFile : (MaySaveFile(request.ConnectedAsClient, request.FileHasSyncedEntries) ? SaveAction.MarkDirty : SaveAction.SaveSuppressed))));
			return new WriteDecision(WriteVerdict.Write, save, null);
		}

		public static bool MaySaveFile(bool connectedAsClient, bool fileHasSyncedEntries)
		{
			return !(connectedAsClient && fileHasSyncedEntries);
		}

		public static bool MayReload(bool connectedAsClient, bool fileHasSyncedEntries)
		{
			return MaySaveFile(connectedAsClient, fileHasSyncedEntries);
		}

		public static bool MayCaptureForProfile(bool connectedAsClient, bool entrySynced)
		{
			return !(connectedAsClient && entrySynced);
		}

		public static HandlerOutcome AfterAssign(bool handlerThrew, bool restoreAlreadyTried)
		{
			if (!handlerThrew)
			{
				return HandlerOutcome.Keep;
			}
			if (!restoreAlreadyTried)
			{
				return HandlerOutcome.RestorePrevious;
			}
			return HandlerOutcome.MarkRejected;
		}
	}
	public static class LockReasonPolicy
	{
		public static string ForReadOnlyTag(bool entrySynced, bool known, bool editable, string? factsReason, string tagText)
		{
			if (!(entrySynced && known) || editable || string.IsNullOrEmpty(factsReason))
			{
				return tagText;
			}
			return factsReason;
		}
	}
	public interface IFileOps
	{
		bool Exists(string path);

		string ReadAllText(string path);

		void WriteAllText(string path, string text);

		void Copy(string source, string destination);

		void Delete(string path);
	}
	public enum SafeWriteStep
	{
		Backup,
		Write,
		Verify,
		Rollback
	}
	public sealed class SafeWriteResult
	{
		public bool Success { get; }

		public bool RolledBack { get; }

		public bool RollbackFailed { get; }

		public string? BackupPath { get; }

		public IReadOnlyList<SafeWriteStep> Steps { get; }

		public string? Error { get; }

		internal SafeWriteResult(bool success, bool rolledBack, bool rollbackFailed, string? backupPath, IReadOnlyList<SafeWriteStep> steps, string? error)
		{
			Success = success;
			RolledBack = rolledBack;
			RollbackFailed = rollbackFailed;
			BackupPath = backupPath;
			Steps = steps;
			Error = error;
		}
	}
	public static class SafeFileWriter
	{
		public static SafeWriteResult Write(IFileOps ops, string path, string text, string backupPath)
		{
			List<SafeWriteStep> list = new List<SafeWriteStep>();
			bool flag = ops.Exists(path);
			string backupPath2 = null;
			if (flag)
			{
				list.Add(SafeWriteStep.Backup);
				try
				{
					ops.Copy(path, backupPath);
					backupPath2 = backupPath;
				}
				catch (Exception ex)
				{
					return new SafeWriteResult(success: false, rolledBack: false, rollbackFailed: false, null, list, "Backup failed: " + ex.Message);
				}
			}
			string text2 = null;
			list.Add(SafeWriteStep.Write);
			try
			{
				ops.WriteAllText(path, text);
			}
			catch (Exception ex2)
			{
				text2 = "Write failed: " + ex2.Message;
			}
			if (text2 == null)
			{
				list.Add(SafeWriteStep.Verify);
				try
				{
					if (!string.Equals(ops.ReadAllText(path), text, StringComparison.Ordinal))
					{
						text2 = "Verify failed: the file does not match what was written.";
					}
				}
				catch (Exception ex3)
				{
					text2 = "Verify failed: " + ex3.Message;
				}
			}
			if (text2 == null)
			{
				return new SafeWriteResult(success: true, rolledBack: false, rollbackFailed: false, backupPath2, list, null);
			}
			list.Add(SafeWriteStep.Rollback);
			try
			{
				if (flag)
				{
					ops.Copy(backupPath, path);
				}
				else if (ops.Exists(path))
				{
					ops.Delete(path);
				}
				return new SafeWriteResult(success: false, rolledBack: true, rollbackFailed: false, backupPath2, list, text2);
			}
			catch (Exception ex4)
			{
				return new SafeWriteResult(success: false, rolledBack: false, rollbackFailed: true, backupPath2, list, text2 + " Rollback failed: " + ex4.Message);
			}
		}
	}
	public static class BackupNaming
	{
		public const string StampFormat = "yyyyMMdd-HHmmss";

		public static string Name(string filePath, DateTime utc)
		{
			string text = ConfigPaths.FileName(filePath);
			int num = text.LastIndexOf('.');
			return string.Concat((num > 0) ? text.Substring(0, num) : text, str3: (num > 0) ? text.Substring(num) : "", str1: ".", str2: utc.ToUniversalTime().ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture));
		}

		public static IReadOnlyList<string> ToPrune(string fileName, IEnumerable<string> existing, int keep)
		{
			int num = fileName.LastIndexOf('.');
			string str = ((num > 0) ? fileName.Substring(0, num) : fileName);
			string str2 = ((num > 0) ? fileName.Substring(num) : "");
			Regex pattern = new Regex("^" + Regex.Escape(str) + "\\.(\\d{8}-\\d{6})" + Regex.Escape(str2) + "$", RegexOptions.CultureInvariant);
			return (from x in (from n in existing
					select (Name: n, Match: pattern.Match(n)) into x
					where x.Match.Success
					select x).OrderByDescending<(string, Match), string>(((string Name, Match Match) x) => x.Match.Groups[1].Value, StringComparer.Ordinal)
				select x.Name).ToList().Skip(Math.Max(0, keep)).Reverse()
				.ToList();
		}
	}
}
namespace ExtraPotions.Almanac.World
{
	public sealed class WorldEditWatch
	{
		private readonly long world;

		private readonly long generation;

		private readonly string? requested;

		public bool Pending { get; private set; }

		public bool Stale { get; private set; }

		public WorldEditWatch(long world, long generation, string? requested, bool applied)
		{
			this.world = world;
			this.generation = generation;
			this.requested = requested;
			Pending = !applied;
		}

		public void Observe(long currentWorld, long currentGeneration, string? nativeWorldText, bool readable)
		{
			if (Pending)
			{
				if (currentWorld != world || currentGeneration != generation)
				{
					Pending = false;
					Stale = true;
				}
				else if (readable && string.Equals(requested, nativeWorldText, StringComparison.Ordinal))
				{
					Pending = false;
				}
			}
		}
	}
}
namespace ExtraPotions.Almanac.Values
{
	public static class ValueCodec
	{
		private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

		public static ParseResult Parse(SettingShape shape, string? text)
		{
			ParseResult parseResult = ParseKind(shape, text ?? "");
			if (!parseResult.Ok)
			{
				return parseResult;
			}
			if (shape.HasRange && (shape.Kind == SettingKind.Integer || shape.Kind == SettingKind.Floating))
			{
				parseResult = Clamp(shape, parseResult);
			}
			if (shape.AcceptableList != null)
			{
				parseResult = CheckList(shape, parseResult);
			}
			return parseResult;
		}

		private static ParseResult ParseKind(SettingShape shape, string text)
		{
			switch (shape.Kind)
			{
			case SettingKind.Bool:
			{
				if (!bool.TryParse(text.Trim(), out var result))
				{
					return ParseResult.Fail("Expected true or false.");
				}
				return ParseResult.Success(result ? "true" : "false", result);
			}
			case SettingKind.Integer:
				return Integer(shape, text);
			case SettingKind.Floating:
				return Floating(shape, text);
			case SettingKind.String:
			{
				string text2 = BepInExText.Unescape(text);
				return ParseResult.Success(BepInExText.Escape(text2), text2);
			}
			case SettingKind.Enum:
				return Enum(shape, text);
			case SettingKind.KeyCode:
			{
				if (!KeyNames.TryCanonical(text.Trim(), shape.KeyNames, out string canonical, out string error2))
				{
					return ParseResult.Fail(error2);
				}
				return ParseResult.Success(canonical, canonical);
			}
			case SettingKind.KeyboardShortcut:
			{
				if (!ShortcutValue.TryParse(text, shape.KeyNames, out ShortcutValue value, out string error))
				{
					return ParseResult.Fail(error);
				}
				return ParseResult.Success(value.ToString(), value);
			}
			case SettingKind.Color:
			{
				if (!ColorValue.TryParse(text, out var color))
				{
					return ParseResult.Fail("Expected a color as RRGGBBAA hex.");
				}
				return ParseResult.Success(color.ToHex(), color);
			}
			case SettingKind.Vector2:
				return Vector(text, "xy");
			case SettingKind.Vector3:
				return Vector(text, "xyz");
			case SettingKind.Vector4:
			case SettingKind.Quaternion:
				return Vector(text, "xyzw");
			default:
				return ParseResult.ConverterNeeded();
			}
		}

		private static ParseResult Integer(SettingShape shape, string text)
		{
			if (!decimal.TryParse(text.Trim(), NumberStyles.AllowLeadingSign, Inv, out var result))
			{
				return ParseResult.Fail("Expected a whole number.");
			}
			if (!IntegerBounds(shape.ShortTypeName, out var min, out var max))
			{
				min = -9223372036854775808m;
				max = 18446744073709551615m;
			}
			if (result < min || result > max)
			{
				return ParseResult.Fail("Expected a whole number from " + min.ToString(Inv) + " to " + max.ToString(Inv) + ".");
			}
			return ParseResult.Success(result.ToString(Inv), result);
		}

		private static bool IntegerBounds(string type, out decimal min, out decimal max)
		{
			switch (type)
			{
			case "SByte":
				min = -128m;
				max = 127m;
				return true;
			case "Byte":
				min = default(decimal);
				max = 255m;
				return true;
			case "Int16":
				min = -32768m;
				max = 32767m;
				return true;
			case "UInt16":
				min = default(decimal);
				max = 65535m;
				return true;
			case "Int32":
				min = -2147483648m;
				max = 2147483647m;
				return true;
			case "UInt32":
				min = default(decimal);
				max = 4294967295m;
				return true;
			case "Int64":
				min = -9223372036854775808m;
				max = 9223372036854775807m;
				return true;
			case "UInt64":
				min = default(decimal);
				max = 18446744073709551615m;
				return true;
			default:
				min = default(decimal);
				max = default(decimal);
				return false;
			}
		}

		private static ParseResult Floating(SettingShape shape, string text)
		{
			string s = text.Trim();
			if (shape.ShortTypeName == "Decimal")
			{
				if (!decimal.TryParse(s, NumberStyles.Float, Inv, out var result))
				{
					return ParseResult.Fail("Expected a number such as 1.5.");
				}
				return ParseResult.Success(result.ToString(Inv), result);
			}
			if (!double.TryParse(s, NumberStyles.Float, Inv, out var result2) || double.IsNaN(result2) || double.IsInfinity(result2))
			{
				return ParseResult.Fail("Expected a number such as 1.5.");
			}
			if (shape.ShortTypeName == "Single")
			{
				if (Math.Abs(result2) > 3.4028234663852886E+38)
				{
					return ParseResult.Fail("The number is too large.");
				}
				float num = (float)result2;
				return ParseResult.Success(num.ToString(Inv), (double)num);
			}
			return ParseResult.Success(result2.ToString("R", Inv), result2);
		}

		private static ParseResult Clamp(SettingShape shape, ParseResult parsed)
		{
			object value = parsed.Value;
			if (!(value is decimal num))
			{
				if (value is double num2)
				{
					if (num2 >= shape.RangeMin && num2 <= shape.RangeMax)
					{
						return parsed;
					}
					double num3 = ((num2 < shape.RangeMin) ? shape.RangeMin : shape.RangeMax);
					return ParseResult.Success((shape.ShortTypeName == "Single") ? ((float)num3).ToString(Inv) : num3.ToString("R", Inv), num3, clamped: true);
				}
				return parsed;
			}
			decimal num4 = (decimal)shape.RangeMin;
			decimal num5 = (decimal)shape.RangeMax;
			if (num >= num4 && num <= num5)
			{
				return parsed;
			}
			decimal num6 = ((num < num4) ? num4 : num5);
			return ParseResult.Success((shape.Kind == SettingKind.Integer) ? decimal.Truncate(num6).ToString(Inv) : num6.ToString(Inv), num6, clamped: true);
		}

		private static ParseResult CheckList(SettingShape shape, ParseResult parsed)
		{
			foreach (string acceptable in shape.AcceptableList)
			{
				ParseResult parseResult = ParseKind(shape, acceptable);
				if (string.Equals(parseResult.Ok ? parseResult.Text : acceptable, parsed.Text, StringComparison.Ordinal))
				{
					return parsed;
				}
			}
			return ParseResult.Fail("Allowed values: " + string.Join(", ", shape.AcceptableList) + ".");
		}

		private static ParseResult Enum(SettingShape shape, string text)
		{
			IReadOnlyList<string> enumNames = shape.EnumNames;
			if (!shape.IsFlags)
			{
				string text2 = enumNames.FirstOrDefault((string n) => string.Equals(n, text.Trim(), StringComparison.OrdinalIgnoreCase));
				if (text2 == null)
				{
					return ParseResult.Fail("Allowed values: " + string.Join(", ", enumNames) + ".");
				}
				return ParseResult.Success(text2, text2);
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			string[] array = text.Split(',');
			foreach (string part in array)
			{
				string text3 = enumNames.FirstOrDefault((string n) => string.Equals(n, part.Trim(), StringComparison.OrdinalIgnoreCase));
				if (text3 == null)
				{
					return ParseResult.Fail("Unknown flag '" + part.Trim() + "'. Allowed: " + string.Join(", ", enumNames) + ".");
				}
				hashSet.Add(text3);
			}
			if (hashSet.Count > 1)
			{
				hashSet.Remove("None");
			}
			string[] value = enumNames.Where(hashSet.Contains).ToArray();
			return ParseResult.Success(string.Join(", ", value), value);
		}

		private static ParseResult Vector(string text, string axes)
		{
			string text2 = text.Trim();
			if (text2.Length < 2 || text2[0] != '{' || text2[text2.Length - 1] != '}')
			{
				return ParseResult.Fail("Expected {\"x\":1.0,...}.");
			}
			text2 = text2.Substring(1, text2.Length - 2);
			Dictionary<char, float> values = new Dictionary<char, float>();
			string[] array = text2.Split(',');
			foreach (string text3 in array)
			{
				int num = text3.IndexOf(':');
				if (num < 0)
				{
					return ParseResult.Fail("Expected \"axis\":number pairs.");
				}
				string text4 = text3.Substring(0, num).Trim();
				if (text4.Length != 3 || text4[0] != '"' || text4[2] != '"' || axes.IndexOf(text4[1]) < 0 || values.ContainsKey(text4[1]))
				{
					return ParseResult.Fail("Unexpected component " + text4 + ".");
				}
				if (!float.TryParse(text3.Substring(num + 1).Trim(), NumberStyles.Float, Inv, out var result) || float.IsNaN(result) || float.IsInfinity(result))
				{
					return ParseResult.Fail("Component " + text4 + " is not a number.");
				}
				values[text4[1]] = result;
			}
			if (values.Count != axes.Length)
			{
				return ParseResult.Fail("Expected components " + string.Join(", ", axes.ToCharArray()) + ".");
			}
			float[] ordered = axes.Select((char a) => values[a]).ToArray();
			return ParseResult.Success("{" + string.Join(",", axes.Select((char a, int num2) => "\"" + a + "\":" + JsonFloat(ordered[num2]))) + "}", ordered);
		}

		private static string JsonFloat(float value)
		{
			string text = value.ToString("R", Inv);
			if (text.IndexOfAny(new char[3] { '.', 'E', 'e' }) < 0)
			{
				return text + ".0";
			}
			return text;
		}
	}
	public sealed class ParseResult
	{
		public bool Ok { get; private set; }

		public string Text { get; private set; } = "";

		public object? Value { get; private set; }

		public string? Error { get; private set; }

		public bool WasClamped { get; private set; }

		public bool NeedsModConverter { get; private set; }

		private ParseResult()
		{
		}

		internal static ParseResult Success(string text, object? value, bool clamped = false)
		{
			return new ParseResult
			{
				Ok = true,
				Text = text,
				Value = value,
				WasClamped = clamped
			};
		}

		internal static ParseResult Fail(string error)
		{
			return new ParseResult
			{
				Error = error
			};
		}

		internal static ParseResult ConverterNeeded()
		{
			return new ParseResult
			{
				Error = "This type is converted by the mod's own converter.",
				NeedsModConverter = true
			};
		}

		public override string ToString()
		{
			if (!Ok)
			{
				return "Error " + Error;
			}
			return "Ok " + Text;
		}
	}
	public readonly struct ColorValue : IEquatable<ColorValue>
	{
		public byte R { get; }

		public byte G { get; }

		public byte B { get; }

		public byte A { get; }

		public ColorValue(byte r, byte g, byte b, byte a)
		{
			R = r;
			G = g;
			B = b;
			A = a;
		}

		public string ToHex()
		{
			return R.ToString("X2", CultureInfo.InvariantCulture) + G.ToString("X2", CultureInfo.InvariantCulture) + B.ToString("X2", CultureInfo.InvariantCulture) + A.ToString("X2", CultureInfo.InvariantCulture);
		}

		public static bool TryParse(string? text, out ColorValue color)
		{
			color = default(ColorValue);
			string text2 = (text ?? "").Trim();
			if (text2.StartsWith("#", StringComparison.Ordinal))
			{
				text2 = text2.Substring(1);
			}
			if (text2.Length != 6 && text2.Length != 8)
			{
				return false;
			}
			byte[] array = new byte[4] { 0, 0, 0, 255 };
			for (int i = 0; i < text2.Length / 2; i++)
			{
				if (!byte.TryParse(text2.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out array[i]))
				{
					return false;
				}
			}
			color = new ColorValue(array[0], array[1], array[2], array[3]);
			return true;
		}

		public bool Equals(ColorValue other)
		{
			if (R == other.R && G == other.G && B == other.B)
			{
				return A == other.A;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is ColorValue other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (R << 24) | (G << 16) | (B << 8) | A;
		}

		public override string ToString()
		{
			return ToHex();
		}
	}
	public sealed class ShortcutValue : IEquatable<ShortcutValue>
	{
		public static readonly ShortcutValue Empty = new ShortcutValue("", new string[0]);

		public string MainKey { get; }

		public IReadOnlyList<string> Modifiers { get; }

		public bool IsEmpty => MainKey.Length == 0;

		private ShortcutValue(string mainKey, IReadOnlyList<string> modifiers)
		{
			MainKey = mainKey;
			Modifiers = modifiers;
		}

		public static ShortcutValue Parse(string? text, IReadOnlyList<string>? knownKeys = null)
		{
			if (!TryParse(text, knownKeys, out ShortcutValue value, out string error))
			{
				throw new FormatException(error);
			}
			return value;
		}

		public static bool TryParse(string? text, IReadOnlyList<string>? knownKeys, out ShortcutValue value, out string error)
		{
			value = Empty;
			error = "";
			string text2 = (text ?? "").Trim();
			if (text2.Length == 0 || string.Equals(text2, "None", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			List<string> list = new List<string>();
			string[] array = text2.Split('+');
			foreach (string obj in array)
			{
				if (!KeyNames.TryCanonical(obj.Trim(), knownKeys, out string canonical, out error))
				{
					return false;
				}
				if (!string.Equals(canonical, "None", StringComparison.OrdinalIgnoreCase) && !list.Any((string k) => string.Equals(k, canonical, StringComparison.OrdinalIgnoreCase)))
				{
					list.Add(canonical);
				}
			}
			if (list.Count == 0)
			{
				return true;
			}
			value = new ShortcutValue(list[0], list.Skip(1).ToArray());
			return true;
		}

		public override string ToString()
		{
			if (!IsEmpty)
			{
				return string.Join(" + ", new string[1] { MainKey }.Concat(Modifiers));
			}
			return "None";
		}

		public bool Equals(ShortcutValue? other)
		{
			if (other == null)
			{
				return false;
			}
			if (!string.Equals(MainKey, other.MainKey, StringComparison.OrdinalIgnoreCase) || Modifiers.Count != other.Modifiers.Count)
			{
				return false;
			}
			return Modifiers.All((string m) => other.Modifiers.Any((string o) => string.Equals(m, o, StringComparison.OrdinalIgnoreCase)));
		}

		public override bool Equals(object? obj)
		{
			return Equals(obj as ShortcutValue);
		}

		public override int GetHashCode()
		{
			return StringComparer.OrdinalIgnoreCase.GetHashCode(MainKey) ^ Modifiers.Count;
		}
	}
	public static class KeyNames
	{
		public static bool TryCanonical(string name, IReadOnlyList<string>? knownKeys, out string canonical, out string error)
		{
			canonical = "";
			error = "";
			if (name.Length == 0 || !name.All((char c) => char.IsLetterOrDigit(c) || c == '_'))
			{
				error = "Not a key name: '" + name + "'";
				return false;
			}
			if (knownKeys != null && knownKeys.Count > 0)
			{
				string text = knownKeys.FirstOrDefault((string k) => string.Equals(k, name, StringComparison.OrdinalIgnoreCase));
				if (text == null)
				{
					error = "Unknown key: '" + name + "'";
					return false;
				}
				canonical = text;
				return true;
			}
			canonical = name;
			return true;
		}
	}
	public static class BepInExText
	{
		public static string Escape(string? text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(text.Length + 2);
			foreach (char c in text)
			{
				switch (c)
				{
				case '\0':
					stringBuilder.Append("\\0");
					break;
				case '\a':
					stringBuilder.Append("\\a");
					break;
				case '\b':
					stringBuilder.Append("\\b");
					break;
				case '\t':
					stringBuilder.Append("\\t");
					break;
				case '\n':
					stringBuilder.Append("\\n");
					break;
				case '\v':
					stringBuilder.Append("\\v");
					break;
				case '\f':
					stringBuilder.Append("\\f");
					break;
				case '\r':
					stringBuilder.Append("\\r");
					break;
				case '\'':
					stringBuilder.Append("\\'");
					break;
				case '\\':
					stringBuilder.Append("\\\\");
					break;
				case '"':
					stringBuilder.Append("\\\"");
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			return stringBuilder.ToString();
		}

		public static string Unescape(string? text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return "";
			}
			if (text.IndexOf('\\') < 0)
			{
				return text;
			}
			StringBuilder stringBuilder = new StringBuilder(text.Length);
			for (int i = 0; i < text.Length; i++)
			{
				char c = text[i];
				if (c != '\\' || i == text.Length - 1)
				{
					stringBuilder.Append(c);
					continue;
				}
				char c2 = text[++i];
				switch (c2)
				{
				case '0':
					stringBuilder.Append('\0');
					break;
				case 'a':
					stringBuilder.Append('\a');
					break;
				case 'b':
					stringBuilder.Append('\b');
					break;
				case 't':
					stringBuilder.Append('\t');
					break;
				case 'n':
					stringBuilder.Append('\n');
					break;
				case 'v':
					stringBuilder.Append('\v');
					break;
				case 'f':
					stringBuilder.Append('\f');
					break;
				case 'r':
					stringBuilder.Append('\r');
					break;
				case '\'':
					stringBuilder.Append('\'');
					break;
				case '\\':
					stringBuilder.Append('\\');
					break;
				case '"':
					stringBuilder.Append('"');
					break;
				default:
					stringBuilder.Append('\\').Append(c2);
					break;
				}
			}
			return stringBuilder.ToString();
		}
	}
	public static class ListText
	{
		public static IReadOnlyList<string> Split(string? text, string separator)
		{
			if (string.IsNullOrEmpty(text))
			{
				return new string[0];
			}
			if (string.IsNullOrEmpty(separator))
			{
				return new string[1] { text.Trim() };
			}
			return (from s in text.Split(new string[1] { separator }, StringSplitOptions.None)
				select s.Trim()).ToArray();
		}

		public static string Join(IEnumerable<string> items, string separator)
		{
			return string.Join(separator ?? "", items);
		}
	}
}
namespace ExtraPotions.Almanac.Localization
{
	public static class TextKey
	{
		public const int MaximumLength = 60;

		private const int HashLength = 8;

		public static string For(string english)
		{
			StringBuilder stringBuilder = new StringBuilder(english.Length);
			bool flag = false;
			for (int i = 0; i < english.Length; i++)
			{
				char c = char.ToLowerInvariant(english[i]);
				if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
				{
					if (flag && stringBuilder.Length > 0)
					{
						stringBuilder.Append('_');
					}
					stringBuilder.Append(c);
					flag = false;
				}
				else
				{
					flag = true;
				}
			}
			string text = ((stringBuilder.Length == 0) ? "t" : stringBuilder.ToString());
			if (text[0] >= '0' && text[0] <= '9')
			{
				text = "t_" + text;
			}
			if (text.Length <= 60)
			{
				return text;
			}
			return text.Substring(0, 51).TrimEnd('_') + "_" + Fnv(english).ToString("x8");
		}

		private static uint Fnv(string value)
		{
			uint num = 2166136261u;
			foreach (char c in value)
			{
				num ^= c;
				num *= 16777619;
			}
			return num;
		}
	}
}
namespace ExtraPotions.Almanac.Index
{
	public sealed class IndexedFile
	{
		private readonly List<ConfigEntryInfo> entries = new List<ConfigEntryInfo>();

		private readonly HashSet<EntryKey> entryKeys = new HashSet<EntryKey>();

		private readonly List<string> sections = new List<string>();

		private readonly List<EntryKey> orphans = new List<EntryKey>();

		private readonly HashSet<DiscoveryRoute> routes = new HashSet<DiscoveryRoute>();

		private int ownerRank;

		public string Path { get; private set; }

		public PluginIdentity? Owner { get; private set; }

		public DataFileRegistration? Registration { get; private set; }

		public IReadOnlyCollection<DiscoveryRoute> Routes => routes;

		public IReadOnlyList<ConfigEntryInfo> Entries => entries;

		public IReadOnlyList<string> Sections => sections;

		public IReadOnlyList<EntryKey> OrphanedKeys => orphans;

		public FileKind Kind
		{
			get
			{
				if (routes.Contains(DiscoveryRoute.CoreConfig))
				{
					return FileKind.CoreConfig;
				}
				if (IsLive)
				{
					return FileKind.BoundConfig;
				}
				if (Registration != null)
				{
					return FileKind.DataFile;
				}
				return FileKind.UnboundConfig;
			}
		}

		public bool EntriesEditable
		{
			get
			{
				if (Kind != FileKind.BoundConfig)
				{
					return Kind == FileKind.CoreConfig;
				}
				return true;
			}
		}

		public bool TextEditable => Kind == FileKind.DataFile;

		public bool AllEntriesAdvanced => Kind == FileKind.CoreConfig;

		public bool AllEntriesRequireRestart => Kind == FileKind.CoreConfig;

		public string GroupKey => Kind switch
		{
			FileKind.CoreConfig => "bepinex:core", 
			FileKind.UnboundConfig => "disk:unbound", 
			FileKind.DataFile => Registration.Product.Guid, 
			_ => Owner?.Guid ?? ("unknown:" + ConfigPaths.FileName(Path)), 
		};

		public string DisplayName
		{
			get
			{
				if (Kind != FileKind.DataFile)
				{
					return ConfigPaths.FileName(Path);
				}
				return Registration.DisplayName;
			}
		}

		internal bool IsLive
		{
			get
			{
				if (!routes.Contains(DiscoveryRoute.PluginInstance) && !routes.Contains(DiscoveryRoute.ConstructorHook))
				{
					return routes.Contains(DiscoveryRoute.FieldScan);
				}
				return true;
			}
		}

		internal IndexedFile(string path)
		{
			Path = path;
		}

		internal void AddRoute(DiscoveryRoute route, string path)
		{
			routes.Add(route);
			if (route == DiscoveryRoute.PluginInstance)
			{
				Path = path;
			}
		}

		internal void OfferOwner(PluginIdentity? owner, int rank)
		{
			if (owner != null && rank > ownerRank)
			{
				Owner = owner;
				ownerRank = rank;
			}
		}

		internal void SetRegistration(DataFileRegistration registration)
		{
			Registration = registration;
		}

		internal bool AddEntries(IEnumerable<ConfigEntryInfo>? incoming)
		{
			bool result = false;
			if (incoming == null)
			{
				return false;
			}
			foreach (ConfigEntryInfo item in incoming)
			{
				if (item != null && entryKeys.Add(item.Key))
				{
					entries.Add(item);
					if (!sections.Contains(item.Section))
					{
						sections.Add(item.Section);
					}
					result = true;
				}
			}
			return result;
		}

		internal void SetOrphans(IEnumerable<EntryKey> keys)
		{
			orphans.Clear();
			foreach (EntryKey key in keys)
			{
				if (!orphans.Contains(key) && !entryKeys.Contains(key))
				{
					orphans.Add(key);
				}
			}
		}

		public override string ToString()
		{
			return Kind.ToString() + " " + Path;
		}
	}
	public sealed class IndexGroup
	{
		public string Key { get; }

		public GroupKind Kind { get; }

		public string DisplayName { get; }

		public IReadOnlyList<IndexedFile> Files { get; }

		internal IndexGroup(string key, GroupKind kind, string displayName, IReadOnlyList<IndexedFile> files)
		{
			Key = key;
			Kind = kind;
			DisplayName = displayName;
			Files = files;
		}

		public override string ToString()
		{
			return Kind.ToString() + " " + DisplayName;
		}
	}
	public sealed class ConfigIndex
	{
		public const string CoreGroupKey = "bepinex:core";

		public const string DiskGroupKey = "disk:unbound";

		public const string DiskGroupName = "Files on disk";

		private const int RankCalling = 1;

		private const int RankFieldScan = 2;

		private const int RankDeclared = 3;

		private const int RankInstance = 4;

		private readonly bool caseInsensitive;

		private readonly Dictionary<string, IndexedFile> byKey = new Dictionary<string, IndexedFile>();

		private readonly List<IndexedFile> order = new List<IndexedFile>();

		public IReadOnlyList<IndexedFile> Files => order;

		public IReadOnlyList<IndexGroup> Groups
		{
			get
			{
				List<IndexGroup> list = new List<IndexGroup>();
				using (IEnumerator<IGrouping<string, IndexedFile>> enumerator = (from f in order
					group f by f.GroupKey).GetEnumerator())
				{
					IGrouping<string, IndexedFile> current;
					List<IndexedFile> list2;
					GroupKind kind;
					string displayName;
					for (; enumerator.MoveNext(); list.Add(new IndexGroup(current.Key, kind, displayName, list2)))
					{
						current = enumerator.Current;
						list2 = (from f in current
							orderby (!f.Routes.Contains(DiscoveryRoute.PluginInstance)) ? 1 : 0, (f.Kind == FileKind.DataFile) ? 1 : 0
							select f).ThenBy<IndexedFile, string>((IndexedFile f) => ConfigPaths.Normalize(f.Path), StringComparer.Ordinal).ToList();
						IndexedFile indexedFile = list2[0];
						switch (indexedFile.Kind)
						{
						case FileKind.CoreConfig:
							kind = GroupKind.Core;
							displayName = "BepInEx";
							continue;
						case FileKind.UnboundConfig:
							kind = GroupKind.DiskFiles;
							displayName = "Files on disk";
							continue;
						case FileKind.DataFile:
							if (list2.All((IndexedFile f) => f.Kind == FileKind.DataFile))
							{
								kind = GroupKind.Plugin;
								displayName = indexedFile.Registration.Product.Name;
								continue;
							}
							break;
						}
						PluginIdentity? pluginIdentity = list2.Select((IndexedFile f) => f.Owner).FirstOrDefault((PluginIdentity o) => o != null);
						kind = ((pluginIdentity == null) ? GroupKind.UnknownOwner : GroupKind.Plugin);
						displayName = pluginIdentity?.Name ?? ConfigPaths.FileName(indexedFile.Path);
					}
				}
				return list.OrderBy((IndexGroup g) => (int)g.Kind).ThenBy<IndexGroup, string>((IndexGroup g) => g.DisplayName, StringComparer.OrdinalIgnoreCase).ThenBy<IndexGroup, string>((IndexGroup g) => g.Key, StringComparer.Ordinal)
					.ToList();
			}
		}

		public ConfigIndex(bool caseInsensitivePaths = true)
		{
			caseInsensitive = caseInsensitivePaths;
		}

		public IndexedFile? Find(string path)
		{
			if (!byKey.TryGetValue(ConfigPaths.Key(path, caseInsensitive), out IndexedFile value))
			{
				return null;
			}
			return value;
		}

		public void AddPluginConfig(PluginIdentity plugin, string path, IEnumerable<ConfigEntryInfo> entries)
		{
			IndexedFile indexedFile = Get(path);
			indexedFile.AddRoute(DiscoveryRoute.PluginInstance, path);
			indexedFile.OfferOwner(plugin ?? throw new ArgumentNullException("plugin"), 4);
			indexedFile.AddEntries(entries);
		}

		public void AddHookedFile(string path, PluginIdentity? declaredOwner, PluginIdentity? callingPlugin, IEnumerable<ConfigEntryInfo> entries)
		{
			IndexedFile indexedFile = Get(path);
			indexedFile.AddRoute(DiscoveryRoute.ConstructorHook, path);
			indexedFile.OfferOwner(declaredOwner, 3);
			indexedFile.OfferOwner(callingPlugin, 1);
			indexedFile.AddEntries(entries);
		}

		public void AddFieldScanFile(PluginIdentity holder, string path, IEnumerable<ConfigEntryInfo> entries)
		{
			IndexedFile indexedFile = Get(path);
			indexedFile.AddRoute(DiscoveryRoute.FieldScan, path);
			indexedFile.OfferOwner(holder, 2);
			indexedFile.AddEntries(entries);
		}

		public void AddCoreConfig(string path, IEnumerable<ConfigEntryInfo> entries)
		{
			IndexedFile indexedFile = Get(path);
			indexedFile.AddRoute(DiscoveryRoute.CoreConfig, path);
			indexedFile.AddEntries(entries);
		}

		public void AddDiskFile(string path)
		{
			if (ConfigPaths.HasExtension(path, ".cfg"))
			{
				Get(path).AddRoute(DiscoveryRoute.DiskScan, path);
			}
		}

		public void AddOrphanedKeys(string path, IEnumerable<EntryKey> keys)
		{
			(Find(path) ?? throw new InvalidOperationException("Unknown file: " + path)).SetOrphans(keys);
		}

		public void AddDataFile(DataFileRegistration registration)
		{
			if (registration == null)
			{
				throw new ArgumentNullException("registration");
			}
			if (!ConfigPaths.HasExtension(registration.Path, ".yml", ".yaml"))
			{
				throw new ArgumentException("Only YAML data files can be registered: " + registration.Path, "registration");
			}
			IndexedFile indexedFile = Find(registration.Path);
			if (indexedFile?.Registration != null)
			{
				if (indexedFile.Registration.Product.Guid != registration.Product.Guid)
				{
					throw new InvalidOperationException("Data file already registered by " + indexedFile.Registration.Product.Guid + ": " + registration.Path);
				}
			}
			else
			{
				IndexedFile obj = indexedFile ?? Get(registration.Path);
				obj.AddRoute(DiscoveryRoute.DataFileRegistry, registration.Path);
				obj.SetRegistration(registration);
			}
		}

		public bool UpdateEntries(string path, IEnumerable<ConfigEntryInfo> entries)
		{
			return (Find(path) ?? throw new InvalidOperationException("Unknown file: " + path)).AddEntries(entries);
		}

		private IndexedFile Get(string path)
		{
			if (string.IsNullOrEmpty(path))
			{
				throw new ArgumentException("Path is required", "path");
			}
			string key = ConfigPaths.Key(path, caseInsensitive);
			if (byKey.TryGetValue(key, out IndexedFile value))
			{
				return value;
			}
			value = new IndexedFile(path);
			byKey.Add(key, value);
			order.Add(value);
			return value;
		}
	}
	public static class ConfigPaths
	{
		public static string Normalize(string path)
		{
			if (string.IsNullOrEmpty(path))
			{
				return "";
			}
			string text = path.Replace('\\', '/');
			string text2 = (text.StartsWith("//", StringComparison.Ordinal) ? "//" : (text.StartsWith("/", StringComparison.Ordinal) ? "/" : ""));
			List<string> list = new List<string>();
			string[] array = text.Split('/');
			foreach (string text3 in array)
			{
				if (text3.Length != 0 && !(text3 == "."))
				{
					if (text3 == ".." && list.Count > 0 && list[list.Count - 1] != ".." && !IsDrive(list[list.Count - 1]))
					{
						list.RemoveAt(list.Count - 1);
					}
					else
					{
						list.Add(text3);
					}
				}
			}
			return text2 + string.Join("/", list);
		}

		public static string Key(string path, bool caseInsensitive)
		{
			string text = Normalize(path);
			if (!caseInsensitive)
			{
				return text;
			}
			return text.ToUpperInvariant();
		}

		public static bool Same(string a, string b, bool caseInsensitive)
		{
			return Key(a, caseInsensitive) == Key(b, caseInsensitive);
		}

		public static string FileName(string path)
		{
			string text = Normalize(path);
			int num = text.LastIndexOf('/');
			if (num >= 0)
			{
				return text.Substring(num + 1);
			}
			return text;
		}

		public static bool HasExtension(string path, params string[] extensions)
		{
			string text = FileName(path);
			foreach (string text2 in extensions)
			{
				if (text.EndsWith(text2, StringComparison.OrdinalIgnoreCase) && text.Length > text2.Length)
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsDrive(string segment)
		{
			if (segment.Length == 2)
			{
				return segment[1] == ':';
			}
			return false;
		}
	}
	public sealed class PluginIdentity
	{
		public string Guid { get; }

		public string Name { get; }

		public string Version { get; }

		public PluginIdentity(string guid, string name, string version)
		{
			if (string.IsNullOrEmpty(guid))
			{
				throw new ArgumentException("Plugin GUID is required", "guid");
			}
			Guid = guid;
			Name = (string.IsNullOrEmpty(name) ? guid : name);
			Version = version ?? "";
		}

		public override string ToString()
		{
			return Name + " (" + Guid + " " + Version + ")";
		}
	}
	public enum DiscoveryRoute
	{
		PluginInstance,
		ConstructorHook,
		FieldScan,
		CoreConfig,
		DiskScan,
		DataFileRegistry
	}
	public enum FileKind
	{
		BoundConfig,
		CoreConfig,
		UnboundConfig,
		DataFile
	}
	public enum GroupKind
	{
		Plugin,
		UnknownOwner,
		Core,
		DiskFiles
	}
	public readonly struct EntryKey : IEquatable<EntryKey>
	{
		public string Section { get; }

		public string Key { get; }

		public EntryKey(string section, string key)
		{
			Section = section ?? "";
			Key = key ?? "";
		}

		public bool Equals(EntryKey other)
		{
			if (string.Equals(Section, other.Section, StringComparison.Ordinal))
			{
				return string.Equals(Key, other.Key, StringComparison.Ordinal);
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is EntryKey other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (StringComparer.Ordinal.GetHashCode(Section ?? "") * 397) ^ StringComparer.Ordinal.GetHashCode(Key ?? "");
		}

		public override string ToString()
		{
			return "[" + Section + "] " + Key;
		}
	}
	public sealed class ConfigEntryInfo
	{
		public EntryKey Key { get; }

		public string Section => Key.Section;

		public string Name => Key.Key;

		public string SettingTypeName { get; }

		public ConfigEntryInfo(string section, string key, string settingTypeName)
		{
			Key = new EntryKey(section, key);
			SettingTypeName = settingTypeName ?? "";
		}

		public override string ToString()
		{
			return Key.ToString() + " : " + SettingTypeName;
		}
	}
	public sealed class DataFileRegistration
	{
		public PluginIdentity Product { get; }

		public string Path { get; }

		public string DisplayName { get; }

		public DataFileRegistration(PluginIdentity product, string path, string displayName)
		{
			Product = product ?? throw new ArgumentNullException("product");
			if (string.IsNullOrEmpty(path))
			{
				throw new ArgumentException("Path is required", "path");
			}
			Path = path;
			DisplayName = (string.IsNullOrEmpty(displayName) ? System.IO.Path.GetFileName(path) : displayName);
		}
	}
	public static class LegacyPluginIds
	{
		private static readonly Dictionary<string, string> Renamed = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			["com.extrapotions.barter"] = "com.meadworks.barter",
			["com.extrapotions.solstice"] = "com.meadworks.solstice"
		};

		public static IReadOnlyDictionary<string, string> All => Renamed;

		public static string Current(string id)
		{
			string text;
			if (id == null || !Renamed.TryGetValue(id, out string value))
			{
				text = id;
				if (text == null)
				{
					return "";
				}
			}
			else
			{
				text = value;
			}
			return text;
		}

		public static bool Same(string a, string b)
		{
			return string.Equals(Current(a), Current(b), StringComparison.Ordinal);
		}

		public static string CurrentFile(string fileKey)
		{
			if (string.IsNullOrEmpty(fileKey) || fileKey.IndexOf('/') >= 0)
			{
				return fileKey ?? "";
			}
			foreach (KeyValuePair<string, string> item in Renamed)
			{
				if (string.Equals(fileKey, item.Key + ".cfg", StringComparison.OrdinalIgnoreCase))
				{
					return item.Value + ".cfg";
				}
			}
			return fileKey;
		}
	}
}
namespace ExtraPotions.Almanac.Files
{
	public sealed class CfgRow
	{
		public string Section { get; }

		public string Key { get; }

		public string Value { get; }

		public string? Note { get; }

		public CfgRow(string section, string key, string value, string? note = null)
		{
			Section = section;
			Key = key;
			Value = value;
			Note = note;
		}
	}
	public sealed class CfgParse
	{
		public IReadOnlyList<CfgRow> Values { get; }

		public IReadOnlyList<string> Errors { get; }

		internal CfgParse(IReadOnlyList<CfgRow> values, IReadOnlyList<string> errors)
		{
			Values = values;
			Errors = errors;
		}
	}
	public sealed class CfgChange
	{
		public string Section { get; }

		public string Key { get; }

		public string Before { get; }

		public string Value { get; }

		internal CfgChange(string section, string key, string before, string value)
		{
			Section = section;
			Key = key;
			Before = before;
			Value = value;
		}
	}
	public sealed class CfgPlan
	{
		public IReadOnlyList<CfgChange> Changes { get; }

		public IReadOnlyList<string> Errors { get; }

		public bool CanApply
		{
			get
			{
				if (Errors.Count == 0)
				{
					return Changes.Count > 0;
				}
				return false;
			}
		}

		internal CfgPlan(IReadOnlyList<CfgChange> changes, IReadOnlyList<string> errors)
		{
			Changes = changes;
			Errors = errors;
		}
	}
	public static class CfgText
	{
		public static string Render(IEnumerable<CfgRow> rows)
		{
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = true;
			foreach (IGrouping<string, CfgRow> item in rows.GroupBy<CfgRow, string>((CfgRow row) => row.Section, StringComparer.Ordinal))
			{
				if (!flag)
				{
					stringBuilder.Append('\n');
				}
				flag = false;
				stringBuilder.Append('[').Append(item.Key).Append("]\n");
				foreach (CfgRow item2 in item)
				{
					if (!string.IsNullOrEmpty(item2.Note))
					{
						string[] array = item2.Note.Split('\n');
						foreach (string text in array)
						{
							stringBuilder.Append("# ").Append(text.TrimEnd('\r')).Append('\n');
						}
					}
					stringBuilder.Append(item2.Key).Append(" = ").Append(item2.Value)
						.Append('\n');
				}
			}
			return stringBuilder.ToString();
		}

		public static CfgParse Parse(string text)
		{
			List<CfgRow> list = new List<CfgRow>();
			List<string> list2 = new List<string>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			string text2 = null;
			string[] array = (text ?? "").Replace("\r\n", "\n").Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text3 = array[i].Trim();
				string text4 = "Line " + (i + 1) + ": ";
				if (text3.Length == 0 || text3.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				if (text3.StartsWith("[", StringComparison.Ordinal) && text3.EndsWith("]", StringComparison.Ordinal))
				{
					text2 = text3.Substring(1, text3.Length - 2).Trim();
					continue;
				}
				int num = text3.IndexOf('=');
				if (num < 0)
				{
					list2.Add(text4 + "expected Key = Value.");
					continue;
				}
				string text5 = text3.Substring(0, num).Trim();
				string value = text3.Substring(num + 1).Trim();
				if (text5.Length == 0)
				{
					list2.Add(text4 + "missing key.");
				}
				else if (text2 == null)
				{
					list2.Add(text4 + "setting outside a [Section].");
				}
				else if (!hashSet.Add(text2 + "\n" + text5))
				{
					list2.Add(text4 + "duplicate setting " + text2 + " / " + text5 + ".");
				}
				else
				{
					list.Add(new CfgRow(text2, text5, value));
				}
			}
			return new CfgParse(list.AsReadOnly(), list2.AsReadOnly());
		}

		public static CfgPlan Plan(IEnumerable<CfgRow> original, CfgParse edited)
		{
			Dictionary<string, CfgRow> dictionary = new Dictionary<string, CfgRow>(StringComparer.Ordinal);
			foreach (CfgRow item in original)
			{
				dictionary[item.Section + "\n" + item.Key] = item;
			}
			List<string> list = new List<string>(edited.Errors);
			List<CfgChange> list2 = new List<CfgChange>();
			foreach (CfgRow value2 in edited.Values)
			{
				if (!dictionary.TryGetValue(value2.Section + "\n" + value2.Key, out var value))
				{
					list.Add("Unknown setting " + value2.Section + " / " + value2.Key + "; only listed settings can be edited.");
				}
				else if (!string.Equals(value.Value, value2.Value, StringComparison.Ordinal))
				{
					list2.Add(new CfgChange(value2.Section, value2.Key, value.Value, value2.Value));
				}
			}
			return new CfgPlan(list2.AsReadOnly(), list.AsReadOnly());
		}
	}
}
namespace ExtraPotions.Almanac.Editors
{
	public enum EditorKind
	{
		Toggle,
		Cycle,
		FlagsDropdown,
		Dropdown,
		Slider,
		Spinner,
		TextField,
		MultilineText,
		KeyCapture,
		ShortcutCapture,
		ColorPicker,
		VectorRow,
		ListEditor,
		RawText,
		ReadOnlyText,
		CustomDrawer,
		UguiDrawer
	}
	public enum CommitRule
	{
		None,
		OnClick,
		OnChoice,
		OnClose,
		OnRelease,
		OnEnter,
		SaveButton,
		OnKey,
		OnKeyRelease,
		ApplyButton
	}
	public enum CodecChoice
	{
		None,
		TomlConverter,
		ModDelegates
	}
	public sealed class EditorChoice
	{
		public EditorKind Kind { get; }

		public CommitRule Commit { get; }

		public int Fields { get; internal set; }

		public bool Integral { get; internal set; }

		public bool Percent { get; internal set; }

		public double? Step { get; internal set; }

		public string? Unit { get; internal set; }

		internal EditorChoice(EditorKind kind, CommitRule commit)
		{
			Kind = kind;
			Commit = commit;
		}

		public override string ToString()
		{
			return Kind.ToString() + " (" + Commit.ToString() + ")";
		}
	}
	public static class EditorSelector
	{
		public const int MultilineThreshold = 120;

		public static EditorChoice Select(SettingShape shape, EntryAttributes attrs, string? currentText)
		{
			bool flag = shape.Kind == SettingKind.KeyCode || shape.Kind == SettingKind.KeyboardShortcut;
			if (attrs.HasCustomUguiDrawer)
			{
				return new EditorChoice(EditorKind.UguiDrawer, CommitRule.None);
			}
			if (attrs.HasCustomDrawer || (flag && attrs.HasCustomHotkeyDrawer))
			{
				return new EditorChoice(EditorKind.CustomDrawer, CommitRule.None);
			}
			return Fallback(shape, attrs, currentText);
		}

		public static EditorChoice Fallback(SettingShape shape, EntryAttributes attrs, string? currentText)
		{
			if (shape.Kind == SettingKind.Bool)
			{
				return new EditorChoice(EditorKind.Toggle, CommitRule.OnClick);
			}
			if (shape.AcceptableList != null)
			{
				return new EditorChoice(EditorKind.Dropdown, CommitRule.OnChoice);
			}
			switch (shape.Kind)
			{
			case SettingKind.Enum:
				if (!shape.IsFlags)
				{
					return new EditorChoice(EditorKind.Cycle, CommitRule.OnChoice);
				}
				return new EditorChoice(EditorKind.FlagsDropdown, CommitRule.OnClose);
			case SettingKind.Integer:
			case SettingKind.Floating:
				return Numeric(shape, attrs);
			case SettingKind.String:
				if (!string.IsNullOrEmpty(attrs.ListSeparator))
				{
					return new EditorChoice(EditorKind.ListEditor, CommitRule.ApplyButton);
				}
				if (attrs.Multiline || (currentText != null && (currentText.IndexOf('\n') >= 0 || currentText.Length > 120)))
				{
					return new EditorChoice(EditorKind.MultilineText, CommitRule.SaveButton);
				}
				return new EditorChoice(EditorKind.TextField, CommitRule.OnEnter);
			case SettingKind.KeyCode:
				return new EditorChoice(EditorKind.KeyCapture, CommitRule.OnKey);
			case SettingKind.KeyboardShortcut:
				return new EditorChoice(EditorKind.ShortcutCapture, CommitRule.OnKeyRelease);
			case SettingKind.Color:
				return new EditorChoice(EditorKind.ColorPicker, CommitRule.OnClose);
			case SettingKind.Vector2:
				return Vector(2);
			case SettingKind.Vector3:
				return Vector(3);
			case SettingKind.Vector4:
			case SettingKind.Quaternion:
				return Vector(4);
			case SettingKind.List:
				if (!shape.HasConverter)
				{
					return ReadOnly();
				}
				return new EditorChoice(EditorKind.ListEditor, CommitRule.ApplyButton);
			default:
				if (Codec(shape, attrs) == CodecChoice.None)
				{
					return ReadOnly();
				}
				return new EditorChoice(EditorKind.RawText, CommitRule.OnEnter);
			}
		}

		public static bool CanEditAsText(SettingShape shape, EntryAttributes attrs)
		{
			if (Codec(shape, attrs) != CodecChoice.None)
			{
				return Fallback(shape, attrs, null).Kind != EditorKind.ReadOnlyText;
			}
			return false;
		}

		public static CodecChoice Codec(SettingShape shape, EntryAttributes attrs)
		{
			if (attrs.HasCodecDelegates)
			{
				return CodecChoice.ModDelegates;
			}
			if (!shape.HasConverter)
			{
				return CodecChoice.None;
			}
			return CodecChoice.TomlConverter;
		}

		private static EditorChoice Numeric(SettingShape shape, EntryAttributes attrs)
		{
			bool flag = shape.Kind == SettingKind.Integer;
			EditorChoice editorChoice = ((!shape.HasRange) ? new EditorChoice(EditorKind.Spinner, CommitRule.OnEnter)
			{
				Step = (attrs.Step ?? (flag ? new double?(1.0) : ((double?)null)))
			} : new EditorChoice(EditorKind.Slider, CommitRule.OnRelease)
			{
				Step = (attrs.Step ?? (flag ? 1.0 : ((shape.RangeMax - shape.RangeMin) / 100.0))),
				Percent = (attrs.ShowRangeAsPercent == true)
			});
			editorChoice.Integral = flag;
			editorChoice.Unit = attrs.Unit;
			return editorChoice;
		}

		private static EditorChoice Vector(int fields)
		{
			return new EditorChoice(EditorKind.VectorRow, CommitRule.OnEnter)
			{
				Fields = fields
			};
		}

		private static EditorChoice ReadOnly()
		{
			return new EditorChoice(EditorKind.ReadOnlyText, CommitRule.None);
		}
	}
	public enum SettingKind
	{
		Bool,
		Enum,
		Integer,
		Floating,
		String,
		KeyCode,
		KeyboardShortcut,
		Color,
		Vector2,
		Vector3,
		Vector4,
		Quaternion,
		List,
		Other
	}
	public sealed class SettingShape
	{
		private static readonly string[] NoNames = new string[0];

		public SettingKind Kind { get; }

		public string TypeName { get; }

		public bool HasConverter { get; }

		public bool IsFlags { get; private set; }

		public IReadOnlyList<string> EnumNames { get; private set; } = NoNames;

		public bool HasRange { get; private set; }

		public double RangeMin { get; private set; }

		public double RangeMax { get; private set; }

		public IReadOnlyList<string>? AcceptableList { get; private set; }

		public IReadOnlyList<string> KeyNames { get; private set; } = NoNames;

		public string ShortTypeName => TagObject.ShortTypeName(TypeName);

		private SettingShape(SettingKind kind, string typeName, bool hasConverter)
		{
			Kind = kind;
			TypeName = typeName ?? "";
			HasConverter = hasConverter;
		}

		public static SettingShape Of(SettingKind kind, string typeName, bool hasConverter = true)
		{
			return new SettingShape(kind, typeName, hasConverter);
		}

		public static SettingShape Bool()
		{
			return Of(SettingKind.Bool, "System.Boolean");
		}

		public static SettingShape String()
		{
			return Of(SettingKind.String, "System.String");
		}

		public static SettingShape Integer(string typeName)
		{
			return Of(SettingKind.Integer, typeName);
		}

		public static SettingShape Floating(string typeName)
		{
			return Of(SettingKind.Floating, typeName);
		}

		public static SettingShape Enum(string typeName, IEnumerable<string> names, bool flags = false)
		{
			SettingShape settingShape = Of(SettingKind.Enum, typeName);
			settingShape.EnumNames = names.ToArray();
			settingShape.IsFlags = flags;
			return settingShape;
		}

		public SettingShape WithRange(double min, double max)
		{
			if (min > max)
			{
				throw new ArgumentException("Range minimum is above maximum");
			}
			SettingShape settingShape = Copy();
			settingShape.HasRange = true;
			settingShape.RangeMin = min;
			settingShape.RangeMax = max;
			return settingShape;
		}

		public SettingShape WithList(IEnumerable<string> values)
		{
			SettingShape settingShape = Copy();
			settingShape.AcceptableList = values.ToArray();
			return settingShape;
		}

		public SettingShape WithKeyNames(IEnumerable<string> names)
		{
			SettingShape settingShape = Copy();
			settingShape.KeyNames = names.ToArray();
			return settingShape;
		}

		public static SettingKind KindOf(string typeName)
		{
			string text = typeName ?? "";
			if (text.EndsWith("[]", StringComparison.Ordinal))
			{
				return SettingKind.List;
			}
			int num = text.IndexOf('`');
			if (num >= 0)
			{
				switch (TagObject.ShortTypeName(text.Substring(0, num)))
				{
				default:
					return SettingKind.Other;
				case "List":
				case "IList":
				case "HashSet":
					return SettingKind.List;
				}
			}
			switch (TagObject.ShortTypeName(text))
			{
			case "Boolean":
				return SettingKind.Bool;
			case "Int64":
			case "SByte":
			case "Int16":
			case "Int32":
			case "UInt64":
			case "UInt16":
			case "UInt32":
			case "Byte":
				return SettingKind.Integer;
			case "Decimal":
			case "Single":
			case "Double":
				return SettingKind.Floating;
			case "String":
				return SettingKind.String;
			case "KeyCode":
				return SettingKind.KeyCode;
			case "KeyboardShortcut":
				return SettingKind.KeyboardShortcut;
			case "Color":
				return SettingKind.Color;
			case "Vector2":
				return SettingKind.Vector2;
			case "Vector3":
				return SettingKind.Vector3;
			case "Vector4":
				return SettingKind.Vector4;
			case "Quaternion":
				return SettingKind.Quaternion;
			default:
				return SettingKind.Other;
			}
		}

		private SettingShape Copy()
		{
			return new SettingShape(Kind, TypeName, HasConverter)
			{
				IsFlags = IsFlags,
				EnumNames = EnumNames,
				HasRange = HasRange,
				RangeMin = RangeMin,
				RangeMax = RangeMax,
				AcceptableList = AcceptableList,
				KeyNames = KeyNames
			};
		}

		public override string ToString()
		{
			return Kind.ToString() + " " + TypeName;
		}
	}
	public sealed class TableShape
	{
		public char Delimiter { get; }

		public int Columns { get; }

		public IReadOnlyList<string[]> Rows { get; }

		internal TableShape(char delimiter, int columns, IReadOnlyList<string[]> rows)
		{
			Delimiter = delimiter;
			Columns = columns;
			Rows = rows;
		}
	}
	public static class TableText
	{
		public const int MaxColumns = 6;

		private static readonly char[] Candidates = new char[6] { '|', ';', ':', '=', ',', '\t' };

		public static TableShape? Detect(IReadOnlyList<string> items, string? listSeparator = null)
		{
			List<string> list = items.Where((string item) => item.Length > 0).ToList();
			if (list.Count == 0)
			{
				return null;
			}
			char[] candidates = Candidates;
			foreach (char delimiter in candidates)
			{
				if (listSeparator != null && listSeparator.IndexOf(delimiter) >= 0)
				{
					continue;
				}
				int count = Count(list[0], delimiter);
				if (count < 1 || count + 1 > 6 || list.Any((string item) => Count(item, delimiter) != count))
				{
					continue;
				}
				List<string[]> rows = items.Select((string item) => (item.Length != 0) ? item.Split(delimiter) : new string[count + 1].Select((string _) => "").ToArray()).ToList();
				return new TableShape(delimiter, count + 1, rows);
			}
			return null;
		}

		public static string Join(IReadOnlyList<string> cells, char delimiter)
		{
			if (!cells.All((string cell) => cell.Length == 0))
			{
				return string.Join(delimiter.ToString(), cells);
			}
			return "";
		}

		public static string CellError(string cell, char delimiter, string? listSeparator)
		{
			if (cell.IndexOf(delimiter) >= 0)
			{
				return "A cell cannot contain the column separator";
			}
			if (!string.IsNullOrEmpty(listSeparator) && cell.Contains(listSeparator))
			{
				return "A cell cannot contain the list separator";
			}
			if (cell.IndexOfAny(new char[2] { '\r', '\n' }) >= 0)
			{
				return "A cell cannot contain a line break";
			}
			return "";
		}

		public static IReadOnlyList<string> Lines(string text, out string lineBreak)
		{
			lineBreak = (text.Contains("\r\n") ? "\r\n" : "\n");
			return text.Split(new string[1] { lineBreak }, StringSplitOptions.None);
		}

		public static string JoinLines(IEnumerable<string> rows, string lineBreak)
		{
			return string.Join(lineBreak, rows);
		}

		private static int Count(string text, char c)
		{
			int num = 0;
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] == c)
				{
					num++;
				}
			}
			return num;
		}
	}
	public enum SyntaxKind
	{
		Plain,
		Cfg,
		Yaml,
		Json
	}
	public sealed class SyntaxIssue
	{
		internal sealed class Failure : Exception
		{
			public SyntaxIssue Issue { get; }

			public Failure(SyntaxIssue issue)
			{
				Issue = issue;
			}
		}

		public int Line { get; }

		public string Message { get; }

		public SyntaxIssue(int line, string message)
		{
			Line = line;
			Message = message;
		}

		public override string ToString()
		{
			return "Line " + Line + ": " + Message;
		}
	}
	public static class TextSyntax
	{
		private sealed class JsonCheck
		{
			private readonly string s;

			private int i;

			private JsonCheck(string text)
			{
				s = text;
			}

			public static void Run(string text)
			{
				JsonCheck jsonCheck = new JsonCheck(text);
				jsonCheck.Space();
				jsonCheck.Value(0);
				jsonCheck.Space();
				if (jsonCheck.i < text.Length)
				{
					jsonCheck.Fail("Unexpected text after the end");
				}
			}

			private void Fail(string message)
			{
				int num = 0;
				for (int i = 0; i < Math.Min(this.i, s.Length); i++)
				{
					if (s[i] == '\n')
					{
						num++;
					}
				}
				Line(num, message);
			}

			private void Space()
			{
				while (i < s.Length && (s[i] == ' ' || s[i] == '\t' || s[i] == '\r' || s[i] == '\n'))
				{
					i++;
				}
			}

			private void Value(int depth)
			{
				if (depth > 256)
				{
					Fail("Nested too deeply");
				}
				if (i >= s.Length)
				{
					Fail("Value expected");
				}
				char c = s[i];
				switch (c)
				{
				case '{':
					Container('}', depth, members: true);
					break;
				case '[':
					Container(']', depth, members: false);
					break;
				case '"':
					String();
					break;
				default:
					if (!char.IsDigit(c))
					{
						if (!Word("true") && !Word("false") && !Word("null"))
						{
							Fail("Unexpected character");
						}
						break;
					}
					goto case '-';
				case '-':
					Number();
					break;
				}
			}

			private void Container(char close, int depth, bool members)
			{
				i++;
				Space();
				if (i < s.Length && s[i] == close)
				{
					i++;
					return;
				}
				while (true)
				{
					if (members)
					{
						if (i >= s.Length || s[i] != '"')
						{
							Fail("Quoted key expected");
						}
						String();
						Space();
						if (i >= s.Length || s[i] != ':')
						{
							Fail("Colon expected after the key");
						}
						i++;
						Space();
					}
					Value(depth + 1);
					Space();
					if (i < s.Length && s[i] == ',')
					{
						i++;
						Space();
						continue;
					}
					if (i < s.Length && s[i] == close)
					{
						break;
					}
					Fail("Comma or closing bracket expected");
				}
				i++;
			}

			private void String()
			{
				i++;
				while (i < s.Length)
				{
					char c = s[i++];
					switch (c)
					{
					case '"':
						return;
					case '\n':
						i--;
						Fail("Unclosed string");
						break;
					}
					if (c != '\\')
					{
						continue;
					}
					if (i >= s.Length)
					{
						break;
					}
					char c2 = s[i++];
					if (c2 == 'u')
					{
						int num = 0;
						while (num < 4)
						{
							if (i >= s.Length || !Uri.IsHexDigit(s[i]))
							{
								Fail("Invalid escape");
							}
							num++;
							i++;
						}
					}
					else if ("\"\\/bfnrt".IndexOf(c2) < 0)
					{
						Fail("Invalid escape");
					}
				}
				Fail("Unclosed string");
			}

			private void Number()
			{
				int num = i;
				if (s[i] == '-')
				{
					i++;
				}
				if (i < s.Length && s[i] == '0')
				{
					i++;
				}
				else if (i < s.Length && char.IsDigit(s[i]))
				{
					while (i < s.Length && char.IsDigit(s[i]))
					{
						i++;
					}
				}
				else
				{
					Fail("Invalid number");
				}
				if (i < s.Length && s[i] == '.')
				{
					i++;
					if (i >= s.Length || !char.IsDigit(s[i]))
					{
						Fail("Invalid number");
					}
					while (i < s.Length && char.IsDigit(s[i]))
					{
						i++;
					}
				}
				if (i < s.Length && (s[i] == 'e' || s[i] == 'E'))
				{
					i++;
					if (i < s.Length && (s[i] == '+' || s[i] == '-'))
					{
						i++;
					}
					if (i >= s.Length || !char.IsDigit(s[i]))
					{
						Fail("Invalid number");
					}
					while (i < s.Length && char.IsDigit(s[i]))
					{
						i++;
					}
				}
				if (i == num)
				{
					Fail("Invalid number");
				}
			}

			private bool Word(string word)
			{
				if (string.CompareOrdinal(s, i, word, 0, word.Length) != 0)
				{
					return false;
				}
				i += word.Length;
				return true;
			}
		}

		public const string KeyColor = "#E8C27A";

		public const string SectionColor = "#F0A050";

		public const string StringColor = "#B8D88A";

		public const string NumberColor = "#8EC4E8";

		public const string CommentColor = "#8A8070";

		public const string MarkColor = "#C8B89A";

		public const int HighlightLimit = 60000;

		public static SyntaxKind Detect(string? fileName, string? text)
		{
			string text2 = (fileName ?? "").ToLowerInvariant();
			if (text2.EndsWith(".json"))
			{
				return SyntaxKind.Json;
			}
			if (text2.EndsWith(".yml") || text2.EndsWith(".yaml"))
			{
				return SyntaxKind.Yaml;
			}
			if (text2.EndsWith(".cfg"))
			{
				return SyntaxKind.Cfg;
			}
			string text3 = (text ?? "").Trim();
			if (text3.Length >= 2 && ((text3[0] == '{' && text3[text3.Length - 1] == '}') || (text3[0] == '[' && text3[text3.Length - 1] == ']')))
			{
				return SyntaxKind.Json;
			}
			if (text3.IndexOf('\n') >= 0)
			{
				string[] array = text3.Split('\n');
				for (int i = 0; i < array.Length; i++)
				{
					string text4 = array[i].TrimStart();
					if (text4.StartsWith("- ") || text4 == "-" || text4 == "---" || YamlKeyEnd(text4) > 0)
					{
						return SyntaxKind.Yaml;
					}
				}
			}
			return SyntaxKind.Plain;
		}

		public static string Highlight(string? text, SyntaxKind kind)
		{
			if (text == null)
			{
				text = "";
			}
			if (kind == SyntaxKind.Plain || text.Length > 60000)
			{
				return Escape(text);
			}
			StringBuilder stringBuilder = new StringBuilder(text.Length * 2);
			if (kind == SyntaxKind.Json)
			{
				Json(text, stringBuilder);
				return stringBuilder.ToString();
			}
			int num = 0;
			while (num <= text.Length)
			{
				int num2 = text.IndexOf('\n', num);
				if (num2 < 0)
				{
					num2 = text.Length;
				}
				string line = text.Substring(num, num2 - num);
				if (kind == SyntaxKind.Cfg)
				{
					CfgLine(line, stringBuilder);
				}
				else
				{
					YamlLine(line, stringBuilder);
				}
				if (num2 < text.Length)
				{
					stringBuilder.Append('\n');
				}
				num = num2 + 1;
			}
			return stringBuilder.ToString();
		}

		public static SyntaxIssue? Find(string? text, SyntaxKind kind)
		{
			if (text == null)
			{
				text = "";
			}
			try
			{
				switch (kind)
				{
				case SyntaxKind.Json:
					JsonCheck.Run(text);
					break;
				case SyntaxKind.Yaml:
					YamlCheck(text);
					break;
				}
				return null;
			}
			catch (SyntaxIssue.Failure failure)
			{
				return failure.Issue;
			}
		}

		public static string Check(string? text, SyntaxKind kind)
		{
			return Find(text, kind)?.ToString() ?? "";
		}

		public static string Escape(string text)
		{
			if (text.IndexOf('<') >= 0)
			{
				return text.Replace("<", "<noparse><</noparse>");
			}
			return text;
		}

		private static void Paint(StringBuilder b, string color, string text)
		{
			if (text.Length != 0)
			{
				b.Append("<color=").Append(color).Append('>')
					.Append(Escape(text))
					.Append("</color>");
			}
		}

		private static void Value(StringBuilder b, string value)
		{
			string text = value.Trim();
			if (text.Length == 0)
			{
				b.Append(Escape(value));
				return;
			}
			int num = value.IndexOf(text[0]);
			b.Append(value, 0, num);
			Paint(b, Scalar(text), text);
			b.Append(value, num + text.Length, value.Length - num - text.Length);
		}

		private static string Scalar(string core)
		{
			if (core.Length > 0 && (core[0] == '"' || core[0] == '\''))
			{
				return "#B8D88A";
			}
			switch (core.ToLowerInvariant())
			{
			default:
			{
				if (!double.TryParse(core, NumberStyles.Float, CultureInfo.InvariantCulture, out var _))
				{
					return "#B8D88A";
				}
				goto case "true";
			}
			case "true":
			case "false":
			case "null":
			case "~":
				return "#8EC4E8";
			}
		}

		private static void CfgLine(string line, StringBuilder b)
		{
			string text = line.TrimStart();
			if (text.StartsWith("#") || text.StartsWith(";"))
			{
				Paint(b, "#8A8070", line);
				return;
			}
			if (text.StartsWith("[") && text.TrimEnd().EndsWith("]"))
			{
				Paint(b, "#F0A050", line);
				return;
			}
			int num = line.IndexOf('=');
			if (num < 0)
			{
				b.Append(Escape(line));
				return;
			}
			Paint(b, "#E8C27A", line.Substring(0, num));
			Paint(b, "#C8B89A", "=");
			Value(b, line.Substring(num + 1));
		}

		private static int YamlKeyEnd(string line)
		{
			int i = 0;
			if (line.Length > 0 && (line[0] == '"' || line[0] == '\''))
			{
				int num = line.IndexOf(line[0], 1);
				if (num < 0)
				{
					return -1;
				}
				i = num + 1;
				if (i >= line.Length || line[i] != ':' || (i + 1 != line.Length && line[i + 1] != ' '))
				{
					return -1;
				}
				return i;
			}
			for (; i < line.Length; i++)
			{
				switch (line[i])
				{
				case '"':
				case '#':
				case '\'':
				case '[':
				case '{':
					return -1;
				case ':':
					if (i + 1 == line.Length || line[i + 1] == ' ')
					{
						if (i <= 0)
						{
							return -1;
						}
						return i;
					}
					break;
				}
			}
			return -1;
		}

		private static int CommentStart(string line)
		{
			char c = '\0';
			for (int i = 0; i < line.Length; i++)
			{
				char c2 = line[i];
				if (c != 0)
				{
					if (c2 == c)
					{
						c = '\0';
					}
					continue;
				}
				switch (c2)
				{
				case '"':
				case '\'':
					c = c2;
					break;
				case '#':
					if (i == 0 || line[i - 1] == ' ' || line[i - 1] == '\t')
					{
						return i;
					}
					break;
				}
			}
			return -1;
		}

		private static void YamlLine(string line, StringBuilder b)
		{
			int num = CommentStart(line);
			string text = ((num >= 0) ? line.Substring(0, num) : line);
			string text2 = ((num >= 0) ? line.Substring(num) : "");
			int i;
			for (i = 0; i < text.Length && text[i] == ' '; i++)
			{
			}
			b.Append(text, 0, i);
			string text3 = text.Substring(i);
			if (text3.TrimEnd() == "---" || text3.TrimEnd() == "...")
			{
				Paint(b, "#C8B89A", text3);
				Paint(b, "#8A8070", text2);
				return;
			}
			while (text3.StartsWith("- ") || text3 == "-")
			{
				Paint(b, "#C8B89A", "-");
				int j;
				for (j = 1; j < text3.Length && text3[j] == ' '; j++)
				{
				}
				b.Append(text3, 1, j - 1);
				text3 = text3.Substring(j);
			}
			int num2 = YamlKeyEnd(text3);
			if (num2 > 0)
			{
				Paint(b, "#E8C27A", text3.Substring(0, num2));
				Paint(b, "#C8B89A", ":");
				text3 = text3.Substring(num2 + 1);
			}
			if (text3.TrimStart().StartsWith("&") || text3.TrimStart().StartsWith("*"))
			{
				Paint(b, "#F0A050", text3);
			}
			else
			{
				Value(b, text3);
			}
			Paint(b, "#8A8070", text2);
		}

		private static void Json(string text, StringBuilder b)
		{
			int num = 0;
			while (num < text.Length)
			{
				char c = text[num];
				switch (c)
				{
				case '"':
				{
					int j;
					for (j = num + 1; j < text.Length && text[j] != '"' && text[j] != '\n'; j += ((text[j] != '\\') ? 1 : 2))
					{
					}
					j = Math.Min(j + 1, text.Length);
					int k;
					for (k = j; k < text.Length && (text[k] == ' ' || text[k] == '\t'); k++)
					{
					}
					Paint(b, (k < text.Length && text[k] == ':') ? "#E8C27A" : "#B8D88A", text.Substring(num, j - num));
					num = j;
					break;
				}
				default:
					if (!char.IsDigit(c) && !char.IsLetter(c))
					{
						if (c == '{' || c == '}' || c == '[' || c == ']' || c == ':' || c == ',')
						{
							Paint(b, "#C8B89A", c.ToString());
							num++;
						}
						else
						{
							b.Append((c == '<') ? "<noparse><</noparse>" : c.ToString());
							num++;
						}
						break;
					}
					goto case '-';
				case '-':
				{
					int i;
					for (i = num; i < text.Length && (char.IsLetterOrDigit(text[i]) || text[i] == '.' || text[i] == '-' || text[i] == '+'); i++)
					{
					}
					Paint(b, "#8EC4E8", text.Substring(num, i - num));
					num = i;
					break;
				}
				}
			}
		}

		private static string YamlCheck(string text)
		{
			string[] array = text.Split('\n');
			int num = 0;
			int index = 0;
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].TrimEnd('\r');
				int j;
				for (j = 0; j < text2.Length && (text2[j] == ' ' || text2[j] == '\t'); j++)
				{
					if (text2[j] == '\t')
					{
						return Line(i, "Tab used for indentation; use spaces");
					}
				}
				char c = '\0';
				for (int k = j; k < text2.Length; k++)
				{
					char c2 = text2[k];
					switch (c)
					{
					case '\'':
						if (c2 == '\'')
						{
							if (k + 1 < text2.Length && text2[k + 1] == '\'')
							{
								k++;
							}
							else
							{
								c = '\0';
							}
						}
						continue;
					case '"':
						switch (c2)
						{
						case '\\':
							k++;
							break;
						case '"':
							c = '\0';
							break;
						}
						continue;
					default:
						if (c2 == '#' && (k == j || text2[k - 1] == ' '))
						{
							break;
						}
						if ((c2 == '"' || c2 == '\'') && (k == j || " :-[{,".IndexOf(text2[k - 1]) >= 0))
						{
							c = c2;
							continue;
						}
						switch (c2)
						{
						case '[':
						case '{':
							if (num == 0)
							{
								index = i;
							}
							num++;
							break;
						case ']':
						case '}':
							if (--num < 0)
							{
								return Line(i, "Closing bracket without an opening one");
							}
							break;
						}
						continue;
					}
					break;
				}
				if (c != 0)
				{
					return Line(i, "Unclosed quote");
				}
			}
			if (num <= 0)
			{
				return "";
			}
			return Line(index, "Unclosed bracket");
		}

		private static string Line(int index, string message)
		{
			throw new SyntaxIssue.Failure(new SyntaxIssue(index + 1, message));
		}
	}
}
namespace ExtraPotions.Almanac.Display
{
	public enum ChangeMark
	{
		Default,
		Modified,
		ChangedThisSession
	}
	public static class ChangeMarks
	{
		public static ChangeMark Of(string current, string resetTarget, string? sessionBefore)
		{
			if (sessionBefore != null && !string.Equals(sessionBefore, current, StringComparison.Ordinal))
			{
				return ChangeMark.ChangedThisSession;
			}
			if (!string.Equals(current, resetTarget, StringComparison.Ordinal))
			{
				return ChangeMark.Modified;
			}
			return ChangeMark.Default;
		}
	}
	public enum PauseAction
	{
		None,
		Pause,
		Unpause
	}
	public sealed class PauseLatch
	{
		public bool Owned { get; private set; }

		public PauseAction Tick(bool wanted, bool paused, bool externalHold)
		{
			if (wanted)
			{
				if (paused)
				{
					return PauseAction.None;
				}
				Owned = true;
				return PauseAction.Pause;
			}
			return Release(paused, externalHold);
		}

		public PauseAction Release(bool paused, bool externalHold)
		{
			if (!Owned)
			{
				return PauseAction.None;
			}
			Owned = false;
			if (!paused || externalHold)
			{
				return PauseAction.None;
			}
			return PauseAction.Unpause;
		}
	}
	public static class WindowFit
	{
		public const float Width = 1100f;

		public const float Height = 760f;

		public const float Margin = 24f;

		public static (float Width, float Height) Size(float canvasWidth, float canvasHeight, float halfWidthLimit = float.PositiveInfinity)
		{
			float val = Math.Min(1100f, Math.Min(canvasWidth - 48f, 2f * (halfWidthLimit - 24f)));
			float val2 = Math.Min(760f, canvasHeight - 48f);
			return (Width: Math.Max(320f, val), Height: Math.Max(300f, val2));
		}
	}
	public static class WindowScale
	{
		public const string Default = "100%";

		public static readonly string[] Presets = new string[5] { "80%", "90%", "100%", "115%", "130%" };

		public static float Factor(string? preset)
		{
			int num = Array.IndexOf(Presets, preset?.Trim());
			if (num >= 0)
			{
				return (float)int.Parse(Presets[num].TrimEnd('%')) / 100f;
			}
			return 1f;
		}

		public static float Bounded(float factor, float width, float height, float canvasWidth, float canvasHeight)
		{
			if (factor <= 1f)
			{
				return factor;
			}
			float val = Math.Min((canvasWidth - 48f) / width, (canvasHeight - 48f) / height);
			return Math.Max(1f, Math.Min(factor, val));
		}
	}
	public static class RowButtons
	{
		public const float Text = 56f;

		public const float Reset = 68f;

		public const float Undo = 68f;

		public static float Width(float english, float caption)
		{
			return Math.Max(english, (float)Math.Ceiling(caption + 18f));
		}

		public static float Cluster(float text, float reset, float undo)
		{
			return 6f + text + 4f + reset + 4f + undo;
		}

		public static (float X, float Value) Columns(float rowWidth, float cluster)
		{
			float num = Math.Max(160f, Math.Min(Math.Max(240f, rowWidth * 0.46f), rowWidth - cluster - 160f));
			return (X: num, Value: Math.Max(100f, rowWidth - num - cluster));
		}
	}
	public static class PluginBlurb
	{
		private static readonly Regex Description = new Regex("\"description\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

		public static string FromManifest(string? json)
		{
			if (string.IsNullOrEmpty(json))
			{
				return "";
			}
			Match match = Description.Match(json);
			if (!match.Success)
			{
				return "";
			}
			try
			{
				return Regex.Unescape(match.Groups[1].Value).Trim();
			}
			catch (ArgumentException)
			{
				return match.Groups[1].Value.Trim();
			}
		}

		public static string Text(string? manifest, string? assembly, string name, string? version)
		{
			if (!string.IsNullOrWhiteSpace(manifest))
			{
				return manifest.Trim();
			}
			if (!string.IsNullOrWhiteSpace(assembly))
			{
				return assembly.Trim();
			}
			if (!string.IsNullOrWhiteSpace(version))
			{
				return name + " " + version;
			}
			return name;
		}
	}
	public enum ChangeTiming
	{
		Live,
		Restart,
		WorldReload,
		NewItemsOnly
	}
	public static class SettingNotes
	{
		private static readonly string[] SyncSuffixes = new string[2] { " [Synced with Server]", " [Not Synced with Server]" };

		private static readonly string[] RestartPhrases = new string[5] { "requires restart", "restart required", "after restart", "relog", "restart the game" };

		private static readonly string[] WorldPhrases = new string[3] { "next time you enter a world", "re-enter the world", "re-entering the world" };

		private static readonly string[] NewItemPhrases = new string[2] { "applies only to items", "applies to items created after" };

		public static string WithoutSyncSuffix(string? description)
		{
			string text = description ?? "";
			string[] syncSuffixes = SyncSuffixes;
			foreach (string text2 in syncSuffixes)
			{
				if (text.EndsWith(text2, StringComparison.Ordinal))
				{
					return text.Substring(0, text.Length - text2.Length);
				}
			}
			return text;
		}

		public static ChangeTiming Classify(string? description, IEnumerable<string>? extraRestartPhrases = null)
		{
			string text = description ?? "";
			if (Contains(text, RestartPhrases))
			{
				return ChangeTiming.Restart;
			}
			if (extraRestartPhrases != null)
			{
				int num = 0;
				foreach (string extraRestartPhrase in extraRestartPhrases)
				{
					if (++num > 256)
					{
						break;
					}
					string text2 = extraRestartPhrase?.Trim() ?? "";
					if (text2.Length > 0 && text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return ChangeTiming.Restart;
					}
				}
			}
			if (Contains(text, WorldPhrases))
			{
				return ChangeTiming.WorldReload;
			}
			if (Contains(text, NewItemPhrases))
			{
				return ChangeTiming.NewItemsOnly;
			}
			return ChangeTiming.Live;
		}

		private static bool Contains(string text, string[] phrases)
		{
			foreach (string value in phrases)
			{
				if (text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}
	}
}
namespace ExtraPotions.Almanac.Coexistence
{
	public sealed class ChordBinding
	{
		public string Id { get; }

		public string Label { get; }

		public string Chord { get; }

		public ChordBinding(string id, string label, string chord)
		{
			Id = id ?? "";
			Label = label ?? "";
			Chord = chord ?? "";
		}
	}
	public static class ChordClashPolicy
	{
		public static IReadOnlyDictionary<string, string[]> Find(IEnumerable<ChordBinding> bindings)
		{
			Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>(StringComparer.Ordinal);
			foreach (IGrouping<string, ChordBinding> item in bindings.Where((ChordBinding b) => b.Chord.Length > 0 && b.Id.Length > 0).GroupBy<ChordBinding, string>((ChordBinding b) => b.Chord, StringComparer.OrdinalIgnoreCase))
			{
				ChordBinding[] array = (from g in item.GroupBy<ChordBinding, string>((ChordBinding b) => b.Id, StringComparer.Ordinal)
					select g.First()).ToArray();
				if (array.Length < 2)
				{
					continue;
				}
				ChordBinding[] array2 = array;
				foreach (ChordBinding member in array2)
				{
					dictionary[member.Id] = (from other in array
						where other != member
						select other.Label).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string label) => label, StringComparer.Ordinal).ToArray();
				}
			}
			return dictionary;
		}
	}
	public enum CoexistenceMode
	{
		Auto,
		AlwaysOwnKey
	}
	public sealed class KnownManager
	{
		public string Guid { get; }

		public string Name { get; }

		public string ToggleSection { get; }

		public string ToggleKey { get; }

		public KnownManager(string guid, string name, string toggleSection, string toggleKey)
		{
			Guid = guid;
			Name = name;
			ToggleSection = toggleSection;
			ToggleKey = toggleKey;
		}
	}
	public static class KnownManagers
	{
		public static readonly IReadOnlyList<KnownManager> All = new KnownManager[3]
		{
			new KnownManager("com.bepis.bepinex.configurationmanager", "Configuration Manager", "General", "Show config manager"),
			new KnownManager("_shudnal.ConfigurationManager", "Valheim Configuration Manager", "General", "Show config manager"),
			new KnownManager("vapok.bepinex.configdrawers", "ConfigDrawers", "General", "Toggle Keybind")
		};
	}
	public sealed class ManagerFacts
	{
		public string Guid { get; }

		public string Name { get; }

		public string? ToggleChord { get; }

		public ManagerFacts(string guid, string name, string? toggleChord)
		{
			Guid = guid;
			Name = name;
			ToggleChord = toggleChord;
		}
	}
	public sealed class HotkeyDecision
	{
		public string Chord { get; }

		public bool UsedFallback { get; }

		public string? NoticeManager { get; }

		public string? CollisionWith { get; }

		public string? NoticeText
		{
			get
			{
				if (NoticeManager != null)
				{
					return "F1 opens " + NoticeManager + ". Almanac opens with Left Alt + F1 (change it in Almanac).";
				}
				return null;
			}
		}

		internal HotkeyDecision(string chord, bool usedFallback, string? noticeManager, string? collisionWith)
		{
			Chord = chord;
			UsedFallback = usedFallback;
			NoticeManager = noticeManager;
			CollisionWith = collisionWith;
		}
	}
	public static class HotkeyPolicy
	{
		public const string DefaultChord = "F1";

		public const string FallbackChord = "F1 + LeftAlt";

		public static HotkeyDecision Decide(string? configuredChord, CoexistenceMode mode, IEnumerable<ManagerFacts> installed)
		{
			List<ManagerFacts> managers = (installed ?? Enumerable.Empty<ManagerFacts>()).ToList();
			ShortcutValue shortcutValue = ShortcutValue.Parse("F1");
			if (!ShortcutValue.TryParse(configuredChord, null, out ShortcutValue value, out string _))
			{
				value = shortcutValue;
			}
			if (value.IsEmpty)
			{
				return new HotkeyDecision("None", usedFallback: false, null, null);
			}
			if (!value.Equals(shortcutValue) || mode == CoexistenceMode.AlwaysOwnKey)
			{
				return new HotkeyDecision(value.ToString(), usedFallback: false, null, Owner(managers, value)?.Name);
			}
			ManagerFacts managerFacts = Owner(managers, shortcutValue);
			if (managerFacts == null)
			{
				return new HotkeyDecision(shortcutValue.ToString(), usedFallback: false, null, null);
			}
			ShortcutValue shortcutValue2 = ShortcutValue.Parse("F1 + LeftAlt");
			return new HotkeyDecision(shortcutValue2.ToString(), usedFallback: true, managerFacts.Name, Owner(managers, shortcutValue2)?.Name);
		}

		private static ManagerFacts? Owner(IEnumerable<ManagerFacts> managers, ShortcutValue chord)
		{
			return managers.FirstOrDefault((ManagerFacts m) => Toggle(m).Equals(chord));
		}

		private static ShortcutValue Toggle(ManagerFacts manager)
		{
			if (!ShortcutValue.TryParse(manager.ToggleChord ?? "F1", null, out ShortcutValue value, out string _))
			{
				return ShortcutValue.Parse("F1");
			}
			return value;
		}
	}
	public static class KeyConflictPolicy
	{
		public static string Describe(string path, bool hasModifiers, IEnumerable<KeyValuePair<string, string>> bindings, bool available)
		{
			if (string.IsNullOrEmpty(path))
			{
				return "";
			}
			if (!available)
			{
				return "Game key bindings are unavailable; native conflicts were not checked.";
			}
			if (!path.StartsWith("<Keyboard>/", StringComparison.OrdinalIgnoreCase))
			{
				return "";
			}
			string[] array = (from b in bindings
				where string.Equals(path, b.Value, StringComparison.OrdinalIgnoreCase) && !b.Key.StartsWith("Joy", StringComparison.Ordinal)
				select Name(b.Key)).Distinct().OrderBy<string, string>((string n) => n, StringComparer.Ordinal).ToArray();
			if (array.Length == 0)
			{
				return "";
			}
			return (hasModifiers ? "Main key also used by " : "Also bound to ") + string.Join(", ", array) + (hasModifiers ? ". Possible overlap depends on modifiers and gameplay context." : ". Gameplay context may prevent overlap.");
		}

		private static string Name(string action)
		{
			if (action.Length == 7 && action.StartsWith("Hotbar", StringComparison.Ordinal) && action[6] >= '1' && action[6] <= '8')
			{
				return "Hotbar slot " + action[6];
			}
			return action;
		}
	}
}
namespace ExtraPotions.Almanac.Attributes
{
	public sealed class TagObject
	{
		public string TypeName { get; }

		public string ShortName { get; }

		public IReadOnlyDictionary<string, object?> Members { get; }

		public TagObject(string typeName, IReadOnlyDictionary<string, object?> members)
		{
			TypeName = typeName ?? "";
			Members = members ?? new Dictionary<string, object>();
			ShortName = ShortTypeName(TypeName);
		}

		public bool TryGet(string member, out object? value)
		{
			return Members.TryGetValue(member, out value);
		}

		public static string ShortTypeName(string typeName)
		{
			string text = typeName ?? "";
			int num = text.IndexOf('`');
			if (num >= 0)
			{
				text = text.Substring(0, num);
			}
			int num2 = Math.Max(text.LastIndexOf('.'), text.LastIndexOf('+'));
			if (num2 < 0)
			{
				return text;
			}
			return text.Substring(num2 + 1);
		}
	}
	public sealed class EntryAttributes
	{
		public int? Order { get; internal set; }

		public bool? Browsable { get; internal set; }

		public string? Category { get; internal set; }

		public bool? IsAdvanced { get; internal set; }

		public bool? ReadOnly { get; internal set; }

		public string? DispName { get; internal set; }

		public string? Description { get; internal set; }

		public bool HasDefaultOverride { get; internal set; }

		public object? DefaultValue { get; internal set; }

		public bool? HideDefaultButton { get; internal set; }

		public bool? HideSettingName { get; internal set; }

		public bool? ShowRangeAsPercent { get; internal set; }

		public bool HasObjToStr { get; internal set; }

		public bool HasStrToObj { get; internal set; }

		public object? ObjToStr { get; internal set; }

		public object? StrToObj { get; internal set; }

		public bool HasCustomDrawer { get; internal set; }

		public bool HasCustomHotkeyDrawer { get; internal set; }

		public bool HasCustomUguiDrawer { get; internal set; }

		public bool? IsAdminOnly { get; internal set; }

		public bool? IsUnlocked { get; internal set; }

		public bool HasEntryColor { get; internal set; }

		public bool HasDescriptionColor { get; internal set; }

		public bool IsJotunnShaped => IsAdminOnly.HasValue;

		public bool RequiresRestart { get; internal set; }

		public string? ListSeparator { get; internal set; }

		public bool Multiline { get; internal set; }

		public string? Unit { get; internal set; }

		public double? Step { get; internal set; }

		public bool CharterReadOnly { get; internal set; }

		public bool GenericReadOnly { get; internal set; }

		public bool HasCodecDelegates
		{
			get
			{
				if (HasObjToStr)
				{
					return HasStrToObj;
				}
				return false;
			}
		}
	}
	public static class AttributeReader
	{
		public const string ManagerTag = "ConfigurationManagerAttributes";

		public const string HintsTag = "AlmanacHints";

		public const string CharterTag = "ReadOnlyTag";

		public static readonly string[] UguiDrawerMembers = new string[3] { "CustomUguiDrawer", "CustomDrawerUGUI", "CustomDrawerUgui" };

		public static EntryAttributes Read(IEnumerable<TagObject>? tags)
		{
			EntryAttributes entryAttributes = new EntryAttributes();
			if (tags == null)
			{
				return entryAttributes;
			}
			foreach (TagObject tag in tags)
			{
				if (tag == null)
				{
					continue;
				}
				switch (tag.ShortName)
				{
				case "ConfigurationManagerAttributes":
					ReadManager(tag, entryAttributes);
					break;
				case "AlmanacHints":
					ReadHints(tag, entryAttributes);
					break;
				case "ReadOnlyTag":
					if (Bool(tag, "ReadOnly") == true)
					{
						entryAttributes.CharterReadOnly = true;
					}
					break;
				default:
					if (Bool(tag, "ReadOnly") == true)
					{
						entryAttributes.GenericReadOnly = true;
					}
					break;
				}
			}
			return entryAttributes;
		}

		private static void ReadManager(TagObject tag, EntryAttributes a)
		{
			EntryAttributes entryAttributes = a;
			if (!entryAttributes.Order.HasValue)
			{
				int? num = (entryAttributes.Order = Int(tag, "Order"));
			}
			entryAttributes = a;
			if (!entryAttributes.Browsable.HasValue)
			{
				bool? flag = (entryAttributes.Browsable = Bool(tag, "Browsable"));
			}
			entryAttributes = a;
			if (entryAttributes.Category == null)
			{
				string text = (entryAttributes.Category = Text(tag, "Category"));
			}
			entryAttributes = a;
			if (!entryAttributes.IsAdvanced.HasValue)
			{
				bool? flag = (entryAttributes.IsAdvanced = Bool(tag, "IsAdvanced"));
			}
			entryAttributes = a;
			if (!entryAttributes.ReadOnly.HasValue)
			{
				bool? flag = (entryAttributes.ReadOnly = Bool(tag, "ReadOnly"));
			}
			entryAttributes = a;
			if (entryAttributes.DispName == null)
			{
				string text = (entryAttributes.DispName = Text(tag, "DispName"));
			}
			entryAttributes = a;
			if (entryAttributes.Description == null)
			{
				string text = (entryAttributes.Description = Text(tag, "Description"));
			}
			if (!a.HasDefaultOverride && tag.TryGet("DefaultValue", out object value) && value != null)
			{
				a.HasDefaultOverride = true;
				a.DefaultValue = value;
			}
			entryAttributes = a;
			if (!entryAttributes.HideDefaultButton.HasValue)
			{
				bool? flag = (entryAttributes.HideDefaultButton = Bool(tag, "HideDefaultButton"));
			}
			entryAttributes = a;
			if (!entryAttributes.HideSettingName.HasValue)
			{
				bool? flag = (entryAttributes.HideSettingName = Bool(tag, "HideSettingName"));
			}
			entryAttributes = a;
			if (!entryAttributes.ShowRangeAsPercent.HasValue)
			{
				bool? flag = (entryAttributes.ShowRangeAsPercent = Bool(tag, "ShowRangeAsPercent"));
			}
			if (!a.HasObjToStr && Present(tag, "ObjToStr", out object value2))
			{
				a.HasObjToStr = true;
				a.ObjToStr = value2;
			}
			if (!a.HasStrToObj && Present(tag, "StrToObj", out object value3))
			{
				a.HasStrToObj = true;
				a.StrToObj = value3;
			}
			a.HasCustomDrawer |= Present(tag, "CustomDrawer", out object value4);
			a.HasCustomHotkeyDrawer |= Present(tag, "CustomHotkeyDrawer", out value4);
			string[] uguiDrawerMembers = UguiDrawerMembers;
			foreach (string member in uguiDrawerMembers)
			{
				a.HasCustomUguiDrawer |= tag.TryGet(member, out object value5) && value5 is Delegate;
			}
			entryAttributes = a;
			if (!entryAttributes.IsAdminOnly.HasValue)
			{
				bool? flag = (entryAttributes.IsAdminOnly = Bool(tag, "IsAdminOnly"));
			}
			entryAttributes = a;
			if (!entryAttributes.IsUnlocked.HasValue)
			{
				bool? flag = (entryAttributes.IsUnlocked = Bool(tag, "IsUnlocked"));
			}
			a.HasEntryColor |= Present(tag, "EntryColor", out value4);
			a.HasDescriptionColor |= Present(tag, "DescriptionColor", out value4);
		}

		private static void ReadHints(TagObject tag, EntryAttributes a)
		{
			if (Bool(tag, "RequiresRestart") == true)
			{
				a.RequiresRestart = true;
			}
			if (Bool(tag, "Multiline") == true)
			{
				a.Multiline = true;
			}
			EntryAttributes entryAttributes = a;
			if (entryAttributes.ListSeparator == null)
			{
				string text = (entryAttributes.ListSeparator = Text(tag, "ListSeparator"));
			}
			entryAttributes = a;
			if (entryAttributes.Unit == null)
			{
				string text = (entryAttributes.Unit = Text(tag, "Unit"));
			}
			entryAttributes = a;
			if (!entryAttributes.Step.HasValue)
			{
				double? num = (entryAttributes.Step = Number(tag, "Step"));
			}
		}

		private static bool Present(TagObject tag, string name, out object? value)
		{
			if (tag.TryGet(name, out value))
			{
				return value != null;
			}
			return false;
		}

		private static bool? Bool(TagObject tag, string name)
		{
			if (!tag.TryGet(name, out object value) || !(value is bool))
			{
				return null;
			}
			return (bool)value;
		}

		private static string? Text(TagObject tag, string name)
		{
			if (!tag.TryGet(name, out object value) || !(value is string result))
			{
				return null;
			}
			return result;
		}

		private static int? Int(TagObject tag, string name)
		{
			if (!tag.TryGet(name, out object value) || value == null)
			{
				return null;
			}
			if (!(value is int))
			{
				long num5;
				if (!(value is short num))
				{
					if (!(value is sbyte b))
					{
						if (!(value is byte b2))
						{
							if (!(value is ushort num2))
							{
								if (!(value is uint num3))
								{
									if (!(value is long num4))
									{
										return null;
									}
									num5 = num4;
								}
								else
								{
									num5 = num3;
								}
							}
							else
							{
								num5 = num2;
							}
						}
						else
						{
							num5 = b2;
						}
					}
					else
					{
						num5 = b;
					}
				}
				else
				{
					num5 = num;
				}
				if (num5 >= int.MinValue && num5 <= int.MaxValue)
				{
					return (int)num5;
				}
				return null;
			}
			return (int)value;
		}

		private static double? Number(TagObject tag, string name)
		{
			if (!tag.TryGet(name, out object value) || value == null)
			{
				return null;
			}
			if (!(value is double))
			{
				if (!(value is float num))
				{
					if (!(value is decimal num2))
					{
						if (!(value is int num3))
						{
							if (!(value is long num4))
							{
								if (value is short num5)
								{
									return num5;
								}
								return null;
							}
							return num4;
						}
						return num3;
					}
					return (double)num2;
				}
				return num;
			}
			return (double)value;
		}
	}
	public enum Visibility
	{
		Visible,
		HiddenNotBrowsable,
		HiddenByServerPolicy,
		HiddenAdvanced
	}
	public static class Presentation
	{
		public static Visibility Decide(EntryAttributes a, bool showAdvanced, bool matchedBySearch, bool hiddenByServerPolicy)
		{
			if (a.Browsable == false)
			{
				return Visibility.HiddenNotBrowsable;
			}
			if (hiddenByServerPolicy)
			{
				return Visibility.HiddenByServerPolicy;
			}
			if (a.IsAdvanced == true && !showAdvanced && !matchedBySearch)
			{
				return Visibility.HiddenAdvanced;
			}
			return Visibility.Visible;
		}

		public static string DisplayName(EntryAttributes a, string key)
		{
			if (!string.IsNullOrEmpty(a.DispName))
			{
				return a.DispName;
			}
			return key;
		}

		public static string Heading(EntryAttributes a, string section)
		{
			if (!string.IsNullOrEmpty(a.Category))
			{
				return a.Category;
			}
			return section;
		}

		public static bool ShowsSettingName(EntryAttributes a, bool drawingCustom)
		{
			if (drawingCustom)
			{
				return a.HideSettingName != true;
			}
			return true;
		}

		public static bool ShowsResetButton(EntryAttributes a)
		{
			return a.HideDefaultButton != true;
		}

		public static object? ResetTarget(EntryAttributes a, object? entryDefault)
		{
			if (!a.HasDefaultOverride)
			{
				return entryDefault;
			}
			return a.DefaultValue;
		}

		public static IEnumerable<T> Sort<T>(IEnumerable<T> rows, Func<T, int?> order, Func<T, string> displayName)
		{
			return rows.OrderByDescending((T r) => order(r).GetValueOrDefault()).ThenBy<T, string>(displayName, StringComparer.OrdinalIgnoreCase).ThenBy<T, string>(displayName, StringComparer.Ordinal);
		}
	}
}