Decompiled source of Beacon v0.1.14

plugins/Beacon/Beacon.Core.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ExtraPotions.Beacon.Secrets;
using ExtraPotions.Beacon.Servers;
using ExtraPotions.Beacon.Text;
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+4312080b663b50769f0cf52f9bd002c67886c90a")]
[assembly: AssemblyProduct("Beacon.Core")]
[assembly: AssemblyTitle("Beacon.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.Beacon
{
	public static class BeaconCore
	{
		public const string PluginGuid = "com.meadworks.beacon";

		public const string ProductName = "Beacon";

		public const string ProductVersion = "0.1.14";

		public const string Family = "Runestone";

		public const string TokenNamespace = "beacon";

		public const string TokenPrefix = "meadworks_beacon_";

		public const string DataFolder = "Meadworks/beacon";

		public const string ServerListFile = "servers.json";

		public const string WindowsSecretFile = "secrets.dat";

		public const string SupportedGameVersion = "1.0.17";
	}
}
namespace ExtraPotions.Beacon.Text
{
	public static class MiniJson
	{
		private const int MaxDepth = 32;

		public static object? Parse(string text)
		{
			if (text == null)
			{
				throw new ArgumentNullException("text");
			}
			int i = 0;
			object? result = Value(text, ref i, 0);
			Skip(text, ref i);
			if (i != text.Length)
			{
				throw new FormatException("Trailing characters after JSON value.");
			}
			return result;
		}

		public static string Quote(string value)
		{
			StringBuilder stringBuilder = new StringBuilder(value.Length + 2).Append('"');
			foreach (char c in value)
			{
				switch (c)
				{
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
					int num = c;
					stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.Append('"').ToString();
		}

		private static void Skip(string s, ref int i)
		{
			while (i < s.Length && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r'))
			{
				i++;
			}
		}

		private static object? Value(string s, ref int i, int depth)
		{
			if (depth > 32)
			{
				throw new FormatException("JSON nested too deeply.");
			}
			Skip(s, ref i);
			if (i >= s.Length)
			{
				throw new FormatException("Unexpected end of JSON.");
			}
			switch (s[i])
			{
			case '{':
				return Obj(s, ref i, depth);
			case '[':
				return Arr(s, ref i, depth);
			case '"':
				return Str(s, ref i);
			default:
				if (Word(s, ref i, "true"))
				{
					return true;
				}
				if (Word(s, ref i, "false"))
				{
					return false;
				}
				if (Word(s, ref i, "null"))
				{
					return null;
				}
				return Num(s, ref i);
			}
		}

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

		private static Dictionary<string, object?> Obj(string s, ref int i, int depth)
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			i++;
			Skip(s, ref i);
			if (i < s.Length && s[i] == '}')
			{
				i++;
				return dictionary;
			}
			while (true)
			{
				Skip(s, ref i);
				if (i >= s.Length || s[i] != '"')
				{
					throw new FormatException("Expected a property name.");
				}
				string key = Str(s, ref i);
				Skip(s, ref i);
				if (i >= s.Length || s[i] != ':')
				{
					throw new FormatException("Expected ':'.");
				}
				i++;
				dictionary[key] = Value(s, ref i, depth + 1);
				Skip(s, ref i);
				if (i >= s.Length || s[i] != ',')
				{
					break;
				}
				i++;
			}
			if (i < s.Length && s[i] == '}')
			{
				i++;
				return dictionary;
			}
			throw new FormatException("Expected ',' or '}'.");
		}

		private static List<object?> Arr(string s, ref int i, int depth)
		{
			List<object> list = new List<object>();
			i++;
			Skip(s, ref i);
			if (i < s.Length && s[i] == ']')
			{
				i++;
				return list;
			}
			while (true)
			{
				list.Add(Value(s, ref i, depth + 1));
				Skip(s, ref i);
				if (i >= s.Length || s[i] != ',')
				{
					break;
				}
				i++;
			}
			if (i < s.Length && s[i] == ']')
			{
				i++;
				return list;
			}
			throw new FormatException("Expected ',' or ']'.");
		}

		private static string Str(string s, ref int i)
		{
			StringBuilder stringBuilder = new StringBuilder();
			i++;
			while (i < s.Length)
			{
				char c = s[i++];
				switch (c)
				{
				case '"':
					return stringBuilder.ToString();
				default:
					stringBuilder.Append(c);
					continue;
				case '\\':
					break;
				}
				if (i >= s.Length)
				{
					break;
				}
				switch (s[i++])
				{
				case '"':
					stringBuilder.Append('"');
					break;
				case '\\':
					stringBuilder.Append('\\');
					break;
				case '/':
					stringBuilder.Append('/');
					break;
				case 'b':
					stringBuilder.Append('\b');
					break;
				case 'f':
					stringBuilder.Append('\f');
					break;
				case 'n':
					stringBuilder.Append('\n');
					break;
				case 'r':
					stringBuilder.Append('\r');
					break;
				case 't':
					stringBuilder.Append('\t');
					break;
				case 'u':
				{
					if (i + 4 > s.Length || !int.TryParse(s.Substring(i, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
					{
						throw new FormatException("Bad \\u escape.");
					}
					stringBuilder.Append((char)result);
					i += 4;
					break;
				}
				default:
					throw new FormatException("Bad escape.");
				}
			}
			throw new FormatException("Unterminated string.");
		}

		private static double Num(string s, ref int i)
		{
			int num = i;
			while (i < s.Length && "+-0123456789.eE".IndexOf(s[i]) >= 0)
			{
				i++;
			}
			if (i == num || !double.TryParse(s.Substring(num, i - num), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				throw new FormatException("Unexpected character in JSON.");
			}
			return result;
		}
	}
}
namespace ExtraPotions.Beacon.Servers
{
	public static class FavoriteMove
	{
		public static bool Resolve(IReadOnlyList<int> visibleToFull, int fullCount, int selected, int offset, out int from, out int to)
		{
			from = -1;
			to = -1;
			if (visibleToFull == null || (offset != 1 && offset != -1))
			{
				return false;
			}
			int num = selected + offset;
			if (selected < 0 || selected >= visibleToFull.Count || num < 0 || num >= visibleToFull.Count)
			{
				return false;
			}
			from = visibleToFull[selected];
			to = visibleToFull[num];
			if (from < 0 || from >= fullCount || to < 0 || to >= fullCount || from == to)
			{
				from = -1;
				to = -1;
				return false;
			}
			return true;
		}
	}
	public enum AddressKind
	{
		Host,
		Steam,
		PlayFab
	}
	public sealed class ServerAddress : IEquatable<ServerAddress>
	{
		public const int DefaultPort = 2456;

		public AddressKind Kind { get; }

		public string Host { get; }

		public int Port { get; }

		private ServerAddress(AddressKind kind, string host, int port)
		{
			Kind = kind;
			Host = host;
			Port = port;
		}

		public static ServerAddress Parse(string text)
		{
			if (!TryParse(text, out ServerAddress address))
			{
				throw new FormatException("Not a server address.");
			}
			return address;
		}

		public static bool TryParse(string? text, out ServerAddress? address)
		{
			address = null;
			string text2 = (text ?? "").Trim();
			if (text2.Length == 0)
			{
				return false;
			}
			string text3 = text2.ToLowerInvariant();
			if (text3.StartsWith("steam:", StringComparison.Ordinal))
			{
				string text4 = text2.Substring(6);
				if (text4.Length == 0 || text4.Length > 20 || !AllDigits(text4))
				{
					return false;
				}
				address = new ServerAddress(AddressKind.Steam, text4, 0);
				return true;
			}
			if (text3.StartsWith("playfab:", StringComparison.Ordinal))
			{
				string text5 = text2.Substring(8);
				if (text5.Length == 0 || text5.Length > 64 || !AllAlphaNumeric(text5))
				{
					return false;
				}
				address = new ServerAddress(AddressKind.PlayFab, text5.ToUpperInvariant(), 0);
				return true;
			}
			int port = 2456;
			string text6;
			if (text3[0] == '[')
			{
				int num = text3.IndexOf(']');
				if (num < 2)
				{
					return false;
				}
				text6 = text3.Substring(1, num - 1);
				string text7 = text3.Substring(num + 1);
				if (text7.Length > 0 && (text7[0] != ':' || !TryPort(text7.Substring(1), out port)))
				{
					return false;
				}
				if (text6.IndexOf(':') < 0)
				{
					return false;
				}
			}
			else
			{
				int num2 = Count(text3, ':');
				if (num2 > 1)
				{
					text6 = text3;
				}
				else if (num2 == 1)
				{
					int num3 = text3.IndexOf(':');
					text6 = text3.Substring(0, num3);
					if (!TryPort(text3.Substring(num3 + 1), out port))
					{
						return false;
					}
				}
				else
				{
					text6 = text3;
				}
			}
			if (!ValidHost(text6))
			{
				return false;
			}
			address = new ServerAddress(AddressKind.Host, text6, port);
			return true;
		}

		public override string ToString()
		{
			return Kind switch
			{
				AddressKind.Steam => "steam:" + Host, 
				AddressKind.PlayFab => "playfab:" + Host, 
				_ => ((Host.IndexOf(':') >= 0) ? ("[" + Host + "]") : Host) + ":" + Port.ToString(CultureInfo.InvariantCulture), 
			};
		}

		public bool Equals(ServerAddress? other)
		{
			if ((object)other != null)
			{
				return string.Equals(ToString(), other.ToString(), StringComparison.Ordinal);
			}
			return false;
		}

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

		public override int GetHashCode()
		{
			return StringComparer.Ordinal.GetHashCode(ToString());
		}

		public static bool operator ==(ServerAddress? a, ServerAddress? b)
		{
			return a?.Equals(b) ?? ((object)b == null);
		}

		public static bool operator !=(ServerAddress? a, ServerAddress? b)
		{
			return !(a == b);
		}

		private static bool TryPort(string text, out int port)
		{
			if (int.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out port) && port >= 1)
			{
				return port <= 65535;
			}
			return false;
		}

		private static int Count(string s, char c)
		{
			int num = 0;
			for (int i = 0; i < s.Length; i++)
			{
				if (s[i] == c)
				{
					num++;
				}
			}
			return num;
		}

		private static bool AllDigits(string s)
		{
			foreach (char c in s)
			{
				if (c < '0' || c > '9')
				{
					return false;
				}
			}
			return true;
		}

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

		private static bool ValidHost(string host)
		{
			if (host.Length == 0 || host.Length > 253)
			{
				return false;
			}
			bool flag = host.IndexOf(':') >= 0;
			foreach (char c in host)
			{
				bool num;
				if (!flag)
				{
					if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '.' || c == '-')
					{
						continue;
					}
					num = c == '_';
				}
				else
				{
					if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || c == ':')
					{
						continue;
					}
					num = c == '.';
				}
				if (!num)
				{
					return false;
				}
			}
			return true;
		}
	}
	public sealed class ServerEntry
	{
		public string Name { get; }

		public ServerAddress Address { get; }

		public IReadOnlyList<ServerAddress> Aliases { get; }

		public string SecretRef { get; }

		public bool IsDefault { get; internal set; }

		public IEnumerable<ServerAddress> AllAddresses => new ServerAddress[1] { Address }.Concat(Aliases);

		internal ServerEntry(string name, ServerAddress address, IReadOnlyList<ServerAddress> aliases, string secretRef)
		{
			Name = name;
			Address = address;
			Aliases = aliases;
			SecretRef = secretRef;
		}

		public bool Matches(ServerAddress address)
		{
			if (!(Address == address))
			{
				return Aliases.Any((ServerAddress a) => a == address);
			}
			return true;
		}
	}
	public sealed class ServerRemoval
	{
		public ServerEntry Entry { get; }

		public int Index { get; }

		public bool WasDefault { get; }

		internal ServerRemoval(ServerEntry entry, int index)
		{
			Entry = entry;
			Index = index;
			WasDefault = entry.IsDefault;
		}
	}
	public sealed class ServerList
	{
		private readonly List<ServerEntry> entries = new List<ServerEntry>();

		private readonly Func<string> newReference;

		private int revision;

		public IReadOnlyList<ServerEntry> Entries => entries;

		public int Count => entries.Count;

		public int Revision => revision;

		public ServerEntry? Default => entries.FirstOrDefault((ServerEntry e) => e.IsDefault);

		public ServerList(Func<string>? newReference = null)
		{
			this.newReference = newReference ?? ((Func<string>)(() => "s" + Guid.NewGuid().ToString("N")));
		}

		public ServerEntry? Find(ServerAddress address)
		{
			if (!(address == null))
			{
				return entries.FirstOrDefault((ServerEntry e) => e.Matches(address));
			}
			return null;
		}

		public ServerEntry? FindByName(string name)
		{
			return entries.FirstOrDefault((ServerEntry e) => string.Equals(e.Name, (name ?? "").Trim(), StringComparison.OrdinalIgnoreCase));
		}

		public ServerEntry Add(string name, ServerAddress address, IEnumerable<ServerAddress>? aliases = null)
		{
			return Insert(name, address, aliases, null);
		}

		internal ServerEntry Insert(string name, ServerAddress address, IEnumerable<ServerAddress>? aliases, string? secretRef)
		{
			string text = (name ?? "").Trim();
			if (text.Length == 0)
			{
				throw new ArgumentException("A server needs a name.", "name");
			}
			if (address == null)
			{
				throw new ArgumentNullException("address");
			}
			if (FindByName(text) != null)
			{
				throw new ArgumentException("A server named '" + text + "' is already saved.", "name");
			}
			List<ServerAddress> list = (aliases ?? Enumerable.Empty<ServerAddress>()).Where((ServerAddress a) => a != null && a != address).Distinct().ToList();
			foreach (ServerAddress item in new ServerAddress[1] { address }.Concat(list))
			{
				if (Find(item) != null)
				{
					throw new ArgumentException("Address " + item?.ToString() + " already belongs to another saved server.", "address");
				}
			}
			string reference = secretRef ?? NewUniqueReference();
			if (entries.Any((ServerEntry e) => e.SecretRef == reference))
			{
				throw new ArgumentException("Duplicate secret reference.", "secretRef");
			}
			ServerEntry serverEntry = new ServerEntry(text, address, list.AsReadOnly(), reference);
			entries.Add(serverEntry);
			revision++;
			return serverEntry;
		}

		public bool Remove(string name)
		{
			ServerEntry serverEntry = FindByName(name);
			if (serverEntry == null || !entries.Remove(serverEntry))
			{
				return false;
			}
			revision++;
			return true;
		}

		public ServerRemoval? RemoveForUndo(string name)
		{
			ServerEntry serverEntry = FindByName(name);
			if (serverEntry == null)
			{
				return null;
			}
			ServerRemoval result = new ServerRemoval(serverEntry, entries.IndexOf(serverEntry));
			entries.Remove(serverEntry);
			revision++;
			return result;
		}

		public ServerEntry Restore(ServerRemoval removal)
		{
			if (removal == null)
			{
				throw new ArgumentNullException("removal");
			}
			ServerEntry entry = removal.Entry;
			ServerEntry serverEntry = Insert(entry.Name, entry.Address, entry.Aliases, entry.SecretRef);
			Move(serverEntry.Name, removal.Index);
			if (removal.WasDefault && Default == null)
			{
				SetDefault(serverEntry.Name);
			}
			return serverEntry;
		}

		public ServerEntry Edit(string name, string nextName, ServerAddress address, IEnumerable<ServerAddress>? aliases = null)
		{
			ServerEntry original = FindByName(name) ?? throw new ArgumentException("Unknown saved server.", "name");
			string trimmed = (nextName ?? "").Trim();
			if (trimmed.Length == 0)
			{
				throw new ArgumentException("A server needs a name.", "nextName");
			}
			if (address == null)
			{
				throw new ArgumentNullException("address");
			}
			List<ServerAddress> list = (aliases ?? Enumerable.Empty<ServerAddress>()).Where((ServerAddress a) => a != null && a != address).Distinct().ToList();
			List<ServerEntry> remaining = entries.Where((ServerEntry e) => e != original).ToList();
			if (remaining.Any((ServerEntry e) => string.Equals(e.Name, trimmed, StringComparison.OrdinalIgnoreCase)))
			{
				throw new ArgumentException("Duplicate server name.", "nextName");
			}
			if (new ServerAddress[1] { address }.Concat(list).Any((ServerAddress a) => remaining.Any((ServerEntry e) => e.Matches(a))))
			{
				throw new ArgumentException("Address already belongs to another server.", "address");
			}
			ServerEntry serverEntry = new ServerEntry(trimmed, address, list.AsReadOnly(), original.SecretRef)
			{
				IsDefault = original.IsDefault
			};
			entries[entries.IndexOf(original)] = serverEntry;
			revision++;
			return serverEntry;
		}

		public void SetDefault(string? name)
		{
			ServerEntry serverEntry = ((name == null) ? null : FindByName(name));
			if (name != null && serverEntry == null)
			{
				throw new ArgumentException("No saved server named '" + name + "'.", "name");
			}
			foreach (ServerEntry entry in entries)
			{
				entry.IsDefault = entry == serverEntry;
			}
			revision++;
		}

		public void Move(string name, int index)
		{
			ServerEntry item = FindByName(name) ?? throw new ArgumentException("No saved server named '" + name + "'.", "name");
			entries.Remove(item);
			entries.Insert(Math.Max(0, Math.Min(entries.Count, index)), item);
			revision++;
		}

		private string NewUniqueReference()
		{
			for (int i = 0; i < 16; i++)
			{
				string r = newReference();
				if (entries.All((ServerEntry e) => e.SecretRef != r))
				{
					return r;
				}
			}
			throw new InvalidOperationException("Could not create a unique secret reference.");
		}
	}
	public static class ServerListCodec
	{
		public const int Schema = 1;

		public static IReadOnlyList<string> FieldNames { get; } = Array.AsReadOnly(new string[7] { "schema", "servers", "name", "address", "aliases", "default", "ref" });

		public static string Write(ServerList list)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("{\n  \"schema\": ").Append(1.ToString(CultureInfo.InvariantCulture)).Append(",\n  \"servers\": [");
			for (int i = 0; i < list.Count; i++)
			{
				ServerEntry serverEntry = list.Entries[i];
				stringBuilder.Append((i == 0) ? "\n" : ",\n");
				stringBuilder.Append("    {\"name\": ").Append(MiniJson.Quote(serverEntry.Name)).Append(", \"address\": ")
					.Append(MiniJson.Quote(serverEntry.Address.ToString()))
					.Append(", \"aliases\": [")
					.Append(string.Join(", ", serverEntry.Aliases.Select((ServerAddress a) => MiniJson.Quote(a.ToString()))))
					.Append(']')
					.Append(", \"default\": ")
					.Append(serverEntry.IsDefault ? "true" : "false")
					.Append(", \"ref\": ")
					.Append(MiniJson.Quote(serverEntry.SecretRef))
					.Append('}');
			}
			stringBuilder.Append((list.Count == 0) ? "]\n}\n" : "\n  ]\n}\n");
			return stringBuilder.ToString();
		}

		public static ServerList Read(string text)
		{
			ServerList serverList = new ServerList();
			if (string.IsNullOrWhiteSpace(text))
			{
				return serverList;
			}
			Dictionary<string, object> obj = (MiniJson.Parse(text) as Dictionary<string, object>) ?? throw new FormatException("servers.json: expected an object.");
			if (!obj.TryGetValue("schema", out var value) || !(value is double num) || num != 1.0)
			{
				throw new FormatException("servers.json: unknown schema.");
			}
			if (!obj.TryGetValue("servers", out var value2) || value2 == null)
			{
				return serverList;
			}
			List<object> obj2 = (value2 as List<object>) ?? throw new FormatException("servers.json: 'servers' must be an array.");
			string text2 = null;
			bool flag = default(bool);
			foreach (object item in obj2)
			{
				if (!(item is Dictionary<string, object> dictionary))
				{
					throw new FormatException("servers.json: a server must be an object.");
				}
				string text3 = Str(dictionary, "name") ?? throw new FormatException("servers.json: a server has no name.");
				ServerAddress address = Address(Str(dictionary, "address"));
				List<ServerAddress> list = new List<ServerAddress>();
				if (dictionary.TryGetValue("aliases", out var value3) && value3 is List<object> list2)
				{
					foreach (object item2 in list2)
					{
						list.Add(Address(item2 as string));
					}
				}
				string text4 = Str(dictionary, "ref");
				if (!SecretReference.IsValid(text4))
				{
					throw new FormatException("servers.json: server '" + text3 + "' has no valid ref.");
				}
				try
				{
					serverList.Insert(text3, address, list, text4);
				}
				catch (ArgumentException ex)
				{
					throw new FormatException("servers.json: " + ex.Message);
				}
				int num2;
				if (dictionary.TryGetValue("default", out var value4))
				{
					if (value4 is bool)
					{
						flag = (bool)value4;
						num2 = 1;
					}
					else
					{
						num2 = 0;
					}
				}
				else
				{
					num2 = 0;
				}
				if (((uint)num2 & (flag ? 1u : 0u)) != 0)
				{
					text2 = text3;
				}
			}
			if (text2 != null)
			{
				serverList.SetDefault(text2);
			}
			return serverList;
		}

		private static string? Str(Dictionary<string, object?> o, string key)
		{
			if (!o.TryGetValue(key, out object value))
			{
				return null;
			}
			return value as string;
		}

		private static ServerAddress Address(string? text)
		{
			if (!ServerAddress.TryParse(text, out ServerAddress address))
			{
				throw new FormatException("servers.json: invalid address.");
			}
			return address;
		}
	}
}
namespace ExtraPotions.Beacon.Secrets
{
	public readonly struct SecretReadResult
	{
		private readonly string? secret;

		public bool Found { get; }

		public string Secret => secret ?? "";

		public SecretReadResult(bool found, string secret)
		{
			Found = found;
			this.secret = (found ? secret : "");
		}
	}
	public interface IAsyncSecretStore : ISecretStore
	{
		Task<bool> RefreshAsync(CancellationToken cancellation = default(CancellationToken));

		Task<bool> StoreAsync(string reference, string secret, CancellationToken cancellation = default(CancellationToken));

		Task<SecretReadResult> ReadAsync(string reference, CancellationToken cancellation = default(CancellationToken));

		Task<bool> ForgetAsync(string reference, CancellationToken cancellation = default(CancellationToken));

		Task<int> ForgetAllAsync(CancellationToken cancellation = default(CancellationToken));
	}
	public sealed class DpapiSecretStore : ISecretStore
	{
		private struct DataBlob
		{
			public int Size;

			public IntPtr Data;

			public static DataBlob From(byte[] bytes)
			{
				DataBlob result = new DataBlob
				{
					Size = bytes.Length,
					Data = Marshal.AllocHGlobal(Math.Max(1, bytes.Length))
				};
				Marshal.Copy(bytes, 0, result.Data, bytes.Length);
				return result;
			}

			public void Free(bool clear)
			{
				if (!(Data == IntPtr.Zero))
				{
					if (clear)
					{
						Zero(Data, Size);
					}
					Marshal.FreeHGlobal(Data);
					Data = IntPtr.Zero;
				}
			}
		}

		private const int UiForbidden = 1;

		private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("com.meadworks.beacon/secrets/1");

		private readonly Func<string?> readFile;

		private readonly Action<string> writeFile;

		private readonly SortedDictionary<string, string> blobs = new SortedDictionary<string, string>(StringComparer.Ordinal);

		public static bool IsSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

		public SecretBackend Backend => SecretBackend.WindowsDataProtection;

		public SecretAvailability Availability
		{
			get
			{
				if (!IsSupported)
				{
					return SecretAvailability.Unavailable;
				}
				return SecretAvailability.Available;
			}
		}

		public SecretFailure LastFailure { get; private set; }

		public DpapiSecretStore(Func<string?> readFile, Action<string> writeFile)
		{
			this.readFile = readFile ?? throw new ArgumentNullException("readFile");
			this.writeFile = writeFile ?? throw new ArgumentNullException("writeFile");
			Load();
		}

		public bool Store(string reference, string secret)
		{
			SecretReference.Require(reference, secret);
			LastFailure = SecretFailure.None;
			if (!IsSupported)
			{
				LastFailure = SecretFailure.Unavailable;
				return false;
			}
			byte[] bytes = Encoding.UTF8.GetBytes(secret);
			try
			{
				byte[] array = Transform(bytes, protect: true);
				if (array == null)
				{
					LastFailure = SecretFailure.ProtectFailed;
					return false;
				}
				blobs[reference] = Convert.ToBase64String(array);
				Save();
				return true;
			}
			finally
			{
				Array.Clear(bytes, 0, bytes.Length);
			}
		}

		public bool TryRead(string reference, out string secret)
		{
			SecretReference.Require(reference);
			secret = "";
			LastFailure = SecretFailure.None;
			if (!blobs.TryGetValue(reference, out string value))
			{
				return false;
			}
			if (!IsSupported)
			{
				LastFailure = SecretFailure.Unavailable;
				return false;
			}
			byte[] array = Transform(Convert.FromBase64String(value), protect: false);
			if (array == null)
			{
				LastFailure = SecretFailure.StoredElsewhere;
				return false;
			}
			try
			{
				secret = Encoding.UTF8.GetString(array);
				return true;
			}
			finally
			{
				Array.Clear(array, 0, array.Length);
			}
		}

		public bool Contains(string reference)
		{
			if (SecretReference.IsValid(reference))
			{
				return blobs.ContainsKey(reference);
			}
			return false;
		}

		public bool Forget(string reference)
		{
			SecretReference.Require(reference);
			if (!blobs.Remove(reference))
			{
				return false;
			}
			Save();
			return true;
		}

		public int ForgetAll()
		{
			int count = blobs.Count;
			blobs.Clear();
			Save();
			return count;
		}

		private void Load()
		{
			string[] array = (readFile() ?? "").Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				int num = text.IndexOf('=');
				if (num > 0)
				{
					string text2 = text.Substring(0, num);
					string text3 = text.Substring(num + 1);
					if (SecretReference.IsValid(text2) && IsBase64(text3))
					{
						blobs[text2] = text3;
					}
				}
			}
		}

		private void Save()
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<string, string> blob in blobs)
			{
				stringBuilder.Append(blob.Key).Append('=').Append(blob.Value)
					.Append('\n');
			}
			writeFile(stringBuilder.ToString());
		}

		private static bool IsBase64(string text)
		{
			if (text.Length == 0)
			{
				return false;
			}
			try
			{
				Convert.FromBase64String(text);
				return true;
			}
			catch (FormatException)
			{
				return false;
			}
		}

		private static byte[]? Transform(byte[] input, bool protect)
		{
			DataBlob dataIn = default(DataBlob);
			DataBlob entropy = default(DataBlob);
			DataBlob dataOut = default(DataBlob);
			try
			{
				dataIn = DataBlob.From(input);
				entropy = DataBlob.From(Entropy);
				if (!(protect ? CryptProtectData(ref dataIn, null, ref entropy, IntPtr.Zero, IntPtr.Zero, 1, ref dataOut) : CryptUnprotectData(ref dataIn, IntPtr.Zero, ref entropy, IntPtr.Zero, IntPtr.Zero, 1, ref dataOut)) || dataOut.Data == IntPtr.Zero)
				{
					return null;
				}
				byte[] array = new byte[dataOut.Size];
				Marshal.Copy(dataOut.Data, array, 0, dataOut.Size);
				return array;
			}
			finally
			{
				dataIn.Free(clear: true);
				entropy.Free(clear: false);
				if (dataOut.Data != IntPtr.Zero)
				{
					Zero(dataOut.Data, dataOut.Size);
					LocalFree(dataOut.Data);
				}
			}
		}

		private static void Zero(IntPtr data, int size)
		{
			for (int i = 0; i < size; i++)
			{
				Marshal.WriteByte(data, i, 0);
			}
		}

		[DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern bool CryptProtectData(ref DataBlob dataIn, string? description, ref DataBlob entropy, IntPtr reserved, IntPtr prompt, int flags, ref DataBlob dataOut);

		[DllImport("crypt32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern bool CryptUnprotectData(ref DataBlob dataIn, IntPtr description, ref DataBlob entropy, IntPtr reserved, IntPtr prompt, int flags, ref DataBlob dataOut);

		[DllImport("kernel32.dll")]
		private static extern IntPtr LocalFree(IntPtr memory);
	}
	internal static class LibSecretNative
	{
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate uint Hash(IntPtr text);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate int Equal(IntPtr a, IntPtr b);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void Free(IntPtr value);

		private struct ListNode
		{
			internal IntPtr Data;

			internal IntPtr Next;

			internal IntPtr Previous;
		}

		private sealed class Call : IDisposable
		{
			internal IntPtr Schema;

			internal IntPtr Attributes;

			internal IntPtr Cancellable;

			private CancellationTokenRegistration registration;

			internal Call(string? reference, CancellationToken token)
			{
				token.ThrowIfCancellationRequested();
				try
				{
					IntPtr intPtr = g_hash_table_new_full(hash, equal, free, null);
					try
					{
						g_hash_table_insert(intPtr, g_strdup("ref"), IntPtr.Zero);
						Schema = secret_schema_newv("com.meadworks.beacon", 0, intPtr);
					}
					finally
					{
						g_hash_table_unref(intPtr);
					}
					Attributes = g_hash_table_new_full(hash, equal, free, free);
					if (reference != null)
					{
						g_hash_table_insert(Attributes, g_strdup("ref"), g_strdup(reference));
					}
					Cancellable = g_cancellable_new();
					if (Schema == IntPtr.Zero || Attributes == IntPtr.Zero || Cancellable == IntPtr.Zero)
					{
						throw new KeyringUnavailableException();
					}
					registration = token.Register(delegate
					{
						g_cancellable_cancel(Cancellable);
					});
				}
				catch
				{
					Dispose();
					throw;
				}
			}

			public void Dispose()
			{
				registration.Dispose();
				if (Cancellable != IntPtr.Zero)
				{
					g_object_unref(Cancellable);
					Cancellable = IntPtr.Zero;
				}
				if (Attributes != IntPtr.Zero)
				{
					g_hash_table_unref(Attributes);
					Attributes = IntPtr.Zero;
				}
				if (Schema != IntPtr.Zero)
				{
					secret_schema_unref(Schema);
					Schema = IntPtr.Zero;
				}
			}
		}

		private const string Secret = "libsecret-1.so.0";

		private const string Glib = "libglib-2.0.so.0";

		private const string Gio = "libgio-2.0.so.0";

		private const string Gobject = "libgobject-2.0.so.0";

		private const int SearchAll = 2;

		private static readonly Hash hash = g_str_hash;

		private static readonly Equal equal = g_str_equal;

		private static readonly Free free = g_free;

		internal static bool Store(string reference, string password, CancellationToken token)
		{
			using Call call = new Call(reference, token);
			IntPtr error;
			int num = secret_password_storev_sync(call.Schema, call.Attributes, "default", "Meadworks Beacon server password", password, call.Cancellable, out error);
			Check(error, token);
			return num != 0;
		}

		internal static SecretReadResult Read(string reference, CancellationToken token)
		{
			using Call call = new Call(reference, token);
			IntPtr error;
			IntPtr intPtr = secret_password_lookupv_sync(call.Schema, call.Attributes, call.Cancellable, out error);
			try
			{
				Check(error, token);
				return (intPtr == IntPtr.Zero) ? default(SecretReadResult) : new SecretReadResult(found: true, Marshal.PtrToStringUTF8(intPtr) ?? "");
			}
			finally
			{
				if (intPtr != IntPtr.Zero)
				{
					secret_password_free(intPtr);
				}
			}
		}

		internal static bool Forget(string reference, CancellationToken token)
		{
			using Call call = new Call(reference, token);
			IntPtr error;
			int num = secret_password_clearv_sync(call.Schema, call.Attributes, call.Cancellable, out error);
			Check(error, token);
			return num != 0;
		}

		internal static HashSet<string> References(CancellationToken token)
		{
			using Call call = new Call(null, token);
			IntPtr error;
			IntPtr intPtr = secret_password_searchv_sync(call.Schema, call.Attributes, 2, call.Cancellable, out error);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			try
			{
				Check(error, token);
				IntPtr intPtr2 = intPtr;
				while (intPtr2 != IntPtr.Zero)
				{
					ListNode listNode = Marshal.PtrToStructure<ListNode>(intPtr2);
					IntPtr intPtr3 = secret_retrievable_get_attributes(listNode.Data);
					try
					{
						string text = Marshal.PtrToStringUTF8(g_hash_table_lookup(intPtr3, "ref"));
						if (SecretReference.IsValid(text))
						{
							hashSet.Add(text);
						}
					}
					finally
					{
						if (intPtr3 != IntPtr.Zero)
						{
							g_hash_table_unref(intPtr3);
						}
					}
					intPtr2 = listNode.Next;
				}
				return hashSet;
			}
			finally
			{
				IntPtr intPtr4 = intPtr;
				while (intPtr4 != IntPtr.Zero)
				{
					ListNode listNode2 = Marshal.PtrToStructure<ListNode>(intPtr4);
					g_object_unref(listNode2.Data);
					intPtr4 = listNode2.Next;
				}
				if (intPtr != IntPtr.Zero)
				{
					g_list_free(intPtr);
				}
			}
		}

		private static void Check(IntPtr error, CancellationToken token)
		{
			if (error != IntPtr.Zero)
			{
				g_error_free(error);
			}
			token.ThrowIfCancellationRequested();
			if (error != IntPtr.Zero)
			{
				throw new KeyringUnavailableException();
			}
		}

		[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr secret_schema_newv([MarshalAs(UnmanagedType.LPUTF8Str)] string name, int flags, IntPtr types);

		[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern void secret_schema_unref(IntPtr schema);

		[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern int secret_password_storev_sync(IntPtr schema, IntPtr attrs, [MarshalAs(UnmanagedType.LPUTF8Str)] string collection, [MarshalAs(UnmanagedType.LPUTF8Str)] string label, [MarshalAs(UnmanagedType.LPUTF8Str)] string password, IntPtr cancel, out IntPtr error);

		[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr secret_password_lookupv_sync(IntPtr schema, IntPtr attrs, IntPtr cancel, out IntPtr error);

		[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern int secret_password_clearv_sync(IntPtr schema, IntPtr attrs, IntPtr cancel, out IntPtr error);

		[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr secret_password_searchv_sync(IntPtr schema, IntPtr attrs, int flags, IntPtr cancel, out IntPtr error);

		[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr secret_retrievable_get_attributes(IntPtr item);

		[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern void secret_password_free(IntPtr secret);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr g_hash_table_new_full(Hash hash, Equal equal, Free keyFree, Free? valueFree);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern int g_hash_table_insert(IntPtr table, IntPtr key, IntPtr value);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr g_hash_table_lookup(IntPtr table, [MarshalAs(UnmanagedType.LPUTF8Str)] string key);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern void g_hash_table_unref(IntPtr table);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern uint g_str_hash(IntPtr value);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern int g_str_equal(IntPtr a, IntPtr b);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr g_strdup([MarshalAs(UnmanagedType.LPUTF8Str)] string value);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern void g_free(IntPtr value);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern void g_error_free(IntPtr error);

		[DllImport("libglib-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern void g_list_free(IntPtr list);

		[DllImport("libgio-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr g_cancellable_new();

		[DllImport("libgio-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern void g_cancellable_cancel(IntPtr value);

		[DllImport("libgobject-2.0.so.0", CallingConvention = CallingConvention.Cdecl)]
		private static extern void g_object_unref(IntPtr value);
	}
	public sealed class LinuxKeyringStore : IAsyncSecretStore, ISecretStore, IDisposable
	{
		private readonly ISecretStore session = MemorySecretStore.SessionOnly();

		private readonly ISecretStore disabled = new DisabledSecretStore();

		private LinuxFallback selectedFallback;

		private long fallbackRevision;

		private readonly SemaphoreSlim gate = new SemaphoreSlim(1);

		private readonly CancellationTokenSource lifetime = new CancellationTokenSource();

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

		private volatile bool unavailable;

		private volatile bool disposed;

		private ISecretStore FallbackStore
		{
			get
			{
				if (selectedFallback != LinuxFallback.Disabled)
				{
					return session;
				}
				return disabled;
			}
		}

		public SecretBackend Backend
		{
			get
			{
				lock (references)
				{
					return (!unavailable) ? SecretBackend.LinuxKeyring : FallbackStore.Backend;
				}
			}
		}

		public SecretAvailability Availability
		{
			get
			{
				lock (references)
				{
					return unavailable ? FallbackStore.Availability : SecretAvailability.Available;
				}
			}
		}

		public long FallbackRevision
		{
			get
			{
				lock (references)
				{
					return fallbackRevision;
				}
			}
		}

		public SecretFailure LastFailure { get; private set; }

		public LinuxKeyringStore(LinuxFallback selectedFallback)
		{
			SetFallback(selectedFallback);
		}

		public void SetFallback(LinuxFallback policy)
		{
			if (policy != LinuxFallback.SessionOnly && policy != LinuxFallback.Disabled)
			{
				throw new ArgumentOutOfRangeException("policy");
			}
			lock (references)
			{
				if (!disposed && selectedFallback != policy)
				{
					selectedFallback = policy;
					fallbackRevision++;
				}
			}
		}

		public bool IsReadCurrent(long revision)
		{
			lock (references)
			{
				return !disposed && (!unavailable || (selectedFallback == LinuxFallback.SessionOnly && fallbackRevision == revision));
			}
		}

		public bool Contains(string reference)
		{
			if (!SecretReference.IsValid(reference) || disposed)
			{
				return false;
			}
			lock (references)
			{
				return unavailable ? FallbackStore.Contains(reference) : references.Contains(reference);
			}
		}

		public bool Store(string reference, string secret)
		{
			SecretReference.Require(reference, secret);
			return false;
		}

		public bool TryRead(string reference, out string secret)
		{
			SecretReference.Require(reference);
			secret = "";
			return false;
		}

		public bool Forget(string reference)
		{
			SecretReference.Require(reference);
			return false;
		}

		public int ForgetAll()
		{
			return 0;
		}

		public Task<bool> RefreshAsync(CancellationToken cancellation = default(CancellationToken))
		{
			return Run(delegate(CancellationToken token)
			{
				HashSet<string> other = LibSecretNative.References(token);
				lock (references)
				{
					if (disposed || token.IsCancellationRequested)
					{
						return false;
					}
					references.Clear();
					references.UnionWith(other);
				}
				return true;
			}, () => false, canceled: false, cancellation);
		}

		public Task<bool> StoreAsync(string reference, string secret, CancellationToken cancellation = default(CancellationToken))
		{
			SecretReference.Require(reference, secret);
			if (secret.IndexOf('\0') >= 0)
			{
				throw new ArgumentException("A password cannot contain a NUL character.", "secret");
			}
			return Run(delegate(CancellationToken token)
			{
				bool flag = LibSecretNative.Store(reference, secret, token);
				if (flag)
				{
					lock (references)
					{
						if (disposed || token.IsCancellationRequested)
						{
							return false;
						}
						references.Add(reference);
					}
				}
				return flag;
			}, delegate
			{
				lock (references)
				{
					return FallbackStore.Store(reference, secret);
				}
			}, canceled: false, cancellation);
		}

		public Task<SecretReadResult> ReadAsync(string reference, CancellationToken cancellation = default(CancellationToken))
		{
			SecretReference.Require(reference);
			return Run((CancellationToken token) => LibSecretNative.Read(reference, token), delegate
			{
				lock (references)
				{
					string secret;
					return FallbackStore.TryRead(reference, out secret) ? new SecretReadResult(found: true, secret) : default(SecretReadResult);
				}
			}, default(SecretReadResult), cancellation);
		}

		public Task<bool> ForgetAsync(string reference, CancellationToken cancellation = default(CancellationToken))
		{
			SecretReference.Require(reference);
			return Run(delegate(CancellationToken token)
			{
				bool flag = LibSecretNative.Forget(reference, token);
				if (flag)
				{
					lock (references)
					{
						references.Remove(reference);
					}
				}
				return flag;
			}, delegate
			{
				lock (references)
				{
					return FallbackStore.Forget(reference);
				}
			}, canceled: false, cancellation);
		}

		public Task<int> ForgetAllAsync(CancellationToken cancellation = default(CancellationToken))
		{
			return Run(delegate(CancellationToken token)
			{
				HashSet<string> hashSet = LibSecretNative.References(token);
				int num = 0;
				foreach (string item in hashSet)
				{
					if (LibSecretNative.Forget(item, token))
					{
						num++;
						lock (references)
						{
							references.Remove(item);
						}
					}
				}
				return num;
			}, delegate
			{
				lock (references)
				{
					return FallbackStore.ForgetAll();
				}
			}, 0, cancellation);
		}

		private Task<T> Run<T>(Func<CancellationToken, T> operation, Func<T> onUnavailable, T canceled, CancellationToken cancellation)
		{
			long revision;
			lock (references)
			{
				if (disposed || cancellation.IsCancellationRequested)
				{
					return Task.FromResult(canceled);
				}
				revision = fallbackRevision;
			}
			return Task.Run(async delegate
			{
				CancellationTokenSource linked = CancellationTokenSource.CreateLinkedTokenSource(cancellation, lifetime.Token);
				try
				{
					bool entered = false;
					try
					{
						await gate.WaitAsync(linked.Token).ConfigureAwait(continueOnCapturedContext: false);
						entered = true;
						if (disposed || linked.IsCancellationRequested)
						{
							return canceled;
						}
						if (unavailable)
						{
							return Fallback();
						}
						using CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { linked.Token });
						cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5.0));
						try
						{
							T result = operation(cancellationTokenSource.Token);
							if (disposed || linked.IsCancellationRequested)
							{
								return canceled;
							}
							LastFailure = SecretFailure.None;
							return result;
						}
						catch (Exception ex) when (ex is DllNotFoundException || ex is EntryPointNotFoundException || ex is BadImageFormatException || ex is KeyringUnavailableException || ex is OperationCanceledException)
						{
							if (linked.IsCancellationRequested)
							{
								return canceled;
							}
							unavailable = true;
							LastFailure = SecretFailure.Unavailable;
							return Fallback();
						}
					}
					catch (OperationCanceledException)
					{
						return canceled;
					}
					finally
					{
						if (entered)
						{
							gate.Release();
						}
					}
				}
				finally
				{
					if (linked != null)
					{
						((IDisposable)linked).Dispose();
					}
				}
			});
			T Fallback()
			{
				lock (references)
				{
					if (disposed || P_0.linked.IsCancellationRequested || revision != fallbackRevision)
					{
						return canceled;
					}
					return onUnavailable();
				}
			}
		}

		public void Dispose()
		{
			if (disposed)
			{
				return;
			}
			disposed = true;
			lifetime.Cancel();
			lock (references)
			{
				references.Clear();
				session.ForgetAll();
			}
		}
	}
	internal sealed class KeyringUnavailableException : Exception
	{
	}
	public sealed class SecretRedactor
	{
		public const string Mask = "***";

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

		public void Track(string? secret)
		{
			if (!string.IsNullOrEmpty(secret))
			{
				secrets[secret] = ((!secrets.TryGetValue(secret, out var value)) ? 1 : (value + 1));
			}
		}

		public void Untrack(string? secret)
		{
			if (!string.IsNullOrEmpty(secret) && secrets.TryGetValue(secret, out var value))
			{
				if (value > 1)
				{
					secrets[secret] = value - 1;
				}
				else
				{
					secrets.Remove(secret);
				}
			}
		}

		public void Clear()
		{
			secrets.Clear();
		}

		public string Clean(string? message)
		{
			if (string.IsNullOrEmpty(message))
			{
				return "";
			}
			string text = message;
			foreach (string item in secrets.Keys.OrderByDescending((string s) => s.Length))
			{
				text = text.Replace(item, "***");
			}
			return text;
		}
	}
	public enum SecretAvailability
	{
		Available,
		SessionOnly,
		Unavailable
	}
	public enum SecretBackend
	{
		WindowsDataProtection,
		LinuxKeyring,
		SessionOnly,
		Disabled,
		Memory
	}
	public enum SecretPlatform
	{
		Windows,
		Linux,
		Other
	}
	public enum LinuxFallback
	{
		SessionOnly,
		Disabled
	}
	public enum SecretFailure
	{
		None,
		StoredElsewhere,
		ProtectFailed,
		Unavailable
	}
	public interface ISecretStore
	{
		SecretBackend Backend { get; }

		SecretAvailability Availability { get; }

		SecretFailure LastFailure { get; }

		bool Store(string reference, string secret);

		bool TryRead(string reference, out string secret);

		bool Contains(string reference);

		bool Forget(string reference);

		int ForgetAll();
	}
	public static class SecretReference
	{
		public static bool IsValid(string? reference)
		{
			if (string.IsNullOrEmpty(reference) || reference.Length > 64)
			{
				return false;
			}
			foreach (char c in reference)
			{
				if ((c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '-' && c != '_')
				{
					return false;
				}
			}
			return true;
		}

		public static void Require(string reference, string secret)
		{
			if (!IsValid(reference))
			{
				throw new ArgumentException("Invalid secret reference.", "reference");
			}
			if (string.IsNullOrEmpty(secret))
			{
				throw new ArgumentException("An empty secret is not stored.", "secret");
			}
		}

		public static void Require(string reference)
		{
			if (!IsValid(reference))
			{
				throw new ArgumentException("Invalid secret reference.", "reference");
			}
		}
	}
	public sealed class MemorySecretStore : ISecretStore
	{
		private readonly Dictionary<string, string> secrets = new Dictionary<string, string>(StringComparer.Ordinal);

		public SecretBackend Backend { get; }

		public SecretAvailability Availability { get; }

		public SecretFailure LastFailure => SecretFailure.None;

		public MemorySecretStore(SecretBackend backend = SecretBackend.Memory, SecretAvailability availability = SecretAvailability.Available)
		{
			Backend = backend;
			Availability = availability;
		}

		public static MemorySecretStore SessionOnly()
		{
			return new MemorySecretStore(SecretBackend.SessionOnly, SecretAvailability.SessionOnly);
		}

		public bool Store(string reference, string secret)
		{
			SecretReference.Require(reference, secret);
			secrets[reference] = secret;
			return true;
		}

		public bool TryRead(string reference, out string secret)
		{
			SecretReference.Require(reference);
			if (secrets.TryGetValue(reference, out string value))
			{
				secret = value;
				return true;
			}
			secret = "";
			return false;
		}

		public bool Contains(string reference)
		{
			if (SecretReference.IsValid(reference))
			{
				return secrets.ContainsKey(reference);
			}
			return false;
		}

		public bool Forget(string reference)
		{
			SecretReference.Require(reference);
			return secrets.Remove(reference);
		}

		public int ForgetAll()
		{
			int count = secrets.Count;
			secrets.Clear();
			return count;
		}
	}
	public sealed class DisabledSecretStore : ISecretStore
	{
		public SecretBackend Backend => SecretBackend.Disabled;

		public SecretAvailability Availability => SecretAvailability.Unavailable;

		public SecretFailure LastFailure { get; private set; }

		public bool Store(string reference, string secret)
		{
			SecretReference.Require(reference, secret);
			LastFailure = SecretFailure.Unavailable;
			return false;
		}

		public bool TryRead(string reference, out string secret)
		{
			SecretReference.Require(reference);
			secret = "";
			return false;
		}

		public bool Contains(string reference)
		{
			return false;
		}

		public bool Forget(string reference)
		{
			return false;
		}

		public int ForgetAll()
		{
			return 0;
		}
	}
	public static class SecretStores
	{
		public const LinuxFallback DefaultLinuxFallback = LinuxFallback.SessionOnly;

		public static SecretBackend Choose(SecretPlatform platform, bool keyringUsable, LinuxFallback fallback)
		{
			if (platform == SecretPlatform.Windows)
			{
				return SecretBackend.WindowsDataProtection;
			}
			if (platform == SecretPlatform.Linux && keyringUsable)
			{
				return SecretBackend.LinuxKeyring;
			}
			if (fallback != LinuxFallback.Disabled)
			{
				return SecretBackend.SessionOnly;
			}
			return SecretBackend.Disabled;
		}

		public static string StorageLineKey(SecretBackend backend)
		{
			return backend switch
			{
				SecretBackend.WindowsDataProtection => "storage_windows", 
				SecretBackend.LinuxKeyring => "storage_keyring", 
				SecretBackend.Disabled => "storage_disabled", 
				_ => "storage_session", 
			};
		}
	}
}
namespace ExtraPotions.Beacon.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 BeaconTextKeys
	{
		public const string StepAsideJoin = "step_aside_join";

		public const string StorageWindows = "storage_windows";

		public const string StorageKeyring = "storage_keyring";

		public const string StorageSession = "storage_session";

		public const string StorageDisabled = "storage_disabled";

		public const string SecretStoreFailed = "secret_store_failed";

		public const string SecretStoredElsewhere = "secret_stored_elsewhere";

		public const string OfferSave = "offer_save";

		public const string PasswordSaved = "password_saved";

		public const string PasswordForgotten = "password_forgotten";

		public const string PasswordsForgottenAll = "passwords_forgotten_all";

		public const string AutoJoinCountdown = "autojoin_countdown";

		public const string AutoJoinCancelled = "autojoin_canceled";

		public const string AutoJoinUnknownServer = "autojoin_unknown_server";

		public const string Reconnecting = "reconnecting";

		public const string ReconnectLocked = "reconnect_locked";

		public const string FavoriteRemoved = "favorite_removed";

		public const string ImportNone = "import_none";

		public const string ImportDone = "import_done";

		public const string ImportPasswordNotStored = "import_password_not_stored";

		public const string ImportSourcePlain = "import_source_plain";

		public const string ImportBlankDone = "import_blank_done";

		public const string CommandHelp = "cmd_help";

		public const string CommandStatus = "cmd_status";

		public const string CommandUsage = "cmd_usage";

		public const string CommandUnknownVerb = "cmd_unknown_verb";

		public const string CommandConfirmBlank = "cmd_confirm_blank";

		public const string CommandJoinUnknown = "cmd_join_unknown";

		public const string CommandJoinNotReady = "cmd_join_not_ready";

		public const string CommandJoinStarted = "cmd_join_started";

		public const string ReconnectCancelled = "reconnect_canceled";

		public const string ReconnectStopped = "reconnect_stopped";

		public const string CommandCancelNone = "cmd_cancel_none";

		public const string AutoJoinUnknownCharacter = "autojoin_unknown_character";

		public const string CardAutoJoin = "card_autojoin";

		public const string CardReconnect = "card_reconnect";

		public const string CardCancelHint = "card_cancel_hint";

		public static IReadOnlyList<TextKey> All { get; } = Array.AsReadOnly(new TextKey[102]
		{
			new TextKey("step_aside_join", 1),
			new TextKey("storage_windows", 0),
			new TextKey("storage_keyring", 0),
			new TextKey("storage_session", 0),
			new TextKey("storage_disabled", 0),
			new TextKey("secret_store_failed", 0),
			new TextKey("secret_stored_elsewhere", 1),
			new TextKey("offer_save", 1),
			new TextKey("password_saved", 1),
			new TextKey("password_forgotten", 1),
			new TextKey("passwords_forgotten_all", 1),
			new TextKey("autojoin_countdown", 2),
			new TextKey("autojoin_canceled", 0),
			new TextKey("autojoin_unknown_server", 1),
			new TextKey("reconnecting", 2),
			new TextKey("reconnect_locked", 0),
			new TextKey("favorite_removed", 1),
			new TextKey("import_none", 0),
			new TextKey("import_done", 2),
			new TextKey("import_password_not_stored", 1),
			new TextKey("import_source_plain", 0),
			new TextKey("import_blank_done", 1),
			new TextKey("cmd_help", 0),
			new TextKey("cmd_status", 3),
			new TextKey("cmd_usage", 0),
			new TextKey("cmd_unknown_verb", 1),
			new TextKey("cmd_confirm_blank", 0),
			new TextKey("cmd_join_unknown", 1),
			new TextKey("cmd_join_not_ready", 0),
			new TextKey("cmd_join_started", 1),
			new TextKey("reconnect_canceled", 0),
			new TextKey("reconnect_stopped", 1),
			new TextKey("cmd_cancel_none", 0),
			new TextKey("autojoin_unknown_character", 1),
			new TextKey("card_autojoin", 2),
			new TextKey("card_reconnect", 3),
			new TextKey("card_cancel_hint", 0),
			new TextKey("ui_servers", 0),
			new TextKey("ui_previous", 0),
			new TextKey("ui_next", 0),
			new TextKey("ui_add", 0),
			new TextKey("ui_edit", 0),
			new TextKey("ui_join", 0),
			new TextKey("ui_remove", 0),
			new TextKey("ui_up", 0),
			new TextKey("ui_down", 0),
			new TextKey("ui_default", 0),
			new TextKey("ui_close", 0),
			new TextKey("ui_forget", 0),
			new TextKey("ui_forget_all", 0),
			new TextKey("ui_confirm", 0),
			new TextKey("ui_cancel", 0),
			new TextKey("ui_save", 0),
			new TextKey("ui_name", 0),
			new TextKey("ui_address", 0),
			new TextKey("ui_aliases", 0),
			new TextKey("ui_password", 0),
			new TextKey("ui_password_hint", 0),
			new TextKey("ui_stored", 0),
			new TextKey("ui_empty", 0),
			new TextKey("ui_read_only", 0),
			new TextKey("ui_step_aside", 0),
			new TextKey("ui_write_failed", 0),
			new TextKey("ui_invalid", 0),
			new TextKey("ui_forget_all_confirm", 0),
			new TextKey("ui_alias_count", 1),
			new TextKey("ui_remove_confirm", 1),
			new TextKey("ui_forget_confirm", 1),
			new TextKey("ui_import", 0),
			new TextKey("import_preview_again", 0),
			new TextKey("import_write_failed", 0),
			new TextKey("import_password_pending", 0),
			new TextKey("ui_import_apply", 0),
			new TextKey("ui_import_blank", 0),
			new TextKey("ui_import_blank_confirm", 0),
			new TextKey("ui_import_results", 0),
			new TextKey("ui_undo", 0),
			new TextKey("ui_undo_conflict", 0),
			new TextKey("ui_favorite_save_failed", 0),
			new TextKey("ui_help_servers", 0),
			new TextKey("ui_help_previous", 0),
			new TextKey("ui_help_next", 0),
			new TextKey("ui_help_add", 0),
			new TextKey("ui_help_edit", 0),
			new TextKey("ui_help_join", 0),
			new TextKey("ui_help_remove", 0),
			new TextKey("ui_help_up", 0),
			new TextKey("ui_help_down", 0),
			new TextKey("ui_help_default", 0),
			new TextKey("ui_help_close", 0),
			new TextKey("ui_help_forget", 0),
			new TextKey("ui_help_forget_all", 0),
			new TextKey("ui_help_import", 0),
			new TextKey("ui_help_select", 0),
			new TextKey("ui_help_cancel", 0),
			new TextKey("ui_help_save", 0),
			new TextKey("ui_help_name", 0),
			new TextKey("ui_help_address", 0),
			new TextKey("ui_help_aliases", 0),
			new TextKey("ui_help_save_password", 0),
			new TextKey("ui_help_undo", 0),
			new TextKey("ui_help_import_apply", 0)
		});
	}
}
namespace ExtraPotions.Beacon.Joining
{
	public enum AutoJoinSkip
	{
		None,
		Disabled,
		LaunchConnect,
		AlreadyRan,
		NoServer,
		UnknownServer
	}
	public readonly struct AutoJoinDecision
	{
		public AutoJoinSkip Skip { get; }

		public ServerEntry? Target { get; }

		public AutoJoinDecision(AutoJoinSkip skip, ServerEntry? target)
		{
			Skip = skip;
			Target = target;
		}
	}
	public static class AutoJoinRules
	{
		public static AutoJoinDecision Decide(bool enabled, string? serverName, ServerList list, bool launchedWithConnect, bool alreadyRan)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			if (!enabled)
			{
				return new AutoJoinDecision(AutoJoinSkip.Disabled, null);
			}
			if (launchedWithConnect)
			{
				return new AutoJoinDecision(AutoJoinSkip.LaunchConnect, null);
			}
			if (alreadyRan)
			{
				return new AutoJoinDecision(AutoJoinSkip.AlreadyRan, null);
			}
			string text = (serverName ?? "").Trim();
			if (text.Length == 0)
			{
				ServerEntry serverEntry = list.Default;
				if (serverEntry != null)
				{
					return new AutoJoinDecision(AutoJoinSkip.None, serverEntry);
				}
				return new AutoJoinDecision(AutoJoinSkip.NoServer, null);
			}
			ServerEntry serverEntry2 = list.FindByName(text);
			if (serverEntry2 != null)
			{
				return new AutoJoinDecision(AutoJoinSkip.None, serverEntry2);
			}
			return new AutoJoinDecision(AutoJoinSkip.UnknownServer, null);
		}

		public static bool LaunchJoins(IReadOnlyList<string> args)
		{
			if (args == null)
			{
				throw new ArgumentNullException("args");
			}
			for (int i = 0; i < args.Count - 1; i++)
			{
				switch (args[i])
				{
				case "+connect":
				case "+connect_lobby":
				case "-joinserverwithcharacter":
				case "-joincode":
					return true;
				}
			}
			return false;
		}

		public static int PickCharacter(IReadOnlyList<string> names, IReadOnlyList<string> files, string? wanted, int selected)
		{
			if (names == null)
			{
				throw new ArgumentNullException("names");
			}
			if (files == null)
			{
				throw new ArgumentNullException("files");
			}
			if (names.Count == 0)
			{
				return -1;
			}
			string text = (wanted ?? "").Trim();
			if (text.Length == 0)
			{
				if (selected < 0 || selected >= names.Count)
				{
					return 0;
				}
				return selected;
			}
			for (int i = 0; i < names.Count; i++)
			{
				if (string.Equals((names[i] ?? "").Trim(), text, StringComparison.OrdinalIgnoreCase))
				{
					return i;
				}
			}
			for (int j = 0; j < files.Count && j < names.Count; j++)
			{
				if (string.Equals((files[j] ?? "").Trim(), text, StringComparison.OrdinalIgnoreCase))
				{
					return j;
				}
			}
			return -1;
		}
	}
	public enum CountdownState
	{
		Counting,
		Join,
		Cancelled
	}
	public sealed class AutoJoinCountdown
	{
		public const int MaxSeconds = 30;

		private float remaining;

		private CountdownState state;

		public int SecondsShown => (int)Math.Ceiling(Math.Max(0f, remaining));

		public CountdownState State => state;

		public AutoJoinCountdown(int seconds)
		{
			remaining = Math.Max(0, Math.Min(30, seconds));
		}

		public CountdownState Tick(float deltaSeconds)
		{
			if (state != CountdownState.Counting)
			{
				return state;
			}
			remaining -= Math.Max(0f, deltaSeconds);
			if (remaining <= 0f)
			{
				state = CountdownState.Join;
			}
			return state;
		}

		public void Cancel()
		{
			if (state == CountdownState.Counting)
			{
				state = CountdownState.Cancelled;
			}
		}
	}
	public enum DisconnectKind
	{
		Unexpected,
		Kicked,
		Banned,
		PlayerQuit,
		WrongPassword,
		VersionMismatch,
		ServerFull,
		ConnectFailed,
		Refused,
		Unknown
	}
	public enum ReconnectDecision
	{
		Reconnect,
		Off,
		Locked,
		NotAfterThis,
		OutOfAttempts
	}
	public static class ReconnectRules
	{
		public const int MinAttempts = 1;

		public const int MaxAttempts = 10;

		public static ReconnectDecision Decide(bool enabled, bool lockedByServer, DisconnectKind kind, int attemptsSoFar, int maxAttempts)
		{
			if (!enabled)
			{
				return ReconnectDecision.Off;
			}
			if (lockedByServer)
			{
				return ReconnectDecision.Locked;
			}
			if (kind != DisconnectKind.Unexpected)
			{
				return ReconnectDecision.NotAfterThis;
			}
			int num = Math.Max(1, Math.Min(10, maxAttempts));
			if (attemptsSoFar < num)
			{
				return ReconnectDecision.Reconnect;
			}
			return ReconnectDecision.OutOfAttempts;
		}

		public static int DelaySeconds(int attemptIndex)
		{
			if (attemptIndex > 0)
			{
				if (attemptIndex < 4)
				{
					return 5 << attemptIndex;
				}
				return 60;
			}
			return 5;
		}
	}
	public readonly struct CardLine
	{
		public string Key { get; }

		public IReadOnlyList<object> Arguments { get; }

		public CardLine(string key, params object[] arguments)
		{
			Key = key;
			Arguments = Array.AsReadOnly(arguments ?? Array.Empty<object>());
		}
	}
	public static class JoinCard
	{
		public static IReadOnlyList<CardLine> Countdown(string serverName, int seconds)
		{
			return new CardLine[2]
			{
				new CardLine("card_autojoin", serverName ?? "", Math.Max(0, seconds)),
				new CardLine("card_cancel_hint")
			};
		}

		public static IReadOnlyList<CardLine> Reconnect(int attempt, int limit, int seconds)
		{
			return new CardLine[2]
			{
				new CardLine("card_reconnect", attempt, limit, Math.Max(0, seconds)),
				new CardLine("card_cancel_hint")
			};
		}
	}
	public enum JoinAction
	{
		None,
		SendStored,
		AskPlayer,
		ReconnectAndAsk,
		StoreTyped,
		OfferSave
	}
	public sealed class JoinAttempt
	{
		private readonly bool hasStored;

		private readonly bool skipPrompt;

		private readonly bool offerToSave;

		private readonly bool alreadyOffered;

		private readonly bool storedRejected;

		private bool sentStored;

		private bool typed;

		public ServerEntry? Entry { get; }

		public JoinAttempt(ServerEntry? entry, bool hasStored, bool skipPrompt, bool offerToSave, bool alreadyOffered)
			: this(entry, hasStored, skipPrompt, offerToSave, alreadyOffered, storedRejected: false)
		{
		}

		private JoinAttempt(ServerEntry? entry, bool hasStored, bool skipPrompt, bool offerToSave, bool alreadyOffered, bool storedRejected)
		{
			Entry = entry;
			this.hasStored = hasStored;
			this.skipPrompt = skipPrompt;
			this.offerToSave = offerToSave;
			this.alreadyOffered = alreadyOffered;
			this.storedRejected = storedRejected;
		}

		public JoinAction OnPasswordRequested()
		{
			if (Entry != null && hasStored && skipPrompt && !storedRejected)
			{
				sentStored = true;
				return JoinAction.SendStored;
			}
			return JoinAction.AskPlayer;
		}

		public void OnPlayerTyped()
		{
			typed = true;
		}

		public JoinAction OnRejected()
		{
			if (sentStored)
			{
				sentStored = false;
				return JoinAction.ReconnectAndAsk;
			}
			typed = false;
			return JoinAction.None;
		}

		public JoinAction OnAccepted()
		{
			if (!typed)
			{
				return JoinAction.None;
			}
			if (Entry != null)
			{
				return JoinAction.StoreTyped;
			}
			if (!offerToSave || alreadyOffered)
			{
				return JoinAction.None;
			}
			return JoinAction.OfferSave;
		}

		public JoinAttempt AfterRejection()
		{
			return new JoinAttempt(Entry, hasStored, skipPrompt, offerToSave, alreadyOffered, storedRejected: true);
		}
	}
	public enum GameStatus
	{
		None,
		Connecting,
		Connected,
		ErrorVersion,
		ErrorDisconnected,
		ErrorConnectFailed,
		ErrorPassword,
		ErrorAlreadyConnected,
		ErrorBanned,
		ErrorFull,
		ErrorPlatformExcluded,
		ErrorCrossplayPrivilege,
		ErrorKicked,
		Unknown
	}
	public static class ConnectionOutcomes
	{
		public static GameStatus Parse(string? name)
		{
			string text = (name ?? "").Trim();
			if (text.Length == 0 || !char.IsLetter(text[0]))
			{
				return GameStatus.Unknown;
			}
			if (!Enum.TryParse<GameStatus>(text, ignoreCase: false, out var result))
			{
				return GameStatus.Unknown;
			}
			return result;
		}

		public static DisconnectKind Classify(GameStatus status, bool wasConnected)
		{
			return status switch
			{
				GameStatus.Connected => DisconnectKind.PlayerQuit, 
				GameStatus.ErrorPassword => DisconnectKind.WrongPassword, 
				GameStatus.ErrorKicked => DisconnectKind.Kicked, 
				GameStatus.ErrorBanned => DisconnectKind.Banned, 
				GameStatus.ErrorVersion => DisconnectKind.VersionMismatch, 
				GameStatus.ErrorFull => DisconnectKind.ServerFull, 
				GameStatus.ErrorAlreadyConnected => DisconnectKind.Refused, 
				GameStatus.ErrorPlatformExcluded => DisconnectKind.Refused, 
				GameStatus.ErrorCrossplayPrivilege => DisconnectKind.Refused, 
				GameStatus.ErrorDisconnected => (!wasConnected) ? DisconnectKind.ConnectFailed : DisconnectKind.Unexpected, 
				GameStatus.ErrorConnectFailed => (!wasConnected) ? DisconnectKind.ConnectFailed : DisconnectKind.Unexpected, 
				_ => DisconnectKind.Unknown, 
			};
		}
	}
	public static class JoinTargets
	{
		public static ServerAddress? FromHost(string? host, int port)
		{
			string text = (host ?? "").Trim();
			if (text.Length == 0 || port < 1 || port > 65535)
			{
				return null;
			}
			int num = 0;
			string text2 = text;
			for (int i = 0; i < text2.Length; i++)
			{
				if (text2[i] == ':')
				{
					num++;
				}
			}
			if (num == 1)
			{
				return null;
			}
			string text3 = port.ToString(CultureInfo.InvariantCulture);
			if (!ServerAddress.TryParse((num > 1 && text[0] != '[') ? ("[" + text + "]:" + text3) : (text + ":" + text3), out ServerAddress address))
			{
				return null;
			}
			return address;
		}

		public static ServerAddress? FromSteam(ulong id)
		{
			if (id == 0L || !ServerAddress.TryParse("steam:" + id.ToString(CultureInfo.InvariantCulture), out ServerAddress address))
			{
				return null;
			}
			return address;
		}

		public static ServerAddress? FromPlayFab(string? id)
		{
			if (!ServerAddress.TryParse("playfab:" + (id ?? "").Trim(), out ServerAddress address))
			{
				return null;
			}
			return address;
		}

		public static bool TryGetSteamId(ServerAddress address, out ulong id)
		{
			id = 0uL;
			if (address != null && address.Kind == AddressKind.Steam && ulong.TryParse(address.Host, NumberStyles.None, CultureInfo.InvariantCulture, out id))
			{
				return id != 0;
			}
			return false;
		}
	}
	public readonly struct JoinOptions
	{
		public bool SkipPrompt { get; }

		public bool OfferToSave { get; }

		public JoinOptions(bool skipPrompt, bool offerToSave)
		{
			SkipPrompt = skipPrompt;
			OfferToSave = offerToSave;
		}
	}
	public enum MenuStep
	{
		None,
		RejoinForPassword
	}
	public readonly struct MenuOutcome
	{
		public DisconnectKind Kind { get; }

		public ServerAddress? Target { get; }

		public MenuStep Step { get; }

		public bool WasConnected { get; }

		public MenuOutcome(DisconnectKind kind, ServerAddress? target, MenuStep step, bool wasConnected)
		{
			Kind = kind;
			Target = target;
			Step = step;
			WasConnected = wasConnected;
		}
	}
	public sealed class JoinCoordinator
	{
		private readonly HashSet<ServerAddress> offered = new HashSet<ServerAddress>();

		private JoinAttempt? attempt;

		private ServerAddress? rejoinForPassword;

		private bool requested;

		public ServerAddress? Target { get; private set; }

		public ServerEntry? Entry => attempt?.Entry;

		public bool Connected { get; private set; }

		public bool AwaitingConnection
		{
			get
			{
				if (Target != null && attempt != null)
				{
					return !Connected;
				}
				return false;
			}
		}

		public void OnJoinStarted(ServerAddress? target, ServerList list, Func<ServerEntry, bool> hasStored, JoinOptions options)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			if (hasStored == null)
			{
				throw new ArgumentNullException("hasStored");
			}
			if (target != null && target == Target && attempt != null && !requested && !Connected)
			{
				return;
			}
			Target = target;
			Connected = false;
			requested = false;
			if (target == null)
			{
				attempt = null;
				rejoinForPassword = null;
				return;
			}
			ServerEntry serverEntry = list.Find(target);
			JoinAttempt joinAttempt = new JoinAttempt(serverEntry, serverEntry != null && hasStored(serverEntry), options.SkipPrompt, options.OfferToSave, offered.Contains(target));
			if (rejoinForPassword == target)
			{
				joinAttempt = joinAttempt.AfterRejection();
			}
			rejoinForPassword = null;
			attempt = joinAttempt;
		}

		public JoinAction OnPasswordRequested()
		{
			if (attempt == null || Connected)
			{
				return JoinAction.None;
			}
			requested = true;
			return attempt.OnPasswordRequested();
		}

		public void OnStoredUnreadable()
		{
			if (attempt != null)
			{
				attempt = attempt.AfterRejection();
			}
		}

		public void OnPlayerTyped()
		{
			attempt?.OnPlayerTyped();
		}

		public JoinAction OnConnected()
		{
			if (attempt == null || Connected)
			{
				return JoinAction.None;
			}
			Connected = true;
			JoinAction num = attempt.OnAccepted();
			if (num == JoinAction.OfferSave)
			{
				offered.Add(Target);
			}
			return num;
		}

		public MenuOutcome OnReturnedToMenu(GameStatus status)
		{
			ServerAddress target = Target;
			bool connected = Connected;
			JoinAttempt joinAttempt = attempt;
			Target = null;
			attempt = null;
			Connected = false;
			requested = false;
			if (target == null)
			{
				return new MenuOutcome(DisconnectKind.Unknown, null, MenuStep.None, wasConnected: false);
			}
			DisconnectKind disconnectKind = ConnectionOutcomes.Classify(status, connected);
			if (disconnectKind == DisconnectKind.WrongPassword && joinAttempt != null && joinAttempt.OnRejected() == JoinAction.ReconnectAndAsk)
			{
				rejoinForPassword = target;
				return new MenuOutcome(disconnectKind, target, MenuStep.RejoinForPassword, connected);
			}
			return new MenuOutcome(disconnectKind, target, MenuStep.None, connected);
		}

		public void Reset()
		{
			offered.Clear();
			attempt = null;
			rejoinForPassword = null;
			Target = null;
			Connected = false;
			requested = false;
		}
	}
	public enum MenuBackAction
	{
		None,
		CloseDialog,
		DeclineSaveOffer,
		ClosePanel,
		CancelWait
	}
	public struct MenuBackState
	{
		public bool Escape;

		public bool Typing;

		public bool TypedLastFrame;

		public bool DialogOpen;

		public bool DialogTyping;

		public bool SaveOfferOpen;

		public bool PanelOpen;

		public bool WaitPending;
	}
	public static class MenuBack
	{
		public static MenuBackAction Decide(MenuBackState state)
		{
			if (!state.Escape || state.Typing || state.TypedLastFrame)
			{
				return MenuBackAction.None;
			}
			if (state.DialogOpen)
			{
				if (!state.DialogTyping)
				{
					return MenuBackAction.CloseDialog;
				}
				return MenuBackAction.None;
			}
			if (state.SaveOfferOpen)
			{
				return MenuBackAction.DeclineSaveOffer;
			}
			if (state.PanelOpen)
			{
				return MenuBackAction.ClosePanel;
			}
			if (!state.WaitPending)
			{
				return MenuBackAction.None;
			}
			return MenuBackAction.CancelWait;
		}
	}
	public sealed class ReconnectServerPolicy
	{
		private bool denied;

		public ServerAddress? Target { get; private set; }

		public void Begin(ServerAddress? target)
		{
			Target = target;
			denied = false;
		}

		public void Observe(ServerAddress? target, bool authenticated, bool allowsReconnect)
		{
			if (authenticated && !(target == null) && !(target != Target))
			{
				denied = !allowsReconnect;
			}
		}

		public bool LockedFor(ServerAddress? target)
		{
			if (denied && target != null)
			{
				return target == Target;
			}
			return false;
		}

		public void Reset()
		{
			Target = null;
			denied = false;
		}
	}
	public enum ReconnectStep
	{
		None,
		Scheduled,
		Stopped,
		Locked
	}
	public readonly struct ReconnectPlan
	{
		public ReconnectStep Step { get; }

		public ReconnectDecision Decision { get; }

		public int Attempt { get; }

		public int Limit { get; }

		public int DelaySeconds { get; }

		public ReconnectPlan(ReconnectStep step, ReconnectDecision decision, int attempt, int limit, int delaySeconds)
		{
			Step = step;
			Decision = decision;
			Attempt = attempt;
			Limit = limit;
			DelaySeconds = delaySeconds;
		}
	}
	public sealed class ReconnectSession
	{
		public const double StableSeconds = 60.0;

		private int attempts;

		private double connectedAt = -1.0;

		private double due = -1.0;

		public ServerAddress? Target { get; private set; }

		public bool Pending => due >= 0.0;

		public int SecondsLeft(double now)
		{
			if (!(due < 0.0))
			{
				return (int)Math.Ceiling(Math.Max(0.0, due - now));
			}
			return 0;
		}

		public void OnJoinStarted(ServerAddress? joining)
		{
			if (Target != null && joining != Target)
			{
				Cancel();
			}
		}

		public void OnConnected(double now)
		{
			connectedAt = now;
		}

		public ReconnectPlan OnDisconnected(ServerAddress target, DisconnectKind kind, bool wasConnected, bool enabled, bool lockedByServer, int maxAttempts, double now)
		{
			if (target == null)
			{
				throw new ArgumentNullException("target");
			}
			due = -1.0;
			if (Target != target)
			{
				Target = target;
				attempts = 0;
			}
			if (wasConnected && connectedAt >= 0.0 && now - connectedAt >= 60.0)
			{
				attempts = 0;
			}
			connectedAt = -1.0;
			DisconnectKind disconnectKind = ((kind != DisconnectKind.ConnectFailed || attempts <= 0) ? kind : DisconnectKind.Unexpected);
			int limit = Math.Max(1, Math.Min(10, maxAttempts));
			ReconnectDecision reconnectDecision = ReconnectRules.Decide(enabled, lockedByServer, disconnectKind, attempts, maxAttempts);
			if (reconnectDecision == ReconnectDecision.Reconnect)
			{
				int num = ReconnectRules.DelaySeconds(attempts);
				attempts++;
				due = now + (double)num;
				return new ReconnectPlan(ReconnectStep.Scheduled, reconnectDecision, attempts, limit, num);
			}
			bool flag = attempts > 0;
			Cancel();
			if (reconnectDecision == ReconnectDecision.Locked && disconnectKind == DisconnectKind.Unexpected)
			{
				return new ReconnectPlan(ReconnectStep.Locked, reconnectDecision, 0, limit, 0);
			}
			return new ReconnectPlan(flag ? ReconnectStep.Stopped : ReconnectStep.None, reconnectDecision, 0, limit, 0);
		}

		public bool Tick(double now)
		{
			if (due < 0.0 || now < due)
			{
				return false;
			}
			due = -1.0;
			return true;
		}

		public bool Cancel()
		{
			bool result = due >= 0.0 || attempts > 0;
			due = -1.0;
			attempts = 0;
			connectedAt = -1.0;
			Target = null;
			return result;
		}
	}
}
namespace ExtraPotions.Beacon.Integration
{
	public enum BeaconModule
	{
		Join
	}
	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 BeaconModule Module { get; }

		public IReadOnlyList<string> PluginNames { get; }

		internal ModuleSource(string guid, string name, BeaconModule 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 BeaconModule Module { get; }

		public string SourceName { get; }

		public string SourceGuid { get; }

		public string Key => KeyOf(Module);

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

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

		public IReadOnlyList<ModuleSource> Present { get; }

		public bool Any => aside.Count > 0;

		public IReadOnlyList<StepAsideNotice> Notices { get; }

		internal ModuleStepAsideDecision(IReadOnlyList<ModuleSource> present)
		{
			Present = present;
			aside = new HashSet<BeaconModule>(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(BeaconModule module)
		{
			return aside.Contains(module);
		}
	}
	public static class ModuleStepAside
	{
		public static readonly ModuleSource AutoConnector = new ModuleSource("MidnightsFX.Autoconnect", "AutoConnector", BeaconModule.Join, "AutoConnector");

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

		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.Beacon.Conversion
{
	public enum ImportProblem
	{
		None,
		MissingAddress,
		InvalidAddress
	}
	public sealed class ImportedServer
	{
		public string Name { get; }

		public string? AddressText { get; }

		public ServerAddress? Address { get; }

		public IReadOnlyList<ServerAddress> Aliases { get; }

		public ImportProblem Problem { get; }

		public bool HasPassword => Password != null;

		public string? Password { get; }

		internal ImportedServer(string name, string? addressText, IReadOnlyList<string> aliasTexts, string? password)
		{
			Name = name;
			AddressText = addressText;
			Password = (string.IsNullOrEmpty(password) ? null : password);
			ServerAddress address;
			if (string.IsNullOrWhiteSpace(addressText))
			{
				Problem = ImportProblem.MissingAddress;
			}
			else if (!ServerAddress.TryParse(addressText, out address))
			{
				Problem = ImportProblem.InvalidAddress;
			}
			else
			{
				Address = address;
			}
			Aliases = (from t in aliasTexts
				select (!ServerAddress.TryParse(t, out ServerAddress address2)) ? null : address2 into x
				where x != null
				select (x)).ToList();
		}

		public override string ToString()
		{
			return Name + " (" + (Address?.ToString() ?? "no address") + "), " + (HasPassword ? "password present" : "no password");
		}
	}
	public enum ImportOutcome
	{
		Add,
		SameName,
		AddressTaken,
		Invalid
	}
	public sealed class ImportPlanItem
	{
		public ImportedServer Server { get; }

		public ImportOutcome Outcome { get; }

		internal ImportPlanItem(ImportedServer server, ImportOutcome outcome)
		{
			Server = server;
			Outcome = outcome;
		}

		public override string ToString()
		{
			return Outcome.ToString() + ": " + Server;
		}
	}
	public sealed class ImportResult
	{
		public int Added { get; }

		public int Skipped { get; }

		public IReadOnlyList<string> PasswordNotStored { get; }

		internal ImportResult(int added, int skipped, IReadOnlyList<string> passwordNotStored)
		{
			Added = added;
			Skipped = skipped;
			PasswordNotStored = passwordNotStored;
		}
	}
	public static class AutoConnectorImport
	{
		public const string SourceGuid = "MidnightsFX.Autoconnect";

		public const string SourceFile = "AutoConnector/servers.yaml";

		private const string AddressKey = "ServerAddress";

		private const string AliasesKey = "ServerAliases";

		private const string PassKey = "ServerPass";

		public static IReadOnlyList<ImportedServer> Parse(string yaml)
		{
			List<ImportedServer> result = new List<ImportedServer>();
			string name = null;
			string address = null;
			string pass = null;
			string listKey = null;
			List<string> aliases = new List<string>();
			string[] array = (yaml ?? "").Replace("\r\n", "\n").Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].TrimEnd('\r', ' ', '\t');
				string text2 = text.TrimStart(' ');
				if (text2.Length == 0 || text2[0] == '#')
				{
					continue;
				}
				if (text.Length - text2.Length == 0 && text2[0] != '-')
				{
					Flush();
					name = TopKey(text2);
				}
				else
				{
					if (name == null)
					{
						continue;
					}
					if (text2.StartsWith("- ", StringComparison.Ordinal) || text2 == "-")
					{
						if (listKey == "ServerAliases")
						{
							aliases.Add(Scalar(text2.Substring(1)));
						}
						continue;
					}
					int num = text2.IndexOf(':');
					if (num <= 0)
					{
						continue;
					}
					string text3 = text2.Substring(0, num).Trim();
					string text4 = text2.Substring(num + 1);
					listKey = text3;
					switch (text3)
					{
					case "ServerAddress":
						address = Scalar(text4);
						break;
					case "ServerPass":
						pass = Scalar(text4);
						break;
					case "ServerAliases":
					{
						string text5 = text4.Trim();
						if (text5.StartsWith("[", StringComparison.Ordinal) && text5.EndsWith("]", StringComparison.Ordinal))
						{
							aliases.AddRange(from s in text5.Substring(1, text5.Length - 2).Split(',').Select(Scalar)
								where s.Length > 0
								select s);
						}
						break;
					}
					}
				}
			}
			Flush();
			return result;
			void Flush()
			{
				if (name != null)
				{
					result.Add(new ImportedServer(name, address, aliases.ToList(), pass));
				}
				name = (address = (pass = (listKey = null)));
				aliases.Clear();
			}
		}

		public static IReadOnlyList<ImportPlanItem> Plan(ServerList existing, IEnumerable<ImportedServer> servers)
		{
			if (existing == null)
			{
				throw new ArgumentNullException("existing");
			}
			HashSet<string> hashSet = new HashSet<string>(existing.Entries.Select((ServerEntry e) => e.Name), StringComparer.OrdinalIgnoreCase);
			HashSet<ServerAddress> hashSet2 = new HashSet<ServerAddress>(existing.Entries.SelectMany((ServerEntry e) => e.AllAddresses));
			List<ImportPlanItem> list = new List<ImportPlanItem>();
			foreach (ImportedServer server in servers)
			{
				ImportOutcome outcome;
				if (server.Address == null || server.Name.Trim().Length == 0)
				{
					outcome = ImportOutcome.Invalid;
				}
				else if (hashSet.Contains(server.Name.Trim()))
				{
					outcome = ImportOutcome.SameName;
				}
				else if (new ServerAddress[1] { server.Address }.Concat(server.Aliases).Any(hashSet2.Contains))
				{
					outcome = ImportOutcome.AddressTaken;
				}
				else
				{
					outcome = ImportOutcome.Add;
					hashSet.Add(server.Name.Trim());
					foreach (ServerAddress item in new ServerAddress[1] { server.Address }.Concat(server.Aliases))
					{
						hashSet2.Add(item);
					}
				}
				list.Add(new ImportPlanItem(server, outcome));
			}
			return list;
		}

		public static IReadOnlyList<string> Preview(IEnumerable<ImportPlanItem> plan)
		{
			return plan.Select(delegate(ImportPlanItem p)
			{
				ImportedServer server = p.Server;
				string text = ((server.Aliases.Count == 0) ? "" : (" (+" + server.Aliases.Count + ((server.Aliases.Count == 1) ? " alias)" : " aliases)")));
				string text2 = p.Outcome switch
				{
					ImportOutcome.Add => "import", 
					ImportOutcome.SameName => "skip, a server with this name is saved", 
					ImportOutcome.AddressTaken => "skip, this address is saved", 
					_ => (server.Problem == ImportProblem.MissingAddress) ? "skip, no address" : "skip, invalid address", 
				};
				return server.Name + "  " + (server.Address?.ToString() ?? "-") + text + ", " + (server.HasPassword ? "password present" : "no password") + ": " + text2;
			}).ToList();
		}

		public static ImportResult Apply(ServerList list, ISecretStore store, IEnumerable<ImportPlanItem> plan)
		{
			if (list == null)
			{
				throw new ArgumentNullException("list");
			}
			if (store == null)
			{
				throw new ArgumentNullException("store");
			}
			int num = 0;
			int num2 = 0;
			List<string> list2 = new List<string>();
			foreach (ImportPlanItem item in plan)
			{
				if (item.Outcome != ImportOutcome.Add)
				{
					num2++;
					continue;
				}
				ImportedServer server = item.Server;
				ServerEntry serverEntry;
				try
				{
					serverEntry = list.Add(server.Name, server.Address, server.Aliases);
				}
				catch (ArgumentException)
				{
					num2++;
					continue;
				}
				num++;
				if (server.Password != null && !store.Store(serverEntry.SecretRef, server.Password))
				{
					list2.Add(serverEntry.Name);
				}
			}
			return new ImportResult(num, num2, list2);
		}

		public static string BlankPasswords(string yaml, out int blanked)
		{
			blanked = 0;
			string text = yaml ?? "";
			StringBuilder stringBuilder = new StringBuilder(text.Length);
			int num = 0;
			while (num <= text.Length)
			{
				int num2 = text.IndexOf('\n', num);
				int num3 = ((num2 < 0) ? text.Length : num2);
				string text2 = text.Substring(num, num3 - num);
				bool num4 = text2.EndsWith("\r", StringComparison.Ordinal);
				string text3 = (num4 ? text2.Substring(0, text2.Length - 1) : text2);
				string text4 = text3.TrimStart(' ');
				if (text3.Length > text4.Length && text4.StartsWith("ServerPass:", StringComparison.Ordinal) && Scalar(text4.Substring("ServerPass".Length + 1)).Length > 0)
				{
					text3 = text3.Substring(0, text3.Length - text4.Length) + "ServerPass: ''";
					blanked++;
				}
				stringBuilder.Append(text3);
				if (num4)
				{
					stringBuilder.Append('\r');
				}
				if (num2 < 0)
				{
					break;
				}
				stringBuilder.Append('\n');
				num = num2 + 1;
			}
			return stringBuilder.ToString();
		}

		private static string TopKey(string body)
		{
			if (body[0] == '\'' || body[0] == '"')
			{
				return Scalar(body);
			}
			for (int i = 0; i < body.Length; i++)
			{
				if (body[i] == ':' && (i + 1 == body.Length || body[i + 1] == ' '))
				{
					return body.Substring(0, i).Trim();
				}
			}
			return body.Trim();
		}

		private static string Scalar(string raw)
		{
			string text = raw.Trim();
			if (text.Length == 0)
			{
				return "";
			}
			if (text[0] == '\'')
			{
				StringBuilder stringBuilder = new StringBuilder();
				for (int i = 1; i < text.Length; i++)
				{
					if (text[i] != '\'')
					{
						stringBuilder.Append(text[i]);
						continue;
					}
					if (i + 1 >= text.Length || text[i + 1] != '\'')
					{
						break;
					}
					stringBuilder.Append('\'');
					i++;
				}
				return stringBuilder.ToString();
			}
			if (text[0] == '"')
			{
				StringBuilder stringBuilder2 = new StringBuilder();
				for (int j = 1; j < text.Length; j++)
				{
					char c = text[j];
					if (c == '"')
					{
						break;
					}
					if (c == '\\' && j + 1 < text.Length)
					{
						char c2 = text[++j];
						stringBuilder2.Append(c2 switch
						{
							't' => '\t', 
							'n' => '\n', 
							_ => c2, 
						});
					}
					else
					{
						stringBuilder2.Append(c);
					}
				}
				return stringBuilder2.ToString();
			}
			if (text[0] == '#')
			{
				return "";
			}
			int num = text.IndexOf(" #", StringComparison.Ordinal);
			return ((num < 0) ? text : text.Substring(0, num)).Trim();
		}
	}
}
namespace ExtraPotions.Beacon.Config
{
	public enum SettingScope
	{
		Local,
		ServerAuthoritative
	}
	public abstract class BeaconSetting
	{
		public string Section { get; }

		public string Key { get; }

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

		public SettingScope Scope { get; }

		public string Description { get; }

		public bool Hotkey { get; }

		public bool ServerLockable { get; }

		public abstract Type ValueType { get; }

		public abstract object DefaultValue { get; }

		public abstract object? Minimum { get; }

		public abstract object? Maximum { get; }

		private protected BeaconSetting(string section, string key, string description, bool hotkey, bool serverLockable, SettingScope scope)
		{
			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 (string.IsNullOrWhiteSpace(description))
			{
				throw new ArgumentException("Expected a description.", "description");
			}
			Section = section;
			Key = key;
			Description = description;
			Hotkey = hotkey;
			ServerLockable = serverLockable;
			Scope = scope;
		}

		public override string ToString()
		{
			return Id;
		}
	}
	public sealed class BeaconSetting<T> : BeaconSetting
	{
		private readonly Func<T, T> normalize;

		public T Default { get; }

		public override Type ValueType => typeof(T);

		public override object DefaultValue => Default;

		public override object? Minimum { get; }

		public override object? Maximum { get; }

		internal BeaconSetting(string section, string key, T defaultValue, string description, Func<T, T> normalize, object? minimum = null, object? maximum = null, bool hotkey = false, bool serverLockable = false, SettingScope scope = SettingScope.Local)
			: base(section, key, description, hotkey, serverLockable, scope)
		{
			Default = defaultValue;
			this.normalize = normalize;
			Minimum = minimum;
			Maximum = maximum;
		}

		public T Normalize(T value)
		{
			return normalize(value);
		}
	}
	public static class BeaconSettings
	{
		public static class General
		{
			public const string Section = "General";

			public static readonly BeaconSetting<bool> AddToFavorites = Toggle("General", "Add To Favorites", value: true, "Adds every saved server to the Favorite tab of the Join Game screen.");

			public static readonly BeaconSetting<bool> SkipPasswordPrompt = Toggle("General", "Skip Password Prompt", value: true, "Sends a saved server's stored password when the game asks for it, so the prompt is not shown.");

			public static readonly BeaconSetting<bool> OfferToSavePasswords = Toggle("General", "Offer To Save Passwords", value: true, "After a server accepts a password you typed, asks once per server whether Beacon should save it.");

			public static readonly BeaconSetting<bool> RemoveEntryWithFavorite = Toggle("General", "Remove Entry With Favorite", value: true, "Removing a saved server from the game's favorites also removes it from Beacon (with an undo message).");

			public static readonly BeaconSetting<string> PanelKey = Text("General", "Panel Key", "Keyboard shortcut that opens the Beacon panel. Empty: only the Join Game screen button opens it.", hotkey: true);
		}

		public static class AutoJoin
		{
			public const string Section = "Auto Join";

			public static readonly BeaconSetting<bool> Enabled = Toggle("Auto Join", "Auto Join", value: false, "Joins a saved server automatically after the game reaches the main menu, after a short countdown you can cancel.");

			public static readonly BeaconSetting<string> Server = Text("Auto Join", "Auto Join Server", "Name of the saved server to join. Empty: the server marked as default.");

			public static readonly BeaconSetting<string> Character = Text("Auto Join", "Auto Join Character", "Character to join with. Empty: the last character used.");

			public static readonly BeaconSetting<int> CountdownSeconds = Integer("Auto Join", "Auto Join Countdown Seconds", 5, 0, 30, "Seconds of countdown before the automatic join; Escape or the pad's B button cancels.");
		}

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

			public static readonly BeaconSetting<bool> Enabled = Toggle("Reconnect", "Reconnect On Disconnect", value: false, "Reconnects after an unexpected disconnect. Never after a kick or ban. A server running Beacon may turn this off.", lockable: true);

			public static readonly BeaconSetting<int> Attempts = Integer("Reconnect", "Reconnect Attempts", 3, 1, 10, "Most reconnect attempts after one disconnect.");
		}

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

			public static readonly BeaconSetting<LinuxFallback> LinuxFallback = new BeaconSetting<LinuxFallback>("Passwords", "Linux Fallback", ExtraPotions.Beacon.Secrets.LinuxFallback.SessionOnly, "On Linux without a usable keyring: SessionOnly remembers passwords until the game closes; Disabled never stores them. On Linux a change applies right away. On systems other than Windows and Linux, restart the game after changing it.", (LinuxFallback v) => Enum.IsDefined(typeof(LinuxFallback), v) ? v : ExtraPotions.Beacon.Secrets.LinuxFallback.SessionOnly);
		}

		public static class Server
		{
			public static readonly BeaconSetting<bool> AllowReconnect = new BeaconSetting<bool>("Server", "Allow Automatic Reconnect", defaultValue: true, "Server policy: false prevents Beacon clients from automatically reconnecting after an unexpected disconnect. True leaves each player's preference unchanged.", (bool v) => v, null, null, hotkey: false, serverLockable: false, SettingScope.ServerAuthoritative);
		}

		public static IReadOnlyList<BeaconSetting> All { get; } = Array.AsReadOnly(new BeaconSetting[13]
		{
			General.AddToFavorites,
			General.SkipPasswordPrompt,
			General.OfferToSavePasswords,
			General.RemoveEntryWithFavorite,
			General.PanelKey,
			AutoJoin.Enabled,
			AutoJoin.Server,
			AutoJoin.Character,
			AutoJoin.CountdownSeconds,
			Reconnect.Enabled,
			Reconnect.Attempts,
			Passwords.LinuxFallback,
			Server.AllowReconnect
		});

		private static BeaconSetting<bool> Toggle(string section, string key, bool value, string description, bool lockable = false)
		{
			return new BeaconSetting<bool>(section, key, value, description, (bool v) => v, null, null, hotkey: false, lockable);
		}

		private static BeaconSetting<int> Integer(string section, string key, int value, int min, int max, string description)
		{
			return new BeaconSetting<int>(section, key, value, description, (int v) => Math.Max(min, Math.Min(max, v)), min, max);
		}

		private static BeaconSetting<string> Text(string section, string key, string description, bool hotkey = false)
		{
			return new BeaconSetting<string>(section, key, "", description, (string v) => (v ?? "").Trim(), null, null, hotkey);
		}
	}
}

plugins/Beacon/Beacon.dll

Decompiled a week ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
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.Beacon.Config;
using ExtraPotions.Beacon.Conversion;
using ExtraPotions.Beacon.Integration;
using ExtraPotions.Beacon.Joining;
using ExtraPotions.Beacon.Secrets;
using ExtraPotions.Beacon.Servers;
using ExtraPotions.Meadworks.Crucible;
using ExtraPotions.Meadworks.Crucible.UI;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
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+4312080b663b50769f0cf52f9bd002c67886c90a")]
[assembly: AssemblyProduct("Beacon")]
[assembly: AssemblyTitle("Beacon")]
[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.Beacon.Runtime
{
	public sealed class BeaconCommand : ConsoleCommand
	{
		private static string? previewSourceHash;

		private static string? previewListHash;

		private static string? blankSourceHash;

		public override string Name => "beacon";

		public override string Help => "Beacon server favorites: status, join <name>, cancel, import preview, import apply, import blank-source confirm.";

		private static string SourcePath => Path.Combine(Paths.ConfigPath, "AutoConnector/servers.yaml".Replace('/', Path.DirectorySeparatorChar));

		internal static (string? Source, string? List) PreviewStamp => (Source: previewSourceHash, List: previewListHash);

		internal static bool CanBlankSource
		{
			get
			{
				if (blankSourceHash == null || BeaconImportSecrets.Busy)
				{
					return false;
				}
				try
				{
					string text = ReadSource();
					return text != null && blankSourceHash == Fingerprint(text);
				}
				catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
				{
					return false;
				}
			}
		}

		private static string Fingerprint(string text)
		{
			using SHA256 sHA = SHA256.Create();
			return Convert.ToBase64String(sHA.ComputeHash(Encoding.UTF8.GetBytes(text)));
		}

		public override void Run(string[] args)
		{
			try
			{
				string[] array = Execute(args ?? Array.Empty<string>());
				for (int i = 0; i < array.Length; i++)
				{
					Print(array[i]);
				}
			}
			catch (Exception ex)
			{
				Print("Beacon command failed: " + ex.GetType().Name);
			}
		}

		private static void Print(string line)
		{
			Console instance = Console.instance;
			if (instance != null)
			{
				instance.Print(BeaconSecrets.Redactor.Clean(line));
			}
		}

		private static string[] Execute(string[] args)
		{
			string text = ((args.Length == 0) ? "status" : args[0].ToLowerInvariant());
			string text2 = ((args.Length > 1) ? args[1].ToLowerInvariant() : "");
			MeadworksLog log = Plugin.Log;
			return text switch
			{
				"help" => new string[1] { BeaconText.Text("cmd_help") }, 
				"status" => new string[1] { Status() }, 
				"join" => new string[1] { Join(string.Join(" ", args.Skip(1))) }, 
				"cancel" => new string[1] { BeaconAutoJoin.Cancel(log) ?? BeaconText.Text("cmd_cancel_none") }, 
				"import" => text2 switch
				{
					"preview" => Preview(), 
					"apply" => Apply(log), 
					"blank-source" => Blank(args.Length > 2 && args[2].Equals("confirm", StringComparison.OrdinalIgnoreCase)), 
					_ => new string[1] { BeaconText.Text("cmd_usage") }, 
				}, 
				_ => new string[1] { BeaconText.Format("cmd_unknown_verb", text) }, 
			};
		}

		private static string Status()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			ISecretStore store = BeaconSecrets.Store;
			int num = BeaconServers.List.Entries.Count((ServerEntry e) => store.Contains(e.SecretRef));
			return BeaconText.Format("cmd_status", BeaconServers.List.Count, num, BeaconText.Text(SecretStores.StorageLineKey(store.Backend)));
		}

		private static string Join(string name)
		{
			if (BeaconImportSecrets.Busy)
			{
				return BeaconText.Text("import_password_pending");
			}
			ServerEntry val = BeaconServers.List.FindByName(name.Trim());
			if (val == null)
			{
				return BeaconText.Format("cmd_join_unknown", name.Trim());
			}
			if (!BeaconJoin.Active)
			{
				return BeaconText.Format("step_aside_join", BeaconModules.StepAside.Notices.Select((StepAsideNotice n) => ((StepAsideNotice)(ref n)).SourceName).FirstOrDefault() ?? "AutoConnector");
			}
			if (!BeaconMenu.Join(val.Address, null))
			{
				return BeaconText.Text("cmd_join_not_ready");
			}
			return BeaconText.Format("cmd_join_started", val.Name);
		}

		private static string? ReadSource()
		{
			if (!File.Exists(SourcePath))
			{
				return null;
			}
			return File.ReadAllText(SourcePath, Encoding.UTF8);
		}

		internal static string[] Preview()
		{
			string text = ReadSource();
			if (text == null)
			{
				return new string[1] { BeaconText.Text("import_none") };
			}
			IReadOnlyList<ImportedServer> readOnlyList = AutoConnectorImport.Parse(text);
			foreach (ImportedServer item in readOnlyList)
			{
				BeaconSecrets.Redactor.Track(item.Password);
			}
			try
			{
				string[] result = ((IEnumerable<string>)AutoConnectorImport.Preview((IEnumerable<ImportPlanItem>)AutoConnectorImport.Plan(BeaconServers.List, (IEnumerable<ImportedServer>)readOnlyList))).Select((Func<string, string>)BeaconSecrets.Redactor.Clean).ToArray();
				previewSourceHash = Fingerprint(text);
				previewListHash = Fingerprint(ServerListCodec.Write(BeaconServers.List));
				return result;
			}
			finally
			{
				foreach (ImportedServer item2 in readOnlyList)
				{
					BeaconSecrets.Redactor.Untrack(item2.Password);
				}
			}
		}

		internal static string[] Apply(MeadworksLog? log)
		{
			string text = ReadSource();
			if (text == null || log == null)
			{
				return new string[1] { BeaconText.Text("import_none") };
			}
			if (BeaconImportSecrets.Busy)
			{
				return new string[1] { BeaconText.Text("import_password_pending") };
			}
			if (previewSourceHash != Fingerprint(text) || previewListHash != Fingerprint(ServerListCodec.Write(BeaconServers.List)))
			{
				return new string[1] { BeaconText.Text("import_preview_again") };
			}
			IReadOnlyList<ImportedServer> readOnlyList = AutoConnectorImport.Parse(text);
			foreach (ImportedServer item in readOnlyList)
			{
				BeaconSecrets.Redactor.Track(item.Password);
			}
			try
			{
				IReadOnlyList<ImportPlanItem> plan = AutoConnectorImport.Plan(BeaconServers.List, (IEnumerable<ImportedServer>)readOnlyList);
				ImportResult result = null;
				if (!BeaconServers.Amend(delegate(ServerList candidate)
				{
					//IL_0002: Unknown result type (might be due to invalid IL or missing references)
					//IL_0012: Expected O, but got Unknown
					result = AutoConnectorImport.Apply(candidate, (ISecretStore)new DisabledSecretStore(), (IEnumerable<ImportPlanItem>)plan);
				}, log))
				{
					return new string[1] { BeaconText.Text("import_write_failed") };
				}
				blankSourceHash = previewSourceHash;
				previewSourceHash = (previewListHash = null);
				foreach (ImportPlanItem item2 in plan.Where((ImportPlanItem p) => (int)p.Outcome == 0 && p.Server.Password != null))
				{
					ServerEntry val = BeaconServers.List.FindByName(item2.Server.Name);
					if (val != null)
					{
						BeaconImportSecrets.Start(val.SecretRef, val.Name, item2.Server.Password);
					}
				}
				return new string[1] { BeaconText.Format("import_done", result.Added, result.Skipped) }.Concat((!BeaconImportSecrets.Busy) ? ((IEnumerable<string>)Array.Empty<string>()) : ((IEnumerable<string>)new string[1] { BeaconText.Text("import_password_pending") })).Concat(new string[2]
				{
					BeaconText.Text("import_source_plain"),
					BeaconText.Text("cmd_confirm_blank")
				}).Select((Func<string, string>)BeaconSecrets.Redactor.Clean)
					.ToArray();
			}
			finally
			{
				foreach (ImportedServer item3 in readOnlyList)
				{
					BeaconSecrets.Redactor.Untrack(item3.Password);
				}
			}
		}

		internal static string[] Blank(bool confirmed)
		{
			if (!BeaconImportSecrets.Busy)
			{
				string text = ReadSource();
				if (text != null)
				{
					if (confirmed)
					{
						if (blankSourceHash != null && !(blankSourceHash != Fingerprint(text)))
						{
							int num = default(int);
							string text2 = AutoConnectorImport.BlankPasswords(text, ref num);
							if (num > 0)
							{
								BeaconServers.WriteAtomically(SourcePath, text2);
							}
							ResetPreview();
							return new string[1] { BeaconText.Format("import_blank_done", num) };
						}
						return new string[1] { BeaconText.Text("import_preview_again") };
					}
					return new string[1] { BeaconText.Text("cmd_confirm_blank") };
				}
				return new string[1] { BeaconText.Text("import_none") };
			}
			return new string[1] { BeaconText.Text("import_password_pending") };
		}

		internal static void ResetPreview()
		{
			previewSourceHash = (previewListHash = (blankSourceHash = null));
		}
	}
	public sealed class BeaconConfig
	{
		private readonly ConfigFile file;

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

		private readonly BeaconSyncReceipt receipt = new BeaconSyncReceipt();

		private long policyGeneration = -1L;

		internal ReconnectServerPolicy ReconnectPolicy { get; } = new ReconnectServerPolicy();

		public ConfigFile File => file;

		private BeaconConfig(ConfigFile file)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			this.file = file;
			bool saveOnConfigSet = file.SaveOnConfigSet;
			file.SaveOnConfigSet = false;
			try
			{
				foreach (BeaconSetting item in BeaconSettings.All)
				{
					BindSetting(item);
				}
			}
			finally
			{
				file.SaveOnConfigSet = saveOnConfigSet;
			}
			file.Save();
			SynchronizationManager.OnConfigurationSynchronized += OnSynchronized;
			MeadworksBlockedOptions.Track("com.meadworks.beacon", Entry((BeaconSetting)(object)Server.AllowReconnect), (ConfigScope)1, true);
		}

		public static BeaconConfig Bind(ConfigFile file, MeadworksLog log)
		{
			if (file == null)
			{
				throw new ArgumentNullException("file");
			}
			if (log == null)
			{
				throw new ArgumentNullException("log");
			}
			BeaconConfig result = new BeaconConfig(file);
			log.Verbose("Configuration bound: " + (BeaconSettings.All.Count - 1) + " Local settings, one server policy, no cheats.");
			return result;
		}

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

		public T Get<T>(BeaconSetting<T> setting)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			if (((BeaconSetting)setting).Hotkey)
			{
				throw new ArgumentException(((BeaconSetting)setting).Id + " is a keyboard shortcut; use Shortcut.", "setting");
			}
			ConfigEntry<T> val = (ConfigEntry<T>)(object)Entry((BeaconSetting)(object)setting);
			return setting.Normalize(((int)((BeaconSetting)setting).Scope == 0) ? val.Value : MeadworksConfig.Effective<T>(val, "com.meadworks.beacon", (ConfigScope)1, setting.Default, true));
		}

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

		internal void BeginReconnectConnection(ServerAddress? target)
		{
			receipt.Reset();
			policyGeneration = -1L;
			ReconnectPolicy.Begin(target);
		}

		internal void RefreshReconnectPolicy()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Invalid comparison between Unknown and I4
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Invalid comparison between Unknown and I4
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: 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_0097: Invalid comparison between Unknown and I4
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Invalid comparison between Unknown and I4
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Invalid comparison between Unknown and I4
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Invalid comparison between Unknown and I4
			ServerAddress target = ReconnectPolicy.Target;
			if (target == (ServerAddress)null || !Plugin.IsReady || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || (int)ZNet.GetConnectionStatus() != 2)
			{
				return;
			}
			SessionSnapshot session = Plugin.Session;
			if ((int)session.Role != 3)
			{
				return;
			}
			if (session.AuthorityReady)
			{
				if (policyGeneration >= 0 && policyGeneration != session.Generation)
				{
					ReconnectPolicy.Begin(target);
				}
				policyGeneration = session.Generation;
			}
			ServerProduct val = MeadworksAuthority.ServerProductOf("com.meadworks.beacon");
			if ((int)MeadworksAuthority.Server == 2 || (session.AuthorityReady && (int)val == 2))
			{
				ReconnectPolicy.Observe(target, true, true);
			}
			else if ((int)val == 1 && receipt.CurrentGeneration == session.Generation && (int)MeadworksAuthority.Decide("com.meadworks.beacon", (ConfigScope)1, true).Source == 1)
			{
				ReconnectPolicy.Observe(target, true, Get<bool>(Server.AllowReconnect));
			}
		}

		private void OnSynchronized(object sender, ConfigurationSynchronizationEventArgs args)
		{
			if (args.UpdatedPluginGUIDs != null && args.UpdatedPluginGUIDs.Contains("com.meadworks.beacon"))
			{
				receipt.Observe();
				RefreshReconnectPolicy();
			}
		}

		public void Shutdown()
		{
			SynchronizationManager.OnConfigurationSynchronized -= OnSynchronized;
			MeadworksBlockedOptions.Untrack(Entry((BeaconSetting)(object)Server.AllowReconnect));
			receipt.Reset();
			policyGeneration = -1L;
			ReconnectPolicy.Reset();
			entries.Clear();
		}

		private void BindSetting(BeaconSetting 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)
			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, (ConfigScope)0, (AcceptableValueBase)null);
			}
			else if (!(setting is BeaconSetting<bool> setting2))
			{
				if (!(setting is BeaconSetting<int> val))
				{
					if (!(setting is BeaconSetting<string> setting3))
					{
						if (!(setting is BeaconSetting<LinuxFallback> setting4))
						{
							throw new NotSupportedException(setting.Id + ": unsupported value type " + setting.ValueType.Name + ".");
						}
						value = (ConfigEntryBase)(object)Bind<LinuxFallback>(setting4, null);
					}
					else
					{
						value = (ConfigEntryBase)(object)Bind<string>(setting3, null);
					}
				}
				else
				{
					value = (ConfigEntryBase)(object)Bind<int>(val, (AcceptableValueBase?)(object)new AcceptableValueRange<int>((int)((BeaconSetting)val).Minimum, (int)((BeaconSetting)val).Maximum));
				}
			}
			else
			{
				value = (ConfigEntryBase)(object)Bind<bool>(setting2, null);
			}
			entries.Add(setting, value);
		}

		private ConfigDescription Describe(BeaconSetting setting, AcceptableValueBase? acceptable, string? text = null)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			ConfigDependency val = null;
			if ((object)setting == AutoJoin.Server || (object)setting == AutoJoin.Character || (object)setting == AutoJoin.CountdownSeconds)
			{
				val = new ConfigDependency((Func<bool>)(() => Get<bool>(AutoJoin.Enabled)), (Func<string>)(() => "Requires " + ((BeaconSetting)AutoJoin.Enabled).Section + ": " + ((BeaconSetting)AutoJoin.Enabled).Key + "."), (Func<bool>)null);
			}
			if ((object)setting == Reconnect.Attempts)
			{
				val = new ConfigDependency((Func<bool>)(() => Get<bool>(Reconnect.Enabled)), (Func<string>)(() => "Requires " + ((BeaconSetting)Reconnect.Enabled).Section + ": " + ((BeaconSetting)Reconnect.Enabled).Key + "."), (Func<bool>)null);
			}
			return new ConfigDescription(text ?? setting.Description, acceptable, (val == null) ? Array.Empty<object>() : new object[1] { val });
		}

		private ConfigEntry<T> Bind<T>(BeaconSetting<T> setting, AcceptableValueBase? acceptable)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			return MeadworksConfig.Bind<T>(file, ((BeaconSetting)setting).Section, ((BeaconSetting)setting).Key, setting.Default, Describe((BeaconSetting)(object)setting, acceptable), (ConfigScope)(((int)((BeaconSetting)setting).Scope != 0) ? 1 : 0));
		}
	}
	internal sealed class BeaconSyncReceipt
	{
		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 Reset()
		{
			network = null;
			server = null;
			epoch = "";
			world = 0L;
		}

		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;
				}
			}
		}
	}
	internal static class BeaconConversion
	{
		private sealed class Report
		{
			internal readonly string[] Lines;

			internal readonly (string? Source, string? List) Stamp;

			internal Report(string[] lines, (string?, string?) stamp)
			{
				Lines = lines;
				Stamp = stamp;
			}

			public override string ToString()
			{
				return string.Join("\n", Lines);
			}
		}

		private static IDisposable? registration;

		private static string SourcePath => Path.Combine(Paths.ConfigPath, "AutoConnector/servers.yaml".Replace('/', Path.DirectorySeparatorChar));

		public static void Register()
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			if (registration == null)
			{
				registration = MeadworksConversion.Register(new ConversionRegistration("beacon.autoconnector", "com.meadworks.beacon", "MidnightsFX.Autoconnect", "AutoConnector", (Func<bool>)(() => Plugin.Ready && !BeaconImportSecrets.Busy && File.Exists(SourcePath)), (Func<object>)(() => new Report(BeaconCommand.Preview(), BeaconCommand.PreviewStamp)), (Func<object, IReadOnlyDictionary<string, string>, bool, object>)Apply));
			}
		}

		private static object Apply(object document, IReadOnlyDictionary<string, string> choices, bool backup)
		{
			if (!(document is Report report))
			{
				throw new ArgumentException("Wrong Beacon import preview.");
			}
			if (report.Stamp.Source != null)
			{
				(string, string) stamp = report.Stamp;
				(string, string) previewStamp = BeaconCommand.PreviewStamp;
				if (!(stamp.Item1 != previewStamp.Item1) && !(stamp.Item2 != previewStamp.Item2))
				{
					if (backup && File.Exists(BeaconServers.FilePath))
					{
						File.Copy(BeaconServers.FilePath, BeaconServers.FilePath + ".bak", overwrite: true);
					}
					return new Report(BeaconCommand.Apply(Plugin.Log), default((string, string)));
				}
			}
			return new Report(new string[1] { BeaconText.Text("import_preview_again") }, default((string, string)));
		}

		public static void Reset()
		{
			registration?.Dispose();
			registration = null;
		}
	}
	public static class BeaconModules
	{
		private const string TranslationProduct = "beacon";

		private static ModuleStepAsideDecision? stepAside;

		private static long noticedSession = -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.beacon").Any())
			{
				MeadworksStepAside.Declare("com.meadworks.beacon", "Beacon", ((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("Beacon " + ((object)((StepAsideNotice)(ref current)).Module/*cast due to .constrained prefix*/).ToString().ToLowerInvariant() + " module steps aside: " + ((StepAsideNotice)(ref current)).SourceName + " is installed.");
			}
		}

		public static bool Active(BeaconModule module)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Ready)
			{
				return !StepAside.SteppedAside(module);
			}
			return false;
		}

		public static void Tick(MeadworksLog log)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: 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_005f: 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)
			//IL_0096: Expected O, but got Unknown
			if (!Plugin.Ready || stepAside == null || !stepAside.Any)
			{
				return;
			}
			SessionSnapshot session = Plugin.Session;
			if (noticedSession == session.Generation)
			{
				return;
			}
			noticedSession = session.Generation;
			foreach (StepAsideNotice notice in stepAside.Notices)
			{
				StepAsideNotice current = notice;
				MeadworksStepAside.ReportModule("com.meadworks.beacon", ((object)((StepAsideNotice)(ref current)).Module/*cast due to .constrained prefix*/).ToString().ToLowerInvariant(), new SourceMod(((StepAsideNotice)(ref current)).SourceGuid, ((StepAsideNotice)(ref current)).SourceName), "beacon", ((StepAsideNotice)(ref current)).Key);
			}
		}

		public static void Reset()
		{
			stepAside = null;
			noticedSession = -1L;
		}
	}
	public static class BeaconText
	{
		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(BeaconText).Assembly;
			foreach (string item in MeadworksLanguages.Supported)
			{
				using Stream stream = assembly.GetManifestResourceStream("Beacon.localization." + item + ".json");
				if (stream != null)
				{
					using StreamReader streamReader = new StreamReader(stream);
					dictionary.Add(item, streamReader.ReadToEnd());
				}
			}
			Translations = new ProductTranslations("beacon", (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);
			}
		}
	}
	public static class BeaconAutoJoin
	{
		private static readonly ReconnectSession reconnect = new ReconnectSession();

		private static AutoJoinCountdown? countdown;

		private static ServerEntry? countdownServer;

		private static int countdownCharacter = -1;

		private static bool autoJoinDecided;

		private static bool subscribed;

		private static int reconnectAttempt;

		private static int reconnectLimit;

		private static double Now => Time.realtimeSinceStartup;

		public static bool Waiting
		{
			get
			{
				if (countdown == null)
				{
					return reconnect.Pending;
				}
				return true;
			}
		}

		public static void Initialize()
		{
			if (!subscribed)
			{
				BeaconJoin.JoinStarted += OnJoinStarted;
				BeaconJoin.ReturnedToMenu += OnMenu;
				BeaconJoin.Connected += OnConnected;
				subscribed = true;
			}
		}

		public static string? Cancel(MeadworksLog? log)
		{
			autoJoinDecided = true;
			string text = null;
			if (countdown != null)
			{
				countdown.Cancel();
				countdown = null;
				countdownServer = null;
				text = BeaconText.Text("autojoin_canceled");
			}
			else if (reconnect.Cancel())
			{
				text = BeaconText.Text("reconnect_canceled");
			}
			if (text != null && log != null)
			{
				log.Info(text);
			}
			BeaconJoinCard.Remove();
			return text;
		}

		public static void Tick(MeadworksLog log)
		{
			if (!BeaconJoin.Active || Plugin.Settings == null)
			{
				return;
			}
			Plugin.Settings.RefreshReconnectPolicy();
			if ((countdown != null || reconnect.Pending) && CancelPressed())
			{
				Cancel(log);
			}
			if (BeaconImportSecrets.Busy)
			{
				return;
			}
			if (reconnect.Pending && BeaconMenu.Ready && reconnect.Tick(Now))
			{
				ServerAddress target = reconnect.Target;
				if (target == (ServerAddress)null || !BeaconMenu.Join(target, null))
				{
					reconnect.Cancel();
				}
			}
			if (!autoJoinDecided && BeaconMenu.Ready && !BeaconJoin.Joining && !reconnect.Pending)
			{
				StartCountdown(log);
			}
			if (countdown != null)
			{
				TickCountdown();
			}
			if (countdown != null && countdownServer != null)
			{
				BeaconJoinCard.Show(JoinCard.Countdown(countdownServer.Name, countdown.SecondsShown));
			}
			else if (reconnect.Pending)
			{
				BeaconJoinCard.Show(JoinCard.Reconnect(reconnectAttempt, reconnectLimit, reconnect.SecondsLeft(Now)));
			}
			else
			{
				BeaconJoinCard.Remove();
			}
		}

		private static void StartCountdown(MeadworksLog log)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			//IL_0066: 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_007e: Invalid comparison between Unknown and I4
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			autoJoinDecided = true;
			BeaconConfig settings = Plugin.Settings;
			string text = settings.Get<string>(AutoJoin.Server);
			AutoJoinDecision val = AutoJoinRules.Decide(settings.Get<bool>(AutoJoin.Enabled), text, BeaconServers.List, AutoJoinRules.LaunchJoins((IReadOnlyList<string>)Environment.GetCommandLineArgs()), false);
			if ((int)((AutoJoinDecision)(ref val)).Skip == 5)
			{
				log.Info(BeaconText.Format("autojoin_unknown_server", text.Trim()));
				return;
			}
			if ((int)((AutoJoinDecision)(ref val)).Skip != 0 || ((AutoJoinDecision)(ref val)).Target == null)
			{
				if ((int)((AutoJoinDecision)(ref val)).Skip != 1)
				{
					log.Verbose("Beacon auto-join skipped: " + ((object)((AutoJoinDecision)(ref val)).Skip/*cast due to .constrained prefix*/).ToString() + ".");
				}
				return;
			}
			string text2 = settings.Get<string>(AutoJoin.Character);
			(IReadOnlyList<string> Names, IReadOnlyList<string> Files, int Selected) tuple = BeaconMenu.Characters();
			IReadOnlyList<string> item = tuple.Names;
			IReadOnlyList<string> item2 = tuple.Files;
			int item3 = tuple.Selected;
			int num = AutoJoinRules.PickCharacter(item, item2, text2, item3);
			if (num < 0)
			{
				log.Info(BeaconText.Format("autojoin_unknown_character", text2.Trim()));
				return;
			}
			countdown = new AutoJoinCountdown(settings.Get<int>(AutoJoin.CountdownSeconds));
			countdownServer = ((AutoJoinDecision)(ref val)).Target;
			countdownCharacter = num;
			log.Info(BeaconText.Format("autojoin_countdown", ((AutoJoinDecision)(ref val)).Target.Name, countdown.SecondsShown));
		}

		private static void TickCountdown()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			if ((int)countdown.Tick(Time.unscaledDeltaTime) == 1)
			{
				ServerEntry val = countdownServer;
				int value = countdownCharacter;
				countdown = null;
				countdownServer = null;
				if (val != null)
				{
					BeaconMenu.Join(val.Address, value);
				}
			}
		}

		private static bool CancelPressed()
		{
			if (MeadworksInput.Typing)
			{
				return false;
			}
			if (!MeadworksInput.PadDown((PadButton)1))
			{
				return false;
			}
			MeadworksInput.PadConsume((PadButton)1);
			return true;
		}

		private static void OnJoinStarted(ServerAddress? target)
		{
			Plugin.Settings?.BeginReconnectConnection(target);
			autoJoinDecided = true;
			countdown = null;
			countdownServer = null;
			BeaconJoinCard.Remove();
			reconnect.OnJoinStarted(target);
		}

		private static void OnConnected(ServerAddress target)
		{
			reconnect.OnConnected(Now);
		}

		private static void OnMenu(MenuOutcome outcome)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			//IL_0047: 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)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: 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_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected I4, but got Unknown
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Invalid comparison between Unknown and I4
			if (((MenuOutcome)(ref outcome)).Target == (ServerAddress)null || Plugin.Settings == null)
			{
				return;
			}
			if ((int)((MenuOutcome)(ref outcome)).Step == 1)
			{
				reconnect.Cancel();
				return;
			}
			BeaconConfig settings = Plugin.Settings;
			settings.RefreshReconnectPolicy();
			ReconnectPlan val = reconnect.OnDisconnected(((MenuOutcome)(ref outcome)).Target, ((MenuOutcome)(ref outcome)).Kind, ((MenuOutcome)(ref outcome)).WasConnected, settings.Get<bool>(Reconnect.Enabled), settings.ReconnectPolicy.LockedFor(((MenuOutcome)(ref outcome)).Target), settings.Get<int>(Reconnect.Attempts), Now);
			MeadworksLog log = Plugin.Log;
			ReconnectStep step = ((ReconnectPlan)(ref val)).Step;
			switch (step - 1)
			{
			case 0:
				reconnectAttempt = ((ReconnectPlan)(ref val)).Attempt;
				reconnectLimit = ((ReconnectPlan)(ref val)).Limit;
				if (log != null)
				{
					log.Info(BeaconText.Format("reconnecting", ((ReconnectPlan)(ref val)).Attempt, ((ReconnectPlan)(ref val)).Limit));
				}
				break;
			case 1:
				if ((int)((ReconnectPlan)(ref val)).Decision == 4 && log != null)
				{
					log.Info(BeaconText.Format("reconnect_stopped", ((ReconnectPlan)(ref val)).Limit));
				}
				break;
			case 2:
				if (log != null)
				{
					log.Info(BeaconText.Text("reconnect_locked"));
				}
				break;
			}
		}

		public static void Reset()
		{
			if (subscribed)
			{
				BeaconJoin.JoinStarted -= OnJoinStarted;
				BeaconJoin.ReturnedToMenu -= OnMenu;
				BeaconJoin.Connected -= OnConnected;
				subscribed = false;
			}
			reconnect.Cancel();
			BeaconJoinCard.Remove();
			countdown = null;
			countdownServer = null;
			countdownCharacter = -1;
			autoJoinDecided = false;
		}
	}
	public static class BeaconJoin
	{
		private static readonly JoinCoordinator coordinator = new JoinCoordinator();

		private static string? sentStored;

		private static string? typed;

		private static bool injected;

		private static bool suppressCapture;

		private static ServerAddress? rejoinForPassword;

		private static CancellationTokenSource? readCancel;

		private static Task<SecretReadResult>? read;

		private static LinuxKeyringStore? readLinux;

		private static long readFallbackRevision;

		private static ZNet? deferredNet;

		private static ZRpc? deferredRpc;

		private static string? deferredSalt;

		private static ServerAddress? deferredTarget;

		private static string? resumedSecret;

		private static bool resuming;

		private static IDisposable? saveOperation;

		private static string? savingSecret;

		internal static JoinCoordinator Coordinator => coordinator;

		public static bool Active => BeaconModules.Active((BeaconModule)0);

		public static bool Joining => coordinator.AwaitingConnection;

		internal static event Action<ServerAddress?>? JoinStarted;

		internal static event Action<MenuOutcome>? ReturnedToMenu;

		internal static event Action<ServerAddress>? Connected;

		internal static void OnJoinServer(ServerJoinData data)
		{
			//IL_000f: 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)
			if (Active && Plugin.Settings != null)
			{
				ServerAddress val = TargetOf(data);
				if (val != coordinator.Target)
				{
					ClearRead();
				}
				BeaconConfig settings = Plugin.Settings;
				coordinator.OnJoinStarted(val, BeaconServers.List, (Func<ServerEntry, bool>)((ServerEntry e) => BeaconSecrets.Store is IAsyncSecretStore || BeaconSecrets.Store.Contains(e.SecretRef)), new JoinOptions(settings.Get<bool>(General.SkipPasswordPrompt), settings.Get<bool>(General.OfferToSavePasswords)));
				BeaconJoin.JoinStarted?.Invoke(val);
			}
		}

		internal static void BeforeHandshake(bool needPassword)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Invalid comparison between Unknown and I4
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Invalid comparison between Unknown and I4
			injected = false;
			suppressCapture = false;
			if (!needPassword || !Active)
			{
				return;
			}
			if (FejdStartup.ServerPassword != null)
			{
				suppressCapture = true;
			}
			else if ((int)coordinator.OnPasswordRequested() == 1)
			{
				ServerEntry entry = coordinator.Entry;
				ISecretStore store = BeaconSecrets.Store;
				string text = resumedSecret;
				bool flag = resumedSecret != null || (!(store is IAsyncSecretStore) && entry != null && store.TryRead(entry.SecretRef, ref text));
				if (entry == null || !flag || string.IsNullOrEmpty(text))
				{
					coordinator.OnStoredUnreadable();
					Report(((int)store.LastFailure == 1 && entry != null) ? BeaconText.Format("secret_stored_elsewhere", entry.Name) : BeaconText.Text("secret_store_failed"));
				}
				else
				{
					BeaconSecrets.Redactor.Track(text);
					sentStored = text;
					suppressCapture = true;
					injected = true;
					FejdStartup.ServerPassword = text;
				}
			}
		}

		internal static void AfterHandshake()
		{
			if (injected)
			{
				FejdStartup.ServerPassword = null;
			}
			injected = false;
			suppressCapture = false;
		}

		internal static void BeforePasswordEntered(string password)
		{
			if (!suppressCapture && Active && !string.IsNullOrEmpty(password) && !(coordinator.Target == (ServerAddress)null))
			{
				ForgetTyped();
				typed = password;
				BeaconSecrets.Redactor.Track(password);
				coordinator.OnPlayerTyped();
			}
		}

		internal static void OnMenu(GameStatus status)
		{
			//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_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Invalid comparison between Unknown and I4
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			ResetAsyncSecrets();
			AfterHandshake();
			ForgetSecrets();
			MenuOutcome obj = coordinator.OnReturnedToMenu(status);
			if (!(((MenuOutcome)(ref obj)).Target == (ServerAddress)null))
			{
				if ((int)((MenuOutcome)(ref obj)).Step == 1)
				{
					rejoinForPassword = ((MenuOutcome)(ref obj)).Target;
				}
				BeaconJoin.ReturnedToMenu?.Invoke(obj);
			}
		}

		public static void Tick(MeadworksLog log)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			if (!Active)
			{
				ResumeWithoutSecret();
				ResetAsyncSecrets();
				return;
			}
			TickHandshake();
			if (coordinator.AwaitingConnection && (Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer() && (int)ZNet.GetConnectionStatus() == 2)
			{
				Accept(log);
			}
			if (rejoinForPassword != (ServerAddress)null && BeaconMenu.Ready)
			{
				ServerAddress? address = rejoinForPassword;
				rejoinForPassword = null;
				BeaconMenu.Join(address, null);
			}
		}

		private static void Accept(MeadworksLog log)
		{
			//IL_001b: 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_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Invalid comparison between Unknown and I4
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Invalid comparison between Unknown and I4
			ServerAddress target = coordinator.Target;
			ServerEntry entry = coordinator.Entry;
			JoinAction val = coordinator.OnConnected();
			if ((int)val == 4 && entry != null && typed != null)
			{
				if (BeaconSecrets.Store is IAsyncSecretStore)
				{
					SaveTypedAsync(entry.SecretRef, typed, entry.Name);
				}
				else if (BeaconSecrets.Store.Store(entry.SecretRef, typed))
				{
					log.Info(BeaconSecrets.Redactor.Clean(BeaconText.Format("password_saved", entry.Name)));
				}
				else
				{
					Report(BeaconText.Text("secret_store_failed"));
				}
			}
			string text = (((int)val == 5) ? typed : null);
			ForgetSecrets();
			if (!string.IsNullOrEmpty(text))
			{
				BeaconSaveOffer.Offer(target, text);
			}
			BeaconJoin.Connected?.Invoke(target);
		}

		public static ServerAddress? TargetOf(ServerJoinData data)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected I4, but got Unknown
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			ServerJoinDataType type = data.m_type;
			switch (type - 1)
			{
			case 2:
			{
				ServerJoinDataDedicated dedicated = ((ServerJoinData)(ref data)).Dedicated;
				return JoinTargets.FromHost(((ServerJoinDataDedicated)(ref dedicated)).m_host, (int)((ServerJoinData)(ref data)).Dedicated.m_port);
			}
			case 0:
				return JoinTargets.FromSteam(((ServerJoinData)(ref data)).SteamUser.m_joinUserID.m_SteamID);
			case 1:
				return JoinTargets.FromPlayFab(((ServerJoinData)(ref data)).PlayFabUser.m_remotePlayerId);
			default:
				return null;
			}
		}

		public static bool TryJoinData(ServerAddress address, out ServerJoinData data)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected I4, but got Unknown
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: 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_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_0062: Unknown result type (might be due to invalid IL or missing references)
			data = ServerJoinData.None;
			AddressKind kind = address.Kind;
			switch ((int)kind)
			{
			case 0:
				data = new ServerJoinData(new ServerJoinDataDedicated(address.Host, (ushort)address.Port));
				return ((ServerJoinData)(ref data)).IsValid;
			case 1:
			{
				ulong num = default(ulong);
				if (!JoinTargets.TryGetSteamId(address, ref num))
				{
					return false;
				}
				data = new ServerJoinData(new ServerJoinDataSteamUser(num));
				return ((ServerJoinData)(ref data)).IsValid;
			}
			case 2:
				data = new ServerJoinData(new ServerJoinDataPlayFabUser(address.Host));
				return ((ServerJoinData)(ref data)).IsValid;
			default:
				return false;
			}
		}

		private static void Report(string text)
		{
			MeadworksLog? log = Plugin.Log;
			if (log != null)
			{
				log.Warning(BeaconSecrets.Redactor.Clean(text));
			}
		}

		private static void ForgetTyped()
		{
			BeaconSecrets.Redactor.Untrack(typed);
			typed = null;
		}

		private static void ForgetSecrets()
		{
			ForgetTyped();
			BeaconSecrets.Redactor.Untrack(sentStored);
			sentStored = null;
		}

		public static void Reset()
		{
			ResetAsyncSecrets();
			AfterHandshake();
			ForgetSecrets();
			coordinator.Reset();
			rejoinForPassword = null;
			BeaconJoin.JoinStarted = null;
			BeaconJoin.ReturnedToMenu = null;
			BeaconJoin.Connected = null;
		}

		internal static bool DeferHandshake(ZNet net, ZRpc rpc, bool needPassword, string salt)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Invalid comparison between Unknown and I4
			if (!resuming && (!Active || FejdStartup.ServerPassword != null) && read != null)
			{
				ClearRead();
				coordinator.OnStoredUnreadable();
			}
			if (!resuming && needPassword && Active && FejdStartup.ServerPassword == null)
			{
				ISecretStore store = BeaconSecrets.Store;
				IAsyncSecretStore val = (IAsyncSecretStore)(object)((store is IAsyncSecretStore) ? store : null);
				if (val != null && coordinator.Entry != null)
				{
					if (read != null && (Object)(object)deferredNet == (Object)(object)net && deferredRpc == rpc)
					{
						return true;
					}
					if ((int)coordinator.OnPasswordRequested() != 1)
					{
						return false;
					}
					ClearRead();
					readCancel = new CancellationTokenSource();
					readLinux = (LinuxKeyringStore?)(object)((val is LinuxKeyringStore) ? val : null);
					LinuxKeyringStore? obj = readLinux;
					readFallbackRevision = ((obj != null) ? obj.FallbackRevision : 0);
					read = val.ReadAsync(coordinator.Entry.SecretRef, readCancel.Token);
					deferredNet = net;
					deferredRpc = rpc;
					deferredSalt = salt;
					deferredTarget = coordinator.Target;
					return true;
				}
			}
			return false;
		}

		private static void TickHandshake()
		{
			//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_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			if (read == null)
			{
				return;
			}
			if (!coordinator.AwaitingConnection || deferredTarget != coordinator.Target || (Object)(object)deferredNet == (Object)null || (Object)(object)deferredNet != (Object)(object)ZNet.instance || deferredRpc == null || !deferredRpc.IsConnected())
			{
				ClearRead();
			}
			else
			{
				if (!read.IsCompleted)
				{
					return;
				}
				SecretReadResult val = (SecretReadResult)((!read.IsCanceled && !read.IsFaulted && (readLinux == null || readLinux.IsReadCurrent(readFallbackRevision))) ? read.Result : default(SecretReadResult));
				ZNet val2 = deferredNet;
				ZRpc val3 = deferredRpc;
				string text = deferredSalt;
				ClearRead();
				bool flag = FejdStartup.ServerPassword != null;
				if (flag)
				{
					coordinator.OnStoredUnreadable();
				}
				if (!flag && ((SecretReadResult)(ref val)).Found && !string.IsNullOrEmpty(((SecretReadResult)(ref val)).Secret))
				{
					resumedSecret = ((SecretReadResult)(ref val)).Secret;
					BeaconSecrets.Redactor.Track(resumedSecret);
				}
				else if (!flag)
				{
					coordinator.OnStoredUnreadable();
					if ((int)BeaconSecrets.Store.LastFailure != 0)
					{
						Report(BeaconText.Text("secret_store_failed"));
					}
				}
				try
				{
					resuming = true;
					val2.RPC_ClientHandshake(val3, true, text);
				}
				catch (Exception ex)
				{
					MeadworksLog? log = Plugin.Log;
					if (log != null)
					{
						log.Warning("Beacon resumed handshake: " + ex.GetType().Name);
					}
				}
				finally
				{
					resuming = false;
					BeaconSecrets.Redactor.Untrack(resumedSecret);
					resumedSecret = null;
				}
			}
		}

		private static void ResumeWithoutSecret()
		{
			if (read == null)
			{
				return;
			}
			bool num = coordinator.AwaitingConnection && deferredTarget == coordinator.Target && (Object)(object)deferredNet != (Object)null && (Object)(object)deferredNet == (Object)(object)ZNet.instance && deferredRpc != null && deferredRpc.IsConnected();
			ZNet val = deferredNet;
			ZRpc val2 = deferredRpc;
			string text = deferredSalt;
			ClearRead();
			coordinator.OnStoredUnreadable();
			if (!num)
			{
				return;
			}
			try
			{
				resuming = true;
				val.RPC_ClientHandshake(val2, true, text);
			}
			catch (Exception ex)
			{
				MeadworksLog? log = Plugin.Log;
				if (log != null)
				{
					log.Warning("Beacon native handshake fallback: " + ex.GetType().Name);
				}
			}
			finally
			{
				resuming = false;
			}
		}

		private static void SaveTypedAsync(string reference, string secret, string name)
		{
			ClearSave();
			savingSecret = secret;
			BeaconSecrets.Redactor.Track(secret);
			saveOperation = BeaconSecretActions.Run((CancellationToken cancel) => BeaconSecretActions.Store(reference, secret, cancel), delegate(bool saved)
			{
				if (saved)
				{
					MeadworksLog? log = Plugin.Log;
					if (log != null)
					{
						log.Info(BeaconSecrets.Redactor.Clean(BeaconText.Format("password_saved", name)));
					}
				}
				else
				{
					Report(BeaconText.Text("secret_store_failed"));
				}
				ClearSave();
			}, delegate
			{
				Report(BeaconText.Text("secret_store_failed"));
				ClearSave();
			});
		}

		private static void ClearSave()
		{
			saveOperation?.Dispose();
			saveOperation = null;
			BeaconSecrets.Redactor.Untrack(savingSecret);
			savingSecret = null;
		}

		private static void ClearRead()
		{
			readCancel?.Cancel();
			readCancel?.Dispose();
			readCancel = null;
			readLinux = null;
			read = null;
			deferredNet = null;
			deferredRpc = null;
			deferredSalt = null;
			deferredTarget = null;
		}

		private static void ResetAsyncSecrets()
		{
			ClearRead();
			ClearSave();
			BeaconSecrets.Redactor.Untrack(resumedSecret);
			resumedSecret = null;
			resuming = false;
		}
	}
	internal static class BeaconJoinCard
	{
		private const float Width = 420f;

		private static KitWindow? card;

		private static TMP_Text? text;

		private static string shown = "";

		public static void Show(IReadOnlyList<CardLine> lines)
		{
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			if (!MeadworksHeadless.AllowsUi)
			{
				return;
			}
			string text = string.Join("\n", lines.Select((CardLine l) => BeaconText.Format(((CardLine)(ref l)).Key, ((CardLine)(ref l)).Arguments.ToArray())));
			if (card == null || (Object)(object)card.Panel == (Object)null || (Object)(object)BeaconJoinCard.text == (Object)null)
			{
				Remove();
				FejdStartup instance = FejdStartup.instance;
				object obj;
				if (instance == null)
				{
					obj = null;
				}
				else
				{
					GameObject mainMenu = instance.m_mainMenu;
					if (mainMenu == null)
					{
						obj = null;
					}
					else
					{
						Canvas componentInParent = mainMenu.GetComponentInParent<Canvas>();
						obj = ((componentInParent != null) ? componentInParent.rootCanvas : null);
					}
				}
				Canvas val = (Canvas)obj;
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				card = KitWindow.Create("BeaconJoinCard", new Vector2(420f, 220f), ((Component)val).transform, false, false);
				if (card == null)
				{
					return;
				}
				BeaconJoinCard.text = KitAlmanacDialog.Label((Transform)(object)card.Panel, "Lines", text, new Vector2(20f, -20f), new Vector2(380f, 120f), 18f);
				BeaconJoinCard.text.alignment = (TextAlignmentOptions)514;
				Button val2 = KitButton.Create(card.Panel, "Cancel", BeaconPanel.Ui("cancel"), (Action)delegate
				{
					BeaconAutoJoin.Cancel(Plugin.Log);
				}, new Vector2(100f, -160f), new Vector2(220f, 38f));
				card.Focusable((Selectable)(object)val2, 0, 0);
				BeaconPanel.Tip(((Component)val2).gameObject, "cancel", BeaconText.Text("card_cancel_hint"));
				card.Closed += Cancel;
				card.Open();
				shown = "";
			}
			if (!(text == shown) || !card.IsOpen)
			{
				BeaconJoinCard.text.text = text;
				shown = text;
				card.Open();
			}
		}

		private static void Cancel()
		{
			BeaconAutoJoin.Cancel(Plugin.Log);
		}

		public static void Remove()
		{
			if (card != null)
			{
				card.Closed -= Cancel;
				card.Close();
				card.Destroy();
			}
			card = null;
			text = null;
			shown = "";
		}
	}
	[HarmonyPatch]
	internal static class BeaconJoinPatches
	{
		[HarmonyPatch(typeof(FejdStartup), "JoinServer")]
		[HarmonyPrefix]
		private static void JoinServer(FejdStartup __instance)
		{
			Guard(delegate
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				BeaconJoin.OnJoinServer(__instance.m_joinServer);
			});
		}

		[HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")]
		[HarmonyPrefix]
		private static bool Handshake(ZNet __instance, ZRpc rpc, bool needPassword, string serverPasswordSalt)
		{
			try
			{
				if (BeaconJoin.DeferHandshake(__instance, rpc, needPassword, serverPasswordSalt))
				{
					return false;
				}
				BeaconJoin.BeforeHandshake(needPassword);
			}
			catch (Exception ex)
			{
				MeadworksLog? log = Plugin.Log;
				if (log != null)
				{
					log.Warning("Beacon join hook: " + ex.GetType().Name);
				}
			}
			return true;
		}

		[HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")]
		[HarmonyFinalizer]
		private static Exception? HandshakeDone(Exception? __exception)
		{
			Guard(BeaconJoin.AfterHandshake);
			return __exception;
		}

		[HarmonyPatch(typeof(ZNet), "OnPasswordEntered")]
		[HarmonyPrefix]
		private static void PasswordEntered(string pwd)
		{
			Guard(delegate
			{
				BeaconJoin.BeforePasswordEntered(pwd);
			});
		}

		[HarmonyPatch(typeof(FejdStartup), "ShowConnectError")]
		[HarmonyPostfix]
		private static void ConnectError(ConnectionStatus statusOverride)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			Guard(delegate
			{
				//IL_0001: 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)
				//IL_0009: 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_0023: Unknown result type (might be due to invalid IL or missing references)
				BeaconJoin.OnMenu(ConnectionOutcomes.Parse(((object)(((int)statusOverride == 0) ? ZNet.GetConnectionStatus() : statusOverride)/*cast due to .constrained prefix*/).ToString()));
			});
		}

		private static void Guard(Action action)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				MeadworksLog? log = Plugin.Log;
				if (log != null)
				{
					log.Warning("Beacon join hook: " + ex.GetType().Name);
				}
			}
		}
	}
	internal static class BeaconMenu
	{
		public static bool Ready
		{
			get
			{
				FejdStartup instance = FejdStartup.instance;
				if ((Object)(object)instance == (Object)null || (Object)(object)Game.instance != (Object)null)
				{
					return false;
				}
				GameObject mainMenu = instance.m_mainMenu;
				if (mainMenu == null || !mainMenu.activeInHierarchy)
				{
					GameObject serverListPanel = instance.m_serverListPanel;
					if (serverListPanel == null || !serverListPanel.activeInHierarchy)
					{
						return false;
					}
				}
				if (CinematicsManager.IsStartedPlaying())
				{
					return false;
				}
				if (((ServerJoinData)(ref instance.m_queuedJoinServer)).IsValid || MatchmakingManager.HasPendingInvite())
				{
					return false;
				}
				if (instance.m_profiles != null)
				{
					return instance.m_profiles.Count > 0;
				}
				return false;
			}
		}

		public static (IReadOnlyList<string> Names, IReadOnlyList<string> Files, int Selected) Characters()
		{
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			FejdStartup instance = FejdStartup.instance;
			if (instance?.m_profiles == null)
			{
				return (Names: list, Files: list2, Selected: -1);
			}
			foreach (PlayerProfile profile in instance.m_profiles)
			{
				list.Add(profile.GetName() ?? "");
				list2.Add(profile.GetFilename() ?? "");
			}
			return (Names: list, Files: list2, Selected: instance.m_profileIndex);
		}

		public static bool Join(ServerAddress address, int? character)
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			if (!Ready || !BeaconJoin.TryJoinData(address, out var data))
			{
				return false;
			}
			FejdStartup instance = FejdStartup.instance;
			int num = character ?? instance.m_profileIndex;
			if (num < 0 || num >= instance.m_profiles.Count)
			{
				return false;
			}
			if ((Object)(object)instance.m_connectionFailedPanel != (Object)null)
			{
				instance.m_connectionFailedPanel.SetActive(false);
			}
			instance.m_profileIndex = num;
			instance.m_queuedJoinServer = data;
			instance.OnCharacterStart();
			return true;
		}
	}
	[BepInPlugin("com.meadworks.beacon", "Beacon", "0.1.14")]
	[BepInDependency("com.meadworks.crucible", "0.3.26")]
	[BepInDependency("com.meadworks.crucible.ui", "0.3.19")]
	[BepInDependency("com.jotunn.jotunn", "2.30.2")]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.meadworks.beacon";

		public const string Name = "Beacon";

		public const string Version = "0.1.14";

		public const string Family = "Runestone";

		private const string CrucibleUiGuid = "com.meadworks.crucible.ui";

		private ProductRegistration? registration;

		private MeadworksLog? log;

		private Harmony? harmony;

		private bool started;

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

		public static bool Ready { get; private set; }

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

		public static MeadworksLog? Log { get; private set; }

		private void Awake()
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			log = MeadworksLog.For(((BaseUnityPlugin)this).Logger, "Beacon");
			Log = log;
			try
			{
				if (!Plugin.IsReady)
				{
					throw new InvalidOperationException("Required Crucible is not ready");
				}
				registration = Plugin.Services.Register(new ProductMetadata("com.meadworks.beacon", "Beacon", new Version("0.1.14"), new Version(0, 1), new Version(0, 1), (MultiplayerPolicy)1, "Runestone"));
				Settings = BeaconConfig.Bind(((BaseUnityPlugin)this).Config, log);
				if (!MeadworksHeadless.AllowsUi)
				{
					log.Debug("Beacon loaded on a headless process: server policy bound; no UI, password storage or joining.");
					return;
				}
				BeaconText.Initialize(((BaseUnityPlugin)this).Info.Metadata, ((BaseUnityPlugin)this).Logger);
				Ready = true;
			}
			catch (Exception ex)
			{
				log.Error((object)("Beacon initialization rejected: " + ex.GetType().Name));
				Shutdown();
			}
		}

		private void Start()
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Expected O, but got Unknown
			if (!Ready || log == null)
			{
				return;
			}
			try
			{
				BeaconModules.Decide(log);
				BeaconSecrets.Initialize(Settings, log);
				BeaconServers.Load(log);
				if (BeaconModules.Active((BeaconModule)0))
				{
					harmony = new Harmony("com.meadworks.beacon");
					harmony.CreateClassProcessor(typeof(BeaconJoinPatches)).Patch();
					harmony.CreateClassProcessor(typeof(BeaconFavoritePatches)).Patch();
					BeaconAutoJoin.Initialize();
				}
				if (harmony == null)
				{
					harmony = new Harmony("com.meadworks.beacon");
				}
				harmony.CreateClassProcessor(typeof(BeaconServerListPatches)).Patch();
				CommandManager.Instance.AddConsoleCommand((ConsoleCommand)(object)new BeaconCommand());
				BeaconConversion.Register();
				started = true;
			}
			catch (Exception ex)
			{
				log.Error((object)("Beacon modules rejected: " + ex.GetType().Name));
				Shutdown();
			}
		}

		private void Update()
		{
			if (Ready)
			{
				BeaconText.Tick();
				if (started && log != null)
				{
					BeaconModules.Tick(log);
					BeaconSecrets.Tick(log);
					BeaconSecretActions.Tick();
					BeaconFavorites.Tick();
					BeaconMenuBack.Tick();
					BeaconPanel.Tick();
					BeaconJoin.Tick(log);
					BeaconAutoJoin.Tick(log);
					BeaconSaveOffer.Tick();
				}
			}
		}

		private void OnDestroy()
		{
			Shutdown();
		}

		private void Shutdown()
		{
			Ready = false;
			started = false;
			try
			{
				Harmony? obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
			}
			catch (Exception ex)
			{
				MeadworksLog? obj2 = log;
				if (obj2 != null)
				{
					obj2.Warning("Beacon unpatch: " + ex.GetType().Name);
				}
			}
			harmony = null;
			Action[] array = new Action[12]
			{
				BeaconConversion.Reset,
				BeaconAutoJoin.Reset,
				BeaconSaveOffer.Reset,
				BeaconFavorites.Reset,
				BeaconPanel.Reset,
				BeaconImportSecrets.Reset,
				BeaconJoin.Reset,
				BeaconSecretActions.Reset,
				BeaconServers.Unload,
				BeaconSecrets.Shutdown,
				BeaconModules.Reset,
				BeaconText.Shutdown
			};
			foreach (Action action in array)
			{
				try
				{
					action();
				}
				catch (Exception ex2)
				{
					MeadworksLog? obj3 = log;
					if (obj3 != null)
					{
						obj3.Warning("Beacon teardown: " + ex2.GetType().Name);
					}
				}
			}
			Settings?.Shutdown();
			Settings = null;
			ProductRegistration? obj4 = registration;
			if (obj4 != null)
			{
				obj4.Dispose();
			}
			registration = null;
		}
	}
	public static class BeaconSecrets
	{
		private static Task<bool>? refresh;

		private static bool reportedFallback;

		private static BeaconConfig? linuxSettings;

		public static ISecretStore Store { get; private set; } = (ISecretStore)new DisabledSecretStore();

		public static SecretRedactor Redactor { get; } = new SecretRedactor();

		public static string SecretFilePath => Path.Combine(BeaconServers.DataDirectory, "secrets.dat");

		public static void Initialize(BeaconConfig settings, MeadworksLog log)
		{
			//IL_001f: 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_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Invalid comparison between Unknown and I4
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Expected O, but got Unknown
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Expected O, but got Unknown
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			SecretPlatform val = (SecretPlatform)((!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) ? (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? 1 : 2) : 0);
			if ((int)val == 1)
			{
				linuxSettings = settings;
				LinuxKeyringStore val2 = new LinuxKeyringStore(settings.Get<LinuxFallback>(Passwords.LinuxFallback));
				Store = (ISecretStore)val2;
				refresh = val2.RefreshAsync(default(CancellationToken));
				reportedFallback = false;
			}
			else
			{
				SecretBackend val3 = SecretStores.Choose(val, false, settings.Get<LinuxFallback>(Passwords.LinuxFallback));
				ISecretStore store = (ISecretStore)(((int)val3 == 0) ? ((object)new DpapiSecretStore((Func<string>)ReadSecretFile, (Action<string>)WriteSecretFile)) : (((int)val3 != 3) ? ((object)MemorySecretStore.SessionOnly()) : ((object)new DisabledSecretStore())));
				Store = store;
				log.Info("Beacon password storage: " + ((object)Store.Backend/*cast due to .constrained prefix*/).ToString() + ".");
			}
		}

		public static void Shutdown()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			if (Store is IDisposable disposable)
			{
				disposable.Dispose();
			}
			Store = (ISecretStore)new DisabledSecretStore();
			refresh = null;
			linuxSettings = null;
			Redactor.Clear();
		}

		public static void Tick(MeadworksLog log)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Invalid comparison between Unknown and I4
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			ISecretStore store = Store;
			LinuxKeyringStore val = (LinuxKeyringStore)(object)((store is LinuxKeyringStore) ? store : null);
			if (val != null && linuxSettings != null)
			{
				val.SetFallback(linuxSettings.Get<LinuxFallback>(Passwords.LinuxFallback));
			}
			if (refresh != null && refresh.IsCompleted)
			{
				if (refresh.IsFaulted)
				{
					log.Warning("Beacon keyring initialization failed; no password was saved.");
				}
				refresh = null;
			}
			if (Store is LinuxKeyringStore && (int)Store.Backend != 1 && !reportedFallback)
			{
				reportedFallback = true;
				log.Warning(BeaconText.Text("secret_store_failed") + " " + BeaconText.Text(SecretStores.StorageLineKey(Store.Backend)));
			}
		}

		private static string? ReadSecretFile()
		{
			if (!File.Exists(SecretFilePath))
			{
				return null;
			}
			return File.ReadAllText(SecretFilePath);
		}

		private static void WriteSecretFile(string text)
		{
			BeaconServers.WriteAtomically(SecretFilePath, text);
		}
	}
	internal static class BeaconFavorites
	{
		internal sealed class RemovalState
		{
			internal ServerJoinData Data;

			internal ServerAddress Address;

			internal ServerAddress Canonical;

			internal string Reference;

			internal RemovalState(ServerJoinData data, ServerAddress address, ServerEntry entry)
			{
				//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)
				Data = data;
				Address = address;
				Reference = entry.SecretRef;
				Canonical = entry.Address;
			}
		}

		private static ServerListGui? owner;

		private static readonly Dictionary<string, ServerAddress> added = new Dictionary<string, ServerAddress>();

		private static readonly HashSet<ServerAddress> suppressed = new HashSet<ServerAddress>();

		private static readonly Queue<RemovalState> deferred = new Queue<RemovalState>();

		private static readonly HashSet<string> reportedErrors = new HashSet<string>();

		private static float nextSync;

		internal static RemovalState? Capture(ServerListGui gui)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			if (!BeaconJoin.Active || gui.m_favoriteServersList == null || (object)gui.m_serverLists[gui.m_currentServerList] != gui.m_favoriteServersList)
			{
				return null;
			}
			int selectedServer = gui.GetSelectedServer();
			List<ServerListEntryData> currentServerListFiltered = gui.CurrentServerListFiltered;
			if (selectedServer < 0 || selectedServer >= currentServerListFiltered.Count)
			{
				return null;
			}
			ServerJoinData joinData = currentServerListFiltered[selectedServer].m_joinData;
			ServerAddress val = BeaconJoin.TargetOf(joinData);
			ServerEntry val2 = ((val == (ServerAddress)null) ? null : BeaconServers.List.Find(val));
			if (val2 != null && !(val == (ServerAddress)null))
			{
				return new RemovalState(joinData, val, val2);
			}
			return null;
		}

		internal static void Removed(ServerListGui gui, RemovalState? state)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			if (state != null && BeaconJoin.Active && !gui.m_favoriteServersList.Contains(state.Data))
			{
				suppressed.Add(state.Canonical);
				BeaconConfig? settings = Plugin.Settings;
				if (settings != null && settings.Get<bool>(General.RemoveEntryWithFavorite))
				{
					deferred.Enqueue(state);
				}
			}
		}

		public static void Tick()
		{
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			if (!BeaconJoin.Active)
			{
				Reset();
				return;
			}
			BeaconFavoriteUndo.Tick();
			if ((Object)(object)Game.instance != (Object)null || BeaconImportSecrets.Busy || Plugin.Settings == null || Plugin.Log == null)
			{
				return;
			}
			ServerListGui s_instance = ServerListGui.s_instance;
			if ((Object)(object)s_instance == (Object)null || s_instance.m_favoriteServersList == null)
			{
				return;
			}
			if (!Plugin.Settings.Get<bool>(General.RemoveEntryWithFavorite))
			{
				deferred.Clear();
			}
			while (deferred.Count > 0)
			{
				RemovalState removal = deferred.Dequeue();
				if (s_instance.m_favoriteServersList.Contains(removal.Data))
				{
					suppressed.Remove(removal.Canonical);
					continue;
				}
				ServerEntry current = ((IEnumerable<ServerEntry>)BeaconServers.List.Entries).FirstOrDefault((Func<ServerEntry, bool>)((ServerEntry e) => e.SecretRef == removal.Reference));
				if (current != null && current.Matches(removal.Address) && !(current.Address != removal.Canonical))
				{
					ServerRemoval undo = null;
					if (BeaconServers.Amend(delegate(ServerList candidate)
					{
						undo = candidate.RemoveForUndo(current.Name);
					}, Plugin.Log) && undo != null)
					{
						BeaconFavoriteUndo.Show(undo, removal.Address);
					}
					else
					{
						BeaconFavoriteUndo.Failure(BeaconPanel.Ui("write_failed"));
					}
				}
			}
			if ((Object)(object)owner != (Object)(object)s_instance)
			{
				owner = s_instance;
				added.Clear();
				nextSync = 0f;
			}
			if (Time.unscaledTime < nextSync)
			{
				return;
			}
			nextSync = Time.unscaledTime + 0.5f;
			try
			{
				Sync(s_instance, Plugin.Settings.Get<bool>(General.AddToFavorites));
			}
			catch (Exception ex)
			{
				if (reportedErrors.Add(ex.GetType().Name))
				{
					Plugin.Log.Warning("Beacon favorites synchronization: " + ex.GetType().Name);
				}
			}
		}

		private static void Sync(ServerListGui gui, bool enabled)
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			LocalServerList favoriteServersList = gui.m_favoriteServersList;
			bool flag = false;
			KeyValuePair<string, ServerAddress>[] array = added.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<string, ServerAddress> pair = array[i];
				ServerEntry val = ((IEnumerable<ServerEntry>)BeaconServers.List.Entries).FirstOrDefault((Func<ServerEntry, bool>)((ServerEntry e) => e.SecretRef == pair.Key));
				if (!enabled || val == null || !(val.Address == pair.Value))
				{
					if (BeaconJoin.TryJoinData(pair.Value, out var data) && favoriteServersList.Contains(data))
					{
						favoriteServersList.Remove(data);
						flag = true;
					}
					added.Remove(pair.Key);
				}
			}
			if (enabled)
			{
				foreach (ServerEntry entry in BeaconServers.List.Entries)
				{
					if (!suppressed.Contains(entry.Address) && BeaconJoin.TryJoinData(entry.Address, out var data2) && !favoriteServersList.Contains(data2))
					{
						MultiBackendMatchmaking.SetServerName(data2, new ServerNameAtTimePoint(entry.Name, DateTime.UtcNow));
						favoriteServersList.Add(data2);
						added[entry.SecretRef] = entry.Address;
						flag = true;
					}
				}
			}
			if (flag)
			{
				Changed(gui);
			}
		}

		private unsafe static void Changed(ServerListGui gui)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			gui.m_filteredListOutdated = true;
			gui.SetButtonsOutdated();
			ServerListGui.UpdateServerListGuiStatic();
			SaveStatusCode val = gui.m_favoriteServersList.Save(false);
			if ((int)val != 0 && (int)val != 1)
			{
				MeadworksLog? log = Plugin.Log;
				if (log != null)
				{
					log.Warning(BeaconText.Text("ui_favorite_save_failed") + " (" + ((object)(*(SaveStatusCode*)(&val))/*cast due to .constrained prefix*/).ToString() + ")");
				}
			}
		}

		internal static bool Undo(ServerRemoval removal, ServerAddress favorite)
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			if (!BeaconJoin.Active || BeaconImportSecrets.Busy || Plugin.Log == null)
			{
				return false;
			}
			try
			{
				if (!BeaconServers.Amend(delegate(ServerList candidate)
				{
					candidate.Restore(removal);
				}, Plugin.Log))
				{
					return false;
				}
				suppressed.Remove(removal.Entry.Address);
				ServerListGui s_instance = ServerListGui.s_instance;
				if ((Object)(object)s_instance != (Object)null && s_instance.m_favoriteServersList != null && BeaconJoin.TryJoinData(favorite, out var data) && !s_instance.m_favoriteServersList.Contains(data))
				{
					s_instance.m_favoriteServersList.Add(data);
					Changed(s_instance);
				}
				nextSync = 0f;
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.Warning("Beacon favorite undo refused: " + ex.GetType().Name);
				return false;
			}
		}

		public static void Reset()
		{
			owner = null;
			added.Clear();
			suppressed.Clear();
			deferred.Clear();
			reportedErrors.Clear();
			nextSync = 0f;
			BeaconFavoriteUndo.Reset();
		}
	}
	[HarmonyPatch(typeof(ServerListGui), "OnRemoveServerConfirm")]
	internal static class BeaconFavoritePatches
	{
		[HarmonyPrefix]
		private static void Before(ServerListGui __instance, out BeaconFavorites.RemovalState? __state)
		{
			__state = null;
			try
			{
				__state = BeaconFavorites.Capture(__instance);
			}
			catch (Exception ex)
			{
				MeadworksLog? log = Plugin.Log;
				if (log != null)
				{
					log.Warning("Beacon favorite removal capture: " + ex.GetType().Name);
				}
			}
		}

		[HarmonyPostfix]
		private static void After(ServerListGui __instance, BeaconFavorites.RemovalState? __state)
		{
			try
			{
				BeaconFavorites.Removed(__instance, __state);
			}
			catch (Exception ex)
			{
				MeadworksLog? log = Plugin.Log;
				if (log != null)
				{
					log.Warning("Beacon favorite removal: " + ex.GetType().Name);
				}
			}
		}
	}
	internal static class BeaconImportSecrets
	{
		private sealed class Pending : IDisposable
		{
			private string? secret;

			internal IDisposable? Operation;

			internal Pending(string secret)
			{
				this.secret = secret;
				BeaconSecrets.Redactor.Track(secret);
			}

			public void Dispose()
			{
				Operation?.Dispose();
				Operation = null;
				BeaconSecrets.Redactor.Untrack(secret);
				secret = null;
				pending.Remove(this);
			}
		}

		private static readonly List<Pending> pending = new List<Pending>();

		private static readonly List<string> reports = new List<string>();

		internal static bool Busy => pending.Count > 0;

		internal static IReadOnlyList<string> Reports => reports.AsReadOnly();

		internal static event Action<string>? Reported;

		internal static void Start(string reference, string name, string password)
		{
			if (pending.Count == 0)
			{
				reports.Clear();
			}
			Pending item = new Pending(password);
			pending.Add(item);
			item.Operation = BeaconSecretActions.Run((CancellationToken cancel) => BeaconSecretActions.Store(reference, password, cancel), delegate(bool stored)
			{
				try
				{
					string text = BeaconSecrets.Redactor.Clean(BeaconText.Format(stored ? "password_saved" : "import_password_not_stored", name));
					reports.Add(text);
					MeadworksLog? log = Plugin.Log;
					if (log != null)
					{
						log.Info(text);
					}
					BeaconImportSecrets.Reported?.Invoke(text);
				}
				finally
				{
					item.Dispose();
				}
			}, delegate
			{
				try
				{
					string text = BeaconSecrets.Redactor.Clean(BeaconText.Format("import_password_not_stored", name));
					reports.Add(text);
					MeadworksLog? log = Plugin.Log;
					if (log != null)
					{
						log.Warning(text);
					}
					BeaconImportSecrets.Reported?.Invoke(text);
				}
				finally
				{
					item.Dispose();
				}
			});
		}

		internal static void Reset()
		{
			Pending[] array = pending.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				array[i].Dispose();
			}
			reports.Clear();
			BeaconImportSecrets.Reported = null;
			BeaconCommand.ResetPreview();
		}
	}
	public static class BeaconServers
	{
		private static bool readOnly;

		public static ServerList List { get; private set; } = new ServerList((Func<string>)null);

		public static string DataDirectory => Path.Combine(Paths.ConfigPath, "Meadworks/beacon".Replace('/', Path.DirectorySeparatorChar));

		public static string FilePath => Path.Combine(DataDirectory, "servers.json");

		public static bool Writable => !readOnly;

		public static void Load(MeadworksLog log)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			readOnly = false;
			List = new ServerList((Func<string>)null);
			if (!File.Exists(FilePath))
			{
				return;
			}
			try
			{
				List = ServerListCodec.Read(File.ReadAllText(FilePath, Encoding.UTF8));
				log.Verbose("Beacon server list: " + List.Count + " servers.");
			}
			catch (Exception ex) when (ex is FormatException || ex is IOException || ex is UnauthorizedAccessException)
			{
				readOnly = true;
				log.Warning("Beacon could not read servers.json (" + ex.GetType().Name + "); it is left unchanged and nothing is saved this session.");
			}
		}

		public static bool Save(MeadworksLog log)
		{
			if (readOnly)
			{
				log.Warning("Beacon did not save servers.json: the file on disk could not be read at start.");
				return false;
			}
			WriteAtomically(FilePath, ServerListCodec.Write(List));
			return true;
		}

		public static void Unload()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			List = new ServerList((Func<string>)null);
			readOnly = false;
		}

		public static bool Amend(Action<ServerList> change, MeadworksLog log)
		{
			if (readOnly)
			{
				return false;
			}
			ServerList val = ServerListCodec.Read(ServerListCodec.Write(List));
			change(val);
			try
			{
				WriteAtomically(FilePath, ServerListCodec.Write(val));
			}
			catch (Exception ex)
			{
				log.Warning("Beacon server list was not changed: " + ex.GetType().Name);
				return false;
			}
			List = val;
			return true;
		}

		public static void WriteAtomically(string path, string text)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			string text2 = path + ".tmp";
			File.WriteAllText(text2, text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			if (File.Exists(path))
			{
				File.Replace(text2, path, null);
			}
			else
			{
				File.Move(text2, path);
			}
		}
	}
	internal static class BeaconEditor
	{
		private static KitAlmanacDialog? dialog;

		private static KitTextField? name;

		private static KitTextField? address;

		private static KitTextField? password;

		private static KitAlmanacMultiline? aliases;

		private static IDisposable? operation;

		private static string? tracked;

		private static bool saving;

		private static TMP_Text? hint;

		private static string Ui(string key)
		{
			return BeaconPanel.Ui(key);
		}

		public static void Open(ServerEntry? entry)
		{
			//IL_0046: 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)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			//IL_029b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0306: Unknown result type (might be due to invalid IL or missing references)
			Reset();
			if (BeaconPanel.Window == null || !BeaconServers.Writable || BeaconImportSecrets.Busy)
			{
				return;
			}
			dialog = KitAlmanacDialog.Create(BeaconPanel.Window, Ui((entry == null) ? "add" : "edit"), BeaconPanel.Size);
			if (dialog == null)
			{
				return;
			}
			dialog.Dim();
			ServerEntry? obj = entry;
			name = Field("name", 65f, ((obj != null) ? obj.Name : null) ?? "");
			ServerEntry? obj2 = entry;
			address = Field("address", 135f, ((obj2 != null) ? ((object)obj2.Address).ToString() : null) ?? "");
			KitAlmanacDialog.Label((Transform)(object)dialog.Panel, "AliasesCaption", Ui("aliases"), new Vector2(20f, -182f), new Vector2(740f, 24f), 16f);
			aliases = KitAlmanacMultiline.Create(dialog.Panel, "Aliases", new Vector2(20f, -205f), new Vector2(740f, 70f), (entry == null) ? "" : string.Join(", ", entry.Aliases), (Action<string>)null, false);
			aliases.Input.VirtualKeyboardTitle = Ui("aliases");
			dialog.Window.Focusable((Selectable)(object)aliases.Input, 0, 2);
			BeaconPanel.Tip(((Component)aliases.Input).gameObject, "aliases");
			password = Field("password", 315f, "", secret: true);
			hint = KitAlmanacDialog.Label((Transform)(object)dialog.Panel, "Hint", Ui("password_hint"), new Vector2(20f, -370f), new Vector2(740f, 62f), 14f);
			name.Validator = (string value) => !string.IsNullOrWhiteSpace(value);
			ServerAddress val2 = default(ServerAddress);
			address.Validator = (string value) => ServerAddress.TryParse(value, ref val2);
			Button save = KitButton.Create(dialog.Panel, "Save", Ui("save"), (Action)delegate
			{
				Save(entry);
			}, new Vector2(20f, -550f), new Vector2(350f, 38f));
			Button val = KitButton.Create(dialog.Panel, "Cancel", Ui("cancel"), (Action)Reset, new Vector2(410f, -550f), new Vector2(350f, 38f));
			BeaconPanel.Tip(((Component)save).gameObject, "save");
			BeaconPanel.Tip(((Component)val).gameObject, "cancel");
			dialog.Window.Focusable((Selectable)(object)save, 0, 4);
			dialog.Window.Focusable((Selectable)(object)val, 1, 4);
			dialog.Tick += delegate
			{
				bool flag = !saving && !BeaconImportSecrets.Busy;
				KitTextField? obj3 = name;
				if (obj3 != null)
				{
					obj3.SetUsable(flag);
				}
				KitTextField? obj4 = address;
				if (obj4 != null)
				{
					obj4.SetUsable(flag);
				}
				KitAlmanacMultiline? obj5 = aliases;
				if (obj5 != null)
				{
					obj5.SetUsable(flag);
				}
				KitTextField? obj6 = password;
				if (obj6 != null)
				{
					obj6.SetUsable(flag);
				}
				if ((Object)(object)save != (Object)null)
				{
					((Selectable)save).interactable = flag;
				}
			};
			dialog.Closed += Reset;
			dialog.Own((Action)delegate
			{
				KitTextField? obj3 = name;
				if (obj3 != null)
				{
					obj3.Destroy();
				}
				KitTextField? obj4 = address;
				if (obj4 != null)
				{
					obj4.Destroy();
				}
				KitAlmanacMultiline? obj5 = aliases;
				if (obj5 != null)
				{
					obj5.Dispose();
				}
				KitTextField? obj6 = password;
				if (obj6 != null)
				{
					obj6.Destroy();
				}
			});
			dialog.TypingField = delegate
			{
				KitTextField? obj3 = name;
				if (obj3 == null || !obj3.Focused)
				{
					KitTextField? obj4 = address;
					if (obj4 == null || !obj4.Focused)
					{
						KitAlmanacMultiline? obj5 = aliases;
						if (obj5 == null || !obj5.Focused)
						{
							KitTextField? obj6 = password;
							if (obj6 == null)
							{
								return false;
							}
							return obj6.Focused;
						}
					}
				}
				return true;
			};
		}

		private static KitTextField Field(string key, float y, string value, bool secret = false)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			KitAlmanacDialog.Label((Transform)(object)dialog.Panel, key + "Caption", Ui(key), new Vector2(20f, 0f - y + 23f), new Vector2(740f, 24f), 16f);
			KitTextField val = KitTextField.Create(dialog.Panel, key, (KitFieldMode)(secret ? 3 : 2), new Vector2(20f, 0f - y), new Vector2(740f, 36f), (string)null, Ui(key));
			val.Text = value;
			BeaconPanel.Tip(((Component)val.Input).gameObject, key, (key == "password") ? Ui("password_hint") : null);
			dialog.Window.Focusable((Selectable)(object)val.Input, 0, (int)((y - 65f) / 70f));
			return val;
		}

		private static void Save(ServerEntry? previous)
		{
			if (saving || BeaconImportSecrets.Busy || dialog == null || name == null || address == null || aliases == null || password == null)
			{
				return;
			}
			string nextName = ((previous != null && name.Text == KitFieldRules.Clip((KitFieldMode)2, previous.Name)) ? previous.Name : name.Text.Trim());
			ServerAddress parsed;
			ServerAddress[] others;
			try
			{
				parsed = ((previous != null && address.Text == KitFieldRules.Clip((KitFieldMode)2, ((object)previous.Address).ToString())) ? previous.Address : ServerAddress.Parse(address.Text));
				others = ((IEnumerable<string>)aliases.Text.Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)).Select((Func<string, ServerAddress>)ServerAddress.Parse).ToArray();
			}
			catch
			{
				hint.text = Ui("invalid");
				return;
			}
			if (!BeaconPanel.Amend(delegate(ServerList candidate)
			{
				if (previous == null)
				{
					candidate.Add(nextName, parsed, (IEnumerable<ServerAddress>)others);
				}
				else
				{
					candidate.Edit(previous.Name, nextName, parsed, (IEnumerable<ServerAddress>)others);
				}
			}))
			{
				hint.text = Ui("invalid");
				return;
			}
			ServerEntry entry = BeaconServers.List.FindByName(nextName);
			string secret = password.Text;
			if (secret.Length == 0)
			{
				Reset();
				return;
			}
			tracked = secret;
			BeaconSecrets.Redactor.Track(secret);
			saving = true;
			name.SetUsable(false);
			address.SetUsable(false);
			aliases.SetUsable(false);
			password.SetUsable(false);
			operation = BeaconSecretActions.Run((CancellationToken cancel) => BeaconSecretActions.Store(entry.SecretRef, secret, cancel), delegate(bool saved)
			{
				BeaconPanel.Message(saved ? BeaconText.Format("password_saved", entry.Name) : BeaconText.Text("secret_store_failed"));
				Reset();
			}, delegate
			{
				BeaconPanel.Message(BeaconText.Text("secret_store_failed"));
				Reset();
			});
		}

		public static void Reset()
		{
			operation?.Dispose();
			operation = null;
			BeaconSecrets.Redactor.Untrack(tracked);
			tracked = null;
			saving = false;
			KitAlmanacDialog val = dialog;
			dialog = null;
			if (val != null)
			{
				val.Closed -= Reset;
				val.Dispose();
			}
			name = (address = (password = null));
			aliases = null;
			hint = null;
		}
	}
	internal static class BeaconFavoriteUndo
	{
		private static KitWindow? window;

		private static ServerRemoval? removal;

		private static ServerAddress? favorite;

		private static TMP_Text? text;

		private static Button? undo;

		private static float expires;

		public static void Show(ServerRemoval removed, ServerAddress address)
		{
			Reset();
			removal = removed;
			favorite = address;
			Create(BeaconText.Format("favorite_removed", removed.Entry.Name));
		}

		public static void Failure(string message)
		{
			Reset();
			Create(message);
		}

		private static void Create(string message)
		{
			//IL_0048: 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_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: 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_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			FejdStartup instance = FejdStartup.instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				GameObject mainMenu = instance.m_mainMenu;
				if (mainMenu == null)
				{
					obj = null;
				}
				else
				{
					Canvas componentInParent = mainMenu.GetComponentInParent<Canvas>();
					obj = ((componentInParent != null) ? componentInParent.rootCanvas : null);
				}
			}
			Canvas val = (Canvas)obj;
			if ((Object)(object)val == (Object)null)
			{
				Reset();
				return;
			}
			window = KitWindow.Create("Beacon_FavoriteUndo", new Vector2(540f, 150f), ((Component)val).transform, false, false);
			if (window == null)
			{
				Reset();
				return;
			}
			RectTransform panel = window.Panel;
			RectTransform panel2 = window.Panel;
			RectTransform panel3 = window.Panel;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(1f, 0f);
			panel3.pivot = val2;
			Vector2 anchorMin = (panel2.anchorMax = val2);
			panel.anchorMin = anchorMin;
			window.Panel.anchoredPosition = new Vector2(-20f, 20f);
			text = KitAlmanacDialog.Label((Transform)(object)window.Panel, "Message", message, new Vector2(20f, -12f), new Vector2(500f, 64f), 16f);
			if (removal != null)
			{
				undo = KitButton.Create(window.Panel, "Undo", BeaconPanel.Ui("undo"), (Action)Undo, new Vector2(20f, -96f), new Vector2(240f, 36f));
				window.Focusable((Selectable)(object)undo, 0, 0);
				BeaconPanel.Tip(((Component)undo).gameObject, "undo");
			}
			Button val4 = KitButton.Create(window.Panel, "Dismiss", BeaconPanel.Ui("close"), (Action)Reset, new Vector2(280f, -96f), new Vector2(240f, 36f));
			BeaconPanel.Tip(((Component)val4).gameObject, "close");
			window.Focusable((Selectable)(object)val4, 1, 0);
			window.Closed += Reset;
			window.Open();
			expires = Time.unscaledTime + 15f;
		}

		private static void Undo()
		{
			if (removal != null && !(favorite == (ServerAddress)null))
			{
				if (BeaconFavorites.Undo(removal, favorite))
				{
					Reset();
				}
				else if ((Object)(object)text != (Object)null)
				{
					text.text = BeaconPanel.Ui("undo_conflict");
				}
			}
		}

		public static void Tick()
		{
			if (window != null)
			{
				if ((Object)(object)Game.instance != (Object)null || (Object)(object)FejdStartup.instance == (Object)null || Time.unscaledTime >= expires)
				{
					Reset();
				}
				else if ((Object)(object)undo != (Object)null)
				{
					((Selectable)undo).interactable = !BeaconImportSecrets.Busy;
				}
			}
		}

		public static void Reset()
		{
			KitWindow val = window;
			window = null;
			removal = null;
			favorite = null;
			text = null;
			undo = null;
			if (val != null)
			{
				val.Closed -= Reset;
				val.Close();
				val.Destroy();
			}
		}
	}
	internal static class BeaconMenuBack
	{
		private static readonly KeyboardShortcut EscapeKey = new KeyboardShortcut((KeyCode)27, Array.Empty<KeyCode>());

		private static bool typedLastFrame;

		public static void Tick()
		{
			//IL_005f: 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_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Expected I4, but got Unknown
			if (!Plugin.Ready || (Object)(object)Game.instance != (Object)null || (Object)(object)FejdStartup.instance == (Object)null || !MeadworksHeadless.AllowsInput)
			{
				typedLastFrame = false;
				return;
			}
			bool flag = MeadworksInput.Typing || KitTextField.Typing;
			KitWindow? window = BeaconPanel.Window;
			bool flag2 = window != null && window.IsOpen;
			KitAlmanacDialog val = (flag2 ? KitAlmanacDialog.Active : null);
			MenuBackState val2 = new MenuBackState
			{
				Escape = (!flag && MeadworksInput.Pressed(EscapeKey, (InputContext)2)),
				Typing = flag,
				TypedLastFrame = typedLastFrame,
				DialogOpen = (val != null),
				DialogTyping = (val != null && val.TypingField?.Invoke() == true),
				SaveOfferOpen = BeaconSaveOffer.Showing,
				PanelOpen = flag2,
				WaitPending = BeaconAutoJoin.Waiting
			};
			typedLastFrame = flag || val2.DialogTyping;
			MenuBackAction val3 = MenuBack.Decide(val2);
			switch (val3 - 1)
			{
			case 0:
				val.Dispose();
				break;
			case 1:
				BeaconSaveOffer.Reset();
				break;
			case 2:
				BeaconPanel.Reset();
				break;
			case 3:
				BeaconAutoJoin.Cancel(Plugin.Log);
				break;
			}
		}
	}
	internal static class BeaconPanel
	{
		private static KitWindow? window;

		private static KitVirtualList? list;

		private static TMP_Text? status;

		private static TMP_Text? storage;

		private static string? selected;

		private static bool refreshNow = true;

		private static ServerList? seenList;

		private static int seenRevision = -1;

		private static SecretBackend seenBackend;

		private static string seenLanguage = "";

		private static int seenSecrets = int.MinValue;

		private static long revision;

		private static IDisposable? refresh;

		private static float nextRefresh;

		private static string title = "";

		private static GameObject? hiddenMenu;

		private static readonly List<Button> selectedActions = new List<Button>();

		private static readonly List<Button> busyActions = new List<Button>();

		internal static readonly Vector2 Size = new Vector2(780f, 610f);

		internal static KitWindow? Window => window;

		internal static ServerEntry? Selected => ((IEnumerable<ServerEntry>)BeaconServers.List.Entries).FirstOrDefault((Func<ServerEntry, bool>)((ServerEntry e) => e.SecretRef == selected));

		internal static string Ui(string key)
		{
			return BeaconText.Text("ui_" + key);
		}

		public static void Tick()
		{
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_01