Decompiled source of Thing v0.1.14

plugins/Thing/Thing.Core.dll

Decompiled 13 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.Security.Cryptography;
using System.Text;
using ExtraPotions.Thing.Admin;
using ExtraPotions.Thing.Communication;
using ExtraPotions.Thing.Config;
using ExtraPotions.Thing.ConfigEditing.Attributes;
using ExtraPotions.Thing.ConfigEditing.Editors;
using ExtraPotions.Thing.Moderation;
using ExtraPotions.Thing.RemoteConfig;
using ExtraPotions.Thing.UI;
using ExtraPotions.Thing.WorldAdmin;
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.14.0")]
[assembly: AssemblyInformationalVersion("0.1.14+source.C5CB98230C177B26BBB9D41F4869FF0DA5DE81F8C31B5A5A34E1D7BBF025C5D7")]
[assembly: AssemblyProduct("Thing.Core")]
[assembly: AssemblyTitle("Thing.Core")]
[assembly: AssemblyVersion("0.1.14.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.Thing
{
	public static class ThingCore
	{
		public const string PluginGuid = "com.meadworks.thing";

		public const string ProductName = "Thing";

		public const string ProductVersion = "0.1.14";

		public const string Family = "Runestone";

		public const string TokenNamespace = "thing";

		public const string TokenPrefix = "meadworks_thing_";

		public const string DataFolder = "Meadworks/thing";

		public const string SupportedGameVersion = "1.0.17";
	}
}
namespace ExtraPotions.Thing.WorldAdmin
{
	public enum DropReceiptOutcome
	{
		Continue,
		Refused,
		Recovery
	}
	public static class DropReceipt
	{
		public static DropReceiptOutcome Decide(string status, int removed, int approved, int previousRemoved)
		{
			if (removed < 0 || approved < 0 || previousRemoved < 0 || removed > approved || (status != "ok" && status != "refused"))
			{
				return DropReceiptOutcome.Recovery;
			}
			if (status == "ok")
			{
				return DropReceiptOutcome.Continue;
			}
			if (previousRemoved <= 0 && removed <= 0)
			{
				return DropReceiptOutcome.Refused;
			}
			return DropReceiptOutcome.Recovery;
		}
	}
	public static class PickupRadius
	{
		public const float Min = 1f;

		public const float Max = 10f;

		public static bool TryParse(string? text, out float? radius)
		{
			radius = null;
			string text2 = (text ?? "").Trim();
			if (text2.Equals("reset", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (!float.TryParse(text2, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result) || !(result >= 1f) || !(result <= 10f))
			{
				return false;
			}
			radius = result;
			return true;
		}

		public static bool Reapply(float? wanted, float live)
		{
			if (wanted.HasValue)
			{
				return Math.Abs(wanted.Value - live) > 0.001f;
			}
			return false;
		}
	}
	public static class PoiData
	{
		public const string CapabilityId = "meadworks.thing.poi";

		public const string OperationId = "meadworks.thing.poi_locations";

		public static readonly Version Version = new Version(1, 0);

		public const int MaximumLocations = 512;

		public const int MaximumBytes = 16384;

		private static readonly HashSet<string> names = new HashSet<string>(StringComparer.Ordinal)
		{
			"Eikthyrnir", "GDKing", "Bonemass", "Dragonqueen", "GoblinKing", "Mistlands_DvergrBossEntrance1", "FaderLocation", "DN_Bossroom", "Vendor_BlackForest", "Hildir_camp",
			"BogWitch_Camp"
		};

		public static bool Known(string name)
		{
			if (name != null)
			{
				return names.Contains(name);
			}
			return false;
		}

		public static bool Trader(string name)
		{
			if (!(name == "Vendor_BlackForest") && !(name == "Hildir_camp"))
			{
				return name == "BogWitch_Camp";
			}
			return true;
		}

		private static bool Coordinate(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return Math.Abs(value) <= 20000f;
			}
			return false;
		}

		private static bool Valid(Tuple<string, float, float, float, bool> row)
		{
			if (row != null && Known(row.Item1) && Coordinate(row.Item2) && Coordinate(row.Item3))
			{
				return Coordinate(row.Item4);
			}
			return false;
		}

		public static IReadOnlyList<Tuple<string, float, float, float, bool>> Select(IEnumerable<Tuple<string, float, float, float, bool>> source)
		{
			Tuple<string, float, float, float, bool>[] source2 = source.Where(Valid).ToArray();
			HashSet<string> confirmed = new HashSet<string>(from r in source2
				where Trader(r.Item1) && r.Item5
				select r.Item1, StringComparer.Ordinal);
			Tuple<string, float, float, float, bool>[] array = source2.Where((Tuple<string, float, float, float, bool> r) => !Trader(r.Item1) || r.Item5 || !confirmed.Contains(r.Item1)).Distinct().OrderBy<Tuple<string, float, float, float, bool>, string>((Tuple<string, float, float, float, bool> r) => r.Item1, StringComparer.Ordinal)
				.ThenBy((Tuple<string, float, float, float, bool> r) => r.Item2)
				.ThenBy((Tuple<string, float, float, float, bool> r) => r.Item4)
				.ToArray();
			if (array.Length > 512 || 5 + array.Sum((Tuple<string, float, float, float, bool> r) => 14 + Encoding.UTF8.GetByteCount(r.Item1)) > 16384)
			{
				throw new InvalidOperationException("PoiLimit");
			}
			return Array.AsReadOnly(array);
		}

		public static byte[] Encode(IReadOnlyList<Tuple<string, float, float, float, bool>> rows)
		{
			if (rows == null || rows.Count > 512 || rows.Any((Tuple<string, float, float, float, bool> r) => !Valid(r)))
			{
				throw new ArgumentException("Invalid POI rows.");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true);
			binaryWriter.Write((byte)1);
			binaryWriter.Write(rows.Count);
			foreach (Tuple<string, float, float, float, bool> row in rows)
			{
				binaryWriter.Write(row.Item1);
				binaryWriter.Write(row.Item2);
				binaryWriter.Write(row.Item3);
				binaryWriter.Write(row.Item4);
				binaryWriter.Write(row.Item5);
			}
			if (memoryStream.Length > 16384)
			{
				throw new ArgumentException("POI reply too large.");
			}
			return memoryStream.ToArray();
		}

		public static IReadOnlyList<Tuple<string, float, float, float, bool>> Decode(byte[] bytes)
		{
			if (bytes == null || bytes.Length < 5 || bytes.Length > 16384)
			{
				throw new FormatException("Invalid POI payload length.");
			}
			using MemoryStream memoryStream = new MemoryStream(bytes, writable: false);
			using BinaryReader binaryReader = new BinaryReader(memoryStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true));
			if (binaryReader.ReadByte() != 1)
			{
				throw new FormatException("Unsupported POI payload.");
			}
			int num = binaryReader.ReadInt32();
			if (num < 0 || num > 512)
			{
				throw new FormatException("Invalid POI count.");
			}
			List<Tuple<string, float, float, float, bool>> list = new List<Tuple<string, float, float, float, bool>>(num);
			for (int i = 0; i < num; i++)
			{
				string item = binaryReader.ReadString();
				float item2 = binaryReader.ReadSingle();
				float item3 = binaryReader.ReadSingle();
				float item4 = binaryReader.ReadSingle();
				byte b = binaryReader.ReadByte();
				Tuple<string, float, float, float, bool> tuple = Tuple.Create(item, item2, item3, item4, b == 1);
				if (b > 1 || !Valid(tuple))
				{
					throw new FormatException("Invalid POI row.");
				}
				list.Add(tuple);
			}
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new FormatException("Trailing POI payload.");
			}
			return list.AsReadOnly();
		}
	}
	public static class TerrainCells
	{
		public static IEnumerable<int> InRadius(int pitch, float centerX, float centerY, float radius, float spacing)
		{
			if (pitch <= 0 || pitch > 1024 || !Finite(centerX) || !Finite(centerY) || !Finite(radius) || radius < 0f || !Finite(spacing) || spacing <= 0f)
			{
				throw new ArgumentOutOfRangeException("pitch", "Invalid terrain grid.");
			}
			float squaredRadius = radius * radius;
			for (int h = 0; h < pitch; h++)
			{
				for (int w = 0; w < pitch; w++)
				{
					float num = ((float)w - centerX) * spacing;
					float num2 = ((float)h - centerY) * spacing;
					if (num * num + num2 * num2 <= squaredRadius)
					{
						yield return h * pitch + w;
					}
				}
			}
		}

		public static bool Shape(int pitch, params int[] lengths)
		{
			if (pitch <= 0 || pitch > 1024)
			{
				return false;
			}
			for (int i = 0; i < lengths.Length; i++)
			{
				if (lengths[i] != pitch * pitch)
				{
					return false;
				}
			}
			return lengths.Length != 0;
		}

		private static bool Finite(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	public sealed class WardConfirmation
	{
		public const double MaxAgeSeconds = 75.0;

		private long generation = -1L;

		private string character = "";

		private double started;

		private double expires;

		public void Confirm(long session, string nativeCharacter, double requestStarted)
		{
			if (session < 0 || string.IsNullOrEmpty(nativeCharacter) || double.IsNaN(requestStarted) || double.IsInfinity(requestStarted) || requestStarted < 0.0)
			{
				Clear();
				return;
			}
			generation = session;
			character = nativeCharacter;
			started = requestStarted;
			expires = requestStarted + 75.0;
		}

		public bool Allows(long session, string nativeCharacter, double now)
		{
			if (character.Length > 0 && generation == session && character == nativeCharacter && !double.IsNaN(now) && !double.IsInfinity(now) && now >= started)
			{
				return now < expires;
			}
			return false;
		}

		public void Clear()
		{
			generation = -1L;
			character = "";
			expires = 0.0;
		}
	}
	public sealed class WardStatusSchedule
	{
		public const double RefreshSeconds = 60.0;

		private double next = double.PositiveInfinity;

		private bool requested;

		public void Confirmed(double requestStarted)
		{
			next = ((double.IsNaN(requestStarted) || double.IsInfinity(requestStarted)) ? double.PositiveInfinity : (requestStarted + 60.0));
		}

		public void Request()
		{
			requested = true;
		}

		public bool Due(bool wanted, double now)
		{
			if (wanted)
			{
				if (!requested)
				{
					return now >= next;
				}
				return true;
			}
			return false;
		}

		public void Sent()
		{
			requested = false;
			next = double.PositiveInfinity;
		}

		public void Clear()
		{
			requested = false;
			next = double.PositiveInfinity;
		}
	}
	public readonly struct PieceFootprint
	{
		public float X { get; }

		public float Z { get; }

		public float Extent { get; }

		public long Creator { get; }

		public PieceFootprint(float x, float z, float extent, long creator)
		{
			X = x;
			Z = z;
			Extent = Math.Max(0f, extent);
			Creator = creator;
		}

		public bool Overlaps(float centerX, float centerZ, float radius)
		{
			float num = X - centerX;
			float num2 = Z - centerZ;
			float num3 = radius + Extent;
			return num * num + num2 * num2 <= num3 * num3;
		}
	}
	public sealed class TerrainArea
	{
		public bool Complete { get; }

		public IReadOnlyList<PieceFootprint> Pieces { get; }

		public TerrainArea(bool complete, IReadOnlyList<PieceFootprint> pieces)
		{
			Complete = complete;
			Pieces = pieces ?? Array.Empty<PieceFootprint>();
		}
	}
	public enum TerrainOutcome
	{
		Proceed,
		RefusedNotAdmin,
		RefusedIncomplete,
		RefusedOtherPieces
	}
	public static class TerrainReset
	{
		public static int Radius(float requested, int serverMaximum)
		{
			int num = Math.Max(1, serverMaximum);
			if (float.IsNaN(requested) || float.IsInfinity(requested))
			{
				return 1;
			}
			return (int)Math.Min(num, Math.Max(1.0, Math.Round(requested, MidpointRounding.AwayFromZero)));
		}

		public static TerrainOutcome Decide(Verdict verdict, long requester, float centerX, float centerZ, int radius, TerrainArea area)
		{
			if (!AdminCheck.Permits(verdict))
			{
				return TerrainOutcome.RefusedNotAdmin;
			}
			if (area == null || !area.Complete)
			{
				return TerrainOutcome.RefusedIncomplete;
			}
			if (verdict == Verdict.Allowed)
			{
				return TerrainOutcome.Proceed;
			}
			if (!area.Pieces.Any((PieceFootprint p) => p.Overlaps(centerX, centerZ, radius) && (p.Creator == 0L || p.Creator != requester)))
			{
				return TerrainOutcome.Proceed;
			}
			return TerrainOutcome.RefusedOtherPieces;
		}
	}
	public enum DropKind
	{
		LooseItem,
		PlacedItem,
		BuildingPiece,
		Container,
		Tombstone,
		SwimmingFish
	}
	public static class DropRules
	{
		public static bool Removes(DropKind kind, float distance, float radius)
		{
			if (kind == DropKind.LooseItem && !float.IsNaN(distance) && distance >= 0f)
			{
				return distance <= radius;
			}
			return false;
		}
	}
	public static class TimeOfDay
	{
		public static double? AtCurrentDay(double seconds, double dayLength, double fraction)
		{
			if (double.IsNaN(seconds) || double.IsInfinity(seconds) || seconds < 0.0 || double.IsNaN(dayLength) || double.IsInfinity(dayLength) || dayLength <= 0.0 || double.IsNaN(fraction) || double.IsInfinity(fraction) || fraction < 0.0 || fraction >= 1.0)
			{
				return null;
			}
			double num = Math.Floor(seconds / dayLength) * dayLength + fraction * dayLength;
			if (!double.IsInfinity(num) && !(num > 315537897599.0))
			{
				return num;
			}
			return null;
		}

		public static double? Parse(string? text)
		{
			string text2 = (text ?? "").Trim();
			switch (text2.ToLowerInvariant())
			{
			case "midnight":
				return 0.0;
			case "sunrise":
				return 0.25;
			case "noon":
				return 0.5;
			case "sunset":
				return 0.75;
			default:
			{
				string[] array = text2.Split(':');
				if (array.Length != 2 || array[0].Length == 0 || array[0].Length > 2 || array[1].Length != 2)
				{
					return null;
				}
				if (!int.TryParse(array[0], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array[1], NumberStyles.None, CultureInfo.InvariantCulture, out var result2))
				{
					return null;
				}
				if (result > 23 || result2 > 59)
				{
					return null;
				}
				return (double)(result * 60 + result2) / 1440.0;
			}
			}
		}

		public static string Format(double fraction)
		{
			int num = (int)Math.Round((double.IsNaN(fraction) ? 0.0 : (fraction - Math.Floor(fraction))) * 1440.0) % 1440;
			return (num / 60).ToString("00", CultureInfo.InvariantCulture) + ":" + (num % 60).ToString("00", CultureInfo.InvariantCulture);
		}
	}
	public sealed class GlobalTimeState
	{
		public string SetBy { get; private set; } = "";

		public double Fraction { get; private set; }

		public bool Frozen { get; private set; }

		public void Apply(string adminId, double fraction, bool freeze)
		{
			SetBy = (adminId ?? "").Trim();
			Fraction = fraction;
			Frozen = freeze;
		}

		public bool ShouldRelease(Func<string, bool> online, Func<string, bool> isAdmin, bool releaseOnLeave)
		{
			if (!Frozen || !releaseOnLeave || SetBy.Length == 0)
			{
				return false;
			}
			if (online(SetBy))
			{
				return !isAdmin(SetBy);
			}
			return true;
		}

		public bool Release()
		{
			if (!Frozen)
			{
				return false;
			}
			Frozen = false;
			return true;
		}
	}
	public sealed class WardOverrideGrants
	{
		private readonly HashSet<string> granted = new HashSet<string>(StringComparer.Ordinal);

		public bool Grant(string id)
		{
			return granted.Add((id ?? "").Trim());
		}

		public bool Revoke(string id)
		{
			return granted.Remove((id ?? "").Trim());
		}

		public bool Has(string id)
		{
			return granted.Contains((id ?? "").Trim());
		}

		public IReadOnlyList<string> Prune(Func<string, bool> stillOnlineAdmin)
		{
			List<string> list = granted.Where((string id) => !stillOnlineAdmin(id)).OrderBy<string, string>((string id) => id, StringComparer.Ordinal).ToList();
			foreach (string item in list)
			{
				granted.Remove(item);
			}
			return list;
		}

		public void Clear()
		{
			granted.Clear();
		}
	}
}
namespace ExtraPotions.Thing.UI
{
	public static class GrantReply
	{
		public static bool IsCurrent(string requestedId, string selectedId)
		{
			if (!string.IsNullOrEmpty(requestedId))
			{
				return string.Equals(requestedId, selectedId, StringComparison.Ordinal);
			}
			return false;
		}

		public static bool TryRead(string requestedId, string selectedId, IReadOnlyList<string> lines, out AdminRight set)
		{
			set = AdminRight.None;
			if (!IsCurrent(requestedId, selectedId) || lines == null)
			{
				return false;
			}
			foreach (string line in lines)
			{
				bool fullAdmin;
				if (line != null && line.StartsWith("GRANTS\t", StringComparison.Ordinal))
				{
					return AdminPermissions.TryWire(line, out fullAdmin, out set);
				}
			}
			return false;
		}
	}
	public sealed class ThingUiRow
	{
		public string Kind { get; }

		public string Id { get; }

		public string Caption { get; }

		public string Detail { get; }

		public ThingUiRow(string kind, string id, string caption, string detail)
		{
			Kind = kind;
			Id = id;
			Caption = caption;
			Detail = detail;
		}
	}
	public sealed class ThingUiPage
	{
		public const int PageSize = 12;

		public const int MaxTotal = 10000;

		private const string Marker = "THING_UI_PAGE";

		public string Page { get; }

		public int Offset { get; }

		public int Total { get; }

		public string Version { get; }

		public IReadOnlyList<ThingUiRow> Rows { get; }

		private ThingUiPage(string page, int offset, int total, IReadOnlyList<ThingUiRow> rows, string version = "")
		{
			Page = page;
			Offset = offset;
			Total = total;
			Rows = rows;
			Version = version;
		}

		public static bool Known(string page)
		{
			switch (page)
			{
			default:
				return page == "config_entries";
			case "players":
			case "moderation":
			case "audit":
			case "permitted":
			case "admins":
			case "grants":
			case "announcements":
			case "delve":
			case "delve_actions":
			case "delve_due":
			case "delve_nearest":
			case "delve_nature":
			case "config_files":
				return true;
			}
		}

		public static bool AdminReadPage(string page)
		{
			if (Known(page) && page != "announcements" && page != "delve" && page != "delve_actions" && page != "delve_due" && page != "delve_nearest" && page != "delve_nature" && page != "config_files")
			{
				return page != "config_entries";
			}
			return false;
		}

		public static bool PlatformId(string id)
		{
			if (!string.IsNullOrWhiteSpace(id) && Encoding.UTF8.GetByteCount(id) <= 256)
			{
				return id.All((char c) => char.IsLetterOrDigit(c) || c == '_' || c == '-' || c == ':' || c == '.');
			}
			return false;
		}

		public static IReadOnlyList<string> Encode(string page, int offset, IEnumerable<ThingUiRow> source, string version = "")
		{
			if (version.Length > 0 && !MessageEditing.Version(version))
			{
				throw new ArgumentException("Invalid page version.");
			}
			if (!Known(page) || offset < 0 || offset >= 10000)
			{
				throw new ArgumentException("Invalid Thing page.");
			}
			List<ThingUiRow> list = source.Take(10000).ToList();
			List<string> list2 = new List<string>();
			list2.Add("THING_UI_PAGE\t" + page + "\t" + offset.ToString(CultureInfo.InvariantCulture) + "\t" + list.Count.ToString(CultureInfo.InvariantCulture) + ((version.Length == 0) ? "" : ("\t" + version)));
			List<string> list3 = list2;
			foreach (ThingUiRow item in list.Skip(offset).Take(12))
			{
				if (!PlatformId(item.Id))
				{
					throw new ArgumentException("Invalid row identity.");
				}
				list3.Add(string.Join("\t", AuditEntry.Escape(Clip(item.Kind, 16)), AuditEntry.Escape(item.Id), AuditEntry.Escape(Clip(item.Caption, 128)), AuditEntry.Escape(Clip(item.Detail, 224))));
			}
			return list3;
		}

		private static string Clip(string text, int bytes)
		{
			if (text == null)
			{
				text = "";
			}
			int num = 0;
			int num2 = 0;
			string text2 = text;
			for (int i = 0; i < text2.Length; i++)
			{
				char c = text2[i];
				int num3 = (char.IsSurrogate(c) ? 3 : Encoding.UTF8.GetByteCount(c.ToString()));
				if (num + num3 > bytes)
				{
					break;
				}
				num += num3;
				num2++;
			}
			if (num2 > 0 && char.IsHighSurrogate(text[num2 - 1]))
			{
				num2--;
			}
			return text.Substring(0, num2);
		}

		public static bool TryDecode(IReadOnlyList<string> lines, out ThingUiPage page)
		{
			page = null;
			if (lines == null || lines.Count < 1 || lines.Count > 13)
			{
				return false;
			}
			string[] array = lines[0].Split('\t');
			if ((array.Length != 4 && array.Length != 5) || (array.Length == 5 && !MessageEditing.Version(array[4])) || array[0] != "THING_UI_PAGE" || !Known(array[1]) || !int.TryParse(array[2], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 0 || result >= 10000 || !int.TryParse(array[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || result2 < 0 || result2 > 10000 || lines.Count - 1 != Math.Min(12, Math.Max(0, result2 - result)))
			{
				return false;
			}
			List<ThingUiRow> list = new List<ThingUiRow>();
			foreach (string item in lines.Skip(1))
			{
				string[] array2 = item.Split('\t');
				string[] array3 = new string[4];
				if (array2.Length != 4)
				{
					return false;
				}
				for (int i = 0; i < array3.Length; i++)
				{
					if (!AuditEntry.TryUnescape(array2[i], out array3[i]))
					{
						return false;
					}
				}
				if (!PlatformId(array3[1]))
				{
					return false;
				}
				list.Add(new ThingUiRow(array3[0], array3[1], array3[2], array3[3]));
			}
			page = new ThingUiPage(array[1], result, result2, list, (array.Length == 5) ? array[4] : "");
			return true;
		}
	}
}
namespace ExtraPotions.Thing.RemoteConfig
{
	public sealed class ConfigListDocument
	{
		public ConfigEntryDocument Element = new ConfigEntryDocument();

		public IReadOnlyList<string> Values = Array.Empty<string>();

		public const int MaxItems = 128;

		public const int MaxValueBytes = 1024;

		public const int MaxValuesBytes = 4096;

		public static IReadOnlyList<string> PackValues(IReadOnlyList<string> values)
		{
			return ConfigWire.Pack(ValueBytes(values));
		}

		private static byte[] ValueBytes(IReadOnlyList<string> values)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter writer = new BinaryWriter(memoryStream, ConfigWire.Utf8, leaveOpen: true))
			{
				WriteValues(writer, values);
			}
			if (memoryStream.Length > 4096)
			{
				throw new FormatException();
			}
			return memoryStream.ToArray();
		}

		private static void WriteValues(BinaryWriter writer, IReadOnlyList<string> values)
		{
			if (values.Count > 128)
			{
				throw new FormatException();
			}
			writer.Write(values.Count);
			foreach (string value in values)
			{
				if (value == null || ConfigWire.Utf8.GetByteCount(value) > 1024)
				{
					throw new FormatException();
				}
				writer.Write(value);
			}
		}

		private static IReadOnlyList<string> ReadValues(BinaryReader reader)
		{
			int num = reader.ReadInt32();
			if (num < 0 || num > 128)
			{
				throw new FormatException();
			}
			string[] array = new string[num];
			for (int i = 0; i < num; i++)
			{
				array[i] = reader.ReadString();
				if (ConfigWire.Utf8.GetByteCount(array[i]) > 1024)
				{
					throw new FormatException();
				}
			}
			if (ValueBytes(array).Length > 4096)
			{
				throw new FormatException();
			}
			return array;
		}

		public static IReadOnlyList<string> UnpackValues(IReadOnlyList<string> chunks)
		{
			using MemoryStream memoryStream = new MemoryStream(ConfigWire.UnpackBytes(chunks, 4096));
			using BinaryReader reader = new BinaryReader(memoryStream, ConfigWire.Utf8);
			IReadOnlyList<string> result = ReadValues(reader);
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new FormatException();
			}
			return result;
		}

		public IReadOnlyList<string> Encode()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, ConfigWire.Utf8, leaveOpen: true))
			{
				binaryWriter.Write((byte)1);
				IReadOnlyList<string> readOnlyList = Element.Encode();
				binaryWriter.Write(readOnlyList.Count);
				foreach (string item in readOnlyList)
				{
					binaryWriter.Write(item);
				}
				WriteValues(binaryWriter, Values);
			}
			if (memoryStream.Length > 7168)
			{
				throw new FormatException();
			}
			return new string[1] { "THING_CONFIG_LIST" }.Concat(ConfigWire.Pack(memoryStream.ToArray())).ToArray();
		}

		public static bool TryDecode(IReadOnlyList<string> lines, out ConfigListDocument document)
		{
			document = null;
			if (lines.Count < 2 || lines.Count > 16 || lines[0] != "THING_CONFIG_LIST")
			{
				return false;
			}
			try
			{
				using MemoryStream memoryStream = new MemoryStream(ConfigWire.UnpackBytes(lines.Skip(1).ToArray(), 7168));
				using BinaryReader binaryReader = new BinaryReader(memoryStream, ConfigWire.Utf8);
				if (binaryReader.ReadByte() != 1)
				{
					return false;
				}
				int num = binaryReader.ReadInt32();
				if (num < 2 || num > 16)
				{
					return false;
				}
				string[] array = new string[num];
				for (int i = 0; i < num; i++)
				{
					array[i] = binaryReader.ReadString();
				}
				if (!ConfigEntryDocument.TryDecode(array, out ConfigEntryDocument document2))
				{
					return false;
				}
				IReadOnlyList<string> values = ReadValues(binaryReader);
				if (memoryStream.Position != memoryStream.Length)
				{
					return false;
				}
				document = new ConfigListDocument
				{
					Element = document2,
					Values = values
				};
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	public sealed class ConfigEditRequest
	{
		public string Verb { get; private set; } = "";

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

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

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

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

		public int Offset { get; private set; }

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

		public int Total { get; private set; }

		public byte[] Bytes { get; private set; } = Array.Empty<byte>();

		public IReadOnlyList<string> ListValues { get; private set; } = Array.Empty<string>();

		public bool Writes
		{
			get
			{
				if (!(Verb == "list-set") && !(Verb == "set") && !(Verb == "yaml-begin") && !(Verb == "yaml-put"))
				{
					return Verb == "yaml-commit";
				}
				return true;
			}
		}

		public ConfigEditRequest WithValue(string value)
		{
			return new ConfigEditRequest
			{
				Verb = "set",
				File = File,
				Entry = Entry,
				Version = Version,
				Value = value
			};
		}

		public static bool TryParse(IReadOnlyList<string> args, out ConfigEditRequest request)
		{
			request = null;
			if (args == null || args.Count == 0)
			{
				return false;
			}
			string text = args[0].ToLowerInvariant();
			ConfigEditRequest configEditRequest = new ConfigEditRequest();
			if (text == "set")
			{
				if (args.Count < 5 || !MessageEditing.Version(args[1]) || !MessageEditing.Version(args[2]) || !MessageEditing.Version(args[3]))
				{
					return false;
				}
				try
				{
					configEditRequest.Value = ConfigWire.Unpack(args.Skip(4).ToArray(), 4096);
				}
				catch
				{
					return false;
				}
				configEditRequest.Verb = "set";
				configEditRequest.File = args[1];
				configEditRequest.Entry = args[2];
				configEditRequest.Version = args[3];
				request = configEditRequest;
				return true;
			}
			if (text != "config" || args.Count < 2)
			{
				return false;
			}
			configEditRequest.Verb = args[1].ToLowerInvariant();
			if (configEditRequest.Verb == "list-get" || configEditRequest.Verb == "list-set")
			{
				if (args.Count < 4 || !MessageEditing.Version(args[2]) || !MessageEditing.Version(args[3]))
				{
					return false;
				}
				configEditRequest.File = args[2];
				configEditRequest.Entry = args[3];
				if (configEditRequest.Verb == "list-get")
				{
					if (args.Count != 4)
					{
						return false;
					}
				}
				else
				{
					if (args.Count < 6 || !MessageEditing.Version(args[4]))
					{
						return false;
					}
					configEditRequest.Version = args[4];
					try
					{
						configEditRequest.ListValues = ConfigListDocument.UnpackValues(args.Skip(5).ToArray());
					}
					catch
					{
						return false;
					}
				}
				request = configEditRequest;
				return true;
			}
			if (configEditRequest.Verb.StartsWith("yaml-", StringComparison.Ordinal))
			{
				if (args.Count < 3 || !MessageEditing.Version(args[2]))
				{
					return false;
				}
				configEditRequest.Token = args[2];
				switch (configEditRequest.Verb)
				{
				case "yaml-open":
					if (args.Count != 3)
					{
						return false;
					}
					configEditRequest.File = args[2];
					break;
				case "yaml-cancel":
				case "yaml-commit":
					if (args.Count != 3)
					{
						return false;
					}
					break;
				case "yaml-read":
				{
					if (args.Count != 4 || !int.TryParse(args[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || result2 < 0 || result2 > 1048576)
					{
						return false;
					}
					configEditRequest.Offset = result2;
					break;
				}
				case "yaml-begin":
				{
					if (args.Count != 5 || !int.TryParse(args[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result3) || result3 < 0 || result3 > 1048576 || !MessageEditing.Version(args[4]))
					{
						return false;
					}
					configEditRequest.Total = result3;
					configEditRequest.Version = args[4];
					break;
				}
				case "yaml-put":
				{
					if (args.Count < 5 || !int.TryParse(args[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 0 || result > 1048576)
					{
						return false;
					}
					try
					{
						configEditRequest.Bytes = YamlTransfer.Unpack(args.Skip(4).ToArray());
					}
					catch
					{
						return false;
					}
					if (configEditRequest.Bytes.Length == 0)
					{
						return false;
					}
					configEditRequest.Offset = result;
					break;
				}
				default:
					return false;
				}
				request = configEditRequest;
				return true;
			}
			if (configEditRequest.Verb == "list")
			{
				if (args.Count != 2 && args.Count != 3)
				{
					return false;
				}
			}
			else
			{
				if (!(configEditRequest.Verb == "entries"))
				{
					if (configEditRequest.Verb == "get")
					{
						if (args.Count != 4 || !MessageEditing.Version(args[2]) || !MessageEditing.Version(args[3]))
						{
							return false;
						}
						configEditRequest.File = args[2];
						configEditRequest.Entry = args[3];
						request = configEditRequest;
						return true;
					}
					return false;
				}
				if ((args.Count != 3 && args.Count != 4) || !MessageEditing.Version(args[2]))
				{
					return false;
				}
				configEditRequest.File = args[2];
			}
			int num = ((configEditRequest.Verb == "list") ? 2 : 3);
			if (args.Count > num && (!int.TryParse(args[num], NumberStyles.None, CultureInfo.InvariantCulture, out var result4) || result4 < 0 || result4 >= 10000))
			{
				return false;
			}
			configEditRequest.Offset = ((args.Count > num) ? int.Parse(args[num], CultureInfo.InvariantCulture) : 0);
			request = configEditRequest;
			return true;
		}
	}
	public sealed class ConfigEntryDocument
	{
		public string File = "";

		public string Entry = "";

		public string Version = "";

		public string Section = "";

		public string Key = "";

		public string Description = "";

		public string Current = "";

		public string Default = "";

		public string Reason = "";

		public string Effective = "";

		public bool Editable;

		public SettingShape Shape = SettingShape.String();

		public EntryAttributes Attributes = AttributeReader.Read(Array.Empty<TagObject>());

		public IReadOnlyList<string> Encode()
		{
			if (!MessageEditing.Version(File) || !MessageEditing.Version(Entry) || !MessageEditing.Version(Version))
			{
				throw new FormatException("Invalid config document identity.");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, ConfigWire.Utf8, leaveOpen: true))
			{
				binaryWriter.Write((byte)1);
				string[] array = new string[10] { File, Entry, Version, Section, Key, Description, Current, Default, Reason, Effective };
				foreach (string value in array)
				{
					binaryWriter.Write(value);
				}
				binaryWriter.Write(Editable);
				binaryWriter.Write((int)Shape.Kind);
				binaryWriter.Write(Shape.TypeName);
				binaryWriter.Write(Shape.HasConverter);
				binaryWriter.Write(Shape.IsFlags);
				binaryWriter.Write(Shape.HasRange);
				binaryWriter.Write(Shape.RangeMin);
				binaryWriter.Write(Shape.RangeMax);
				WriteList(binaryWriter, Shape.EnumNames);
				WriteList(binaryWriter, Shape.AcceptableList);
				WriteList(binaryWriter, Shape.KeyNames);
				binaryWriter.Write(Attributes.RequiresRestart);
				binaryWriter.Write(Attributes.Multiline);
				binaryWriter.Write(Attributes.ListSeparator ?? "");
				binaryWriter.Write(Attributes.Unit ?? "");
				binaryWriter.Write(Attributes.Step.HasValue);
				binaryWriter.Write(Attributes.Step.GetValueOrDefault());
				binaryWriter.Write(Attributes.ShowRangeAsPercent == true);
			}
			if (memoryStream.Length > 7168)
			{
				throw new FormatException("Config metadata exceeds one bounded frame.");
			}
			return new string[1] { "THING_CONFIG_ENTRY" }.Concat(ConfigWire.Pack(memoryStream.ToArray())).ToArray();
		}

		private static void WriteList(BinaryWriter writer, IReadOnlyList<string>? list)
		{
			if (list != null && list.Count > 512)
			{
				throw new FormatException("Too many metadata choices.");
			}
			writer.Write(list?.Count ?? (-1));
			if (list == null)
			{
				return;
			}
			foreach (string item in list)
			{
				writer.Write(item);
			}
		}

		private static string[]? ReadList(BinaryReader reader)
		{
			int num = reader.ReadInt32();
			if (num == -1)
			{
				return null;
			}
			if (num < 0 || num > 512)
			{
				throw new FormatException();
			}
			string[] array = new string[num];
			for (int i = 0; i < num; i++)
			{
				array[i] = reader.ReadString();
			}
			return array;
		}

		public static bool TryDecode(IReadOnlyList<string> lines, out ConfigEntryDocument document)
		{
			document = null;
			if (lines == null || lines.Count < 2 || lines.Count > 16 || lines[0] != "THING_CONFIG_ENTRY")
			{
				return false;
			}
			try
			{
				using MemoryStream memoryStream = new MemoryStream(ConfigWire.UnpackBytes(lines.Skip(1).ToArray(), 7168));
				using BinaryReader binaryReader = new BinaryReader(memoryStream, ConfigWire.Utf8);
				if (binaryReader.ReadByte() != 1)
				{
					return false;
				}
				ConfigEntryDocument configEntryDocument = new ConfigEntryDocument
				{
					File = binaryReader.ReadString(),
					Entry = binaryReader.ReadString(),
					Version = binaryReader.ReadString(),
					Section = binaryReader.ReadString(),
					Key = binaryReader.ReadString(),
					Description = binaryReader.ReadString(),
					Current = binaryReader.ReadString(),
					Default = binaryReader.ReadString(),
					Reason = binaryReader.ReadString(),
					Effective = binaryReader.ReadString(),
					Editable = binaryReader.ReadBoolean()
				};
				if (!MessageEditing.Version(configEntryDocument.File) || !MessageEditing.Version(configEntryDocument.Entry) || !MessageEditing.Version(configEntryDocument.Version) || configEntryDocument.Current.Length > 4096 || configEntryDocument.Default.Length > 4096)
				{
					return false;
				}
				SettingKind settingKind = (SettingKind)binaryReader.ReadInt32();
				if (!Enum.IsDefined(typeof(SettingKind), settingKind))
				{
					return false;
				}
				string typeName = binaryReader.ReadString();
				bool hasConverter = binaryReader.ReadBoolean();
				bool flags = binaryReader.ReadBoolean();
				bool num = binaryReader.ReadBoolean();
				double num2 = binaryReader.ReadDouble();
				double num3 = binaryReader.ReadDouble();
				string[] array = ReadList(binaryReader);
				string[] array2 = ReadList(binaryReader);
				string[] array3 = ReadList(binaryReader);
				configEntryDocument.Shape = ((settingKind == SettingKind.Enum) ? SettingShape.Enum(typeName, array ?? Array.Empty<string>(), flags) : SettingShape.Of(settingKind, typeName, hasConverter));
				if (num)
				{
					if (double.IsNaN(num2) || double.IsInfinity(num2) || double.IsNaN(num3) || double.IsInfinity(num3))
					{
						return false;
					}
					configEntryDocument.Shape = configEntryDocument.Shape.WithRange(num2, num3);
				}
				if (array2 != null)
				{
					configEntryDocument.Shape = configEntryDocument.Shape.WithList(array2);
				}
				if (array3 != null)
				{
					configEntryDocument.Shape = configEntryDocument.Shape.WithKeyNames(array3);
				}
				configEntryDocument.Attributes.RequiresRestart = binaryReader.ReadBoolean();
				configEntryDocument.Attributes.Multiline = binaryReader.ReadBoolean();
				string text = binaryReader.ReadString();
				configEntryDocument.Attributes.ListSeparator = ((text.Length == 0) ? null : text);
				string text2 = binaryReader.ReadString();
				configEntryDocument.Attributes.Unit = ((text2.Length == 0) ? null : text2);
				bool num4 = binaryReader.ReadBoolean();
				double num5 = binaryReader.ReadDouble();
				if (num4)
				{
					if (double.IsNaN(num5) || double.IsInfinity(num5) || num5 <= 0.0)
					{
						return false;
					}
					configEntryDocument.Attributes.Step = num5;
				}
				configEntryDocument.Attributes.ShowRangeAsPercent = binaryReader.ReadBoolean();
				if (memoryStream.Position != memoryStream.Length)
				{
					return false;
				}
				document = configEntryDocument;
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	public static class ConfigWire
	{
		public const string Marker = "THING_CONFIG_ENTRY";

		public const int MaxDocumentBytes = 7168;

		internal static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		public static IReadOnlyList<string> PackValue(string value)
		{
			return Pack(Utf8.GetBytes(value ?? ""));
		}

		public static IReadOnlyList<string> WriteEcho(string version, string value)
		{
			if (!MessageEditing.Version(version) || Utf8.GetByteCount(value) > 4096)
			{
				throw new FormatException();
			}
			return new string[1] { "THING_CONFIG_WRITE\t" + version }.Concat(PackValue(value)).ToArray();
		}

		public static bool TryWriteEcho(IReadOnlyList<string> lines, out string version, out string value)
		{
			version = (value = "");
			if (lines == null || lines.Count < 2 || lines.Count > 16)
			{
				return false;
			}
			string[] array = lines[0].Split('\t');
			if (array.Length != 2 || array[0] != "THING_CONFIG_WRITE" || !MessageEditing.Version(array[1]))
			{
				return false;
			}
			try
			{
				value = Unpack(lines.Skip(1).ToArray(), 4096);
				version = array[1];
				return true;
			}
			catch
			{
				return false;
			}
		}

		internal static IReadOnlyList<string> Pack(byte[] bytes)
		{
			string text = Convert.ToBase64String(bytes);
			List<string> list = new List<string>();
			for (int i = 0; i < text.Length; i += 700)
			{
				list.Add(text.Substring(i, Math.Min(700, text.Length - i)));
			}
			if (list.Count == 0)
			{
				list.Add("");
			}
			return list;
		}

		internal static string Unpack(IReadOnlyList<string> parts, int max)
		{
			return Utf8.GetString(UnpackBytes(parts, max));
		}

		internal static byte[] UnpackBytes(IReadOnlyList<string> parts, int max)
		{
			if (parts.Count < 1 || parts.Count > 15 || parts.Any((string p) => p == null || p.Length > 700))
			{
				throw new FormatException();
			}
			byte[] array = Convert.FromBase64String(string.Concat(parts));
			if (array.Length > max)
			{
				throw new FormatException();
			}
			return array;
		}
	}
	public enum FileVerdict
	{
		Allowed,
		RefusedPath,
		RefusedLink,
		RefusedAdminList,
		RefusedExtension,
		RefusedScope,
		RefusedTooLarge
	}
	public readonly struct FileRequest
	{
		public string Path { get; }

		public long SizeBytes { get; }

		public bool IsLink { get; }

		public FileRequest(string path, long sizeBytes, bool isLink)
		{
			Path = path ?? "";
			SizeBytes = sizeBytes;
			IsLink = isLink;
		}
	}
	public sealed class FilePolicy
	{
		public RemoteConfigScope Scope { get; }

		public bool CaseSensitive { get; }

		public ISet<string> AllowList { get; }

		public ISet<string> RegisteredYaml { get; }

		public FilePolicy(RemoteConfigScope scope, string allowList, IEnumerable<string> registeredYaml, bool caseSensitive)
		{
			Scope = scope;
			CaseSensitive = caseSensitive;
			StringComparer comparer = (caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase);
			AllowList = new HashSet<string>(from n in ThingSettings.NormalizeList(allowList ?? "").Split(',')
				where n.Length > 0
				select n, comparer);
			RegisteredYaml = new HashSet<string>(registeredYaml ?? Enumerable.Empty<string>(), comparer);
		}
	}
	public static class RemoteFiles
	{
		public const long MaxBytes = 1048576L;

		public const string MeadworksPrefix = "com.meadworks.";

		public const string ThingConfig = "com.meadworks.thing.cfg";

		public const string ThingFolder = "Meadworks/thing/";

		public static FileVerdict Check(FileRequest request, FilePolicy policy)
		{
			if (policy == null)
			{
				throw new ArgumentNullException("policy");
			}
			string path = request.Path;
			if (!SafePath(path))
			{
				return FileVerdict.RefusedPath;
			}
			if (request.IsLink)
			{
				return FileVerdict.RefusedLink;
			}
			if (string.Equals(path, "adminlist.txt", StringComparison.OrdinalIgnoreCase))
			{
				return FileVerdict.RefusedAdminList;
			}
			bool flag = path.IndexOf('/') < 0;
			bool flag2 = path.EndsWith(".cfg", StringComparison.OrdinalIgnoreCase);
			bool flag3 = path.EndsWith(".yml", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase);
			if (flag3 ? (!policy.RegisteredYaml.Contains(path)) : (!flag2))
			{
				return FileVerdict.RefusedExtension;
			}
			StringComparison comparisonType = (policy.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
			bool flag4 = flag3 || (flag && path.StartsWith("com.meadworks.", comparisonType));
			if (policy.Scope switch
			{
				RemoteConfigScope.AllowList => (flag4 || (flag && policy.AllowList.Contains(path))) ? 1 : 0, 
				RemoteConfigScope.AllConfigFiles => (flag4 || flag) ? 1 : 0, 
				_ => flag4 ? 1 : 0, 
			} == 0)
			{
				return FileVerdict.RefusedScope;
			}
			if (request.SizeBytes < 0 || request.SizeBytes > 1048576)
			{
				return FileVerdict.RefusedTooLarge;
			}
			return FileVerdict.Allowed;
		}

		public static bool SafePath(string? path)
		{
			if (string.IsNullOrEmpty(path) || path[0] == '/')
			{
				return false;
			}
			if (path.Any((char c) => c == '\\' || c == ':' || char.IsControl(c)))
			{
				return false;
			}
			string[] array = path.Split('/');
			foreach (string text in array)
			{
				if (text.Length == 0 || text == "." || text == "..")
				{
					return false;
				}
				if (text.Trim() != text || text.EndsWith(".", StringComparison.Ordinal))
				{
					return false;
				}
			}
			return true;
		}

		public static bool RawTextEditable(string path, IEnumerable<string> registeredYaml, bool caseSensitive)
		{
			return new FilePolicy(RemoteConfigScope.MeadworksOnly, "", registeredYaml, caseSensitive).RegisteredYaml.Contains(path ?? "");
		}

		public static bool Protected(string path)
		{
			if (!string.Equals(path, "com.meadworks.thing.cfg", StringComparison.OrdinalIgnoreCase))
			{
				return (path ?? "").StartsWith("Meadworks/thing/", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}
	}
	public static class FileVersion
	{
		public static string Hash(byte[] bytes)
		{
			using SHA256 sHA = SHA256.Create();
			byte[] array = sHA.ComputeHash(bytes ?? Array.Empty<byte>());
			StringBuilder stringBuilder = new StringBuilder(64);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}

		public static bool Matches(string expectedHash, byte[] current)
		{
			return string.Equals(expectedHash, Hash(current), StringComparison.OrdinalIgnoreCase);
		}
	}
	public static class Backups
	{
		private const string StampFormat = "yyyyMMdd-HHmmss";

		private const string Suffix = ".bak";

		public static string Name(string path, DateTime utc)
		{
			return path + "." + utc.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + ".bak";
		}

		public static IReadOnlyList<string> ToDelete(string path, IEnumerable<string> existing, int keep)
		{
			string text = path + ".";
			List<(DateTime, string)> list = new List<(DateTime, string)>();
			foreach (string item in existing ?? Enumerable.Empty<string>())
			{
				if (item.StartsWith(text, StringComparison.Ordinal) && item.EndsWith(".bak", StringComparison.Ordinal) && DateTime.TryParseExact(item.Substring(text.Length, item.Length - text.Length - ".bak".Length), "yyyyMMdd-HHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
				{
					list.Add((result, item));
				}
			}
			List<string> list2 = (from m in list.OrderBy<(DateTime, string), DateTime>(((DateTime Stamp, string Name) m) => m.Stamp).ThenBy<(DateTime, string), string>(((DateTime Stamp, string Name) m) => m.Name, StringComparer.Ordinal)
				select m.Name).ToList();
			return list2.Take(Math.Max(0, list2.Count - Math.Max(0, keep))).ToList();
		}

		public static bool FirstWriteThisSession(string path, ISet<string> writtenThisSession)
		{
			return !writtenThisSession.Contains(path);
		}
	}
	public static class YamlTransfer
	{
		public const int MaxBytes = 1048576;

		public const int ChunkBytes = 6144;

		public static IReadOnlyList<string> Pack(byte[] bytes)
		{
			return ConfigWire.Pack(bytes);
		}

		public static byte[] Unpack(IReadOnlyList<string> parts)
		{
			return ConfigWire.UnpackBytes(parts, 6144);
		}

		public static string Text(byte[] bytes)
		{
			return ConfigWire.Utf8.GetString(bytes);
		}

		public static byte[] Bytes(string text)
		{
			return ConfigWire.Utf8.GetBytes(text);
		}

		public static IReadOnlyList<string> Header(string token, string version, int total)
		{
			return new string[1] { "THING_YAML\t" + token + "\t" + version + "\t" + total.ToString(CultureInfo.InvariantCulture) };
		}

		public static bool TryHeader(IReadOnlyList<string> lines, out string token, out string version, out int total)
		{
			token = (version = "");
			total = 0;
			if (lines.Count != 1)
			{
				return false;
			}
			string[] array = lines[0].Split('\t');
			if (array.Length != 4 || array[0] != "THING_YAML" || !MessageEditing.Version(array[1]) || !MessageEditing.Version(array[2]) || !int.TryParse(array[3], NumberStyles.None, CultureInfo.InvariantCulture, out total) || total < 0 || total > 1048576)
			{
				return false;
			}
			token = array[1];
			version = array[2];
			return true;
		}

		public static IReadOnlyList<string> Chunk(string token, int offset, byte[] bytes)
		{
			return new string[1] { "THING_YAML_CHUNK\t" + token + "\t" + offset.ToString(CultureInfo.InvariantCulture) }.Concat(Pack(bytes)).ToArray();
		}

		public static bool TryChunk(IReadOnlyList<string> lines, string token, int offset, out byte[] bytes)
		{
			bytes = Array.Empty<byte>();
			if (lines.Count < 2 || lines.Count > 16 || lines[0] != "THING_YAML_CHUNK\t" + token + "\t" + offset.ToString(CultureInfo.InvariantCulture))
			{
				return false;
			}
			try
			{
				bytes = Unpack(lines.Skip(1).ToArray());
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	public sealed class YamlUpload
	{
		private byte[]? bytes;

		private readonly string hash;

		public int Received { get; private set; }

		public int Length { get; }

		public YamlUpload(int length, string hash)
		{
			if (length < 0 || length > 1048576 || !MessageEditing.Version(hash))
			{
				throw new ArgumentException();
			}
			bytes = new byte[length];
			Length = length;
			this.hash = hash;
		}

		public bool Append(int offset, byte[] chunk)
		{
			if (bytes == null || chunk == null || chunk.Length == 0 || chunk.Length > 6144 || offset != Received || chunk.Length > Length - Received)
			{
				return false;
			}
			Buffer.BlockCopy(chunk, 0, bytes, Received, chunk.Length);
			Received += chunk.Length;
			return true;
		}

		public bool Take(out string text)
		{
			text = "";
			byte[] array = bytes;
			bytes = null;
			if (array == null || Received != Length || !FileVersion.Matches(hash, array))
			{
				return false;
			}
			try
			{
				text = YamlTransfer.Text(array);
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
}
namespace ExtraPotions.Thing.Moderation
{
	public static class GameListIdentity
	{
		public static bool Valid(string text)
		{
			if (string.IsNullOrEmpty(text) || text.Length > 160)
			{
				return false;
			}
			if (text.All((char c) => c >= '0' && c <= '9'))
			{
				return true;
			}
			int num = text.IndexOf('_');
			if (num > 0 && num < text.Length - 1 && text.Substring(0, num).All((char c) => (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')))
			{
				return text.Substring(num + 1).All((char c) => char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == ':' || c == '.');
			}
			return false;
		}
	}
	public readonly struct Actor
	{
		public string Id { get; }

		public string Name { get; }

		public Actor(string id, string name)
		{
			Id = (id ?? "").Trim();
			Name = name ?? "";
		}
	}
	public enum LedgerChange
	{
		Unchanged,
		Added,
		Updated,
		Removed
	}
	public enum RecordKind
	{
		Ban,
		Mute,
		Warn
	}
	public sealed class LedgerRecord
	{
		public RecordKind Kind { get; }

		public Actor Target { get; }

		public Actor Admin { get; }

		public string Reason { get; }

		public DateTime AtUtc { get; }

		public DateTime? ExpiresUtc { get; }

		public LedgerRecord(RecordKind kind, Actor target, Actor admin, string reason, DateTime atUtc, DateTime? expiresUtc)
		{
			Kind = kind;
			Target = target;
			Admin = admin;
			Reason = reason ?? "";
			AtUtc = atUtc;
			ExpiresUtc = expiresUtc;
		}

		public bool ActiveAt(DateTime nowUtc)
		{
			if (ExpiresUtc.HasValue)
			{
				return nowUtc < ExpiresUtc.Value;
			}
			return true;
		}
	}
	public sealed class ModerationLedger
	{
		private const string Header = "# Thing moderation ledger (managed by Thing on the server; one entry per line, tab-separated)";

		private const string TimeFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'";

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

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

		private readonly List<LedgerRecord> warnings = new List<LedgerRecord>();

		public IReadOnlyDictionary<string, LedgerRecord> Bans => bans;

		public IReadOnlyDictionary<string, LedgerRecord> Mutes => mutes;

		public IReadOnlyList<LedgerRecord> Warnings => warnings;

		public LedgerChange Ban(Actor target, Actor admin, string reason, DateTime nowUtc, TimeSpan? duration)
		{
			return Put(bans, RecordKind.Ban, target, admin, reason, nowUtc, duration);
		}

		public LedgerChange Unban(string id)
		{
			if (!bans.Remove((id ?? "").Trim()))
			{
				return LedgerChange.Unchanged;
			}
			return LedgerChange.Removed;
		}

		public bool IsBanned(string id, DateTime nowUtc)
		{
			if (bans.TryGetValue((id ?? "").Trim(), out LedgerRecord value))
			{
				return value.ActiveAt(nowUtc);
			}
			return false;
		}

		public IReadOnlyList<LedgerRecord> TakeExpired(DateTime nowUtc)
		{
			return Take(bans, nowUtc);
		}

		public LedgerChange Mute(Actor target, Actor admin, string reason, DateTime nowUtc, TimeSpan? duration)
		{
			return Put(mutes, RecordKind.Mute, target, admin, reason, nowUtc, duration);
		}

		public LedgerChange Unmute(string id)
		{
			if (!mutes.Remove((id ?? "").Trim()))
			{
				return LedgerChange.Unchanged;
			}
			return LedgerChange.Removed;
		}

		public bool IsMuted(string id, DateTime nowUtc)
		{
			if (mutes.TryGetValue((id ?? "").Trim(), out LedgerRecord value))
			{
				return value.ActiveAt(nowUtc);
			}
			return false;
		}

		public IReadOnlyList<LedgerRecord> TakeExpiredMutes(DateTime nowUtc)
		{
			return Take(mutes, nowUtc);
		}

		public void Warn(Actor target, Actor admin, string reason, DateTime nowUtc)
		{
			Require(target);
			warnings.Add(new LedgerRecord(RecordKind.Warn, target, admin, Reasons.Normalize(reason), Second(nowUtc), null));
		}

		public IReadOnlyList<LedgerRecord> WarningsFor(string id)
		{
			return warnings.Where((LedgerRecord w) => w.Target.Id == (id ?? "").Trim()).ToList();
		}

		public string Save()
		{
			StringBuilder stringBuilder = new StringBuilder("# Thing moderation ledger (managed by Thing on the server; one entry per line, tab-separated)").Append('\n');
			foreach (LedgerRecord item in bans.Values.OrderBy<LedgerRecord, string>((LedgerRecord r) => r.Target.Id, StringComparer.Ordinal))
			{
				stringBuilder.Append(Line(item)).Append('\n');
			}
			foreach (LedgerRecord item2 in mutes.Values.OrderBy<LedgerRecord, string>((LedgerRecord r) => r.Target.Id, StringComparer.Ordinal))
			{
				stringBuilder.Append(Line(item2)).Append('\n');
			}
			foreach (LedgerRecord warning in warnings)
			{
				stringBuilder.Append(Line(warning)).Append('\n');
			}
			return stringBuilder.ToString();
		}

		public static ModerationLedger Load(string? text, out int skipped)
		{
			ModerationLedger moderationLedger = new ModerationLedger();
			skipped = 0;
			string[] array = (text ?? "").Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].TrimEnd('\r');
				if (text2.Trim().Length == 0 || text2.StartsWith("#", StringComparison.Ordinal))
				{
					continue;
				}
				if (!TryParse(text2, out LedgerRecord record) || record.Target.Id.Length == 0)
				{
					skipped++;
					continue;
				}
				switch (record.Kind)
				{
				case RecordKind.Ban:
					moderationLedger.bans[record.Target.Id] = record;
					break;
				case RecordKind.Mute:
					moderationLedger.mutes[record.Target.Id] = record;
					break;
				default:
					moderationLedger.warnings.Add(record);
					break;
				}
			}
			return moderationLedger;
		}

		private static LedgerChange Put(Dictionary<string, LedgerRecord> map, RecordKind kind, Actor target, Actor admin, string reason, DateTime nowUtc, TimeSpan? duration)
		{
			Require(target);
			if (duration.HasValue && duration.Value <= TimeSpan.Zero)
			{
				throw new ArgumentException("Expected a positive duration.", "duration");
			}
			DateTime dateTime = Second(nowUtc);
			DateTime? expiresUtc = (duration.HasValue ? new DateTime?(dateTime + duration.Value) : ((DateTime?)null));
			string text = Reasons.Normalize(reason);
			if (map.TryGetValue(target.Id, out LedgerRecord value))
			{
				if (value.Reason == text && !value.ExpiresUtc.HasValue && !expiresUtc.HasValue)
				{
					return LedgerChange.Unchanged;
				}
				map[target.Id] = new LedgerRecord(kind, target, admin, text, dateTime, expiresUtc);
				return LedgerChange.Updated;
			}
			map[target.Id] = new LedgerRecord(kind, target, admin, text, dateTime, expiresUtc);
			return LedgerChange.Added;
		}

		private static IReadOnlyList<LedgerRecord> Take(Dictionary<string, LedgerRecord> map, DateTime nowUtc)
		{
			List<LedgerRecord> list = map.Values.Where((LedgerRecord r) => !r.ActiveAt(nowUtc)).OrderBy<LedgerRecord, string>((LedgerRecord r) => r.Target.Id, StringComparer.Ordinal).ToList();
			foreach (LedgerRecord item in list)
			{
				map.Remove(item.Target.Id);
			}
			return list;
		}

		private static void Require(Actor target)
		{
			if (target.Id.Length == 0)
			{
				throw new ArgumentException("A moderation target needs a platform ID.", "target");
			}
		}

		private static DateTime Second(DateTime utc)
		{
			return DateTime.SpecifyKind(new DateTime(utc.Ticks - utc.Ticks % 10000000), DateTimeKind.Utc);
		}

		private static string Time(DateTime utc)
		{
			return utc.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture);
		}

		private static string Line(LedgerRecord r)
		{
			return string.Join("\t", r.Kind.ToString().ToLowerInvariant(), AuditEntry.Escape(r.Target.Id), AuditEntry.Escape(r.Target.Name), AuditEntry.Escape(r.Admin.Id), AuditEntry.Escape(r.Admin.Name), Time(r.AtUtc), r.ExpiresUtc.HasValue ? Time(r.ExpiresUtc.Value) : "-", AuditEntry.Escape(r.Reason));
		}

		private static bool TryParse(string line, out LedgerRecord record)
		{
			record = null;
			string[] array = line.Split('\t');
			if (array.Length != 8)
			{
				return false;
			}
			RecordKind recordKind;
			switch (array[0])
			{
			case "ban":
				recordKind = RecordKind.Ban;
				break;
			case "mute":
				recordKind = RecordKind.Mute;
				break;
			case "warn":
				recordKind = RecordKind.Warn;
				break;
			default:
				return false;
			}
			if (!TryTime(array[5], out var utc))
			{
				return false;
			}
			DateTime? expiresUtc = null;
			if (array[6] != "-")
			{
				if (recordKind == RecordKind.Warn || !TryTime(array[6], out var utc2))
				{
					return false;
				}
				expiresUtc = utc2;
			}
			if (!AuditEntry.TryUnescape(array[1], out string value) || !AuditEntry.TryUnescape(array[2], out string value2) || !AuditEntry.TryUnescape(array[3], out string value3) || !AuditEntry.TryUnescape(array[4], out string value4) || !AuditEntry.TryUnescape(array[7], out string value5))
			{
				return false;
			}
			record = new LedgerRecord(recordKind, new Actor(value, value2), new Actor(value3, value4), value5, utc, expiresUtc);
			return true;
		}

		private static bool TryTime(string text, out DateTime utc)
		{
			bool result = DateTime.TryParseExact(text, "yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out utc);
			utc = DateTime.SpecifyKind(utc, DateTimeKind.Utc);
			return result;
		}
	}
	public enum DurationUnit
	{
		Minutes,
		Hours
	}
	public static class Durations
	{
		public static TimeSpan? Parse(string? text, DurationUnit defaultUnit, int maximum)
		{
			string text2 = (text ?? "").Trim().ToLowerInvariant();
			if (text2.Length == 0)
			{
				return null;
			}
			char c = text2[text2.Length - 1];
			string text3 = (char.IsDigit(c) ? text2 : text2.Substring(0, text2.Length - 1));
			if (text3.Length == 0 || !text3.All(char.IsDigit) || !long.TryParse(text3, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0)
			{
				return null;
			}
			double val;
			switch ((char)((!char.IsDigit(c)) ? c : ((defaultUnit == DurationUnit.Hours) ? 104 : 109)))
			{
			case 'm':
				val = result;
				break;
			case 'h':
				val = (double)result * 60.0;
				break;
			case 'd':
				val = (double)result * 1440.0;
				break;
			default:
				return null;
			}
			double val2 = ((defaultUnit == DurationUnit.Hours) ? ((double)maximum * 60.0) : ((double)maximum));
			return TimeSpan.FromMinutes(Math.Min(val, val2));
		}

		public static string Describe(TimeSpan span)
		{
			long num = (long)Math.Max(0.0, Math.Round(span.TotalMinutes));
			List<string> list = new List<string>();
			if (num / 1440 > 0)
			{
				list.Add((num / 1440).ToString(CultureInfo.InvariantCulture) + "d");
			}
			if (num / 60 % 24 > 0)
			{
				list.Add((num / 60 % 24).ToString(CultureInfo.InvariantCulture) + "h");
			}
			if (num % 60 > 0 || list.Count == 0)
			{
				list.Add((num % 60).ToString(CultureInfo.InvariantCulture) + "m");
			}
			return string.Join(" ", list);
		}
	}
	public static class Reasons
	{
		public const int MaxLength = 200;

		public static string Normalize(string? text)
		{
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			string text2 = (text ?? "").Trim();
			foreach (char c in text2)
			{
				if (char.IsWhiteSpace(c) || char.IsControl(c))
				{
					flag = true;
					continue;
				}
				if (flag && stringBuilder.Length > 0)
				{
					stringBuilder.Append(' ');
				}
				flag = false;
				stringBuilder.Append(c);
			}
			if (stringBuilder.Length <= 200)
			{
				return stringBuilder.ToString();
			}
			return stringBuilder.ToString(0, 200);
		}

		public static bool Acceptable(string? text, bool required)
		{
			if (required)
			{
				return Normalize(text).Length > 0;
			}
			return true;
		}
	}
	public static class GameListFile
	{
		public static IReadOnlyList<string> Parse(string? text)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			List<string> list = new List<string>();
			string[] array = (text ?? "").Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length != 0 && !text2.StartsWith("//", StringComparison.Ordinal) && hashSet.Add(text2))
				{
					list.Add(text2);
				}
			}
			return list;
		}

		public static bool Contains(IEnumerable<string> list, string? id)
		{
			string wanted = (id ?? "").Trim();
			if (wanted.Length == 0)
			{
				return false;
			}
			return list.Any((string entry) => Same(entry, wanted));
		}

		private static bool Same(string a, string b)
		{
			if (string.Equals(a, b, StringComparison.Ordinal))
			{
				return true;
			}
			int num = a.IndexOf('_');
			int num2 = b.IndexOf('_');
			if (num >= 0 && num2 >= 0)
			{
				return false;
			}
			string text = ((num >= 0) ? a.Substring(num + 1) : a);
			string b2 = ((num2 >= 0) ? b.Substring(num2 + 1) : b);
			if (text.Length > 0 && text.All(char.IsDigit))
			{
				return string.Equals(text, b2, StringComparison.Ordinal);
			}
			return false;
		}
	}
	public readonly struct PlayerFreezeUpdate
	{
		public string PlayerId { get; }

		public long Peer { get; }

		public long Revision { get; }

		public bool Frozen { get; }

		public PlayerFreezeUpdate(string playerId, long peer, long revision, bool frozen)
		{
			PlayerId = playerId;
			Peer = peer;
			Revision = revision;
			Frozen = frozen;
		}
	}
	public sealed class PlayerFreezeSet
	{
		private sealed class State
		{
			internal PlayerFreezeUpdate Update;

			internal bool Acknowledged;

			internal double LastSent = double.NegativeInfinity;
		}

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

		private long revision;

		public void Retain(IEnumerable<KeyValuePair<string, long>> online)
		{
			Dictionary<string, long> dictionary = new Dictionary<string, long>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, long> item in online)
			{
				if (!string.IsNullOrEmpty(item.Key) && item.Value != 0L)
				{
					dictionary[item.Key] = item.Value;
				}
			}
			string[] array = players.Keys.ToArray();
			foreach (string key in array)
			{
				if (!dictionary.TryGetValue(key, out var value) || value != players[key].Update.Peer)
				{
					players.Remove(key);
				}
			}
			foreach (KeyValuePair<string, long> item2 in dictionary)
			{
				if (!players.ContainsKey(item2.Key))
				{
					players.Add(item2.Key, new State
					{
						Update = new PlayerFreezeUpdate(item2.Key, item2.Value, ++revision, frozen: false)
					});
				}
			}
		}

		public bool TrySet(string playerId, long peer, bool frozen, out PlayerFreezeUpdate update)
		{
			update = default(PlayerFreezeUpdate);
			if (!players.TryGetValue(playerId, out State value) || peer == 0L || value.Update.Peer != peer)
			{
				return false;
			}
			update = new PlayerFreezeUpdate(playerId, peer, ++revision, frozen);
			value.Update = update;
			value.Acknowledged = false;
			value.LastSent = double.NegativeInfinity;
			return true;
		}

		public bool IsCurrent(PlayerFreezeUpdate update)
		{
			if (players.TryGetValue(update.PlayerId, out State value) && value.Update.Peer == update.Peer)
			{
				return value.Update.Revision == update.Revision;
			}
			return false;
		}

		public bool Acknowledge(long peer, long receivedRevision, bool frozen)
		{
			foreach (State value in players.Values)
			{
				PlayerFreezeUpdate update = value.Update;
				if (update.Peer == peer && update.Revision == receivedRevision && update.Frozen == frozen && !value.Acknowledged)
				{
					value.Acknowledged = true;
					return true;
				}
			}
			return false;
		}

		public IReadOnlyList<PlayerFreezeUpdate> Due(double now)
		{
			List<PlayerFreezeUpdate> list = new List<PlayerFreezeUpdate>();
			foreach (State value in players.Values)
			{
				if (!(now - value.LastSent < 1.0))
				{
					value.LastSent = now;
					list.Add(value.Update);
				}
			}
			return list;
		}

		public void ClearAll()
		{
			players.Clear();
		}

		public void Disconnect(string playerId)
		{
			players.Remove(playerId);
		}
	}
	public sealed class PlayerFreezeLease
	{
		public const double LeaseSeconds = 4.0;

		private long revision;

		private bool desired;

		private double expires;

		public bool IsFrozen(double now)
		{
			if (desired)
			{
				return now < expires;
			}
			return false;
		}

		public bool Receive(long receivedRevision, bool frozen, double now)
		{
			if (receivedRevision <= 0 || receivedRevision < revision || (receivedRevision == revision && frozen != desired))
			{
				return false;
			}
			revision = receivedRevision;
			desired = frozen;
			expires = now + 4.0;
			return true;
		}

		public void Clear()
		{
			revision = 0L;
			desired = false;
			expires = 0.0;
		}
	}
}
namespace ExtraPotions.Thing.Localization
{
	public readonly struct TextKey
	{
		public string Key { get; }

		public int Arguments { get; }

		public TextKey(string key, int arguments)
		{
			Key = key;
			Arguments = arguments;
		}
	}
	public static class ThingTextKeys
	{
		public const string OffNoCrucible = "off_no_crucible";

		public const string OffNoThing = "off_no_thing";

		public const string ImportDone = "import_done";

		public const string HintNotAdmin = "hint_not_admin";

		public const string Kicked = "kicked";

		public const string Banned = "banned";

		public const string BannedFor = "banned_for";

		public const string Warned = "warned";

		public const string MutedFor = "muted_for";

		public const string MutedUntilLifted = "muted_until_lifted";

		public const string Unmuted = "unmuted";

		public const string YouAreMuted = "you_are_muted";

		public const string ShutdownWarning = "shutdown_warning";

		public const string ShutdownSaving = "shutdown_saving";

		public const string ShutdownCancelled = "shutdown_canceled";

		public const string TimeReleased = "time_released";

		public const string TempBanLifted = "temp_ban_lifted";

		public const string NoReason = "no_reason";

		public const string CommandHelp = "cmd_help";

		public const string CommandDone = "cmd_done";

		public const string CommandUnknownPlayer = "cmd_unknown_player";

		public const string CommandUsage = "cmd_usage";

		public const string CommandReasonRequired = "cmd_reason_required";

		public const string CommandStatus = "cmd_status";

		public const string CommandUnknownVerb = "cmd_unknown_verb";

		public const string CommandFailed = "cmd_failed";

		public const string CommandRemoteSent = "cmd_remote_sent";

		public const string CommandRemoteFailed = "cmd_remote_failed";

		public static IReadOnlyList<TextKey> All { get; } = Array.AsReadOnly(new TextKey[347]
		{
			new TextKey("time_unavailable", 0),
			new TextKey("time_set", 1),
			new TextKey("time_frozen", 1),
			new TextKey("time_resumed", 0),
			new TextKey("step_aside_worldtools", 1),
			new TextKey("off_no_crucible", 0),
			new TextKey("off_no_thing", 0),
			new TextKey("refused_not_admin", 0),
			new TextKey("refused_no_other_players", 0),
			new TextKey("refused_needs_delve", 0),
			new TextKey("refused_disabled_by_server", 0),
			new TextKey("refused_product_off", 0),
			new TextKey("refused_needs_relay", 0),
			new TextKey("import_done", 2),
			new TextKey("hint_not_admin", 0),
			new TextKey("kicked", 1),
			new TextKey("player_frozen", 0),
			new TextKey("player_unfrozen", 0),
			new TextKey("banned", 1),
			new TextKey("banned_for", 2),
			new TextKey("warned", 1),
			new TextKey("muted_for", 1),
			new TextKey("muted_until_lifted", 0),
			new TextKey("unmuted", 0),
			new TextKey("you_are_muted", 0),
			new TextKey("shutdown_warning", 1),
			new TextKey("shutdown_saving", 0),
			new TextKey("shutdown_canceled", 0),
			new TextKey("time_released", 0),
			new TextKey("temp_ban_lifted", 1),
			new TextKey("no_reason", 0),
			new TextKey("cmd_help", 0),
			new TextKey("cmd_done", 1),
			new TextKey("cmd_unknown_player", 1),
			new TextKey("cmd_usage", 1),
			new TextKey("cmd_reason_required", 0),
			new TextKey("cmd_status", 4),
			new TextKey("cmd_unknown_verb", 1),
			new TextKey("cmd_failed", 1),
			new TextKey("cmd_remote_sent", 0),
			new TextKey("cmd_remote_failed", 1),
			new TextKey("terrain_usage", 0),
			new TextKey("terrain_incomplete", 0),
			new TextKey("terrain_ownership", 0),
			new TextKey("terrain_other_pieces", 0),
			new TextKey("terrain_canceled", 0),
			new TextKey("terrain_expired", 0),
			new TextKey("terrain_preview", 1),
			new TextKey("terrain_done", 1),
			new TextKey("terrain_unknown", 0),
			new TextKey("drops_usage", 0),
			new TextKey("drops_unavailable", 0),
			new TextKey("drops_canceled", 0),
			new TextKey("drops_expired", 0),
			new TextKey("drops_preview", 2),
			new TextKey("drops_done", 1),
			new TextKey("drops_unknown", 0),
			new TextKey("seed_value", 1),
			new TextKey("seed_unavailable", 0),
			new TextKey("ward_usage", 0),
			new TextKey("ward_unavailable", 0),
			new TextKey("ward_on", 0),
			new TextKey("ward_off", 0),
			new TextKey("ui_close", 0),
			new TextKey("ui_refresh", 0),
			new TextKey("ui_previous", 0),
			new TextKey("ui_next", 0),
			new TextKey("ui_ledger", 0),
			new TextKey("ui_audit", 0),
			new TextKey("ui_kick", 0),
			new TextKey("ui_ban", 0),
			new TextKey("ui_tempban", 0),
			new TextKey("ui_warn", 0),
			new TextKey("ui_unban", 0),
			new TextKey("ui_select_ban", 0),
			new TextKey("ui_select_player", 0),
			new TextKey("ui_duration", 0),
			new TextKey("ui_reason", 0),
			new TextKey("ui_broadcast", 0),
			new TextKey("ui_text", 0),
			new TextKey("ui_shutdown", 0),
			new TextKey("ui_minutes", 0),
			new TextKey("ui_cancel_shutdown", 0),
			new TextKey("ui_save_world", 0),
			new TextKey("ui_world_step_aside", 0),
			new TextKey("ui_seed", 0),
			new TextKey("ui_time", 0),
			new TextKey("ui_clock", 0),
			new TextKey("ui_freeze", 0),
			new TextKey("config_unavailable", 0),
			new TextKey("config_bound", 0),
			new TextKey("config_yaml", 0),
			new TextKey("ui_config_yaml_hint", 0),
			new TextKey("config_yaml_saved", 0),
			new TextKey("config_unbound", 0),
			new TextKey("config_readonly", 0),
			new TextKey("config_invalid", 0),
			new TextKey("config_stale", 0),
			new TextKey("config_saved", 0),
			new TextKey("config_unknown", 0),
			new TextKey("config_failed", 0),
			new TextKey("ui_config_back", 0),
			new TextKey("ui_config_hint", 0),
			new TextKey("ui_settings_delegation", 0),
			new TextKey("ui_config_open", 0),
			new TextKey("ui_config_select", 0),
			new TextKey("ui_config_write_warning", 0),
			new TextKey("ui_config_restart", 0),
			new TextKey("ui_config_choose_value", 0),
			new TextKey("teleport_usage", 0),
			new TextKey("teleport_unavailable", 0),
			new TextKey("teleport_unknown", 0),
			new TextKey("teleport_expired", 0),
			new TextKey("teleport_declined", 0),
			new TextKey("teleport_busy", 0),
			new TextKey("teleport_waiting", 0),
			new TextKey("teleport_accepted", 0),
			new TextKey("teleport_prompt_title", 0),
			new TextKey("teleport_prompt_goto", 1),
			new TextKey("teleport_prompt_bring", 1),
			new TextKey("teleport_accept", 0),
			new TextKey("teleport_decline", 0),
			new TextKey("teleport_unsafe", 0),
			new TextKey("teleport_arrived", 0),
			new TextKey("teleport_recovering", 0),
			new TextKey("ui_goto", 0),
			new TextKey("ui_bring", 0),
			new TextKey("delve_unavailable", 0),
			new TextKey("delve_not_checked", 0),
			new TextKey("delve_counts", 5),
			new TextKey("delve_schedule", 4),
			new TextKey("delve_none_active", 0),
			new TextKey("delve_requested_skip", 1),
			new TextKey("delve_requested_due", 1),
			new TextKey("ui_delve_actions", 0),
			new TextKey("ui_delve_kinds", 0),
			new TextKey("ui_delve_request", 0),
			new TextKey("ui_delve_select", 0),
			new TextKey("ui_delve_request_warning", 0),
			new TextKey("ui_delve_dryrun", 0),
			new TextKey("ui_delve_dryrun_hint", 0),
			new TextKey("ui_delve_scope", 0),
			new TextKey("delve_kind_fullreset", 0),
			new TextKey("delve_kind_chests", 0),
			new TextKey("delve_kind_mudpiles", 0),
			new TextKey("delve_kind_frostcavecrystals", 0),
			new TextKey("delve_kind_blackcores", 0),
			new TextKey("delve_kind_morkhalla", 0),
			new TextKey("delve_kind_hole", 0),
			new TextKey("delve_kind_ancientarmor", 0),
			new TextKey("delve_kind_obsidian", 0),
			new TextKey("delve_kind_flametal", 0),
			new TextKey("delve_kind_ice", 0),
			new TextKey("delve_kind_queen", 0),
			new TextKey("delve_kind_campspawners", 0),
			new TextKey("delve_kind_dungeonspawners", 0),
			new TextKey("ui_top_left", 0),
			new TextKey("ui_mute", 0),
			new TextKey("ui_unmute", 0),
			new TextKey("ui_freeze_player", 0),
			new TextKey("ui_unfreeze_player", 0),
			new TextKey("ui_mute_duration", 0),
			new TextKey("mute_persistence_unknown", 0),
			new TextKey("ui_on", 0),
			new TextKey("ui_off", 0),
			new TextKey("ui_resume_time", 0),
			new TextKey("ui_ward_on", 0),
			new TextKey("ui_ward_off", 0),
			new TextKey("ui_terrain", 0),
			new TextKey("ui_drops", 0),
			new TextKey("ui_world_console_result", 0),
			new TextKey("ui_invalid", 0),
			new TextKey("ui_request_failed", 0),
			new TextKey("ui_pending", 0),
			new TextKey("ui_apply", 0),
			new TextKey("ui_cancel", 0),
			new TextKey("ui_confirm", 0),
			new TextKey("ui_hold", 0),
			new TextKey("ui_hold_progress", 1),
			new TextKey("ui_read_invalid", 0),
			new TextKey("ui_read_failed", 0),
			new TextKey("ui_page", 3),
			new TextKey("ui_audit_bounded", 0),
			new TextKey("ui_radius", 0),
			new TextKey("ui_tab_people", 0),
			new TextKey("ui_tab_moderation", 0),
			new TextKey("ui_tab_messages", 0),
			new TextKey("ui_tab_world", 0),
			new TextKey("ui_tab_regeneration", 0),
			new TextKey("ui_tab_server_config", 0),
			new TextKey("ui_players", 0),
			new TextKey("ui_permitted", 0),
			new TextKey("ui_admins", 0),
			new TextKey("ui_permit", 0),
			new TextKey("ui_unpermit", 0),
			new TextKey("ui_admin_add", 0),
			new TextKey("ui_admin_remove", 0),
			new TextKey("ui_platform_id", 0),
			new TextKey("ui_admin_readonly", 0),
			new TextKey("ui_select_identity", 0),
			new TextKey("message_saved", 0),
			new TextKey("message_stale", 0),
			new TextKey("message_invalid", 0),
			new TextKey("message_unknown", 0),
			new TextKey("message_failed", 0),
			new TextKey("ui_motd", 0),
			new TextKey("ui_motd_hint", 0),
			new TextKey("ui_announcements", 0),
			new TextKey("ui_announcement_hint", 0),
			new TextKey("ui_add", 0),
			new TextKey("ui_edit", 0),
			new TextKey("ui_remove", 0),
			new TextKey("ui_clear", 0),
			new TextKey("ui_announcement_every", 1),
			new TextKey("ui_add_announcement", 0),
			new TextKey("ui_edit_announcement", 0),
			new TextKey("ui_select_announcement", 0),
			new TextKey("ui_repeat_minutes", 0),
			new TextKey("ui_remove_announcement", 0),
			new TextKey("ui_clear_announcements_warning", 0),
			new TextKey("ui_delve_due", 0),
			new TextKey("ui_delve_nearest", 0),
			new TextKey("delve_position_unknown", 0),
			new TextKey("delve_site_requested", 0),
			new TextKey("delve_site_unknown", 0),
			new TextKey("delve_site_due", 0),
			new TextKey("ui_delve_site_scope", 0),
			new TextKey("delve_nearest_none", 0),
			new TextKey("delve_nearest_queued", 2),
			new TextKey("delve_nearest_refused", 0),
			new TextKey("ui_delve_reset_nearest", 0),
			new TextKey("ui_delve_reset_nearest_warning", 0),
			new TextKey("ui_delve_nature", 0),
			new TextKey("ui_delve_nature_scope", 0),
			new TextKey("pickup_usage", 0),
			new TextKey("pickup_set", 1),
			new TextKey("pickup_reset", 1),
			new TextKey("ui_pickup_radius", 0),
			new TextKey("ui_pickup_radius_hint", 0),
			new TextKey("ui_regenerate_nature", 0),
			new TextKey("ui_regenerate_nature_warning", 0),
			new TextKey("ui_reset_resources", 0),
			new TextKey("ui_reset_resources_warning", 0),
			new TextKey("ui_settings_delegation_hint", 0),
			new TextKey("ui_freeze_hint", 0),
			new TextKey("ui_top_left_hint", 0),
			new TextKey("ui_clock_hint", 0),
			new TextKey("ui_reason_hint", 0),
			new TextKey("ui_minutes_hint", 0),
			new TextKey("ui_platform_id_hint", 0),
			new TextKey("ui_duration_hint", 0),
			new TextKey("ui_terrain_radius_hint", 0),
			new TextKey("ui_message_hint", 0),
			new TextKey("ui_apply_hint", 0),
			new TextKey("ui_cancel_hint", 0),
			new TextKey("ui_help_close", 0),
			new TextKey("ui_help_refresh", 0),
			new TextKey("ui_help_previous", 0),
			new TextKey("ui_help_next", 0),
			new TextKey("ui_help_identity_lists", 0),
			new TextKey("ui_help_delve_views", 0),
			new TextKey("ui_help_moderation_views", 0),
			new TextKey("ui_help_select", 0),
			new TextKey("ui_help_tab_people", 0),
			new TextKey("ui_help_tab_moderation", 0),
			new TextKey("ui_help_tab_messages", 0),
			new TextKey("ui_help_tab_world", 0),
			new TextKey("ui_help_tab_regeneration", 0),
			new TextKey("ui_help_kick", 0),
			new TextKey("ui_help_ban", 0),
			new TextKey("ui_help_tempban", 0),
			new TextKey("ui_help_warn", 0),
			new TextKey("ui_help_mute", 0),
			new TextKey("ui_help_unmute", 0),
			new TextKey("ui_help_freeze_player", 0),
			new TextKey("ui_help_unfreeze_player", 0),
			new TextKey("ui_help_goto", 0),
			new TextKey("ui_help_bring", 0),
			new TextKey("ui_help_unban", 0),
			new TextKey("ui_help_permit", 0),
			new TextKey("ui_help_unpermit", 0),
			new TextKey("ui_help_admin_add", 0),
			new TextKey("ui_help_admin_remove", 0),
			new TextKey("ui_help_broadcast", 0),
			new TextKey("ui_help_shutdown", 0),
			new TextKey("ui_help_cancel_shutdown", 0),
			new TextKey("ui_help_save_world", 0),
			new TextKey("ui_help_motd", 0),
			new TextKey("ui_help_announcements", 0),
			new TextKey("ui_help_seed", 0),
			new TextKey("ui_help_time", 0),
			new TextKey("ui_help_resume_time", 0),
			new TextKey("ui_help_ward_on", 0),
			new TextKey("ui_help_ward_off", 0),
			new TextKey("ui_help_terrain", 0),
			new TextKey("ui_help_drops", 0),
			new TextKey("ui_help_add", 0),
			new TextKey("ui_help_edit", 0),
			new TextKey("ui_help_remove", 0),
			new TextKey("ui_grants", 0),
			new TextKey("ui_help_grants", 0),
			new TextKey("ui_grant_world", 0),
			new TextKey("ui_help_grant_world", 0),
			new TextKey("ui_grant_spawn", 0),
			new TextKey("ui_help_grant_spawn", 0),
			new TextKey("ui_grant_teleport", 0),
			new TextKey("ui_help_grant_teleport", 0),
			new TextKey("ui_grant_freeze", 0),
			new TextKey("ui_help_grant_freeze", 0),
			new TextKey("ui_grant_inventory", 0),
			new TextKey("ui_help_grant_inventory", 0),
			new TextKey("ui_grant_ward", 0),
			new TextKey("ui_help_grant_ward", 0),
			new TextKey("ui_inventory", 0),
			new TextKey("ui_help_inventory", 0),
			new TextKey("ui_inventory_empty", 0),
			new TextKey("ui_inventory_take", 0),
			new TextKey("ui_inventory_delete", 0),
			new TextKey("ui_inventory_take_body", 2),
			new TextKey("ui_inventory_delete_body", 2),
			new TextKey("ui_inventory_delete_again", 2),
			new TextKey("ui_inventory_stored", 0),
			new TextKey("ui_inventory_dropped", 0),
			new TextKey("ui_inventory_deleted", 0),
			new TextKey("ui_inventory_missing", 0),
			new TextKey("ui_inventory_busy", 0),
			new TextKey("ui_inventory_denied", 0),
			new TextKey("ui_inventory_failed", 0),
			new TextKey("ui_inventory_offline", 0),
			new TextKey("ui_select_item", 0),
			new TextKey("ui_permissions_loading", 0),
			new TextKey("ui_permissions_failed", 0),
			new TextKey("ui_permissions_empty", 0),
			new TextKey("ui_grant_add", 0),
			new TextKey("ui_help_grant_add", 0),
			new TextKey("ui_grant_remove", 0),
			new TextKey("ui_help_grant_remove", 0),
			new TextKey("grant_saved", 0),
			new TextKey("grant_unchanged", 0),
			new TextKey("grant_unsaved", 0),
			new TextKey("ui_grant_subadmin", 0),
			new TextKey("ui_help_grant_subadmin", 0),
			new TextKey("ui_subadmin_confirm_1", 1),
			new TextKey("ui_subadmin_confirm_2", 1),
			new TextKey("ui_subadmin_revoke_self", 0),
			new TextKey("ui_subadmin_revoke_self_hint", 0),
			new TextKey("ui_subadmin_revoke_self_confirm", 0)
		});
	}
}
namespace ExtraPotions.Thing.Integration
{
	public enum ThingModule
	{
		WorldTools
	}
	public readonly struct InstalledPlugin
	{
		public string Guid { get; }

		public string Name { get; }

		public InstalledPlugin(string guid, string name)
		{
			Guid = guid ?? "";
			Name = name ?? "";
		}
	}
	public sealed class ModuleSource
	{
		public string Guid { get; }

		public string Name { get; }

		public ThingModule Module { get; }

		public IReadOnlyList<string> PluginNames { get; }

		public string ModuleId => Module.ToString().ToLowerInvariant();

		internal ModuleSource(string guid, string name, ThingModule module, params string[] pluginNames)
		{
			Guid = guid;
			Name = name;
			Module = module;
			PluginNames = pluginNames;
		}

		public bool Matches(InstalledPlugin plugin)
		{
			if (!string.Equals(plugin.Guid, Guid, StringComparison.Ordinal))
			{
				return PluginNames.Any((string n) => string.Equals(n, plugin.Name, StringComparison.OrdinalIgnoreCase));
			}
			return true;
		}
	}
	public readonly struct StepAsideNotice
	{
		public ThingModule Module { get; }

		public string SourceName { get; }

		public string SourceGuid { get; }

		public string Key => KeyOf(Module);

		public StepAsideNotice(ThingModule module, string sourceName, string sourceGuid)
		{
			Module = module;
			SourceName = sourceName;
			SourceGuid = sourceGuid;
		}

		public static string KeyOf(ThingModule module)
		{
			return "step_aside_" + module.ToString().ToLowerInvariant();
		}
	}
	public sealed class ModuleStepAsideDecision
	{
		private readonly HashSet<ThingModule> aside;

		public IReadOnlyList<ModuleSource> Present { get; }

		public bool Any => aside.Count > 0;

		public bool AdminToolLoaded => SteppedAside(ThingModule.WorldTools);

		public IReadOnlyList<StepAsideNotice> Notices { get; }

		internal ModuleStepAsideDecision(IReadOnlyList<ModuleSource> present)
		{
			Present = present;
			aside = new HashSet<ThingModule>(present.Select((ModuleSource p) => p.Module));
			Notices = (from p in present
				orderby p.Module
				select new StepAsideNotice(p.Module, p.Name, p.Guid)).ToList();
		}

		public bool SteppedAside(ThingModule module)
		{
			return aside.Contains(module);
		}
	}
	public static class ModuleStepAside
	{
		public static readonly ModuleSource AdminTool = new ModuleSource("com.icecub.valheimadmintool", "Valheim_Admin_Tool", ThingModule.WorldTools);

		public static IReadOnlyList<ModuleSource> Sources { get; } = Array.AsReadOnly(new ModuleSource[1] { AdminTool });

		public static ModuleStepAsideDecision Decide(IEnumerable<InstalledPlugin> installed)
		{
			if (installed == null)
			{
				throw new ArgumentNullException("installed");
			}
			List<InstalledPlugin> list = installed.ToList();
			return new ModuleStepAsideDecision(Sources.Where((ModuleSource s) => list.Any(s.Matches)).ToList());
		}
	}
}
namespace ExtraPotions.Thing.Conversion
{
	public readonly struct ImportValue
	{
		public string SettingId { get; }

		public string Value { get; }

		public string Source { get; }

		public ImportValue(string settingId, string value, string source)
		{
			SettingId = settingId;
			Value = value;
			Source = source;
		}
	}
	public sealed class ImportPlan
	{
		public IReadOnlyList<ImportValue> Values { get; }

		public IReadOnlyList<string> Notes { get; }

		internal ImportPlan(IReadOnlyList<ImportValue> values, IReadOnlyList<string> notes)
		{
			Values = values;
			Notes = notes;
		}

		public string Report()
		{
			return string.Join("\n", new string[1] { "Source: Valheim_Admin_Tool" }.Concat(Values.Select((ImportValue v) => "Set " + v.SettingId + " = " + v.Value + " (from " + v.Source + ")")).Concat(Notes.Select((string n) => "Note: " + n)));
		}
	}
	public readonly struct SourceEntry
	{
		public string Section { get; }

		public string Key { get; }

		public string Value { get; }

		public SourceEntry(string section, string key, string value)
		{
			Section = section;
			Key = key;
			Value = value;
		}
	}
	public static class SourceConfig
	{
		public static IReadOnlyList<SourceEntry> Parse(string? text)
		{
			List<SourceEntry> list = new List<SourceEntry>();
			string section = "";
			string[] array = (text ?? "").Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length == 0 || text2[0] == '#')
				{
					continue;
				}
				if (text2[0] == '[' && text2[text2.Length - 1] == ']')
				{
					section = text2.Substring(1, text2.Length - 2).Trim();
					continue;
				}
				int num = text2.IndexOf('=');
				if (num > 0)
				{
					string text3 = text2.Substring(0, num).Trim();
					if (text3.Length != 0)
					{
						list.Add(new SourceEntry(section, text3, text2.Substring(num + 1).Trim()));
					}
				}
			}
			return list;
		}
	}
	public static class SourceImport
	{
		public const string AdminToolFile = "com.icecub.valheimadmintool.cfg";

		private const string HotkeySection = "Hotkeys";

		public static IReadOnlyDictionary<string, string> Targets { get; } = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			{
				"WardOverride",
				ThingSettings.Hotkeys.WardOverride.Id
			},
			{
				"RemoveDrops",
				ThingSettings.Hotkeys.RemoveDrops.Id
			}
		};

		public static ImportPlan Plan(string? text)
		{
			List<ImportValue> list = new List<ImportValue>();
			List<string> list2 = new List<string>();
			IReadOnlyList<SourceEntry> source = SourceConfig.Parse(text);
			foreach (SourceEntry item in source.Where((SourceEntry e) => e.Section == "Hotkeys"))
			{
				if (Targets.TryGetValue(item.Key, out string value) && item.Value.Length != 0 && !string.Equals(item.Value, "None", StringComparison.OrdinalIgnoreCase))
				{
					list.Add(new ImportValue(value, item.Value, "[Hotkeys] " + item.Key));
				}
			}
			if (source.Any((SourceEntry e) => e.Section == "Automatic resets"))
			{
				list2.Add("[Automatic resets]: dungeon and resource resets belong to Delve; its converter imports them");
			}
			SourceEntry sourceEntry = source.FirstOrDefault((SourceEntry e) => e.Section == "General" && e.Key == "Toggle menu");
			if (sourceEntry.Key != null)
			{
				list2.Add("[General] Toggle menu = " + sourceEntry.Value + ": not imported; that menu also holds the cheats (Hex), Thing keeps its own Admin Window Key");
			}
			return new ImportPlan(list, list2);
		}
	}
}
namespace ExtraPotions.Thing.Config
{
	public sealed class LockList
	{
		private readonly HashSet<string> locked;

		public static LockList Empty { get; } = new LockList(Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>());

		public IReadOnlyList<string> Locked { get; }

		public IReadOnlyList<string> Unknown { get; }

		public IReadOnlyList<string> NotLockable { get; }

		private LockList(IReadOnlyList<string> lockedIds, IReadOnlyList<string> unknown, IReadOnlyList<string> notLockable)
		{
			Locked = lockedIds;
			Unknown = unknown;
			NotLockable = notLockable;
			locked = new HashSet<string>(lockedIds, StringComparer.OrdinalIgnoreCase);
		}

		public static LockList Parse(string? text)
		{
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			List<string> list3 = new List<string>();
			string[] array = (text ?? "").Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = array[i].Trim();
				if (text2.Length != 0)
				{
					ThingSetting thingSetting = ThingSettings.Find(text2);
					List<string> list4 = ((thingSetting == null) ? list2 : (thingSetting.Lockable ? list : list3));
					string text3 = thingSetting?.Id ?? text2;
					if (!list4.Contains<string>(text3, StringComparer.OrdinalIgnoreCase))
					{
						list4.Add(text3);
					}
				}
			}
			if (list.Count + list2.Count + list3.Count != 0)
			{
				return new LockList(list, list2, list3);
			}
			return Empty;
		}

		public bool IsLocked(string? id)
		{
			if (id != null)
			{
				return locked.Contains(id.Trim());
			}
			return false;
		}

		public string Describe()
		{
			string text = Locked.Count + " locked";
			if (Unknown.Count > 0)
			{
				text = text + "; unknown: " + string.Join(", ", Unknown);
			}
			if (NotLockable.Count > 0)
			{
				text = text + "; not lockable: " + string.Join(", ", NotLockable);
			}
			return text;
		}
	}
	public static class LockedValues
	{
		public static string Encode(IEnumerable<KeyValuePair<string, string>> values)
		{
			if (values == null)
			{
				throw new ArgumentNullException("values");
			}
			return string.Join(";", values.Select<KeyValuePair<string, string>, string>((KeyValuePair<string, string> v) => Escape(v.Key) + "=" + Escape(v.Value ?? "")));
		}

		public static IReadOnlyDictionary<string, string> Decode(string? text)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			if (string.IsNullOrEmpty(text))
			{
				return dictionary;
			}
			foreach (string item in Split(text, ';'))
			{
				List<string> list = Split(item, '=').ToList();
				if (list.Count == 2)
				{
					string text2 = Unescape(list[0]);
					string text3 = Unescape(list[1]);
					if (!string.IsNullOrWhiteSpace(text2) && text3 != null && !dictionary.ContainsKey(text2.Trim()))
					{
						dictionary.Add(text2.Trim(), text3);
					}
				}
			}
			return dictionary;
		}

		public static string Compose(LockList list, Func<string, string?> ownText)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			if (ownText == null)
			{
				throw new ArgumentNullException("ownText");
			}
			return Encode(from id in list.Locked
				select new KeyValuePair<string, string>(id, ownText(id)) into p
				where p.Value != null
				select new KeyValuePair<string, string>(p.Key, p.Value));
		}

		private static string Escape(string text)
		{
			StringBuilder stringBuilder = new StringBuilder(text.Length);
			foreach (char c in text)
			{
				if (c == '\\' || c == ';' || c == '=')
				{
					stringBuilder.Append('\\');
				}
				stringBuilder.Append(c);
			}
			return stringBuilder.ToString();
		}

		private static IEnumerable<string> Split(string text, char separator)
		{
			int num = 0;
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] == '\\')
				{
					i++;
				}
				else if (text[i] == separator)
				{
					yield return text.Substring(num, i - num);
					num = i + 1;
				}
			}
			yield return text.Substring(num);
		}

		private static string? Unescape(string text)
		{
			StringBuilder stringBuilder = new StringBuilder(text.Length);
			for (int i = 0; i < text.Length; i++)
			{
				if (text[i] == '\\' && ++i >= text.Length)
				{
					return null;
				}
				stringBuilder.Append(text[i]);
			}
			return stringBuilder.ToString();
		}
	}
	public readonly struct PlayerLock
	{
		public bool Locked { get; }

		public string? ServerText { get; }

		public PlayerLock(bool locked, string? serverText)
		{
			Locked = locked;
			ServerText = (locked ? serverText : null);
		}

		public override string ToString()
		{
			if (!Locked)
			{
				return "Own";
			}
			return "Locked (" + ServerText + ")";
		}
	}
	public static class PlayerLocks
	{
		public static PlayerLock Evaluate(string id, bool serverBinds, LockList list, IReadOnlyDictionary<string, string> values)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			if (values == null)
			{
				throw new ArgumentNullException("values");
			}
			if (!serverBinds || !list.IsLocked(id) || !values.TryGetValue(id.Trim(), out string value))
			{
				return new PlayerLock(locked: false, null);
			}
			return new PlayerLock(locked: true, value);
		}
	}
	public enum SessionKind
	{
		SinglePlayer,
		Host,
		DedicatedServer,
		RemoteClient
	}
	public enum ServerKind
	{
		Unknown,
		Vanilla,
		CrucibleWithoutThing,
		Thing
	}
	public enum BlockReason
	{
		None,
		SetByServer,
		VanillaServer,
		ProductDisabled,
		AuthorityPending
	}
	public readonly struct SessionFacts
	{
		public SessionKind Kind { get; }

		public ServerKind Server { get; }

		public bool Admin { get; }

		public bool IsAuthority => Kind != SessionKind.RemoteClient;

		public SessionFacts(SessionKind kind, ServerKind server, bool admin)
		{
			Kind = kind;
			Server = server;
			Admin = admin;
		}
	}
	public static class SettingAccess
	{
		public static BlockReason Decide(SettingScope scope, SessionFacts facts)
		{
			if (facts.IsAuthority || scope == SettingScope.Local)
			{
				return BlockReason.None;
			}
			switch (facts.Server)
			{
			case ServerKind.Unknown:
				return BlockReason.AuthorityPending;
			case ServerKind.Vanilla:
				return BlockReason.VanillaServer;
			case ServerKind.CrucibleWithoutThing:
				return BlockReason.ProductDisabled;
			default:
				if (!facts.Admin)
				{
					return BlockReason.SetByServer;
				}
				return BlockReason.None;
			}
		}
	}
	public enum SettingScope
	{
		Local,
		ServerAuthoritative
	}
	public abstract class ThingSetting
	{
		private static readonly IReadOnlyList<string> NoChoices = Array.Empty<string>();

		public string Section { get; }

		public string Key { get; }

		public string Id => Section + "." + Key;

		public SettingScope Scope { get; }

		public string Description { get; }

		public IReadOnlyList<string> Choices { get; }

		public bool Hotkey { get; }

		public bool Lockable => Scope == SettingScope.Local;

		public abstract Type ValueType { get; }

		public abstract object DefaultValue { get; }

		public abstract object? Minimum { get; }

		public abstract object? Maximum { get; }

		private protected ThingSetting(string section, string key, SettingScope scope, string description, IReadOnlyList<string>? choices, bool hotkey)
		{
			if (string.IsNullOrWhiteSpace(section) || section.Trim() != section)
			{
				throw new ArgumentException("Expected a trimmed section name.", "section");
			}
			if (string.IsNullOrWhiteSpace(key) || key.Trim() != key)
			{
				throw new ArgumentException("Expected a trimmed key.", "key");
			}
			if (!Enum.IsDefined(typeof(SettingScope), scope))
			{
				throw new ArgumentException("Unknown scope.", "scope");
			}
			if (string.IsNullOrWhiteSpace(description))
			{
				throw new ArgumentException("Expected a description.", "description");
			}
			if (hotkey && scope != SettingScope.Local)
			{
				throw new ArgumentException(section + "." + key + ": a keyboard shortcut is a Local setting.", "hotkey");
			}
			Section = section;
			Key = key;
			Scope = scope;
			Description = description;
			Choices = choices ?? NoChoices;
			Hotkey = hotkey;
		}

		public abstract object NormalizeBoxed(object? value);

		public override string ToString()
		{
			return Id + " (" + Scope.ToString() + ")";
		}

		public static ThingSetting<bool> Toggle(string section, string key, bool defaultValue, SettingScope scope, string description)
		{
			return new ThingSetting<bool>(section, key, scope, description, defaultValue, null, (bool v) => v);
		}

		public static ThingSetting<int> Integer(string section, string key, int defaultValue, int min, int max, SettingScope scope, string description)
		{
			if (min > max)
			{
				throw new ArgumentException("Minimum above maximum.", "min");
			}
			return new ThingSetting<int>(section, key, scope, description, defaultValue, (min, max), (int v) => Math.Min(max, Math.Max(min, v)));
		}

		public static ThingSetting<TEnum> Choice<TEnum>(string section, string key, TEnum defaultValue, SettingScope scope, string description) where TEnum : struct, Enum
		{
			return new ThingSetting<TEnum>(section, key, scope, description, defaultValue, null, (TEnum v) => (!Enum.IsDefined(typeof(TEnum), v)) ? defaultValue : v, Enum.GetNames(typeof(TEnum)));
		}

		public static ThingSetting<string> Text(string section, string key, string defaultValue, SettingScope scope, string description, Func<string, string> normalize)
		{
			if (normalize == null)
			{
				throw new ArgumentNullException("normalize");
			}
			return new ThingSetting<string>(section, key, scope, description, defaultValue, null, normalize);
		}

		public static ThingSetting<string> Shortcut(string section, string key, string defaultValue, SettingScope scope, string description)
		{
			return new ThingSetting<string>(section, key, scope, description, defaultValue, null, (string v) => (v ?? "").Trim(), null, hotkey: true);
		}
	}
	public sealed class ThingSetting<T> : ThingSetting
	{
		private readonly Func<T, T> normalize;

		private readonly (T Min, T Max)? range;

		public T Default { get; }

		public override Type ValueType => typeof(T);

		public override object DefaultValue => Default;

		public override object? Minimum
		{
			get
			{
				if (!range.HasValue)
				{
					return null;
				}
				return range.Value.Min;
			}
		}

		public override object? Maximum
		{
			get
			{
				if (!range.HasValue)
				{
					return null;
				}
				return range.Value.Max;
			}
		}

		internal ThingSetting(string section, string key, SettingScope scope, string description, T defaultValue, (T, T)? range, Func<T, T> normalize, IReadOnlyList<string>? choices = null, bool hotkey = false)
			: base(section, key, scope, description, choices, hotkey)
		{
			this.normalize = normalize ?? throw new ArgumentNullException("normalize");
			this.range = range;
			if (!EqualityComparer<T>.Default.Equals(normalize(defaultValue), defaultValue))
			{
				throw new ArgumentException(base.Id + ": the default is outside its own rule.", "defaultValue");
			}
			Default = defaultValue;
		}

		public T Normalize(T value)
		{
			return normalize(value);
		}

		public override object NormalizeBoxed(object? value)
		{
			return (value is T value2) ? Normalize(value2) : Default;
		}
	}
	public enum RemoteConfigScope
	{
		MeadworksOnly,
		AllowList,
		AllConfigFiles
	}
	public static class ThingSettings
	{
		public static class General
		{
			public const string Section = "General";

			public static readonly ThingSetting<string> AdminWindowKey = ThingSetting.Shortcut("General", "Admin Window Key", "", SettingScope.Local, "Opens and closes the Thing admin window (admins only; others see a short hint). Empty: none.");

			public static readonly ThingSetting<bool> ConfirmDestructive = ThingSetting.Toggle("General", "Confirm Destructive Actions", defaultValue: true, SettingScope.Local, "Asks before kick, ban, terrain reset, remove drops and remote configuration writes.");

			public static readonly ThingSetting<bool> AdminListEditable = ThingSetting.Toggle("General", "Admin List Editable In Game", defaultValue: false, SettingScope.ServerAuthoritative, "Lets existing admins add and remove admins in game. Off: adminlist.txt is changed only on the server.");

			public static readonly ThingSetting<bool> AllowSubAdminDelegation = ThingSetting.Toggle("General", "Allow Sub-Admin Delegation", defaultValue: true, SettingScope.ServerAuthoritative, "Allows the server host or current admin to delegate all non-destructive permissions to online players as sub-admin.");

			public static readonly ThingSetting<int> AuditLogDays = ThingSetting.Integer("General", "Audit Log Days", 30, 1, 365, SettingScope.ServerAuthoritative, "Days the audit log keeps its daily files.");

			public static readonly ThingSetting<bool> ShowPlatformIds = ThingSetting.Toggle("General", "Show Platform IDs", defaultValue: true, SettingScope.ServerAuthoritative, "Shows platform IDs in the player list (to admins only either way).");

			public static readonly ThingSetting<string> LockedPlayerSettings = ThingSetting.Text("General", "Locked Player Settings", "", SettingScope.ServerAuthoritative, "Comma list of Local Thing settings (Section.Key) every player uses with the server's value.", NormalizeList);
		}

		public static class Moderation
		{
			public const string Section = "Moderation";

			public static readonly ThingSetting<bool> KickReasonRequired = ThingSetting.Toggle("Moderation", "Kick Reason Required", defaultValue: false, SettingScope.ServerAuthoritative, "Kicks, bans and warnings need a reason.");

			public static readonly ThingS

plugins/Thing/Thing.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExtraPotions.Meadworks.Contracts;
using ExtraPotions.Meadworks.Crucible;
using ExtraPotions.Meadworks.Crucible.UI;
using ExtraPotions.Thing.Admin;
using ExtraPotions.Thing.Commands;
using ExtraPotions.Thing.Communication;
using ExtraPotions.Thing.Config;
using ExtraPotions.Thing.ConfigEditing.Attributes;
using ExtraPotions.Thing.ConfigEditing.Editors;
using ExtraPotions.Thing.ConfigEditing.Values;
using ExtraPotions.Thing.Conversion;
using ExtraPotions.Thing.Integration;
using ExtraPotions.Thing.Moderation;
using ExtraPotions.Thing.RemoteConfig;
using ExtraPotions.Thing.Runtime.ConfigEditing;
using ExtraPotions.Thing.UI;
using ExtraPotions.Thing.WorldAdmin;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[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.14.0")]
[assembly: AssemblyInformationalVersion("0.1.14+source.C5CB98230C177B26BBB9D41F4869FF0DA5DE81F8C31B5A5A34E1D7BBF025C5D7")]
[assembly: AssemblyProduct("Thing")]
[assembly: AssemblyTitle("Thing")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.14.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ExtraPotions.Thing.Runtime
{
	public sealed class ThingCommand : ConsoleCommand
	{
		public override string Name => "thing";

		public override string Help => "Thing server administration: help, status, kick, ban, tempban, unban, warn, mute, unmute, freeze <player>, unfreeze <player>, say, saytop, shutdown, cancelshutdown, save, permitted [offset], permit <platform id>, unpermit <platform id>, admins [offset], admin add|remove <platform id>, grant self, grant self remove, grant <platform id> <world|spawn|teleport|freeze|inventory|ward|subadmin> <on|off>, seed, time <HH:mm|preset> [freeze], resumetime, ward [on|off], terrain <radius>, terrain confirm, terrain cancel, drops, drops confirm, drops cancel, pickup <1-10|reset>, regenerate nature, resetresources.";

		public override void Run(string[] args)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Invalid comparison between Unknown and I4
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Invalid comparison between Unknown and I4
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			if (ThingTeleportCommands.Run(args) || ThingTerrainCommands.Run(args) || ThingDropCommands.Run(args) || ThingWard.Run(args) || ThingPickup.Run(args))
			{
				return;
			}
			ZNet instance = ZNet.instance;
			ParsedCommand val = CommandParser.Parse((IReadOnlyList<string>)args);
			if ((int)val.Kind == 2 && (int)val.Action == 32 && !ThingModules.Active((ThingModule)0))
			{
				Console instance2 = Console.instance;
				if (instance2 != null)
				{
					instance2.Print(ThingText.Text(AdminCheck.TextKey((Verdict)6)));
				}
				return;
			}
			if ((Object)(object)instance != (Object)null && !instance.IsServer() && RemoteAdmin.Sendable(val) && ThingRemoteCommands.Available && ThingModules.ProductActive(out var _))
			{
				ThingRemoteCommands.Send(args);
				Console instance3 = Console.instance;
				if (instance3 != null)
				{
					instance3.Print(ThingText.Text("cmd_remote_sent"));
				}
				return;
			}
			foreach (string item in Execute(args, Admin(), fromLocalAuthority: true, requesterIsAdmin: true))
			{
				Console instance4 = Console.instance;
				if (instance4 != null)
				{
					instance4.Print(item);
				}
			}
		}

		public static IEnumerable<string> Execute(IReadOnlyList<string> args, Actor admin, bool fromLocalAuthority, bool requesterIsAdmin, string requesterConnection = "")
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			ParsedCommand val = CommandParser.Parse(args);
			CommandKind kind = val.Kind;
			switch ((int)kind)
			{
			case 0:
				yield return ThingText.Text("cmd_help");
				yield break;
			case 4:
				yield return ThingText.Format("cmd_unknown_verb", val.Text);
				yield break;
			case 3:
				yield return ThingText.Format("cmd_usage", val.Text);
				yield break;
			}
			ThingConfig settings = Plugin.Settings;
			if (settings == null || !ThingModules.ProductActive(out var _) || (Object)(object)ZNet.instance == (Object)null)
			{
				yield return ThingText.Text(AdminCheck.TextKey((Verdict)6));
				yield break;
			}
			if (!ZNet.instance.IsServer() || !ThingServer.Active)
			{
				yield return ThingText.Text(AdminCheck.TextKey((Verdict)7));
				yield break;
			}
			if ((int)val.Kind == 2 && (int)val.Action == 38 && val.Text == "self")
			{
				bool flag = fromLocalAuthority || requesterIsAdmin;
				yield return AdminPermissions.Wire(flag, (AdminRight)(flag ? 127 : ((int)ThingAccess.Of(((Actor)(ref admin)).Id))));
				yield break;
			}
			if (!fromLocalAuthority && !requesterIsAdmin && (int)val.Kind == 1 && args.Count >= 2 && string.Equals(args[0], "ui-read", StringComparison.OrdinalIgnoreCase) && string.Equals(args[1], "players", StringComparison.OrdinalIgnoreCase) && AdminPermissions.Covers(ThingAccess.Of(((Actor)(ref admin)).Id), (ThingAction)39) && ThingUiQueries.TryRead(args, admin, fromLocalAuthority, requesterIsAdmin, out IReadOnlyList<string> lines, playersOnly: true))
			{
				foreach (string item in lines)
				{
					yield return item;
				}
				yield break;
			}
			RequestFacts val2 = default(RequestFacts);
			((RequestFacts)(ref val2))..ctor(Role(), fromLocalAuthority, requesterIsAdmin, ThingDelve.Present, false, (AdminRight)((!(fromLocalAuthority || requesterIsAdmin)) ? ((int)ThingAccess.Of(((Actor)(ref admin)).Id)) : 0));
			if (!ActionCatalog.Visible(val.Action, ThingModules.StepAside.AdminToolLoaded))
			{
				yield return ThingText.Text(AdminCheck.TextKey((Verdict)6));
				yield break;
			}
			Verdict val3 = AdminCheck.Decide(val.Action, val2, settings.Rules());
			if (!AdminCheck.Permits(val3))
			{
				yield return ThingText.Text(AdminCheck.TextKey(val3));
				yield break;
			}
			if (ThingUiQueries.TryRead(args, admin, fromLocalAuthority, requesterIsAdmin, out IReadOnlyList<string> lines2))
			{
				foreach (string item2 in lines2)
				{
					yield return item2;
				}
				yield break;
			}
			if ((int)val.Kind == 1)
			{
				ModerationLedger ledger = ThingServer.Ledger;
				yield return ThingText.Format("cmd_status", ledger.Bans.Count, ledger.Bans.Values.Count((LedgerRecord b) => b.ExpiresUtc.HasValue), ledger.Mutes.Count, ledger.Warnings.Count);
				yield break;
			}
			if (((int)val.Action == 1 || (int)val.Action == 2 || (int)val.Action == 3) && !Reasons.Acceptable(val.Text, settings.Get<bool>(Moderation.KickReasonRequired)))
			{
				yield return ThingText.Text("cmd_reason_required");
				yield break;
			}
			IReadOnlyList<OnlinePlayer> source = ThingServer.Online();
			OnlinePlayer? online = null;
			if (val.Target.Length > 0 && val.Target != "self")
			{
				Actor? found = PlayerMatch.Find(val.Target, source.Select((OnlinePlayer o) => o.Actor));
				if (found.HasValue)
				{
					online = source.First(delegate(OnlinePlayer o)
					{
						//IL_0002: Unknown result type (might be due to invalid IL or missing references)
						//IL_0007: Unknown result type (might be due to invalid IL or missing references)
						//IL_0015: Unknown result type (might be due to invalid IL or missing references)
						//IL_001a: Unknown result type (might be due to invalid IL or missing references)
						Actor val6 = o.Actor;
						string id = ((Actor)(ref val6)).Id;
						val6 = found.Value;
						return id == ((Actor)(ref val6)).Id;
					});
				}
			}
			ThingAction action = val.Action;
			Actor target;
			switch (action - 1)
			{
			case 33:
			case 34:
				if (val.ConfigFile == null)
				{
					yield return ThingText.Text("config_invalid");
					break;
				}
				foreach (string item3 in ThingConfigFiles.Run(val.ConfigFile, admin, fromLocalAuthority, requesterConnection))
				{
					yield return item3;
				}
				break;
			case 4:
			case 5:
			case 14:
			case 15:
			case 36:
				foreach (string item4 in ThingLists.Run(val, admin, fromLocalAuthority))
				{
					yield return item4;
				}
				break;
			case 37:
				if (val.Target == "self")
				{
					val = new ParsedCommand(val.Kind, val.Action, ((Actor)(ref admin)).Id, val.Text, (TimeSpan?)null, 0, false, (MessageEditRequest)null, false, false, (RegenerationRequest)null, (ConfigEditRequest)null);
				}
				foreach (string item5 in ThingAccess.Edit(val, admin, fromLocalAuthority || requesterIsAdmin))
				{
					yield return item5;
				}
				break;
			case 17:
			case 18:
				if (val.Message == null)
				{
					yield return ThingText.Text("message_invalid");
					break;
				}
				foreach (string item6 in ThingMessages.Run(val.Message, admin, fromLocalAuthority))
				{
					yield return item6;
				}
				break;
			case 24:
			case 25:
			case 26:
			case 27:
				foreach (string item7 in ThingDelve.Run(val, admin, fromLocalAuthority))
				{
					yield return item7;
				}
				break;
			case 22:
			case 23:
				yield return ThingTime.Run(val, admin, fromLocalAuthority);
				break;
			case 0:
			case 12:
				if (!online.HasValue)
				{
					yield return ThingText.Format("cmd_unknown_player", val.Target);
					break;
				}
				if ((int)val.Action == 1)
				{
					ThingServer.Kick(online.Value, admin, val.Text);
				}
				else
				{
					ThingServer.Warn(online.Value, admin, val.Text);
				}
				yield return Done(val.Action, online.Value.Actor);
				break;
			case 1:
			case 2:
			{
				Actor? val4 = (online.HasValue ? new Actor?(online.GetValueOrDefault().Actor) : (LooksLikeId(val.Target) ? new Actor?(new Actor(val.Target, "")) : ((Actor?)null)));
				if (!val4.HasValue)
				{
					yield return ThingText.Format("cmd_unknown_player", val.Target);
				}
				else
				{
					ThingServer.Ban(val4.Value, online, admin, val.Text, ((int)val.Action == 3) ? val.Duration : ((TimeSpan?)null));
					yield return Done(val.Action, val4.Value);
				}
				break;
			}
			case 3:
				ThingServer.Unban(val.Target, admin);
				yield return Done(val.Action, new Actor(val.Target, ""));
				break;
			case 8:
				if (!online.HasValue)
				{
					yield return ThingText.Format("cmd_unknown_player", val.Target);
				}
				else
				{
					TimeSpan? duration = (val.UntilLifted ? ((TimeSpan?)null) : new TimeSpan?(val.Duration ?? TimeSpan.FromMinutes(settings.Get<int>(Moderation.MuteDefaultMinutes))));
					yield return MuteReply(ThingServer.Mute(online.Value, admin, val.Text, duration), val.Action, online.Value.Actor);
				}
				break;
			case 10:
			case 11:
			{
				if (!online.HasValue)
				{
					yield return ThingText.Format("cmd_unknown_player", val.Target);
					break;
				}
				string text;
				if (!ThingServer.SetFrozen(online.Value, admin, (int)val.Action == 11))
				{
					object[] array = new object[1];
					string verb = ActionCatalog.Info(val.Action).Verb;
					target = online.Value.Actor;
					array[0] = verb + " " + ((Actor)(ref target)).Id;
					text = ThingText.Format("cmd_failed", array);
				}
				else
				{
					text = ThingText.Text("ui_pending");
				}
				yield return text;
				break;
			}
			case 9:
			{
				Actor? val5 = (online.HasValue ? new Actor?(online.GetValueOrDefault().Actor) : (LooksLikeId(val.Target) ? new Actor?(new Actor(val.Target, "")) : ((Actor?)null)));
				if (!val5.HasValue)
				{
					yield return ThingText.Format("cmd_unknown_player", val.Target);
				}
				else
				{
					yield return MuteReply(ThingServer.Unmute(val5.Value, online, admin), val.Action, val5.Value);
				}
				break;
			}
			case 16:
				yield return ThingText.Format(ThingServer.Say(admin, val.Text, val.TopLeft) ? "cmd_done" : "cmd_failed", val.TopLeft ? "saytop" : "say");
				break;
			case 19:
				ThingServer.StartShutdown(admin, val.Minutes);
				yield return ThingText.Format("cmd_done", "shutdown " + val.Minutes);
				break;
			case 20:
				yield return ThingServer.CancelShutdown(admin) ? ThingText.Format("cmd_done", "cancelshutdown") : ThingText.Format("cmd_failed", "no shutdown is running");
				break;
			case 21:
				ThingServer.SaveWorld(admin);
				yield return ThingText.Format("cmd_done", "save");
				break;
			case 31:
			{
				World world = ZNet.instance.GetWorld();
				if (world == null)
				{
					yield return ThingText.Text("seed_unavailable");
					break;
				}
				Actor admin2 = admin;
				ThingAction action2 = val.Action;
				target = default(Actor);
				ThingServer.Audit(admin2, action2, target, (AuditOutcome)0, "world seed requested");
				yield return ThingText.Format("seed_value", world.m_seedName);
				break;
			}
			default:
				yield return ThingText.Format("cmd_unknown_verb", ActionCatalog.Info(val.Action).Verb);
				break;
			}
		}

		private static string MuteReply(MuteWriteResult result, ThingAction action, Actor target)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			return result switch
			{
				MuteWriteResult.LiveOnly => ThingText.Text("mute_persistence_unknown"), 
				MuteWriteResult.Saved => Done(action, target), 
				_ => ThingText.Text("refused_product_off"), 
			};
		}

		private static string Done(ThingAction action, Actor target)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			return ThingText.Format("cmd_done", ActionCatalog.Info(action).Verb + " " + ((((Actor)(ref target)).Name.Length > 0) ? (((Actor)(ref target)).Name + " (" + ((Actor)(ref target)).Id + ")") : ((Actor)(ref target)).Id));
		}

		private static bool LooksLikeId(string text)
		{
			if (text.Length > 0)
			{
				if (text.IndexOf('_') <= 0)
				{
					return text.All(char.IsDigit);
				}
				return true;
			}
			return false;
		}

		private static ThingRole Role()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Invalid comparison between Unknown and I4
			SessionRole val = (SessionRole)((!Plugin.IsReady) ? 1 : ((int)Plugin.Session.Role));
			if ((int)val != 2)
			{
				if ((int)val == 4)
				{
					return (ThingRole)2;
				}
				return (ThingRole)0;
			}
			return (ThingRole)1;
		}

		private static Actor Admin()
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			string text = (((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "console");
			return new Actor(((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsDedicated()) ? "server" : "host", text);
		}
	}
	public static class ThingRemoteCommands
	{
		private sealed class Codec : IOperationCodec<IReadOnlyList<string>, IReadOnlyList<string>>
		{
			public byte[] EncodeRequest(IReadOnlyList<string> request)
			{
				return RemoteAdmin.EncodeRequest(request);
			}

			public IReadOnlyList<string> DecodeRequest(byte[] bytes)
			{
				return RemoteAdmin.DecodeRequest(bytes);
			}

			public byte[] EncodeReply(IReadOnlyList<string> reply)
			{
				return RemoteAdmin.EncodeReply((IEnumerable<string>)reply);
			}

			public IReadOnlyList<string> DecodeReply(byte[] bytes)
			{
				return RemoteAdmin.DecodeReply(bytes);
			}
		}

		private static IDisposable? operation;

		private static readonly List<Task<OperationResult<IReadOnlyList<string>>>> pending = new List<Task<OperationResult<IReadOnlyList<string>>>>();

		private static MeadworksLog? log;

		public static bool Available => operation != null;

		public static void Initialize(ProductRegistration registration, MeadworksLog logger)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			log = logger;
			operation = Plugin.Services.Operations.Register<IReadOnlyList<string>, IReadOnlyList<string>>(registration, new OperationDescriptor("meadworks.thing.admin_command", RemoteAdmin.OperationVersion), (IOperationCodec<IReadOnlyList<string>, IReadOnlyList<string>>)(object)new Codec(), (Func<AuthorityContext, IReadOnlyList<string>, OperationExecution<IReadOnlyList<string>>>)Apply, (Func<RequestIdentity, OperationExecution<IReadOnlyList<string>>>)null);
		}

		public static void Send(string[] args)
		{
			pending.Add(Plugin.RequestClient.InvokeAsync<IReadOnlyList<string>, IReadOnlyList<string>>("meadworks.thing.admin_command", (IReadOnlyList<string>)args.Where((string a) => !string.IsNullOrWhiteSpace(a)).ToArray(), CancellationToken.None));
		}

		public static void Tick()
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Invalid comparison between Unknown and I4
			if (pending.Count == 0)
			{
				return;
			}
			Task<OperationResult<IReadOnlyList<string>>>[] array = pending.ToArray();
			foreach (Task<OperationResult<IReadOnlyList<string>>> task in array)
			{
				if (!task.IsCompleted)
				{
					continue;
				}
				pending.Remove(task);
				IEnumerable<string> enumerable;
				if (task.Status != TaskStatus.RanToCompletion)
				{
					enumerable = new string[1] { ThingText.Format("cmd_remote_failed", "Faulted") };
				}
				else
				{
					OperationResult<IReadOnlyList<string>> result = task.Result;
					if ((int)result.State == 2)
					{
						enumerable = result.Reply;
					}
					else
					{
						string text = RemoteAdmin.TextKey(result.Code);
						enumerable = new string[1] { (text == "cmd_remote_failed") ? ThingText.Format(text, result.Code) : ThingText.Text(text) };
					}
				}
				foreach (string item in enumerable)
				{
					Console instance = Console.instance;
					if (instance != null)
					{
						instance.Print(item);
					}
				}
			}
		}

		public static void Shutdown()
		{
			operation?.Dispose();
			operation = null;
			pending.Clear();
			log = null;
		}

		private static OperationExecution<IReadOnlyList<string>> Apply(AuthorityContext context, IReadOnlyList<string> args)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Invalid comparison between Unknown and I4
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Invalid comparison between Unknown and I4
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Invalid comparison between Unknown and I4
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Invalid comparison between Unknown and I4
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			ZNet instance = ZNet.instance;
			ConfigBlockReason reason;
			bool flag = Plugin.Settings != null && (Object)(object)instance != (Object)null && instance.IsServer() && ThingServer.Active && ThingModules.ProductActive(out reason);
			string hostName;
			Actor admin = Caller(context, out hostName);
			bool flag2 = context.IsLocal || (context.IsAdmin && hostName.Length > 0 && (Object)(object)instance != (Object)null && instance.IsAdmin(hostName));
			ParsedCommand val = CommandParser.Parse(args);
			AdminRight val2 = (AdminRight)((!flag2) ? ((int)ThingAccess.Of(hostName)) : 0);
			bool flag3 = (int)val.Kind == 2 && (int)val.Action == 38 && val.Text == "self";
			bool flag4 = (int)val.Kind == 2 && AdminPermissions.Covers(val2, val.Action);
			bool flag5 = (int)val.Kind == 1 && args.Count >= 2 && string.Equals(args[0], "ui-read", StringComparison.OrdinalIgnoreCase) && string.Equals(args[1], "players", StringComparison.OrdinalIgnoreCase) && AdminPermissions.Covers(val2, (ThingAction)39);
			string text = RemoteAdmin.Refusal(flag, flag2 || flag3 || flag4 || flag5);
			if (text != null)
			{
				MeadworksLog? obj = log;
				if (obj != null)
				{
					obj.Info("Thing remote command from " + ((Actor)(ref admin)).Id + " refused: " + text + ".");
				}
				if (flag && text == "NotAdmin")
				{
					ThingServer.Audit(admin, ActionOf(args), default(Actor), (AuditOutcome)1, "remote: not admin");
				}
				return OperationExecution<IReadOnlyList<string>>.Rejected(text);
			}
			context.MarkDecisionMade();
			return OperationExecution<IReadOnlyList<string>>.Success((IReadOnlyList<string>)ThingCommand.Execute(args, admin, context.IsLocal, flag2, context.SenderId).ToList());
		}

		private static Actor Caller(AuthorityContext context, out string hostName)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			hostName = "";
			if (context.IsLocal)
			{
				return new Actor("host", ((Object)(object)Player.m_localPlayer != (Object)null) ? Player.m_localPlayer.GetPlayerName() : "console");
			}
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null || !long.TryParse(context.SenderId, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var uid))
			{
				return new Actor("peer:" + context.SenderId, "");
			}
			ZNetPeer val = ((IEnumerable<ZNetPeer>)instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p != null && p.m_uid == uid));
			object obj;
			if (val == null)
			{
				obj = null;
			}
			else
			{
				ISocket socket = val.m_socket;
				obj = ((socket != null) ? socket.GetHostName() : null);
			}
			if (obj == null)
			{
				obj = "";
			}
			hostName = (string)obj;
			return new Actor((hostName.Length > 0) ? hostName : ("peer:" + context.SenderId), val?.m_playerName ?? "");
		}

		private static ThingAction ActionOf(IReadOnlyList<string> args)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			ParsedCommand val = CommandParser.Parse(args);
			if ((int)val.Kind != 2)
			{
				return (ThingAction)0;
			}
			return val.Action;
		}
	}
	public sealed class ThingConfig
	{
		public const string LockedValuesKey = "Locked Player Values";

		public const string AdminSettingsGrantKey = "Allow Admin Settings Editing";

		public const string AdminSettingsStatusKey = "Admin Settings Editing Status";

		private const string Product = "com.meadworks.thing";

		private const float RefreshSeconds = 0.5f;

		private readonly ConfigFile file;

		private readonly MeadworksLog log;

		private readonly Dictionary<ThingSetting, ConfigEntryBase> entries = new Dictionary<ThingSetting, ConfigEntryBase>();

		private readonly HashSet<ThingSetting> trackedLocks = new HashSet<ThingSetting>();

		private readonly ThingSyncReceipt syncReceipt = new ThingSyncReceipt();

		private readonly ConfigEntry<string> lockedValues;

		private readonly ConfigEntry<bool> settingsGrant;

		private readonly ConfigEntry<bool> settingsStatus;

		private string? lockListText;

		private LockList lockList = LockList.Empty;

		private string? valuesText;

		private IReadOnlyDictionary<string, string> values = new Dictionary<string, string>();

		private float nextRefresh;

		public ConfigFile File => file;

		public bool AllowAdminSettingsEditing
		{
			get
			{
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0012: Invalid comparison between Unknown and I4
				if (!Plugin.IsReady || (int)Plugin.Session.Role != 3)
				{
					return settingsGrant.Value;
				}
				if (ServerBinds())
				{
					return settingsStatus.Value;
				}
				return false;
			}
		}

		public LockList Locks => lockList;

		public event Action? Changed;

		private ThingConfig(ConfigFile file, MeadworksLog log)
		{
			this.file = file;
			this.log = log;
			bool saveOnConfigSet = file.SaveOnConfigSet;
			file.SaveOnConfigSet = false;
			try
			{
				foreach (ThingSetting item in ThingSettings.All)
				{
					BindSetting(item);
				}
				settingsGrant = MeadworksConfig.BindHidden<bool>(file, "General", "Allow Admin Settings Editing", false, "Host-only: allow listed server admins to edit settings. This local server policy is never synchronized from clients.", (ConfigScope)0, (AcceptableValueBase)null);
				settingsStatus = MeadworksConfig.BindHidden<bool>(file, "General", "Admin Settings Editing Status", false, "Managed by Thing: display-only projection of the host's settings delegation.", (ConfigScope)1, (AcceptableValueBase)null);
				lockedValues = MeadworksConfig.BindHidden<string>(file, "General", "Locked Player Values", "", "Managed by Thing: the server's values for Locked Player Settings. Do not edit.", (ConfigScope)1, (AcceptableValueBase)null);
			}
			finally
			{
				file.SaveOnConfigSet = saveOnConfigSet;
			}
			file.Save();
			SynchronizationManager.OnConfigurationSynchronized += OnSynchronized;
		}

		public bool IsSettingsPolicy(ConfigEntryBase entry)
		{
			if ((object)entry != settingsGrant)
			{
				return (object)entry == settingsStatus;
			}
			return true;
		}

		public bool SetAdminSettingsEditing(bool enabled, long expectedGeneration)
		{
			if (!Plugin.IsReady || !Plugin.Session.AuthorityReady || !Plugin.Session.IsAuthority || Plugin.Session.Generation != expectedGeneration || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return false;
			}
			settingsGrant.Value = enabled;
			file.Save();
			PublishSettingsAccess();
			return true;
		}

		private void PublishSettingsAccess()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			if (Plugin.IsReady && (int)Plugin.Session.Role != 3 && settingsStatus.Value != settingsGrant.Value)
			{
				MeadworksConfig.Amend<bool>(settingsStatus, "com.meadworks.thing", (ConfigScope)1, settingsGrant.Value, (string)null, true);
			}
		}

		public static ThingConfig Bind(ConfigFile file, MeadworksLog log)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			if (file == null)
			{
				throw new ArgumentNullException("file");
			}
			if (log == null)
			{
				throw new ArgumentNullException("log");
			}
			ThingConfig thingConfig = new ThingConfig(file, log);
			foreach (KeyValuePair<ThingSetting, ConfigEntryBase> item in thingConfig.entries.Where<KeyValuePair<ThingSetting, ConfigEntryBase>>((KeyValuePair<ThingSetting, ConfigEntryBase> p) => (int)p.Key.Scope > 0))
			{
				MeadworksBlockedOptions.Track("com.meadworks.thing", item.Value, Map(item.Key.Scope), true);
			}
			file.SettingChanged += thingConfig.OnSettingChanged;
			file.ConfigReloaded += thingConfig.OnReloaded;
			thingConfig.Refresh(force: true);
			log.Verbose("Configuration bound: " + ThingSettings.All.Count((ThingSetting s) => (int)s.Scope == 0) + " Local, " + ThingSettings.All.Count((ThingSetting s) => (int)s.Scope == 1) + " ServerAuthoritative, no cheats.");
			return thingConfig;
		}

		public static ConfigScope Map(SettingScope scope)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if ((int)scope == 0)
			{
				return (ConfigScope)0;
			}
			return (ConfigScope)1;
		}

		public ConfigEntryBase Entry(ThingSetting setting)
		{
			if (!entries.TryGetValue(setting ?? throw new ArgumentNullException("setting"), out ConfigEntryBase value))
			{
				throw new ArgumentException("Not a Thing setting.", "setting");
			}
			return value;
		}

		public T Get<T>(ThingSetting<T> setting)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Invalid comparison between Unknown and I4
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			if (((ThingSetting)setting).Hotkey)
			{
				throw new ArgumentException(((ThingSetting)setting).Id + " is a keyboard shortcut; use Shortcut.", "setting");
			}
			ConfigEntry<T> val = (ConfigEntry<T>)(object)Entry((ThingSetting)(object)setting);
			if ((int)((ThingSetting)setting).Scope == 1)
			{
				return setting.Normalize(MeadworksConfig.Effective<T>(val, "com.meadworks.thing", (ConfigScope)1, setting.Default, true));
			}
			PlayerLock val2 = Lock((ThingSetting)(object)setting);
			if (((PlayerLock)(ref val2)).Locked && TryConvert<T>(((PlayerLock)(ref val2)).ServerText, out var value))
			{
				return setting.Normalize(value);
			}
			return setting.Normalize(val.Value);
		}

		public ConfigEntry<KeyboardShortcut> Shortcut(ThingSetting<string> setting)
		{
			if (!((ThingSetting)setting).Hotkey)
			{
				throw new ArgumentException(((ThingSetting)setting).Id + " is not a keyboard shortcut.", "setting");
			}
			return (ConfigEntry<KeyboardShortcut>)(object)Entry((ThingSetting)(object)setting);
		}

		public KeyboardShortcut ShortcutValue(ThingSetting<string> setting)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<KeyboardShortcut> val = Shortcut(setting);
			PlayerLock val2 = Lock((ThingSetting)(object)setting);
			if (((PlayerLock)(ref val2)).Locked)
			{
				try
				{
					return KeyboardShortcut.Deserialize(((PlayerLock)(ref val2)).ServerText);
				}
				catch (Exception)
				{
				}
			}
			return val.Value;
		}

		public bool IsLocked(ThingSetting setting)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (setting != null && setting.Lockable)
			{
				PlayerLock val = Lock(setting);
				return ((PlayerLock)(ref val)).Locked;
			}
			return false;
		}

		public void Tick(float now)
		{
			if (!(now < nextRefresh))
			{
				nextRefresh = now + 0.5f;
				Refresh(force: false);
			}
		}

		private void Refresh(bool force)
		{
			try
			{
				PublishSettingsAccess();
				PublishLockedValues();
				ReadLocks(force);
				foreach (ThingSetting item in ThingSettings.All.Where((ThingSetting s) => s.Lockable))
				{
					bool flag = IsLocked(item);
					if (flag && trackedLocks.Add(item))
					{
						MeadworksBlockedOptions.Track("com.meadworks.thing", entries[item], (ConfigScope)1, true);
					}
					else if (!flag && trackedLocks.Remove(item))
					{
						MeadworksBlockedOptions.Untrack(entries[item]);
					}
				}
			}
			catch (Exception ex)
			{
				log.Warning("Configuration refresh failed: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private void PublishLockedValues()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.IsReady && (int)Plugin.Session.Role != 3)
			{
				string text = LockedValues.Compose(LockList.Parse(((ConfigEntry<string>)(object)entries[(ThingSetting)(object)General.LockedPlayerSettings]).Value), (Func<string, string>)delegate(string id)
				{
					ThingSetting val2 = ThingSettings.Find(id);
					ConfigEntryBase value;
					return (val2 == null || !entries.TryGetValue(val2, out value)) ? null : value.GetSerializedValue();
				});
				if (!(text == lockedValues.Value))
				{
					ConfigAmendResult val = MeadworksConfig.Amend<string>(lockedValues, "com.meadworks.thing", (ConfigScope)1, text, (string)null, true);
					log.Verbose("Locked player values: " + ((object)val.Route/*cast due to .constrained prefix*/).ToString() + (val.Applied ? " applied" : " not applied"));
				}
			}
		}

		private void ReadLocks(bool force)
		{
			string text = Get<string>(General.LockedPlayerSettings);
			if (force || text != lockListText)
			{
				lockListText = text;
				lockList = LockList.Parse(text);
				if (lockList.Unknown.Count > 0 || lockList.NotLockable.Count > 0)
				{
					log.Warning("Locked Player Settings: " + lockList.Describe());
				}
				else
				{
					log.Verbose("Locked Player Settings: " + lockList.Describe());
				}
			}
			string text2 = MeadworksConfig.Effective<string>(lockedValues, "com.meadworks.thing", (ConfigScope)1, "", true);
			if (force || text2 != valuesText)
			{
				valuesText = text2;
				values = LockedValues.Decode(text2);
			}
		}

		private PlayerLock Lock(ThingSetting setting)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			return PlayerLocks.Evaluate(setting.Id, ServerBinds(), lockList, values);
		}

		private bool ServerBinds()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Invalid comparison between Unknown and I4
			if (!Plugin.IsReady)
			{
				return false;
			}
			SessionSnapshot session = Plugin.Session;
			if ((int)session.Role != 3 || syncReceipt.CurrentGeneration != session.Generation)
			{
				return false;
			}
			return (int)MeadworksAuthority.Decide("com.meadworks.thing", (ConfigScope)1, true).Source == 1;
		}

		private void OnSynchronized(object sender, ConfigurationSynchronizationEventArgs args)
		{
			if (args.UpdatedPluginGUIDs != null && args.UpdatedPluginGUIDs.Contains("com.meadworks.thing") && Plugin.IsReady)
			{
				syncReceipt.Observe();
				Refresh(force: true);
			}
		}

		private static bool TryConvert<T>(string text, out T value)
		{
			try
			{
				value = (T)TomlTypeConverter.ConvertToValue(text, typeof(T));
				return true;
			}
			catch (Exception)
			{
				value = default(T);
				return false;
			}
		}

		public ServerRules Rules()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			return ThingSettings.Rules(Get<bool>(General.AdminListEditable), Get<bool>(Remote.Editing), Get<bool>(World.WardOverrideAllowed), Get<bool>(World.TerrainNonAdminOwnPieces), Get<bool>(World.RemoveDropsForEveryone));
		}

		public void Shutdown()
		{
			file.SettingChanged -= OnSettingChanged;
			file.ConfigReloaded -= OnReloaded;
			SynchronizationManager.OnConfigurationSynchronized -= OnSynchronized;
			trackedLocks.Clear();
			foreach (ConfigEntryBase value in entries.Values)
			{
				MeadworksBlockedOptions.Untrack(value);
			}
			entries.Clear();
			this.Changed = null;
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs e)
		{
			if ((object)e.ChangedSetting == settingsGrant || (object)e.ChangedSetting == settingsStatus)
			{
				PublishSettingsAccess();
			}
			this.Changed?.Invoke();
		}

		private void OnReloaded(object sender, EventArgs e)
		{
			this.Changed?.Invoke();
		}

		private void BindSetting(ThingSetting setting)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntryBase value;
			if (setting.Hotkey)
			{
				string text = (string)setting.DefaultValue;
				value = (ConfigEntryBase)(object)MeadworksConfig.Bind<KeyboardShortcut>(file, setting.Section, setting.Key, (text.Length == 0) ? KeyboardShortcut.Empty : KeyboardShortcut.Deserialize(text), setting.Description, Map(setting.Scope), (AcceptableValueBase)null);
			}
			else if (!(setting is ThingSetting<bool> setting2))
			{
				if (!(setting is ThingSetting<int> val))
				{
					if (!(setting is ThingSetting<string> setting3))
					{
						if (!(setting is ThingSetting<RemoteConfigScope> setting4))
						{
							throw new NotSupportedException(setting.Id + ": unsupported value type " + setting.ValueType.Name + ".");
						}
						value = (ConfigEntryBase)(object)Bind<RemoteConfigScope>(setting4, null);
					}
					else
					{
						value = (ConfigEntryBase)(object)Bind<string>(setting3, null);
					}
				}
				else
				{
					value = (ConfigEntryBase)(object)Bind<int>(val, (AcceptableValueBase?)(object)new AcceptableValueRange<int>((int)((ThingSetting)val).Minimum, (int)((ThingSetting)val).Maximum));
				}
			}
			else
			{
				value = (ConfigEntryBase)(object)Bind<bool>(setting2, null);
			}
			entries.Add(setting, value);
		}

		private ConfigEntry<T> Bind<T>(ThingSetting<T> setting, AcceptableValueBase? acceptable)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Expected O, but got Unknown
			if ((object)setting == Remote.AllowList)
			{
				return MeadworksConfig.Bind<T>(file, ((ThingSetting)setting).Section, ((ThingSetting)setting).Key, setting.Default, new ConfigDescription(((ThingSetting)setting).Description, acceptable, new object[1] { (object)new ConfigDependency((Func<bool>)(() => (int)Get<RemoteConfigScope>(Remote.Scope) == 1), (Func<string>)(() => "Requires Remote Config Scope: AllowList."), (Func<bool>)null) }), Map(((ThingSetting)setting).Scope));
			}
			return MeadworksConfig.Bind<T>(file, ((ThingSetting)setting).Section, ((ThingSetting)setting).Key, setting.Default, ((ThingSetting)setting).Description, Map(((ThingSetting)setting).Scope), acceptable);
		}
	}
	internal sealed class ThingSyncReceipt
	{
		private ZNet? network;

		private ZRpc? server;

		private string epoch = "";

		private long world;

		internal long CurrentGeneration
		{
			get
			{
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				//IL_0067: Invalid comparison between Unknown and I4
				if (!Plugin.IsReady || (Object)(object)network == (Object)null || server == null || !server.IsConnected() || network != ZNet.instance || network.IsServer() || server != network.GetServerRPC())
				{
					return -1L;
				}
				SessionSnapshot session = Plugin.Session;
				if ((int)session.Role != 3 || !session.AuthorityReady || session.WorldId == 0L || string.IsNullOrEmpty(session.SessionId))
				{
					return -1L;
				}
				if (epoch.Length == 0)
				{
					epoch = session.SessionId;
					world = session.WorldId;
				}
				if (!(epoch == session.SessionId) || world != session.WorldId)
				{
					return -1L;
				}
				return session.Generation;
			}
		}

		internal void Observe()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Invalid comparison between Unknown and I4
			network = ZNet.instance;
			server = (((Object)(object)network != (Object)null && !network.IsServer()) ? network.GetServerRPC() : null);
			epoch = "";
			world = 0L;
			if (server != null && server.IsConnected() && Plugin.IsReady)
			{
				SessionSnapshot session = Plugin.Session;
				if ((int)session.Role == 3 && !string.IsNullOrEmpty(session.SessionId))
				{
					epoch = session.SessionId;
					world = session.WorldId;
				}
			}
		}
	}
	public sealed class ThingImport : IDisposable
	{
		public const string Section = "Import";

		private const string SourceName = "Valheim_Admin_Tool";

		private readonly ConfigFile config;

		private readonly ThingConfig settings;

		private readonly MeadworksLog log;

		private readonly ConfigEntry<string> marker;

		private readonly IDisposable registration;

		private bool disposed;

		private static string SourcePath => Path.Combine(Paths.ConfigPath, "com.icecub.valheimadmintool.cfg");

		private bool Imported => marker.Value.Split(',').Contains("Valheim_Admin_Tool");

		public ThingImport(ConfigFile config, ThingConfig settings, MeadworksLog log)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			this.config = config;
			this.settings = settings;
			this.log = log;
			marker = MeadworksConfig.BindHidden<string>(config, "Import", "Imported Sources", "", "Managed by Thing: sources already imported.", (ConfigScope)0, (AcceptableValueBase)null);
			ModuleSource adminTool = ModuleStepAside.AdminTool;
			registration = MeadworksConversion.Register(new ConversionRegistration("thing.valheimadmintool", "com.meadworks.thing", adminTool.Guid, adminTool.Name, (Func<bool>)(() => !disposed && File.Exists(SourcePath) && !Imported && MeadworksHeadless.AllowsUi), (Func<object>)(() => Preview()), (Func<object, IReadOnlyDictionary<string, string>, bool, object>)((object document, IReadOnlyDictionary<string, string> resolutions, bool backup) => Apply((ImportPlan)(((document is ImportPlan) ? document : null) ?? throw new ArgumentException("Wrong Thing import preview.")), backup))));
		}

		public ImportPlan Preview()
		{
			ImportPlan val = SourceImport.Plan(File.ReadAllText(SourcePath));
			string text = Path.Combine(ThingPaths.DataFolder, "import-valheimadmintool.txt");
			Directory.CreateDirectory(Path.GetDirectoryName(text));
			File.WriteAllText(text, val.Report());
			log.Info("Import preview for Valheim_Admin_Tool: " + val.Values.Count + " hotkeys, " + val.Notes.Count + " notes (" + text + ").");
			return val;
		}

		private object Apply(ImportPlan plan, bool backup)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			if (!backup)
			{
				throw new InvalidOperationException("Thing import requires a backup.");
			}
			MeadworksConversion.Target(config).Backup();
			bool saveOnConfigSet = config.SaveOnConfigSet;
			config.SaveOnConfigSet = false;
			int num = 0;
			try
			{
				foreach (ImportValue value in plan.Values)
				{
					ImportValue current = value;
					if (ThingSettings.Find(((ImportValue)(ref current)).SettingId) is ThingSetting<string> val && ((ThingSetting)val).Hotkey)
					{
						try
						{
							settings.Shortcut(val).Value = KeyboardShortcut.Deserialize(((ImportValue)(ref current)).Value);
							num++;
						}
						catch (Exception ex)
						{
							log.Warning("Import skipped " + ((ImportValue)(ref current)).SettingId + ": " + ex.GetType().Name);
						}
					}
				}
				marker.Value = string.Join(",", (from p in marker.Value.Split(',')
					where p.Length > 0
					select p).Append("Valheim_Admin_Tool").Distinct());
			}
			finally
			{
				config.SaveOnConfigSet = saveOnConfigSet;
			}
			config.Save();
			string text = ThingText.Format("import_done", num, "Valheim_Admin_Tool");
			log.Info(text);
			return text;
		}

		public void Dispose()
		{
			if (!disposed)
			{
				disposed = true;
				registration.Dispose();
			}
		}
	}
	public static class ThingModules
	{
		private const string TranslationProduct = "thing";

		private static ModuleStepAsideDecision? stepAside;

		private static long noticedSession = -1L;

		private static long offNoticedSession = -1L;

		public static ModuleStepAsideDecision StepAside => stepAside ?? ModuleStepAside.Decide(Enumerable.Empty<InstalledPlugin>());

		public static void Decide(MeadworksLog log)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Services.StepAside.Declared("com.meadworks.thing").Any())
			{
				MeadworksStepAside.Declare("com.meadworks.thing", "Thing", ((IEnumerable<ModuleSource>)ModuleStepAside.Sources).Select((Func<ModuleSource, SourceMod>)((ModuleSource s) => new SourceMod(s.Guid, s.Name))));
			}
			stepAside = ModuleStepAside.Decide(((IEnumerable<PluginInfo>)Chainloader.PluginInfos.Values).Select((Func<PluginInfo, InstalledPlugin>)((PluginInfo p) => new InstalledPlugin(p.Metadata.GUID, p.Metadata.Name))));
			foreach (StepAsideNotice notice in stepAside.Notices)
			{
				StepAsideNotice current = notice;
				log.Info("Thing " + ((object)((StepAsideNotice)(ref current)).Module/*cast due to .constrained prefix*/).ToString().ToLowerInvariant() + " module steps aside: " + ((StepAsideNotice)(ref current)).SourceName + " is installed.");
			}
		}

		public static bool ProductActive(out ConfigBlockReason reason)
		{
			reason = (ConfigBlockReason)0;
			if (Plugin.Ready)
			{
				return MeadworksAuthority.ProductActive("com.meadworks.thing", ref reason);
			}
			return false;
		}

		public static bool Active(ThingModule module)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Settings != null && ProductActive(out var _))
			{
				return !StepAside.SteppedAside(module);
			}
			return false;
		}

		public static void Tick(MeadworksLog log)
		{
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Invalid comparison between Unknown and I4
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Invalid comparison between Unknown and I4
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Invalid comparison between Unknown and I4
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			if (!Plugin.Ready || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				return;
			}
			SessionSnapshot session = Plugin.Session;
			if (stepAside != null && stepAside.Any && noticedSession != session.Generation)
			{
				noticedSession = session.Generation;
				foreach (StepAsideNotice notice in stepAside.Notices)
				{
					StepAsideNotice current = notice;
					MeadworksStepAside.ReportModule("com.meadworks.thing", ((object)((StepAsideNotice)(ref current)).Module/*cast due to .constrained prefix*/).ToString().ToLowerInvariant(), new SourceMod(((StepAsideNotice)(ref current)).SourceGuid, ((StepAsideNotice)(ref current)).SourceName), "thing", ((StepAsideNotice)(ref current)).Key);
				}
			}
			if (offNoticedSession != session.Generation && session.AuthorityReady && !ProductActive(out var reason) && ((int)reason == 4 || (int)reason == 5))
			{
				offNoticedSession = session.Generation;
				string text = ThingText.Text(((int)reason == 4) ? "off_no_crucible" : "off_no_thing");
				ThingText.Centre(text, log);
				log.Info(text);
			}
		}

		public static void Reset()
		{
			stepAside = null;
			noticedSession = -1L;
			offNoticedSession = -1L;
		}
	}
	public static class ThingText
	{
		private static MeadworksTranslations? catalog;

		public static ProductTranslations? Translations { get; private set; }

		public static void Initialize(BepInPlugin metadata, ManualLogSource log)
		{
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Expected O, but got Unknown
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			Assembly assembly = typeof(ThingText).Assembly;
			foreach (string item in MeadworksLanguages.Supported)
			{
				using Stream stream = assembly.GetManifestResourceStream("Thing.localization." + item + ".json");
				if (stream != null)
				{
					using StreamReader streamReader = new StreamReader(stream);
					dictionary.Add(item, streamReader.ReadToEnd());
				}
			}
			Translations = new ProductTranslations("thing", (IReadOnlyDictionary<string, string>)dictionary);
			catalog = MeadworksTranslations.Register(metadata, Translations, (string)null, log);
		}

		public static string Text(string key)
		{
			MeadworksTranslations? obj = catalog;
			return ((obj != null) ? obj.Text(key) : null) ?? key;
		}

		public static string Format(string key, params object[] arguments)
		{
			MeadworksTranslations? obj = catalog;
			return ((obj != null) ? obj.Format(key, arguments) : null) ?? key;
		}

		public static void Tick()
		{
			MeadworksTranslations? obj = catalog;
			if (obj != null)
			{
				obj.Update();
			}
		}

		public static void Shutdown()
		{
			MeadworksTranslations? obj = catalog;
			if (obj != null)
			{
				obj.Dispose();
			}
			catalog = null;
			Translations = null;
		}

		public static void Centre(string text, MeadworksLog log)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			if (MeadworksHeadless.AllowsUi && (Object)(object)MessageHud.instance != (Object)null)
			{
				ProductRegistration source = Plugin.Source;
				if (source != null && source.IsActive && CrucibleUiPlugin.Active)
				{
					KitNotices.Show(source, new KitNotice(text, (KitNoticeKind)1, "", 0, (Sprite)null, true, false, true));
				}
				else
				{
					MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true);
				}
			}
			else
			{
				log.Info(text);
			}
		}
	}
	[BepInPlugin("com.meadworks.thing", "Thing", "0.1.14")]
	[BepInDependency("com.meadworks.crucible", "0.3.33")]
	[BepInDependency("com.meadworks.crucible.ui", "0.3.22")]
	[BepInDependency("com.jotunn.jotunn", "2.30.2")]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.meadworks.thing";

		public const string Name = "Thing";

		public const string Version = "0.1.14";

		public const string Family = "Runestone";

		private ProductRegistration? registration;

		private MeadworksLog? log;

		private ThingImport? import;

		private bool started;

		internal static ProductRegistration? Source
		{
			get
			{
				if (!Chainloader.PluginInfos.TryGetValue("com.meadworks.thing", out var value))
				{
					return null;
				}
				return (value.Instance as Plugin)?.registration;
			}
		}

		public static bool Ready { get; private set; }

		public static bool AllowsUi { get; private set; }

		public static ThingConfig? Settings { get; private set; }

		private void Awake()
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			log = MeadworksLog.For(((BaseUnityPlugin)this).Logger, "Thing");
			try
			{
				if (!Plugin.IsReady)
				{
					throw new InvalidOperationException("Required Crucible is not ready");
				}
				registration = Plugin.Services.Register(new ProductMetadata("com.meadworks.thing", "Thing", new Version("0.1.14"), new Version(0, 1), new Version(0, 1), (MultiplayerPolicy)2, "Runestone"));
				AllowsUi = MeadworksHeadless.AllowsUi;
				Settings = ThingConfig.Bind(((BaseUnityPlugin)this).Config, log);
				ThingText.Initialize(((BaseUnityPlugin)this).Info.Metadata, ((BaseUnityPlugin)this).Logger);
				ThingNotices.Initialize(log);
				ThingPlayerFreeze.Initialize(log);
				ThingServer.Initialize(log);
				ThingPickup.Initialize(log);
				Ready = true;
				log.Info(AllowsUi ? "Thing registered; modules start after plugin discovery." : "Thing registered on a headless process; no UI is created.");
			}
			catch (Exception ex)
			{
				log.Error((object)("Thing initialization rejected: " + ex.GetType().Name + ": " + ex.Message));
				Shutdown();
			}
		}

		private void Start()
		{
			if (!Ready || log == null)
			{
				return;
			}
			try
			{
				ThingModules.Decide(log);
				import = new ThingImport(((BaseUnityPlugin)this).Config, Settings, log);
				ThingRemoteCommands.Initialize(registration, log);
				ThingTerrainCommands.Initialize(registration);
				ThingDropCommands.Initialize(registration);
				ThingTeleportCommands.Initialize(registration);
				ThingWard.Initialize(registration);
				ThingPoi.Initialize(registration);
				ThingTime.Initialize();
				ThingChatMute.Initialize();
				ThingHexAudit.Initialize(registration);
				ThingAccess.Initialize(log);
				ThingInventory.Initialize(registration, log);
				registration.AddCapability<Func<string, string, bool>>("meadworks.thing.admin_permission", AdminPermissions.CapabilityVersion, (Func<string, string, bool>)ThingAccess.Allows);
				registration.AddCapability<Func<string[], bool>>("meadworks.admin.command", AdminCommandContract.ContractVersion, (Func<string[], bool>)delegate(string[] args)
				{
					((ConsoleCommand)new ThingCommand()).Run(args);
					return true;
				});
				CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new ThingCommand());
				started = true;
			}
			catch (Exception ex)
			{
				log.Error((object)("Thing modules rejected: " + ex.GetType().Name + ": " + ex.Message));
				Shutdown();
			}
		}

		private void Update()
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			if (!Ready)
			{
				return;
			}
			ThingText.Tick();
			if (!started || log == null)
			{
				return;
			}
			Settings?.Tick(Time.unscaledTime);
			ThingModules.Tick(log);
			ThingNotices.Tick();
			ThingServer.Tick();
			ThingRemoteCommands.Tick();
			ThingYamlFiles.Tick();
			ThingTeleportCommands.Tick();
			ThingInventory.Tick();
			ThingWard.Tick();
			if (!((Object)(object)Player.m_localPlayer != (Object)null))
			{
				return;
			}
			ThingPlayerFreeze.Tick();
			ThingTime.Tick();
			ThingTerrainCommands.Tick();
			ThingDropCommands.Tick();
			ThingPickup.Tick();
			ThingPanel.Tick();
			if (Settings != null && ThingModules.Active((ThingModule)0))
			{
				if (MeadworksInput.Pressed(Settings.ShortcutValue(Hotkeys.WardOverride), (InputContext)0))
				{
					ThingWard.Run(new string[1] { "ward" });
				}
				if (MeadworksInput.Pressed(Settings.ShortcutValue(Hotkeys.RemoveDrops), (InputContext)0))
				{
					ThingDropCommands.Run(new string[1] { "drops" });
				}
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Shutdown()
		{
			Ready = false;
			started = false;
			Action[] array = new Action[19]
			{
				ThingPanel.Shutdown,
				ThingConfigFiles.Reset,
				ThingYamlFiles.Reset,
				ThingTeleportCommands.Shutdown,
				ThingChatMute.Shutdown,
				ThingTime.Shutdown,
				ThingPoi.Shutdown,
				ThingWard.Shutdown,
				ThingPickup.Shutdown,
				ThingDropCommands.Shutdown,
				ThingTerrainCommands.Shutdown,
				ThingInventory.Shutdown,
				ThingRemoteCommands.Shutdown,
				ThingServer.Unload,
				ThingNotices.Shutdown,
				ThingPlayerFreeze.Shutdown,
				delegate
				{
					import?.Dispose();
				},
				ThingModules.Reset,
				ThingText.Shutdown
			};
			foreach (Action action in array)
			{
				try
				{
					action();
				}
				catch (Exception ex)
				{
					MeadworksLog? obj = log;
					if (obj != null)
					{
						obj.Warning("Thing teardown: " + ex.GetType().Name);
					}
				}
			}
			import = null;
			Settings?.Shutdown();
			Settings = null;
			ProductRegistration? obj2 = registration;
			if (obj2 != null)
			{
				obj2.Dispose();
			}
			registration = null;
		}
	}
	internal static class ThingConfigFiles
	{
		private sealed class FileItem
		{
			internal string Path = "";

			internal string Relative = "";

			internal string Id = "";

			internal ConfigFile? File;
		}

		private sealed class HandlerErrors : ILogListener, IDisposable
		{
			private readonly int thread = Thread.CurrentThread.ManagedThreadId;

			internal Exception? Error { get; private set; }

			internal HandlerErrors()
			{
				Logger.Listeners.Add((ILogListener)(object)this);
			}

			public void LogEvent(object sender, LogEventArgs args)
			{
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_0019: Unknown result type (might be due to invalid IL or missing references)
				if (Thread.CurrentThread.ManagedThreadId == thread && (args.Level & 2) != 0 && args.Data is Exception ex && Error == null)
				{
					Exception ex2 = (Error = ex);
				}
			}

			public void Dispose()
			{
				Logger.Listeners.Remove((ILogListener)(object)this);
			}
		}

		private static long generation = -1L;

		private static readonly HashSet<string> backedUp = new HashSet<string>(StringComparer.Ordinal);

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

		private static bool Windows => Path.DirectorySeparatorChar == '\\';

		private static StringComparison Comparison
		{
			get
			{
				if (!Windows)
				{
					return StringComparison.Ordinal;
				}
				return StringComparison.OrdinalIgnoreCase;
			}
		}

		private static string Canonical(string path)
		{
			if (!Windows)
			{
				return Path.GetFullPath(path);
			}
			return Path.GetFullPath(path).ToUpperInvariant();
		}

		internal static string Id(string value)
		{
			return FileVersion.Hash(utf8.GetBytes(value));
		}

		private static string EntryId(ConfigEntryBase entry)
		{
			return Id(entry.Definition.Section + "\0" + entry.Definition.Key);
		}

		internal static bool Authorized(Actor admin, bool local)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Ready || !Plugin.IsReady || !Plugin.Session.AuthorityReady || !ThingServer.CurrentAuthority || !ThingModules.ProductActive(out var _) || Plugin.Settings == null)
			{
				return false;
			}
			if (local)
			{
				return true;
			}
			ZNet instance = ZNet.instance;
			if (SettingsAccess.CanConfigure(false, (Object)(object)instance != (Object)null && instance.IsAdmin(((Actor)(ref admin)).Id), Plugin.Settings.AllowAdminSettingsEditing, Plugin.Settings.Get<bool>(Remote.Editing)) && (Object)(object)instance != (Object)null)
			{
				return instance.GetPeers().Any(delegate(ZNetPeer p)
				{
					if (p != null && p.IsReady())
					{
						ZRpc rpc = p.m_rpc;
						if (rpc != null && rpc.IsConnected())
						{
							ISocket socket = p.m_socket;
							return ((socket != null) ? socket.GetHostName() : null) == ((Actor)(ref admin)).Id;
						}
					}
					return false;
				});
			}
			return false;
		}

		private static FilePolicy Policy()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Expected O, but got Unknown
			return new FilePolicy(Plugin.Settings.Get<RemoteConfigScope>(Remote.Scope), Plugin.Settings.Get<string>(Remote.AllowList), from f in MeadworksDataFiles.Registered
				select Relative(f.Path) into p
				where p != null
				select p, !Windows);
		}

		internal static string? Relative(string path)
		{
			string text = Path.GetFullPath(Paths.ConfigPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
			string fullPath = Path.GetFullPath(path);
			if (!fullPath.StartsWith(text, Comparison))
			{
				return null;
			}
			return fullPath.Substring(text.Length).Replace(Path.DirectorySeparatorChar, '/');
		}

		internal static bool Allowed(string path)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Invalid comparison between Unknown and I4
			string text = Relative(path);
			if (text == null || !File.Exists(path))
			{
				return false;
			}
			FileInfo fileInfo = new FileInfo(path);
			bool flag = (fileInfo.Attributes & FileAttributes.ReparsePoint) != 0;
			for (DirectoryInfo directoryInfo = fileInfo.Directory; directoryInfo != null; directoryInfo = directoryInfo.Parent)
			{
				flag |= (directoryInfo.Attributes & FileAttributes.ReparsePoint) != 0;
				if (Canonical(directoryInfo.FullName) == Canonical(Paths.ConfigPath))
				{
					break;
				}
			}
			return (int)RemoteFiles.Check(new FileRequest(text, fileInfo.Length, flag), Policy()) == 0;
		}

		private static FileItem[] Discover()
		{
			Dictionary<string, FileItem> files = new Dictionary<string, FileItem>(Windows ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
			HashSet<string> ambiguous = new HashSet<string>(files.Comparer);
			PluginInfo[] array = Chainloader.PluginInfos.Values.Take(4096).ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				BaseUnityPlugin instance = array[i].Instance;
				if ((Object)(object)instance == (Object)null)
				{
					continue;
				}
				Add(instance.Config.ConfigFilePath, instance.Config);
				Type type = ((object)instance).GetType();
				while (type != null && type != typeof(object))
				{
					foreach (FieldInfo item in type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Take(4096))
					{
						if (!typeof(ConfigFile).IsAssignableFrom(item.FieldType))
						{
							continue;
						}
						try
						{
							object? value = item.GetValue(item.IsStatic ? null : instance);
							ConfigFile val = (ConfigFile)((value is ConfigFile) ? value : null);
							if (val != null)
							{
								Add(val.ConfigFilePath, val);
							}
						}
						catch
						{
						}
					}
					type = type.BaseType;
				}
			}
			foreach (string item2 in Directory.EnumerateFiles(Paths.ConfigPath, "*.cfg", SearchOption.TopDirectoryOnly).Take(10000))
			{
				Add(item2, null);
			}
			foreach (DataFileRegistration item3 in MeadworksDataFiles.Registered.Take(10000))
			{
				Add(item3.Path, null);
			}
			foreach (string item4 in ambiguous)
			{
				files[item4].File = null;
			}
			return files.Values.OrderBy<FileItem, string>((FileItem f) => f.Relative, StringComparer.Ordinal).Take(10000).ToArray();
			void Add(string path, ConfigFile? handle)
			{
				try
				{
					if (Allowed(path))
					{
						string text = Canonical(path);
						if (!files.TryGetValue(text, out FileItem value2))
						{
							Dictionary<string, FileItem> dictionary = files;
							FileItem obj2 = new FileItem
							{
								Path = Path.GetFullPath(path),
								Relative = Relative(path),
								Id = Id(text)
							};
							value2 = obj2;
							dictionary[text] = obj2;
						}
						if (handle != null)
						{
							if (value2.File != null && value2.File != handle)
							{
								ambiguous.Add(text);
							}
							else
							{
								value2.File = handle;
							}
						}
					}
				}
				catch
				{
				}
			}
		}

		private static bool Sensitive(ConfigEntryBase entry)
		{
			string text = new string((entry.Definition.Section + entry.Definition.Key).Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray());
			return new string[7] { "password", "passwd", "token", "secret", "apikey", "credential", "privatekey" }.Any(text.Contains);
		}

		private static ConfigEntryBase[] Entries(FileItem item)
		{
			ConfigEntryMetadata val = default(ConfigEntryMetadata);
			ConfigEntryMetadata val2 = default(ConfigEntryMetadata);
			if (item.File != null)
			{
				return (from p in (IEnumerable<KeyValuePair<ConfigDefinition, ConfigEntryBase>>)item.File
					select p.Value into e
					where !Sensitive(e) && (!MeadworksConfig.TryGetMetadata(e, ref val) || (int)val.Visibility == 0) && NativeTags.Read(e.Description.Tags).Attributes.Browsable != false && (MeadworksConfig.TryGetMetadata(e, ref val2) || !ThingExternalConfig.Inspect(e).Hidden)
					select e).OrderBy<ConfigEntryBase, string>((ConfigEntryBase e) => e.Definition.Section, StringComparer.Ordinal).ThenBy<ConfigEntryBase, string>((ConfigEntryBase e) => e.Definition.Key, StringComparer.Ordinal).Take(10000)
					.ToArray();
			}
			return Array.Empty<ConfigEntryBase>();
		}

		private static string Version(FileItem file, ConfigEntryBase entry)
		{
			using MemoryStream memoryStream = new MemoryStream();
			byte[] array = ThingMessages.ReadBounded(file.Path);
			memoryStream.Write(array, 0, array.Length);
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, utf8, leaveOpen: true))
			{
				binaryWriter.Write(Plugin.Session.Generation);
				binaryWriter.Write(EntryId(entry));
				binaryWriter.Write(TomlTypeConverter.ConvertToString(entry.BoxedValue, entry.SettingType));
				ConfigEntryMetadata val = default(ConfigEntryMetadata);
				binaryWriter.Write(MeadworksConfig.TryGetMetadata(entry, ref val) ? (((object)val.Decision).ToString() + "\n" + TomlTypeConverter.ConvertToString(val.SafeValue, entry.SettingType)) : ("unmanaged\n" + ThingExternalConfig.Inspect(entry).Stamp));
			}
			return FileVersion.Hash(memoryStream.ToArray());
		}

		private static bool Editable(NativeEntry native, out ConfigEntryMetadata metadata)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntryBase entry = native.Entry;
			ThingConfig? settings = Plugin.Settings;
			if (settings != null && settings.IsSettingsPolicy(entry))
			{
				metadata = null;
				return false;
			}
			bool flag = MeadworksConfig.TryGetMetadata(entry, ref metadata);
			if (Sensitive(entry) || native.Attributes.Browsable == false || native.Attributes.ReadOnly == true || native.Attributes.CharterReadOnly || native.Attributes.GenericReadOnly || !native.Shape.HasConverter || native.Attributes.HasCodecDelegates)
			{
				return false;
			}
			if (flag)
			{
				if ((int)metadata.Visibility == 0)
				{
					return metadata.Decision.Allowed;
				}
				return false;
			}
			ThingExternalConfig.Facts facts = ThingExternalConfig.Inspect(entry);
			if (facts.Allowed)
			{
				return !facts.Hidden;
			}
			return false;
		}

		internal static IReadOnlyList<string> Run(ConfigEditRequest request, Actor admin, bool local, string connection = "")
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0316: Unknown result type (might be due to invalid IL or missing references)
			//IL_031b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0321: Unknown result type (might be due to invalid IL or missing references)
			//IL_0308: Unknown result type (might be due to invalid IL or missing references)
			//IL_0351: Unknown result type (might be due to invalid IL or missing references)
			//IL_0356: Unknown result type (might be due to invalid IL or missing references)
			//IL_0362: Unknown result type (might be due to invalid IL or missing references)
			//IL_036f: Unknown result type (might be due to invalid IL or missing references)
			//IL_037d: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_0431: Unknown result type (might be due to invalid IL or missing references)
			//IL_0444: Unknown result type (might be due to invalid IL or missing references)
			//IL_044a: Invalid comparison between Unknown and I4
			//IL_0499: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_04bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c9: Unknown result type (might be due to invalid IL or missing references)
			if (!Authorized(admin, local))
			{
				return Reply("config_unavailable");
			}
			if (generation != Plugin.Session.Generation)
			{
				generation = Plugin.Session.Generation;
				backedUp.Clear();
			}
			try
			{
				if (request.Verb.StartsWith("yaml-", StringComparison.Ordinal))
				{
					return ThingYamlFiles.Run(request, admin, local, connection);
				}
				ThingExternalConfig.BeginRequest();
				FileItem[] source = Discover();
				if (request.Verb == "list")
				{
					ThingServer.Audit(admin, (ThingAction)34, default(Actor), (AuditOutcome)0, "allowed config file list");
					return ThingUiPage.Encode("config_files", request.Offset, ((IEnumerable<FileItem>)source).Select((Func<FileItem, ThingUiRow>)((FileItem f) => new ThingUiRow((ThingYamlFiles.Registered(f.Path) != null) ? "yaml" : ((f.File == null) ? "readonly" : "bound"), f.Id, f.Relative, ThingText.Text((ThingYamlFiles.Registered(f.Path) != null) ? "config_yaml" : ((f.File == null) ? "config_unbound" : "config_bound"))))), "");
				}
				FileItem fileItem = source.SingleOrDefault((FileItem f) => f.Id == request.File);
				if (fileItem == null || fileItem.File == null)
				{
					return Reply("config_unbound");
				}
				ConfigEntryBase[] source2 = Entries(fileItem);
				if (request.Verb == "entries")
				{
					ThingServer.Audit(admin, (ThingAction)34, default(Actor), (AuditOutcome)0, "config entries " + fileItem.Relative);
					return ThingUiPage.Encode("config_entries", request.Offset, ((IEnumerable<ConfigEntryBase>)source2).Select((Func<ConfigEntryBase, ThingUiRow>)((ConfigEntryBase e) => new ThingUiRow("entry", EntryId(e), e.Definition.Section + "." + e.Definition.Key, e.SettingType.Name))), "");
				}
				ConfigEntryBase val = ((IEnumerable<ConfigEntryBase>)source2).SingleOrDefault((Func<ConfigEntryBase, bool>)((ConfigEntryBase e) => EntryId(e) == request.Entry));
				if (val == null)
				{
					return Reply("config_unavailable");
				}
				NativeEntry nativeEntry = new NativeEntry(val);
				ConfigEntryMetadata metadata;
				bool flag = Editable(nativeEntry, out metadata);
				if (request.Verb == "list-get")
				{
					ThingServer.Audit(admin, (ThingAction)34, default(Actor), (AuditOutcome)0, "typed list read " + fileItem.Relative + " " + (object)val.Definition);
					return ThingConfigLists.Read(fileItem.Id, EntryId(val), Version(fileItem, val), nativeEntry, flag);
				}
				if (request.Verb == "list-set")
				{
					if (!flag)
					{
						return Reply("config_readonly");
					}
					if (Version(fileItem, val) != request.Version)
					{
						return Reply("config_stale");
					}
					try
					{
						request = request.WithValue(ThingConfigLists.Serialize(nativeEntry, request.ListValues));
					}
					catch
					{
						return Reply("config_invalid");
					}
				}
				if (request.Writes)
				{
					return Write(request, fileItem, nativeEntry, admin, local);
				}
				ThingServer.Audit(admin, (ThingAction)34, default(Actor), (AuditOutcome)0, "config entry " + fileItem.Relative + " " + (object)val.Definition);
				ConfigEntryMetadata val2 = default(ConfigEntryMetadata);
				return new ConfigEntryDocument
				{
					File = fileItem.Id,
					Entry = EntryId(val),
					Version = Version(fileItem, val),
					Section = val.Definition.Section,
					Key = val.Definition.Key,
					Description = val.Description.Description,
					Current = (nativeEntry.Shape.HasConverter ? nativeEntry.RawSerialized() : (val.BoxedValue?.ToString() ?? "")),
					Default = (nativeEntry.Shape.HasConverter ? TomlTypeConverter.ConvertToString(val.DefaultValue, val.SettingType) : (val.DefaultValue?.ToString() ?? "")),
					Effective = ((MeadworksConfig.TryGetMetadata(val, ref val2) && (int)val2.Decision.Source == 2) ? TomlTypeConverter.ConvertToString(val2.SafeValue, val.SettingType) : (nativeEntry.Shape.HasConverter ? nativeEntry.RawSerialized() : (val.BoxedValue?.ToString() ?? ""))),
					Editable = flag,
					Reason = (flag ? "" : ThingText.Text("config_readonly")),
					Shape = nativeEntry.Shape,
					Attributes = nativeEntry.Attributes
				}.Encode();
			}
			catch
			{
				return Reply("config_unavailable");
			}
		}

		private static IReadOnlyList<string> Write(ConfigEditRequest request, FileItem file, NativeEntry native, Actor admin, bool local)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0492: Unknown result type (might be due to invalid IL or missing references)
			//IL_049b: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Invalid comparison between Unknown and I4
			//IL_0294: Unknown result type (might be due to invalid IL or missing references)
			//IL_029b: Expected O, but got Unknown
			//IL_0332: Unknown result type (might be due to invalid IL or missing references)
			//IL_0339: Expected O, but got Unknown
			if (!Editable(native, out ConfigEntryMetadata metadata))
			{
				return Reply("config_readonly");
			}
			ConfigEntryBase entry = native.Entry;
			if (!Authorized(admin, local) || Version(file, entry) != request.Version)
			{
				return Reply("config_stale");
			}
			NativeParsedValue parsed = native.Parse(request.Value);
			if (!parsed.Valid && (int)native.Shape.Kind == 1 && native.Shape.IsFlags && request.Value == "0")
			{
				object obj = Enum.ToObject(entry.SettingType, 0);
				if (entry.Description.AcceptableValues == null || entry.Description.AcceptableValues.IsValid(obj))
				{
					parsed = new NativeParsedValue(obj, TomlTypeConverter.ConvertToString(obj, entry.SettingType), clamped: false);
				}
			}
			if (!parsed.Valid || utf8.GetByteCount(parsed.Serialized) > 4096)
			{
				return Reply("config_invalid");
			}
			bool started = false;
			string text = "config_failed";
			try
			{
				if (!Current())
				{
					return Reply("config_stale");
				}
				IConversionTarget target = MeadworksConversion.Target(file.File);
				Action action = delegate
				{
					if (!Current())
					{
						throw new InvalidOperationException("StaleRemoteConfig");
					}
					if (!backedUp.Contains(Canonical(file.Path)))
					{
						string destFileName = Backups.Name(file.Path, DateTime.UtcNow);
						File.Copy(file.Path, destFileName, overwrite: false);
						backedUp.Add(Canonical(file.Path));
					}
					if (!Current())
					{
						throw new InvalidOperationException("StaleRemoteConfig");
					}
					started = true;
					using HandlerErrors handlerErrors = new HandlerErrors();
					target.WriteAtomic((IReadOnlyDictionary<string, object>)new Dictionary<string, object> { [entry.Definition.Section + "." + entry.Definition.Key] = parsed.Value });
					if (handlerErrors.Error != null)
					{
						throw new InvalidOperationException("OwnerHandlerFailed", handlerErrors.Error);
					}
				};
				if ((metadata != null) ? MeadworksConfig.AmendBatch(metadata.ProductId, (IReadOnlyList<ConfigEntryBase>)(object)new ConfigEntryBase[1] { entry }, action, entry.Definition.Key).Applied : ThingExternalConfig.Apply(entry, action, Current))
				{
					string text2 = native.RawSerialized();
					ConfigFile obj2 = new ConfigFile(file.Path, false);
					ConfigEntryBase val = (ConfigEntryBase)typeof(ConfigFile).GetMethods().Single((MethodInfo m) => m.Name == "Bind" && m.IsGenericMethodDefinition && m.GetParameters().Length == 3 && m.GetParameters()[0].ParameterType == typeof(ConfigDefinition)).MakeGenericMethod(entry.SettingType)
						.Invoke(obj2, new object[3] { entry.Definition, entry.DefaultValue, entry.Description });
					text = ((text2 == parsed.Serialized && TomlTypeConverter.ConvertToString(val.BoxedValue, entry.SettingType) == text2) ? "config_saved" : "config_unknown");
					if (text == "config_saved")
					{
						foreach (string item in Backups.ToDelete(file.Path, Directory.EnumerateFiles(Path.GetDirectoryName(file.Path), Path.GetFileName(file.Path) + ".*.bak"), Plugin.Settings.Get<int>(Remote.BackupCount)))
						{
							try
							{
								File.Delete(item);
							}
							catch
							{
							}
						}
					}
				}
				else
				{
					text = (started ? "config_unknown" : "config_failed");
				}
			}
			catch (InvalidOperationException ex) when (ex.Message == "StaleRemoteConfig" && !started)
			{
				text = "config_stale";
			}
			catch
			{
				text = (started ? "config_unknown" : "config_failed");
			}
			ThingServer.Audit(admin, (ThingAction)35, default(Actor), (AuditOutcome)((!(text == "config_saved")) ? 2 : 0), file.Relative + " " + ((object)entry.Definition)?.ToString() + ": " + text);
			if (text == "config_saved")
			{
				try
				{
					return ConfigWire.WriteEcho(Version(file, entry), native.RawSerialized());
				}
				catch
				{
					return Reply("config_unknown");
				}
			}
			return Reply(text);
			bool Current()
			{
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				if (metadata == null)
				{
					ThingExternalConfig.BeginRequest();
				}
				if (!Authorized(admin, local) || generation != Plugin.Session.Generation || !Allowed(file.Path) || !Editable(native, out ConfigEntryMetadata _))
				{
					return false;
				}
				FileItem fileItem = Discover().SingleOrDefault((FileItem f) => f.Id == file.Id);
				if (fileItem != null && fileItem.File == file.File && fileItem.File[entry.Definition] == entry)
				{
					return Version(file, entry) == request.Version;
				}
				return false;
			}
		}

		private static IReadOnlyList<string> Reply(string key)
		{
			return new string[1] { ThingText.Text(key) };
		}

		internal static void Reset()
		{
			backedUp.Clear();
			generation = -1L;
			ThingExternalConfig.Reset();
		}
	}
	internal static class ThingConfigLists
	{
		private static Type Element(NativeEntry native)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			Type settingType = native.Entry.SettingType;
			if ((int)native.Shape.Kind != 12 || !native.Shape.HasConverter || native.Attributes.HasCodecDelegates)
			{
				throw new NotSupportedException();
			}
			Type type = ((settingType.IsArray && settingType.GetArrayRank() == 1) ? settingType.GetElementType() : ((!settingType.IsAbstract && typeof(IList).IsAssignableFrom(settingType) && settingType.GetConstructor(Type.EmptyTypes) != null) ? settingType.GetGenericArguments().FirstOrDefault() : null));
			if (type == null || !TomlTypeConverter.CanConvert(type))
			{
				throw new NotSupportedException();
			}
			return type;
		}

		private static SettingShape Shape(Type element)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Invalid comparison between Unknown and I4
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Invalid comparison between Unknown and I4
			string text = element.FullName ?? element.Name;
			SettingKind val = SettingShape.KindOf(text);
			SettingShape val2 = ((element.IsEnum && (int)val != 5) ? SettingShape.Enum(text, (IEnumerable<string>)Enum.GetNames(element), element.IsDefined(typeof(FlagsAttribute), inherit: false)) : SettingShape.Of(val, text, TomlTypeConverter.CanConvert(element)));
			if ((int)val != 5 && (int)val != 6)
			{
				return val2;
			}
			return val2.WithKeyNames((IEnumerable<string>)Enum.GetNames(typeof(KeyCode)));
		}

		internal static IReadOnlyList<string> Read(string file, string entry, string version, NativeEntry native, bool editable)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Expected O, but got Unknown
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			Type element = Element(native);
			string[] values = (from value in (native.Entry.BoxedValue as IEnumerable)?.Cast<object>().Take(129)
				select TomlTypeConverter.ConvertToString(value, element)).ToArray() ?? Array.Empty<string>();
			return new ConfigListDocument
			{
				Element = new ConfigEntryDocument
				{
					File = file,
					Entry = entry,
					Version = version,
					Shape = Shape(element),
					Editable = editable,
					Section = native.Entry.Definition.Section,
					Key = native.Entry.Definition.Key,
					Attributes = native.Attributes
				},
				Values = values
			}.Encode();
		}

		internal static string Serialize(NativeEntry native, IReadOnlyList<string> values)
		{
			Type element = Element(native);
			SettingShape shape = Shape(element);
			ConfigListDocument.PackValues(values);
			object[] array = values.Select(delegate(string text2)
			{
				//IL_0023: Unknown result type (might be due to invalid IL or missing references)
				//IL_0029: Invalid comparison between Unknown and I4
				ParseResult val = ValueCodec.Parse(shape, text2);
				if (!val.Ok && !val.NeedsModConverter && ((int)shape.Kind != 1 || !shape.IsFlags || !(text2 == "0")))
				{
					throw new FormatException();
				}
				return TomlTypeConverter.ConvertToValue(text2, element);
			}).ToArray();
			object obj;
			if (native.Entry.SettingType.IsArray)
			{
				Array array2 = Array.CreateInstance(element, array.Length);
				for (int num = 0; num < array.Length; num++)
				{
					array2.SetValue(array[num], num);
				}
				obj = array2;
			}
			else
			{
				IList list = (IList)Activator.CreateInstance(native.Entry.SettingType);
				object[] array3 = array;
				foreach (object value in array3)
				{
					list.Add(value);
				}
				obj = list;
			}
			string text = TomlTypeConverter.ConvertToString(obj, native.Entry.SettingType);
			if (YamlTransfer.Bytes(text).Length > 4096)
			{
				throw new FormatException();
			}
			return text;
		}
	}
	internal static class ThingExternalConfig
	{
		internal sealed class Facts
		{
			internal bool Allowed;

			internal bool Hidden;

			internal string Kind = "unknown";

			internal object? System;

			internal object? Data;

			internal MethodInfo? Cache;

			internal MethodInfo? Send;

			internal string Stamp => Kind + ":" + Allowed + ":" + Hidden + ":" + Identity(System) + ":" + Identity(Data);
		}

		private sealed class Owner
		{
			internal object System;

			internal object Data;

			internal bool Conditional;
		}

		private const BindingFlags Members = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly Dictionary<ConfigEntryBase, Owner> owners = new Dictionary<ConfigEntryBase, Owner>();

		private static readonly HashSet<ConfigEntryBase> ambiguous = new HashSet<ConfigEntryBase>();

		private static bool scanFailed = true;

		private static bool Authority
		{
			get
			{
				if (Plugin.IsReady && Plugin.Session.AuthorityReady && ThingServer.CurrentAuthority && (Object)(object)ZNet.instance != (Object)null)
				{
					return ZNet.instance.IsServer();
				}
				return false;
			}
		}

		private static string Identity(object? value)
		{
			if (value != null)
			{
				return RuntimeHelpers.GetHashCode(value).ToString(CultureInfo.InvariantCulture);
			}
			return "-";
		}

		internal static object? Read(object target, string name)
		{
			Type obj = (target as Type) ?? target.GetType();
			object obj2 = ((target is Type) ? null : target);
			Type type = obj;
			while (type != null)
			{
				PropertyInfo property = type.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null)
				{
					return property.GetValue(obj2);
				}
				FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					return field.GetValue(obj2);
				}
				type = type.BaseType;
			}
			return null;
		}

		private static object[] Bounded(IEnumerable sequence, int max)
		{
			object[] array = sequence.Cast<object>().Take(max + 1).ToArray();
			if (array.Length > max)
			{
				throw new InvalidOperationException("ConfigRegistryBound");
			}
			return array;
		}

		private static bool CurrentOwner(Owner owner, ConfigEntryBase entry)
		{
			if (Read(owner.Data, "BaseConfig") == entry && Read(owner.System.GetType(), "configSyncs") is IEnumerable sequence && Bounded(sequence, 4096).Any((object system) => system == owner.System) && Read(owner.System, "allConfigs") is IEnumerable sequence2)
			{
				return Bounded(sequence2, 10000).Any((object data) => data == owner.Data);
			}
			return false;
		}

		private static bool Writable(Owner owner, ConfigEntryBase entry)
		{
			if (CurrentOwner(owner, entry))
			{
				object obj = Read(owner.System, "IsSourceOfTruth");
				if (obj is bool && (bool)obj)
				{
					string name = (owner.Conditional ? "IsWritableConfig" : "isWritableConfig");
					Type type = owner.System.GetType();
					while (type != null)
					{
						MethodInfo methodInfo = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).SingleOrDefault((MethodInfo m) => m.Name == name && m.ReturnType == typeof(bool) && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsInstanceOfType(owner.Data));
						if (methodInfo != null)
						{
							obj = methodInfo.Invoke(methodInfo.IsStatic ? null : owner.System, new object[1] { owner.Data });
							if (obj is bool)
							{
								return (bool)obj;
							}
							return false;
						}
						type = type.BaseType;
					}
					return false;
				}
			}
			return false;
		}

		internal static void BeginRequest()
		{
			owners.Clear();
			ambiguous.Clear();
			scanFailed = false;
			if (!Authority)
			{
				scanFailed = true;
				return;
			}
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				if (assemblies.Length > 4096)
				{
					throw new InvalidOperationException("ConfigAssemblyBound");
				}
				Assembly[] array = assemblies;
				foreach (Assembly assembly in array)
				{
					string[] array2 = new string[2] { "ServerSync.ConfigSync", "ConditionalConfigSync.ConditionalConfigSync" };
					foreach (string text in array2)
					{
						Type type = assembly.GetType(text, throwOnError: false);
						if (type == null)
						{
							continue;
						}
						if (!(Read(type, "configSyncs") is IEnumerable sequence))
						{
							scanFailed = true;
							continue;
						}
						object[] array3 = Bounded(sequence, 4096);
						foreach (object obj in array3)
						{
							if (obj == null)
							{
								continue;
							}
							if (!(Read(obj, "allConfigs") is IEnumerable sequence2))
							{
								scanFailed = true;
								continue;
							}
							object[] array4 = Bounded(sequence2, 10000);
							foreach (object obj2 in array4)
							{
								if (obj2 == null)
								{
									continue;
								}
								object? obj3 = Read(obj2, "BaseConfig");
								ConfigEntryBase val = (ConfigEntryBase)((obj3 is ConfigEntryBase) ? obj3 : null);
								if (val != null && !ambiguous.Contains(val))
								{
									if (owners.TryGetValue(val, out Owner value) && (value.System != obj || value.Data != obj2))
									{
										owners.Remove(val);
										ambiguous.Add(val);
										continue;
									}
									owners[val] = new Owner
									{
										System = obj,
										Data = obj2,
										Conditional = text.StartsWith("Conditional", StringComparison.Ordinal)
									};
								}
							}
						}
					}
				}
			}
			catch
			{
				scanFailed = true;
			}
		}

		internal static void Reset()
		{
			owners.Clear();
			ambiguous.Clear();
			scanFailed = true;
		}

		internal static Facts Inspect(ConfigEntryBase entry)
		{
			Facts facts = new Facts();
			if (!Authority)
			{
				return new Facts
				{
					Hidden = true
				};
			}
			ConfigEntryMetadata val = default(ConfigEntryMetadata);
			if (MeadworksConfig.TryGetMetadata(entry, ref val))
			{
				return facts;
			}
			try
			{
				NativeTags nativeTags = NativeTags.Read(entry.Description.Tags);
				if (nativeTags.Errors.Count != 0)
				{
					return new Facts
					{
						Hidden = true
					};
				}
				bool flag = nativeTags.Attributes.ReadOnly == true || nativeTags.Attributes.CharterReadOnly || nativeTags.Attributes.GenericReadOnly;
				if (scanFailed)
				{
					return new Facts
					{
						Hidden = true
					};
				}
				owners.TryGetValue(entry, out Owner value);
				if (ambiguous.Contains(entry))
				{
					return new Facts
					{
						Hidden = true
					};
				}
				if (value != null)
				{
					object obj = entry.Description.Tags.FirstOrDefault((object tag) => tag != null && tag.GetType().Assembly == typeof(SynchronizationManager).Assembly && tag.GetType().Name == "ConfigurationManagerAttributes");
					if (obj != null)
					{
						object obj2 = Read(obj, "IsAdminOnly");
						if (obj2 is bool && (bool)obj2)
						{
							MethodInfo method = typeof(SynchronizationManager).GetMethod("ShouldManageConfig", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(ConfigFile) }, null);
							if (SynchronizationManager.Instance != null)
							{
								obj2 = method?.Invoke(SynchronizationManager.Instance, new object[1] { entry.ConfigFile });
								if (obj2 is bool && !(bool)obj2)
								{
									goto IL_01b1;
								}
							}
							return new Facts
							{
								Hidden = true
							};
						}
					}
					goto IL_01b1;
				}
				object obj3 = entry.Description.Tags.FirstOrDefault((object tag) => tag != null && tag.GetType().Assembly == typeof(SynchronizationManager).Assembly && tag.GetType().Name == "ConfigurationManagerAttributes");
				if (nativeTags.Attributes.IsAdminOnly == true)
				{
					if (obj3 != null)
					{
						object obj2 = Read(obj3, "IsAdminOnly");
						if (obj2 is bool && (bool)obj2)
						{
							SynchronizationManager instance = SynchronizationManager.Instance;
							MethodInfo method2 = typeof(SynchronizationManager).GetMethod("ShouldManageConfig", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(ConfigFile) }, null);
							if (instance == null || !(method2?.Invoke(instance, new object[1] { entry.ConfigFile }) is bool flag2))
							{
								return facts;
							}
							facts.Kind = (flag2 ? "Jotunn" : "NativeBound");
							facts.System = instance;
							facts.Data = entry;
							if (flag2)
							{
								facts.Cache = typeof(SynchronizationManager).GetMethod("CacheConfigurationValues", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
								facts.Send = typeof(SynchronizationManager).GetMethod("SynchronizeChangedConfig", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
								facts.Allowed = !flag && facts.Cache != null && facts.Send != null;
							}
							else
							{
								facts.Allowed = !flag;
							}
							return Authority ? facts : new Facts();
						}
					}
					return facts;
				}
				bool num = entry.Description.Tags.Any((object tag) => tag?.GetType().Name.StartsWith("SyncedConfigEntry", StringComparison.Ordinal) ?? false);
				bool flag3 = Path.GetFileName(entry.ConfigFile.ConfigFilePath).StartsWith("com.meadworks.", StringComparison.OrdinalIgnoreCase);
				if (num || flag3)
				{
					return facts;
				}
				facts.Kind = "NativeBound";
				facts.System = entry.ConfigFile;
				facts.Data = entry;
				facts.Allowed = !flag && Authority;
				return facts;
				IL_0268:
				int allowed = 0;
				goto IL_0269;
				IL_01b1:
				facts.Kind = (value.Conditional ? "ConditionalConfigSync" : "ServerSync");
				facts.System = value.System;
				facts.Data = value.Data;
				int hidden;
				if (value.Conditional)
				{
					object obj2 = Read(value.Data, "IsHidden");
					hidden = ((!(obj2 is bool) || (bool)obj2) ? 1 : 0);
				}
				else
				{
					hidden = 0;
				}
				facts.Hidden = (byte)hidden != 0;
				if (flag || facts.Hidden)
				{
					goto IL_0268;
				}
				if (value.Conditional)
				{
					object obj2 = Read(value.Data, "IsPolicyStateInitialized");
					if (!(obj2 is bool) || !(bool)obj2)
					{
						goto IL_0268;
					}
				}
				allowed = (Writable(value, entry) ? 1 : 0);
				goto IL_0269;
				IL_0269:
				facts.Allowed = (byte)allowed != 0;
				return Authority ? facts : new Facts();
			}
			catch
			{
				return new Facts
				{
					Hidden = true
				};
			}
		}

		private static bool Same(Facts first, Facts current)
		{
			if (first.Kind == current.Kind && current.Allowed && !current.Hidden && first.System == current.System)
			{
				return first.Data == current.Data;
			}
			return false;
		}

		internal static bool Apply(ConfigEntryBase entry, Action apply, Func<bool> current)
		{
			BeginRequest();
			Facts facts = Inspect(entry);
			if (!facts.Allowed || facts.Hidden || !current())
			{
				return false;
			}
			if (facts.Kind == "Jotunn")
			{
				facts.Cache.Invoke(facts.System, null);
			}
			BeginRequest();
			if (!Authority || !Same(facts, Inspect(entry)) || !current())
			{
				return false;
			}
			apply();
			BeginRequest();
			if (!Authority || !Same(facts, Inspect(entry)))
			{
				throw new InvalidOperationException("ConfigOwnerChangedAfterWrite");
			}
			if (facts.Kind == "Jotunn")
			{
				facts.Send.Invoke(facts.System, null);
			}
			return true;
		}
	}
	internal static class ThingYamlFiles
	{
		private sealed class Document
		{
			internal string Token = "";

			internal string Version = "";

			internal string DiskHash = "";

			internal string Connection = "";

			internal Actor Admin;

			internal bool Local;

			internal long Generation;

			internal ZNet Net;

			internal ZNetPeer? Peer;

			internal ZRpc? Rpc;

			internal object? Socket;

			internal DataFileRegistration Owner;

			internal byte[] Original = Array.Empty<byte>();

			internal YamlUpload? Upload;

			internal double Expires;
		}

		private const int Budget = 4194304;

		private static readonly Stopwatch clock = Stopwatch.StartNew();

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

		private static readonly HashSet<string> backups = new HashSet<string>(StringComparer.Ordinal);

		private static long generation = -1L;

		private static int Reserved => documents.Values.Sum(delegate(Document d)
		{
			int num = d.Original.Length;
			YamlUpload? upload = d.Upload;
			return num + ((upload != null) ? upload.Length : 0);
		});

		internal static DataFileRegistration? Registered(string path)
		{
			string full = Path.GetFullPath(path);
			StringComparison comparison = ((Path.DirectorySeparatorChar == '\\') ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
			if (!full.EndsWith(".yml", comparison) && !full.EndsWith(".yaml", comparison))
			{
				return null;
			}
			return ((IEnumerable<DataFileRegistration>)MeadworksDataFiles.Registered).SingleOrDefault((Func<DataFileRegistration, bool>)((DataFileRegistration f) => string.Equals(f.Path, full, comparison)));
		}

		private static ZNetPeer? Peer(Actor admin, string connection)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if (!long.TryParse(connection, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var uid))
			{
				return null;
			}
			ZNet instance = ZNet.instance;
			if (instance == null)
			{
				return null;
			}
			return ((IEnumerable<