Decompiled source of FischywebFusionLayer v1.0.2

FischywebFusionLayer.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using FischywebFusionLayer;
using FischywebNetworker.Shared;
using FischywebNetworker.Shared.Crypto;
using LabFusion.Data;
using LabFusion.Network;
using LabFusion.Network.Fischyweb;
using LabFusion.Player;
using LabFusion.Senders;
using LabFusion.Support;
using LabFusion.UI.Popups;
using LabFusion.Utilities;
using LabFusion.Voice;
using LabFusion.Voice.Unity;
using MelonLoader;
using MelonLoader.Utils;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(FischywebLayerMod), "Fischyweb Fusion Layer", "1.0.2", "FischyWeb", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("FischywebFusionLayer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2")]
[assembly: AssemblyProduct("FischywebFusionLayer")]
[assembly: AssemblyTitle("FischywebFusionLayer")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.2.0")]
[module: UnverifiableCode]
namespace FischywebNetworker.Shared
{
	public static class MiniJson
	{
		public sealed class JsonException : Exception
		{
			public JsonException(string message)
				: base(message)
			{
			}
		}

		private sealed class Parser
		{
			private readonly string _s;

			private int _i;

			public bool End => _i >= _s.Length;

			public Parser(string s)
			{
				_s = s;
			}

			public JsonException Error(string msg)
			{
				int num = 1;
				int num2 = 1;
				for (int i = 0; i < _i && i < _s.Length; i++)
				{
					if (_s[i] == '\n')
					{
						num++;
						num2 = 1;
					}
					else
					{
						num2++;
					}
				}
				return new JsonException($"{msg} at line {num}, column {num2}");
			}

			public void SkipWs()
			{
				while (_i < _s.Length)
				{
					switch (_s[_i])
					{
					case '\t':
					case '\n':
					case '\r':
					case ' ':
					case '\ufeff':
						_i++;
						break;
					case '/':
						if (_i + 1 < _s.Length && _s[_i + 1] == '/')
						{
							while (_i < _s.Length && _s[_i] != '\n')
							{
								_i++;
							}
							break;
						}
						return;
					default:
						return;
					}
				}
			}

			public object ParseValue(int depth)
			{
				if (depth > 32)
				{
					throw Error("nesting too deep");
				}
				if (End)
				{
					throw Error("unexpected end of input");
				}
				char c = _s[_i];
				switch (c)
				{
				case '{':
					return ParseObj(depth);
				case '[':
					return ParseArr(depth);
				case '"':
					return ParseStr();
				case 't':
					Expect("true");
					return true;
				case 'f':
					Expect("false");
					return false;
				case 'n':
					Expect("null");
					return null;
				default:
					switch (c)
					{
					case '-':
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						return ParseNum();
					default:
						throw Error($"unexpected character '{c}'");
					}
				}
			}

			private void Expect(string word)
			{
				if (string.CompareOrdinal(_s, _i, word, 0, word.Length) != 0)
				{
					throw Error("expected '" + word + "'");
				}
				_i += word.Length;
			}

			private Dictionary<string, object> ParseObj(int depth)
			{
				Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
				_i++;
				SkipWs();
				if (!End && _s[_i] == '}')
				{
					_i++;
					return dictionary;
				}
				while (true)
				{
					SkipWs();
					if (End || _s[_i] != '"')
					{
						throw Error("expected property name");
					}
					string text = ParseStr();
					SkipWs();
					if (End || _s[_i] != ':')
					{
						throw Error("expected ':'");
					}
					_i++;
					SkipWs();
					if (dictionary.ContainsKey(text))
					{
						throw Error("duplicate key '" + text + "'");
					}
					dictionary[text] = ParseValue(depth + 1);
					SkipWs();
					if (End)
					{
						throw Error("unterminated object");
					}
					if (_s[_i] != ',')
					{
						break;
					}
					_i++;
					SkipWs();
					if (!End && _s[_i] == '}')
					{
						_i++;
						return dictionary;
					}
				}
				if (_s[_i] == '}')
				{
					_i++;
					return dictionary;
				}
				throw Error("expected ',' or '}'");
			}

			private List<object> ParseArr(int depth)
			{
				List<object> list = new List<object>();
				_i++;
				SkipWs();
				if (!End && _s[_i] == ']')
				{
					_i++;
					return list;
				}
				while (true)
				{
					SkipWs();
					list.Add(ParseValue(depth + 1));
					SkipWs();
					if (End)
					{
						throw Error("unterminated array");
					}
					if (_s[_i] != ',')
					{
						break;
					}
					_i++;
				}
				if (_s[_i] == ']')
				{
					_i++;
					return list;
				}
				throw Error("expected ',' or ']'");
			}

			private string ParseStr()
			{
				StringBuilder stringBuilder = new StringBuilder();
				_i++;
				while (true)
				{
					if (End)
					{
						throw Error("unterminated string");
					}
					char c = _s[_i++];
					if (c == '"')
					{
						return stringBuilder.ToString();
					}
					if (c == '\\')
					{
						if (End)
						{
							throw Error("bad escape");
						}
						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)
							{
								throw Error("bad unicode escape");
							}
							stringBuilder.Append((char)int.Parse(_s.Substring(_i, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture));
							_i += 4;
							break;
						default:
							throw Error("bad escape");
						}
					}
					else
					{
						if (c < ' ')
						{
							break;
						}
						stringBuilder.Append(c);
					}
				}
				throw Error("control character in string");
			}

			private double ParseNum()
			{
				int i = _i;
				if (_s[_i] == '-')
				{
					_i++;
				}
				while (_i < _s.Length && "0123456789.eE+-".IndexOf(_s[_i]) >= 0)
				{
					_i++;
				}
				if (!double.TryParse(_s.Substring(i, _i - i), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					throw Error("invalid number");
				}
				return result;
			}
		}

		public static object Parse(string text)
		{
			Parser parser = new Parser(text ?? "");
			parser.SkipWs();
			object result = parser.ParseValue(0);
			parser.SkipWs();
			if (!parser.End)
			{
				throw parser.Error("unexpected trailing characters");
			}
			return result;
		}

		public static Dictionary<string, object> ParseObject(string text)
		{
			return (Parse(text) as Dictionary<string, object>) ?? throw new JsonException("top-level value must be an object");
		}

		public static string Serialize(object value, bool indent = true)
		{
			StringBuilder stringBuilder = new StringBuilder();
			Write(stringBuilder, value, indent, 0);
			if (indent)
			{
				stringBuilder.Append('\n');
			}
			return stringBuilder.ToString();
		}

		private static void Write(StringBuilder sb, object v, bool indent, int level)
		{
			if (v != null)
			{
				if (!(v is bool flag))
				{
					if (!(v is string s))
					{
						if (!(v is int num))
						{
							if (!(v is long num2))
							{
								if (!(v is float num3))
								{
									if (!(v is double num4))
									{
										if (!(v is IDictionary<string, object> dictionary))
										{
											if (v is IEnumerable enumerable)
											{
												sb.Append('[');
												bool flag2 = true;
												foreach (object item in enumerable)
												{
													if (!flag2)
													{
														sb.Append(indent ? ", " : ",");
													}
													flag2 = false;
													Write(sb, item, indent, level + 1);
												}
												sb.Append(']');
											}
											else
											{
												WriteString(sb, Convert.ToString(v, CultureInfo.InvariantCulture));
											}
											return;
										}
										sb.Append('{');
										bool flag3 = true;
										foreach (KeyValuePair<string, object> item2 in dictionary)
										{
											if (!flag3)
											{
												sb.Append(',');
											}
											flag3 = false;
											if (indent)
											{
												sb.Append('\n');
												sb.Append(' ', (level + 1) * 2);
											}
											WriteString(sb, item2.Key);
											sb.Append(indent ? ": " : ":");
											Write(sb, item2.Value, indent, level + 1);
										}
										if (indent && dictionary.Count > 0)
										{
											sb.Append('\n');
											sb.Append(' ', level * 2);
										}
										sb.Append('}');
									}
									else
									{
										sb.Append(num4.ToString("R", CultureInfo.InvariantCulture));
									}
								}
								else
								{
									sb.Append(((double)num3).ToString("R", CultureInfo.InvariantCulture));
								}
							}
							else
							{
								sb.Append(num2.ToString(CultureInfo.InvariantCulture));
							}
						}
						else
						{
							sb.Append(num.ToString(CultureInfo.InvariantCulture));
						}
					}
					else
					{
						WriteString(sb, s);
					}
				}
				else
				{
					sb.Append(flag ? "true" : "false");
				}
			}
			else
			{
				sb.Append("null");
			}
		}

		private static void WriteString(StringBuilder sb, string s)
		{
			sb.Append('"');
			foreach (char c in s)
			{
				switch (c)
				{
				case '"':
					sb.Append("\\\"");
					continue;
				case '\\':
					sb.Append("\\\\");
					continue;
				case '\n':
					sb.Append("\\n");
					continue;
				case '\r':
					sb.Append("\\r");
					continue;
				case '\t':
					sb.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder = sb.Append("\\u");
					int num = c;
					stringBuilder.Append(num.ToString("x4"));
				}
				else
				{
					sb.Append(c);
				}
			}
			sb.Append('"');
		}
	}
	public sealed class JsonSection
	{
		private readonly Dictionary<string, object> _d;

		private readonly string _path;

		public readonly List<string> Errors;

		public IEnumerable<string> Keys => _d.Keys;

		public JsonSection(Dictionary<string, object> d, string path, List<string> errors)
		{
			_d = d ?? new Dictionary<string, object>();
			_path = path;
			Errors = errors;
		}

		public bool Has(string key)
		{
			return _d.ContainsKey(key);
		}

		public JsonSection Section(string key)
		{
			if (_d.TryGetValue(key, out var value) && value is Dictionary<string, object> d)
			{
				return new JsonSection(d, Path(key), Errors);
			}
			if (_d.ContainsKey(key) && !(_d[key] is Dictionary<string, object>))
			{
				Errors.Add(Path(key) + " must be an object");
			}
			return new JsonSection(null, Path(key), Errors);
		}

		private string Path(string key)
		{
			if (!string.IsNullOrEmpty(_path))
			{
				return _path + "." + key;
			}
			return key;
		}

		public string String(string key, string def, int maxLength = 512)
		{
			if (!_d.TryGetValue(key, out var value) || value == null)
			{
				return def;
			}
			if (value is string text)
			{
				if (text.Length > maxLength)
				{
					Errors.Add($"{Path(key)} is longer than {maxLength} characters");
					return def;
				}
				return text;
			}
			Errors.Add(Path(key) + " must be a string");
			return def;
		}

		public bool Bool(string key, bool def)
		{
			if (!_d.TryGetValue(key, out var value) || value == null)
			{
				return def;
			}
			if (value is bool)
			{
				return (bool)value;
			}
			Errors.Add(Path(key) + " must be true or false");
			return def;
		}

		public int Int(string key, int def, int min, int max)
		{
			if (!_d.TryGetValue(key, out var value) || value == null)
			{
				return def;
			}
			if (value is double num && Math.Abs(num - Math.Round(num)) < 1E-09)
			{
				if (num < (double)min || num > (double)max)
				{
					Errors.Add($"{Path(key)} must be between {min} and {max} (was {num})");
					return def;
				}
				return (int)num;
			}
			Errors.Add(Path(key) + " must be a whole number");
			return def;
		}

		public double Number(string key, double def, double min, double max)
		{
			if (!_d.TryGetValue(key, out var value) || value == null)
			{
				return def;
			}
			if (value is double num)
			{
				if (num < min || num > max)
				{
					Errors.Add($"{Path(key)} must be between {min} and {max} (was {num})");
					return def;
				}
				return num;
			}
			Errors.Add(Path(key) + " must be a number");
			return def;
		}

		public List<string> StringList(string key)
		{
			List<string> list = new List<string>();
			if (!_d.TryGetValue(key, out var value) || value == null)
			{
				return list;
			}
			if (value is List<object> list2)
			{
				{
					foreach (object item2 in list2)
					{
						if (item2 is string item)
						{
							list.Add(item);
						}
						else
						{
							Errors.Add(Path(key) + " must contain only strings");
						}
					}
					return list;
				}
			}
			Errors.Add(Path(key) + " must be an array");
			return list;
		}
	}
	public static class FrameIO
	{
		public static async Task<(byte[] Prefix, byte[] Body)> ReadFrameAsync(Stream stream, int maxLength, CancellationToken ct)
		{
			byte[] prefix = new byte[4];
			await ReadExactlyAsync(stream, prefix, ct).ConfigureAwait(continueOnCapturedContext: false);
			int num = prefix[0] | (prefix[1] << 8) | (prefix[2] << 16) | (prefix[3] << 24);
			if (num == 0 || num > maxLength)
			{
				throw new ProtocolException($"frame length {num} out of range");
			}
			byte[] body = new byte[num];
			await ReadExactlyAsync(stream, body, ct).ConfigureAwait(continueOnCapturedContext: false);
			return (Prefix: prefix, Body: body);
		}

		public static async Task ReadExactlyAsync(Stream stream, byte[] buffer, CancellationToken ct)
		{
			int num;
			for (int read = 0; read < buffer.Length; read += num)
			{
				num = await stream.ReadAsync(buffer, read, buffer.Length - read, ct).ConfigureAwait(continueOnCapturedContext: false);
				if (num <= 0)
				{
					throw new EndOfStreamException("connection closed");
				}
			}
		}

		public static byte[] PlainFrame(ReadOnlySpan<byte> body)
		{
			if (body.Length == 0)
			{
				throw new ArgumentException("bad frame length");
			}
			byte[] array = new byte[body.Length + 4];
			array[0] = (byte)body.Length;
			array[1] = (byte)(body.Length >> 8);
			array[2] = (byte)(body.Length >> 16);
			array[3] = (byte)(body.Length >> 24);
			body.CopyTo(array.AsSpan(4));
			return array;
		}
	}
	public static class MonoClock
	{
		private static readonly Stopwatch Watch = Stopwatch.StartNew();

		public static long Milliseconds => Watch.ElapsedMilliseconds;

		public static double Seconds => Watch.Elapsed.TotalSeconds;

		public static uint WireMs => (uint)Watch.ElapsedMilliseconds;

		public static int Diff(uint a, uint b)
		{
			return (int)(a - b);
		}
	}
	public sealed class TokenBucket
	{
		private readonly double _ratePerSecond;

		private readonly double _burst;

		private double _tokens;

		private long _lastMs;

		public TokenBucket(double ratePerSecond, double burst)
		{
			_ratePerSecond = ratePerSecond;
			_burst = burst;
			_tokens = burst;
			_lastMs = MonoClock.Milliseconds;
		}

		public bool TryTake(double cost = 1.0, long nowMs = -1L)
		{
			if (nowMs < 0)
			{
				nowMs = MonoClock.Milliseconds;
			}
			double num = (double)(nowMs - _lastMs) / 1000.0;
			_lastMs = nowMs;
			if (num > 0.0)
			{
				_tokens = Math.Min(_burst, _tokens + num * _ratePerSecond);
			}
			if (_tokens < cost)
			{
				return false;
			}
			_tokens -= cost;
			return true;
		}
	}
	public static class NameRules
	{
		public static string Sanitize(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(name.Length);
			bool flag = true;
			foreach (char c in name)
			{
				if (char.IsControl(c) || char.IsSurrogate(c) || char.GetUnicodeCategory(c) == UnicodeCategory.Format || c == '<' || c == '>' || c == '{' || c == '}')
				{
					continue;
				}
				if (char.IsWhiteSpace(c))
				{
					if (!flag)
					{
						stringBuilder.Append(' ');
					}
					flag = true;
					continue;
				}
				stringBuilder.Append(c);
				flag = false;
				if (stringBuilder.Length >= 32)
				{
					break;
				}
			}
			return stringBuilder.ToString().Trim();
		}
	}
	public static class Handshake
	{
		public const int ClientHelloSize = 39;

		public const int ServerHelloSize = 87;

		public static byte[] BuildClientHello(ReadOnlySpan<byte> clientEphemeralPublic)
		{
			NetWriter netWriter = new NetWriter(39);
			netWriter.WriteByte(1);
			netWriter.WriteUInt32(1380865862u);
			netWriter.WriteUInt16(1);
			netWriter.WriteBytes(clientEphemeralPublic);
			return netWriter.ToArray();
		}

		public static bool TryParseClientHello(ReadOnlySpan<byte> frame, out ushort version, out byte[] clientEphemeralPublic)
		{
			NetReader netReader = new NetReader(frame);
			if (netReader.ReadByte() != 1)
			{
				throw new ProtocolException("expected ClientHello");
			}
			if (netReader.ReadUInt32() != 1380865862)
			{
				throw new ProtocolException("bad magic");
			}
			version = netReader.ReadUInt16();
			if (version != 1)
			{
				clientEphemeralPublic = null;
				return false;
			}
			clientEphemeralPublic = netReader.ReadBytes(32).ToArray();
			netReader.ExpectEnd();
			return true;
		}

		public static byte[] BuildServerHello(ReadOnlySpan<byte> serverStaticPublic, ReadOnlySpan<byte> serverEphemeralPublic, ReadOnlySpan<byte> confirmTag)
		{
			NetWriter netWriter = new NetWriter(87);
			netWriter.WriteByte(2);
			netWriter.WriteUInt32(1380865862u);
			netWriter.WriteUInt16(1);
			netWriter.WriteBytes(serverStaticPublic);
			netWriter.WriteBytes(serverEphemeralPublic);
			netWriter.WriteBytes(confirmTag);
			return netWriter.ToArray();
		}

		public static void ParseServerHello(ReadOnlySpan<byte> frame, out byte[] serverStaticPublic, out byte[] serverEphemeralPublic, out byte[] confirmTag)
		{
			NetReader netReader = new NetReader(frame);
			if (netReader.ReadByte() != 2)
			{
				throw new ProtocolException("expected ServerHello");
			}
			if (netReader.ReadUInt32() != 1380865862)
			{
				throw new ProtocolException("bad magic");
			}
			if (netReader.ReadUInt16() != 1)
			{
				throw new ProtocolException("version mismatch");
			}
			serverStaticPublic = netReader.ReadBytes(32).ToArray();
			serverEphemeralPublic = netReader.ReadBytes(32).ToArray();
			confirmTag = netReader.ReadBytes(16).ToArray();
			netReader.ExpectEnd();
		}

		public static byte[] BuildReject(DisconnectReason reason, string text)
		{
			NetWriter netWriter = new NetWriter(64);
			netWriter.WriteByte(3);
			netWriter.WriteUInt32(1380865862u);
			netWriter.WriteUInt16(1);
			netWriter.WriteByte((byte)reason);
			netWriter.WriteShortString(text ?? "", 200);
			return netWriter.ToArray();
		}

		public static void ParseReject(ReadOnlySpan<byte> frame, out ushort serverVersion, out DisconnectReason reason, out string text)
		{
			NetReader netReader = new NetReader(frame);
			if (netReader.ReadByte() != 3)
			{
				throw new ProtocolException("expected ServerReject");
			}
			if (netReader.ReadUInt32() != 1380865862)
			{
				throw new ProtocolException("bad magic");
			}
			serverVersion = netReader.ReadUInt16();
			reason = (DisconnectReason)netReader.ReadByte();
			text = netReader.ReadShortString(200);
		}

		public static byte[] TranscriptHash(ReadOnlySpan<byte> clientHello, ReadOnlySpan<byte> serverStaticPublic, ReadOnlySpan<byte> serverEphemeralPublic)
		{
			Sha256 sha = new Sha256();
			sha.Update(Encoding.ASCII.GetBytes("FWN relay handshake v1"));
			sha.Update(clientHello);
			sha.Update(serverStaticPublic);
			sha.Update(serverEphemeralPublic);
			byte[] array = new byte[32];
			sha.Final(array);
			return array;
		}

		public static SessionKeys DeriveKeys(ReadOnlySpan<byte> transcriptHash, ReadOnlySpan<byte> dhEs, ReadOnlySpan<byte> dhEe)
		{
			byte[] array = new byte[64];
			dhEs.CopyTo(array);
			dhEe.CopyTo(array.AsSpan(32));
			byte[] array2 = Hkdf.DeriveKey(transcriptHash, array, Encoding.ASCII.GetBytes("FWN relay keys v1"), 160);
			CryptoUtil.Zero(array);
			SessionKeys result = new SessionKeys(array2);
			CryptoUtil.Zero(array2);
			return result;
		}

		public static byte[] ComputeConfirmTag(SessionKeys keys, ReadOnlySpan<byte> transcriptHash)
		{
			FischywebNetworker.Shared.Crypto.ChaCha20Poly1305 chaCha20Poly = new FischywebNetworker.Shared.Crypto.ChaCha20Poly1305(keys.Confirm);
			byte[] array = new byte[16];
			chaCha20Poly.Encrypt(new byte[12], ReadOnlySpan<byte>.Empty, Span<byte>.Empty, array, transcriptHash);
			return array;
		}

		public static SessionKeys ServerRespond(ReadOnlySpan<byte> clientHello, ReadOnlySpan<byte> clientEphemeralPublic, ReadOnlySpan<byte> serverStaticPrivate, ReadOnlySpan<byte> serverStaticPublic, out byte[] serverHello)
		{
			byte[] array = X25519.GeneratePrivateKey();
			byte[] array2 = X25519.PublicKeyFromPrivate(array);
			byte[] array3 = new byte[32];
			byte[] array4 = new byte[32];
			try
			{
				if (!X25519.SharedSecret(array3, serverStaticPrivate, clientEphemeralPublic) || !X25519.SharedSecret(array4, array, clientEphemeralPublic))
				{
					throw new ProtocolException("degenerate client key");
				}
				byte[] array5 = TranscriptHash(clientHello, serverStaticPublic, array2);
				SessionKeys sessionKeys = DeriveKeys(array5, array3, array4);
				serverHello = BuildServerHello(serverStaticPublic, array2, ComputeConfirmTag(sessionKeys, array5));
				return sessionKeys;
			}
			finally
			{
				CryptoUtil.Zero(array);
				CryptoUtil.Zero(array3);
				CryptoUtil.Zero(array4);
			}
		}

		public static SessionKeys ClientFinish(ReadOnlySpan<byte> clientHello, ReadOnlySpan<byte> clientEphemeralPrivate, ReadOnlySpan<byte> serverStaticPublic, ReadOnlySpan<byte> serverEphemeralPublic, ReadOnlySpan<byte> confirmTag)
		{
			byte[] array = new byte[32];
			byte[] array2 = new byte[32];
			try
			{
				if (!X25519.SharedSecret(array, clientEphemeralPrivate, serverStaticPublic) || !X25519.SharedSecret(array2, clientEphemeralPrivate, serverEphemeralPublic))
				{
					throw new ProtocolException("degenerate server key");
				}
				byte[] array3 = TranscriptHash(clientHello, serverStaticPublic, serverEphemeralPublic);
				SessionKeys sessionKeys = DeriveKeys(array3, array, array2);
				if (!CryptoUtil.FixedTimeEquals(ComputeConfirmTag(sessionKeys, array3), confirmTag))
				{
					sessionKeys.Dispose();
					throw new ProtocolException("server failed key confirmation");
				}
				return sessionKeys;
			}
			finally
			{
				CryptoUtil.Zero(array);
				CryptoUtil.Zero(array2);
			}
		}
	}
	public sealed class SessionKeys : IDisposable
	{
		public readonly byte[] Confirm = new byte[32];

		public readonly byte[] TcpClientToServer = new byte[32];

		public readonly byte[] TcpServerToClient = new byte[32];

		public readonly byte[] UdpClientToServer = new byte[32];

		public readonly byte[] UdpServerToClient = new byte[32];

		public SessionKeys(ReadOnlySpan<byte> okm)
		{
			okm.Slice(0, 32).CopyTo(Confirm);
			okm.Slice(32, 32).CopyTo(TcpClientToServer);
			okm.Slice(64, 32).CopyTo(TcpServerToClient);
			okm.Slice(96, 32).CopyTo(UdpClientToServer);
			okm.Slice(128, 32).CopyTo(UdpServerToClient);
		}

		public void Dispose()
		{
			CryptoUtil.Zero(Confirm);
			CryptoUtil.Zero(TcpClientToServer);
			CryptoUtil.Zero(TcpServerToClient);
			CryptoUtil.Zero(UdpClientToServer);
			CryptoUtil.Zero(UdpServerToClient);
		}
	}
	public sealed class TcpCipher
	{
		private readonly FischywebNetworker.Shared.Crypto.ChaCha20Poly1305 _aead;

		private ulong _counter;

		public TcpCipher(byte[] key)
		{
			_aead = new FischywebNetworker.Shared.Crypto.ChaCha20Poly1305(key);
		}

		public byte[] Seal(ReadOnlySpan<byte> plaintext)
		{
			int num = plaintext.Length + 16;
			if (num > 1048832)
			{
				throw new InvalidOperationException("TCP message too large");
			}
			byte[] array = new byte[4 + num];
			array[0] = (byte)num;
			array[1] = (byte)(num >> 8);
			array[2] = (byte)(num >> 16);
			array[3] = (byte)(num >> 24);
			Span<byte> span = stackalloc byte[12];
			NonceFromCounter(span, _counter++);
			_aead.Encrypt(span, plaintext, array.AsSpan(4, plaintext.Length), array.AsSpan(4 + plaintext.Length, 16), array.AsSpan(0, 4));
			return array;
		}

		public byte[] Open(ReadOnlySpan<byte> lengthPrefix, ReadOnlySpan<byte> body)
		{
			if (body.Length < 16)
			{
				return null;
			}
			byte[] array = new byte[body.Length - 16];
			Span<byte> span = stackalloc byte[12];
			NonceFromCounter(span, _counter);
			if (!_aead.Decrypt(span, body.Slice(0, array.Length), body.Slice(array.Length, 16), array, lengthPrefix))
			{
				return null;
			}
			_counter++;
			return array;
		}

		internal static void NonceFromCounter(Span<byte> nonce, ulong counter)
		{
			nonce.Slice(0, 4).Clear();
			for (int i = 0; i < 8; i++)
			{
				nonce[4 + i] = (byte)(counter >> 8 * i);
			}
		}
	}
	public sealed class UdpCipher
	{
		public const byte DataKind = 16;

		private readonly FischywebNetworker.Shared.Crypto.ChaCha20Poly1305 _sendAead;

		private readonly FischywebNetworker.Shared.Crypto.ChaCha20Poly1305 _recvAead;

		private ulong _sendCounter;

		public readonly ReplayWindow Replay = new ReplayWindow();

		public ulong LastSentPacketNumber => _sendCounter;

		public UdpCipher(byte[] sendKey, byte[] receiveKey)
		{
			_sendAead = new FischywebNetworker.Shared.Crypto.ChaCha20Poly1305(sendKey);
			_recvAead = new FischywebNetworker.Shared.Crypto.ChaCha20Poly1305(receiveKey);
		}

		public static bool TryPeekConnectionId(ReadOnlySpan<byte> datagram, out uint connectionId)
		{
			connectionId = 0u;
			if (datagram.Length < 29 || datagram.Length > 1200)
			{
				return false;
			}
			if (datagram[0] != 16)
			{
				return false;
			}
			connectionId = (uint)(datagram[1] | (datagram[2] << 8) | (datagram[3] << 16) | (datagram[4] << 24));
			return connectionId != 0;
		}

		public int Seal(uint connectionId, ReadOnlySpan<byte> plaintext, byte[] output)
		{
			int num = 13 + plaintext.Length + 16;
			if (num > 1200 || output.Length < num)
			{
				throw new InvalidOperationException("datagram too large");
			}
			ulong num2 = ++_sendCounter;
			output[0] = 16;
			output[1] = (byte)connectionId;
			output[2] = (byte)(connectionId >> 8);
			output[3] = (byte)(connectionId >> 16);
			output[4] = (byte)(connectionId >> 24);
			for (int i = 0; i < 8; i++)
			{
				output[5 + i] = (byte)(num2 >> 8 * i);
			}
			Span<byte> span = stackalloc byte[12];
			TcpCipher.NonceFromCounter(span, num2);
			_sendAead.Encrypt(span, plaintext, output.AsSpan(13, plaintext.Length), output.AsSpan(13 + plaintext.Length, 16), output.AsSpan(0, 13));
			return num;
		}

		public int Open(ReadOnlySpan<byte> datagram, Span<byte> plaintext, out ulong packetNumber)
		{
			packetNumber = 0uL;
			if (datagram.Length < 29 || datagram.Length > 1200)
			{
				return -1;
			}
			ulong num = 0uL;
			for (int i = 0; i < 8; i++)
			{
				num |= (ulong)datagram[5 + i] << 8 * i;
			}
			if (num == 0L || !Replay.CanAccept(num))
			{
				return -1;
			}
			int num2 = datagram.Length - 13 - 16;
			Span<byte> span = stackalloc byte[12];
			TcpCipher.NonceFromCounter(span, num);
			if (!_recvAead.Decrypt(span, datagram.Slice(13, num2), datagram.Slice(13 + num2, 16), plaintext, datagram.Slice(0, 13)))
			{
				return -1;
			}
			Replay.Mark(num);
			packetNumber = num;
			return num2;
		}
	}
	public sealed class ReplayWindow
	{
		private const int WindowBits = 1024;

		private readonly ulong[] _bitmap = new ulong[16];

		private ulong _highest;

		public ulong Highest => _highest;

		public bool CanAccept(ulong pn)
		{
			if (pn == 0L)
			{
				return false;
			}
			if (pn > _highest)
			{
				return true;
			}
			if (_highest - pn >= 1024)
			{
				return false;
			}
			int num = (int)(pn % 1024);
			return (_bitmap[num >> 6] & (ulong)(1L << num)) == 0;
		}

		public void Mark(ulong pn)
		{
			if (pn > _highest)
			{
				if (pn - _highest >= 1024)
				{
					Array.Clear(_bitmap, 0, _bitmap.Length);
				}
				else
				{
					for (ulong num = _highest + 1; num < pn; num++)
					{
						int num2 = (int)(num % 1024);
						_bitmap[num2 >> 6] &= (ulong)(~(1L << num2));
					}
				}
				_highest = pn;
			}
			int num3 = (int)(pn % 1024);
			_bitmap[num3 >> 6] |= (ulong)(1L << num3);
		}
	}
	public static class Protocol
	{
		public const uint Magic = 1380865862u;

		public const ushort Version = 1;

		public const string ProductName = "Fischyweb Networker";

		public const string RelayVersion = "1.0.0";

		public const string DefaultHostname = "sync.fischyweb.com";

		public const int DefaultTcpPort = 17777;

		public const int DefaultUdpPort = 17778;

		public const string HandshakeLabel = "FWN relay handshake v1";

		public const string KeyScheduleLabel = "FWN relay keys v1";

		public const string IdentityLabel = "FWN player id v1";
	}
	public static class ProtocolLimits
	{
		public const int MaxPreHandshakeFrame = 512;

		public const int MaxMessage = 1048576;

		public const int MaxTcpFrame = 1048832;

		public const int MaxTcpPlaintext = 1048816;

		public const int MaxUdpDatagram = 1200;

		public const int UdpHeaderSize = 13;

		public const int MaxUdpPlaintext = 1171;

		public const int MaxUnreliablePayload = 1158;

		public const int MaxUsernameChars = 32;

		public const int MaxUsernameBytes = 96;

		public const int MaxMetadataKeyBytes = 128;

		public const int MaxMetadataValueBytes = 61440;

		public const int MaxMetadataTotalBytes = 262144;

		public const int MaxMetadataKeys = 64;

		public const int SecretSize = 32;
	}
	public enum HandshakeFrame : byte
	{
		ClientHello = 1,
		ServerHello,
		ServerReject
	}
	public enum DisconnectReason : byte
	{
		None = 0,
		ProtocolMismatch = 1,
		ServerFull = 2,
		Banned = 5,
		ServerShuttingDown = 6,
		RateLimited = 7,
		Kicked = 8,
		Timeout = 9,
		ProtocolError = 10,
		ReplacedBySession = 11,
		ServerError = 12,
		ClientLeft = 13,
		HandshakeFailed = 14,
		TooManyConnections = 15,
		HostLeft = 20,
		LobbyNotFound = 21,
		LobbyFull = 22,
		KickedByHost = 23
	}
	public static class DisconnectReasonText
	{
		public static string Describe(DisconnectReason r)
		{
			return r switch
			{
				DisconnectReason.ProtocolMismatch => "Version mismatch - update Fischyweb Networker", 
				DisconnectReason.ServerFull => "The Fischyweb server is full", 
				DisconnectReason.Banned => "You are banned from this Fischyweb server", 
				DisconnectReason.ServerShuttingDown => "The Fischyweb server is restarting", 
				DisconnectReason.RateLimited => "Too much traffic - slow down", 
				DisconnectReason.Kicked => "Kicked by the server administrator", 
				DisconnectReason.Timeout => "Connection timed out", 
				DisconnectReason.ProtocolError => "Protocol error", 
				DisconnectReason.ReplacedBySession => "Logged in from another game", 
				DisconnectReason.ServerError => "Server error", 
				DisconnectReason.ClientLeft => "Left", 
				DisconnectReason.HandshakeFailed => "Secure handshake failed", 
				DisconnectReason.TooManyConnections => "Too many connections from your network", 
				DisconnectReason.HostLeft => "The host closed the server", 
				DisconnectReason.LobbyNotFound => "That server no longer exists", 
				DisconnectReason.LobbyFull => "That server is full", 
				DisconnectReason.KickedByHost => "Kicked by the host", 
				_ => "Disconnected", 
			};
		}

		public static bool IsRetryable(DisconnectReason r)
		{
			if (r != DisconnectReason.None && r != DisconnectReason.Timeout && r != DisconnectReason.ServerShuttingDown && r != DisconnectReason.ServerError && r != DisconnectReason.RateLimited && r != DisconnectReason.TooManyConnections)
			{
				return r == DisconnectReason.ServerFull;
			}
			return true;
		}
	}
	public enum RelayMessage : byte
	{
		Login = 1,
		Ping = 2,
		StartServer = 16,
		SetLobbyMetadata = 17,
		RequestLobbies = 18,
		JoinServer = 19,
		Leave = 20,
		DisconnectUser = 21,
		ToHost = 32,
		ToMember = 33,
		ToAllMembers = 34,
		UdpBind = 35,
		LoginOk = 128,
		LoginFailed = 129,
		Pong = 130,
		ServerStarted = 144,
		LobbyList = 145,
		JoinResult = 146,
		LobbyClosed = 147,
		MemberLeft = 148,
		FromMember = 160,
		FromHost = 161,
		UdpBindAck = 163,
		Notice = 192,
		Disconnect = 193
	}
	public static class Identity
	{
		public static ulong PlatformIdFromSecret(ReadOnlySpan<byte> secret)
		{
			Sha256 sha = new Sha256();
			sha.Update(Encoding.ASCII.GetBytes("FWN player id v1"));
			sha.Update(secret);
			byte[] array = new byte[32];
			sha.Final(array);
			ulong num = 0uL;
			for (int i = 0; i < 8; i++)
			{
				num |= (ulong)array[i] << 8 * i;
			}
			num &= 0x7FFFFFFFFFFFFFFFL;
			if (num != 0L)
			{
				return num;
			}
			return 1uL;
		}
	}
	public sealed class RelayClientOptions
	{
		public string Hostname = "sync.fischyweb.com";

		public int TcpPort = 17777;

		public int UdpPort = 17778;

		public string ServerPublicKey = "";

		public KnownServers KnownServers;

		public string Username = "";

		public byte[] Secret;

		public int FusionVersionMajor;

		public int FusionVersionMinor;

		public string ClientVersion = "1.0.0";

		public int ConnectionTimeoutSeconds = 15;

		public Action<string> Log = delegate
		{
		};
	}
	public enum RelayEventKind
	{
		FromHost,
		FromMember,
		ServerStarted,
		LobbyList,
		JoinResult,
		LobbyClosed,
		MemberLeft,
		Notice,
		Disconnected
	}
	public sealed class LobbyEntry
	{
		public ulong LobbyId;

		public Dictionary<string, string> Metadata = new Dictionary<string, string>();
	}
	public sealed class RelayEvent
	{
		public RelayEventKind Kind;

		public ulong Sender;

		public byte[] Data;

		public bool Reliable;

		public uint RequestId;

		public List<LobbyEntry> Lobbies;

		public ulong LobbyId;

		public bool Ok;

		public DisconnectReason Reason;

		public string Text;
	}
	public sealed class RelayConnectResult
	{
		public bool Success;

		public RelayClient Client;

		public string Error;

		public DisconnectReason Reason;

		public bool Retryable;

		public string ServerFingerprint;
	}
	public sealed class LobbyQuery
	{
		public readonly List<KeyValuePair<string, string>> Equal = new List<KeyValuePair<string, string>>();

		public readonly List<KeyValuePair<string, string>> NotEqual = new List<KeyValuePair<string, string>>();

		public int MaxResults = 100;

		public const int MaxFilters = 16;

		public const int MaxValueBytes = 256;

		public LobbyQuery WithEqual(string key, string value)
		{
			Equal.Add(new KeyValuePair<string, string>(key, value ?? ""));
			return this;
		}

		public LobbyQuery WithNotEqual(string key, string value)
		{
			NotEqual.Add(new KeyValuePair<string, string>(key, value ?? ""));
			return this;
		}

		public void Write(NetWriter w)
		{
			w.WriteByte((byte)Math.Min(Equal.Count, 16));
			for (int i = 0; i < Equal.Count && i < 16; i++)
			{
				w.WriteString(Equal[i].Key, 128);
				w.WriteString(Equal[i].Value, 256);
			}
			w.WriteByte((byte)Math.Min(NotEqual.Count, 16));
			for (int j = 0; j < NotEqual.Count && j < 16; j++)
			{
				w.WriteString(NotEqual[j].Key, 128);
				w.WriteString(NotEqual[j].Value, 256);
			}
			w.WriteUInt16((ushort)Math.Max(1, Math.Min(MaxResults, 500)));
		}

		public static LobbyQuery Read(ref NetReader r)
		{
			LobbyQuery lobbyQuery = new LobbyQuery();
			int num = r.ReadByte();
			if (num > 16)
			{
				throw new ProtocolException("too many lobby filters");
			}
			for (int i = 0; i < num; i++)
			{
				lobbyQuery.WithEqual(r.ReadString(128), r.ReadString(256));
			}
			int num2 = r.ReadByte();
			if (num2 > 16)
			{
				throw new ProtocolException("too many lobby filters");
			}
			for (int j = 0; j < num2; j++)
			{
				lobbyQuery.WithNotEqual(r.ReadString(128), r.ReadString(256));
			}
			lobbyQuery.MaxResults = Math.Max(1, Math.Min((int)r.ReadUInt16(), 500));
			return lobbyQuery;
		}

		public bool Matches(IReadOnlyDictionary<string, string> metadata)
		{
			foreach (KeyValuePair<string, string> item in Equal)
			{
				if (!metadata.TryGetValue(item.Key, out var value) || value != item.Value)
				{
					return false;
				}
			}
			foreach (KeyValuePair<string, string> item2 in NotEqual)
			{
				if (metadata.TryGetValue(item2.Key, out var value2) && value2 == item2.Value)
				{
					return false;
				}
			}
			return true;
		}
	}
	public sealed class RelayClient : IDisposable
	{
		private const double PingInterval = 2.0;

		private const double UdpPingInterval = 1.0;

		private readonly RelayClientOptions _options;

		private readonly Socket _tcp;

		private readonly NetworkStream _stream;

		private readonly TcpCipher _tcpSend;

		private readonly TcpCipher _tcpRecv;

		private readonly object _tcpLock = new object();

		private readonly BlockingCollection<byte[]> _outbox = new BlockingCollection<byte[]>(new ConcurrentQueue<byte[]>());

		private readonly ConcurrentQueue<RelayEvent> _events = new ConcurrentQueue<RelayEvent>();

		private readonly Socket _udp;

		private readonly UdpCipher _udpCipher;

		private readonly object _udpLock = new object();

		private readonly byte[] _udpSendBuffer = new byte[1200];

		private readonly IPEndPoint[] _udpCandidates;

		private volatile IPEndPoint _udpTarget;

		private readonly uint _connectionId;

		private readonly Thread _writer;

		private readonly ManualResetEventSlim _readEnded = new ManualResetEventSlim(initialState: false);

		private volatile bool _closed;

		private int _disconnectRaised;

		private long _lastTcpReceiveMs;

		private double _lastPing;

		private double _lastUdpPing;

		private double _lastBind;

		private double _startedAt = -1.0;

		private uint _pingSeq;

		private int _requestSeq;

		public long BytesSent;

		public long BytesReceived;

		public ulong PlatformId { get; }

		public string Username { get; }

		public string ServerName { get; }

		public string Motd { get; }

		public IPAddress ServerAddress { get; }

		public bool UdpBound { get; private set; }

		public float RttMs { get; private set; } = -1f;

		public bool IsClosed => _closed;

		private RelayClient(RelayClientOptions o, Socket tcp, NetworkStream stream, SessionKeys keys, TcpCipher send, TcpCipher recv, IPAddress address, ulong platformId, string username, uint connectionId, string serverName, string motd, List<byte[]> lan)
		{
			_options = o;
			_tcp = tcp;
			_stream = stream;
			_tcpSend = send;
			_tcpRecv = recv;
			ServerAddress = address;
			PlatformId = platformId;
			Username = username;
			ServerName = serverName;
			Motd = motd;
			_connectionId = connectionId;
			_udpCipher = new UdpCipher(keys.UdpClientToServer, keys.UdpServerToClient);
			keys.Dispose();
			_lastTcpReceiveMs = MonoClock.Milliseconds;
			_udpCandidates = UdpCandidates(address, (IPEndPoint)tcp.LocalEndPoint, lan, o.UdpPort);
			if (_udpCandidates.Length > 1)
			{
				o.Log("Trying UDP via " + string.Join(" and ", _udpCandidates.Select((IPEndPoint c) => c.ToString())));
			}
			try
			{
				_udp = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
				_udp.Bind(new IPEndPoint((address.AddressFamily == AddressFamily.InterNetworkV6) ? IPAddress.IPv6Any : IPAddress.Any, 0));
				_udp.ReceiveTimeout = 500;
				if (_udpCandidates.Length == 1)
				{
					_udpTarget = _udpCandidates[0];
				}
			}
			catch (SocketException ex)
			{
				o.Log($"UDP unavailable ({ex.SocketErrorCode}); real-time data will use TCP");
				_udp = null;
			}
			Thread thread = new Thread(TcpReadLoop);
			thread.IsBackground = true;
			thread.Name = "FWN-TCP-Read";
			thread.Start();
			_writer = new Thread(TcpWriteLoop)
			{
				IsBackground = true,
				Name = "FWN-TCP-Write"
			};
			_writer.Start();
			if (_udp != null)
			{
				Thread thread2 = new Thread(UdpReadLoop);
				thread2.IsBackground = true;
				thread2.Name = "FWN-UDP-Read";
				thread2.Start();
			}
		}

		public static async Task<RelayConnectResult> ConnectAsync(RelayClientOptions o, CancellationToken cancel)
		{
			if (o.Secret == null || o.Secret.Length != 32)
			{
				throw new ArgumentException("identity secret must be 32 bytes");
			}
			string host = (o.Hostname ?? "").Trim();
			int num = Math.Max(3, o.ConnectionTimeoutSeconds) * 1000;
			using CancellationTokenSource deadline = CancellationTokenSource.CreateLinkedTokenSource(cancel);
			deadline.CancelAfter(num);
			CancellationToken ct = deadline.Token;
			IPAddress[] source;
			try
			{
				if (IPAddress.TryParse(host, out IPAddress address))
				{
					source = new IPAddress[1] { address };
				}
				else
				{
					Task<IPAddress[]> dns = Dns.GetHostAddressesAsync(host);
					if (await Task.WhenAny(dns, Task.Delay(Math.Min(num, 8000), ct)).ConfigureAwait(continueOnCapturedContext: false) != dns)
					{
						return Fail(DisconnectReason.Timeout, "DNS lookup for " + host + " timed out", retryable: true);
					}
					source = dns.Result;
				}
			}
			catch (Exception e)
			{
				return Fail(DisconnectReason.None, $"Could not resolve {host} ({Inner(e).Message})", retryable: true);
			}
			source = source.OrderBy((IPAddress a) => (a.AddressFamily != AddressFamily.InterNetwork) ? 1 : 0).ToArray();
			if (source.Length == 0)
			{
				return Fail(DisconnectReason.None, host + " has no addresses", retryable: true);
			}
			string msg = "unreachable";
			IPAddress[] array = source;
			foreach (IPAddress address2 in array)
			{
				if (ct.IsCancellationRequested)
				{
					break;
				}
				RelayConnectResult relayConnectResult = await TryAddressAsync(o, host, address2, ct, cancel).ConfigureAwait(continueOnCapturedContext: false);
				if (relayConnectResult.Success || !relayConnectResult.Retryable || relayConnectResult.Reason != DisconnectReason.None)
				{
					return relayConnectResult;
				}
				msg = relayConnectResult.Error;
			}
			return Fail(DisconnectReason.None, msg, retryable: true);
		}

		private static RelayConnectResult Fail(DisconnectReason r, string msg, bool retryable)
		{
			return new RelayConnectResult
			{
				Success = false,
				Reason = r,
				Error = msg,
				Retryable = retryable
			};
		}

		private static Exception Inner(Exception e)
		{
			if (!(e is AggregateException { InnerException: not null } ex))
			{
				return e;
			}
			return ex.InnerException;
		}

		private static async Task<RelayConnectResult> TryAddressAsync(RelayClientOptions o, string host, IPAddress address, CancellationToken ct, CancellationToken userCancel)
		{
			Socket socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
			{
				NoDelay = true
			};
			bool keep = false;
			using (ct.Register(delegate
			{
				if (!keep)
				{
					try
					{
						socket.Dispose();
					}
					catch (Exception)
					{
					}
				}
			}))
			{
				_ = 4;
				try
				{
					try
					{
						await socket.ConnectAsync(new IPEndPoint(address, o.TcpPort)).ConfigureAwait(continueOnCapturedContext: false);
					}
					catch (Exception e)
					{
						if (ct.IsCancellationRequested)
						{
							throw new OperationCanceledException();
						}
						SocketException ex = Inner(e) as SocketException;
						string value = ((ex != null && ex.SocketErrorCode == SocketError.ConnectionRefused) ? "connection refused - is the server running?" : (ex?.SocketErrorCode.ToString() ?? Inner(e).Message));
						return Fail(DisconnectReason.None, $"Cannot reach {host}:{o.TcpPort}: {value}", retryable: true);
					}
					socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, optionValue: true);
					NetworkStream stream = new NetworkStream(socket, ownsSocket: true);
					byte[] eph = X25519.GeneratePrivateKey();
					byte[] hello = Handshake.BuildClientHello(X25519.PublicKeyFromPrivate(eph));
					byte[] array = FrameIO.PlainFrame(hello);
					await stream.WriteAsync(array, 0, array.Length, ct).ConfigureAwait(continueOnCapturedContext: false);
					byte[] item = (await FrameIO.ReadFrameAsync(stream, 512, ct).ConfigureAwait(continueOnCapturedContext: false)).Item2;
					if (item[0] == 3)
					{
						Handshake.ParseReject(item, out var _, out var reason, out var text);
						stream.Dispose();
						return Fail(reason, string.IsNullOrEmpty(text) ? DisconnectReasonText.Describe(reason) : text, DisconnectReasonText.IsRetryable(reason));
					}
					Handshake.ParseServerHello(item, out var sStatic, out var serverEphemeralPublic, out var confirmTag);
					string text2 = CryptoUtil.ToHex(sStatic);
					string text3 = (string.IsNullOrEmpty(o.ServerPublicKey) ? null : o.ServerPublicKey.Trim().ToLowerInvariant());
					string text4 = o.KnownServers?.Get(host, o.TcpPort);
					if (text3 != null && text3 != text2)
					{
						stream.Dispose();
						return Fail(DisconnectReason.HandshakeFailed, "Server identity does not match the pinned key", retryable: false);
					}
					if (text3 == null && text4 != null && text4 != text2)
					{
						stream.Dispose();
						return Fail(DisconnectReason.HandshakeFailed, "Server identity changed since your last visit (delete known_servers.json if the server was reinstalled)", retryable: false);
					}
					SessionKeys keys = Handshake.ClientFinish(hello, eph, sStatic, serverEphemeralPublic, confirmTag);
					CryptoUtil.Zero(eph);
					if (text3 == null && text4 == null)
					{
						o.KnownServers?.Remember(host, o.TcpPort, text2);
					}
					TcpCipher send = new TcpCipher(keys.TcpClientToServer);
					TcpCipher recv = new TcpCipher(keys.TcpServerToClient);
					NetWriter netWriter = new NetWriter(160);
					netWriter.WriteByte(1);
					netWriter.WriteUInt16((ushort)o.FusionVersionMajor);
					netWriter.WriteUInt16((ushort)o.FusionVersionMinor);
					netWriter.WriteShortString(o.ClientVersion, 32);
					netWriter.WriteShortString(o.Username, 96);
					netWriter.WriteBytes(o.Secret);
					byte[] array2 = send.Seal(netWriter.Span);
					await stream.WriteAsync(array2, 0, array2.Length, ct).ConfigureAwait(continueOnCapturedContext: false);
					if (!ParseLogin(await ReadRecordAsync(stream, recv, ct).ConfigureAwait(continueOnCapturedContext: false), out var id, out var name, out var cid, out var serverName, out var motd, out var lan, out var reason2, out var text5))
					{
						stream.Dispose();
						return Fail(reason2, text5, DisconnectReasonText.IsRetryable(reason2));
					}
					keep = true;
					RelayClient client = new RelayClient(o, socket, stream, keys, send, recv, address, id, name, cid, serverName, motd, lan);
					return new RelayConnectResult
					{
						Success = true,
						Client = client,
						ServerFingerprint = CryptoUtil.Fingerprint(sStatic)
					};
				}
				catch (OperationCanceledException)
				{
					bool isCancellationRequested = userCancel.IsCancellationRequested;
					return Fail(isCancellationRequested ? DisconnectReason.ClientLeft : DisconnectReason.Timeout, isCancellationRequested ? "Cancelled" : $"Timed out connecting to {host}:{o.TcpPort}", !isCancellationRequested);
				}
				catch (ProtocolException ex3)
				{
					return Fail(DisconnectReason.HandshakeFailed, "Handshake failed: " + ex3.Message, retryable: false);
				}
				catch (Exception ex4) when (ex4 is IOException || ex4 is SocketException || ex4 is ObjectDisposedException)
				{
					if (ct.IsCancellationRequested)
					{
						return Fail(DisconnectReason.Timeout, $"Timed out connecting to {host}:{o.TcpPort}", retryable: true);
					}
					return Fail(DisconnectReason.None, $"Connection to {host}:{o.TcpPort} closed during login ({Inner(ex4).Message})", retryable: true);
				}
				finally
				{
					if (!keep)
					{
						try
						{
							socket.Dispose();
						}
						catch (Exception)
						{
						}
					}
				}
			}
		}

		private static async Task<byte[]> ReadRecordAsync(NetworkStream stream, TcpCipher recv, CancellationToken ct)
		{
			(byte[], byte[]) obj = await FrameIO.ReadFrameAsync(stream, 1048832, ct).ConfigureAwait(continueOnCapturedContext: false);
			byte[] item = obj.Item1;
			byte[] item2 = obj.Item2;
			byte[] array = recv.Open(item, item2);
			if (array == null || array.Length == 0)
			{
				throw new ProtocolException("relay frame failed authentication");
			}
			return array;
		}

		private static bool ParseLogin(byte[] m, out ulong id, out string name, out uint cid, out string serverName, out string motd, out List<byte[]> lan, out DisconnectReason reason, out string text)
		{
			id = 0uL;
			name = "";
			cid = 0u;
			serverName = "";
			motd = "";
			lan = new List<byte[]>();
			reason = DisconnectReason.None;
			text = "";
			NetReader netReader = new NetReader(new ReadOnlySpan<byte>(m, 1, m.Length - 1));
			RelayMessage relayMessage = (RelayMessage)m[0];
			switch (relayMessage)
			{
			case RelayMessage.LoginFailed:
			case RelayMessage.Disconnect:
				reason = (DisconnectReason)netReader.ReadByte();
				text = netReader.ReadString(1024);
				if (string.IsNullOrEmpty(text))
				{
					text = DisconnectReasonText.Describe(reason);
				}
				return false;
			default:
				throw new ProtocolException($"unexpected {relayMessage} during login");
			case RelayMessage.LoginOk:
			{
				id = netReader.ReadUInt64();
				name = netReader.ReadShortString(96);
				cid = netReader.ReadUInt32();
				serverName = netReader.ReadShortString(128);
				motd = netReader.ReadString(1024);
				int num = netReader.ReadCount(8, 4);
				for (int i = 0; i < num; i++)
				{
					lan.Add(netReader.ReadBytes(4).ToArray());
				}
				return true;
			}
			}
		}

		public static IPEndPoint[] UdpCandidates(IPAddress server, IPEndPoint local, List<byte[]> lan, int udpPort)
		{
			List<IPEndPoint> list = new List<IPEndPoint>
			{
				new IPEndPoint(server, udpPort)
			};
			if (local == null || lan == null || server.AddressFamily != AddressFamily.InterNetwork)
			{
				return list.ToArray();
			}
			byte[] addressBytes = local.Address.MapToIPv4().GetAddressBytes();
			if (addressBytes[0] != 10 && (addressBytes[0] != 172 || addressBytes[1] < 16 || addressBytes[1] > 31) && (addressBytes[0] != 192 || addressBytes[1] != 168) && addressBytes[0] != 127)
			{
				return list.ToArray();
			}
			foreach (byte[] item in lan)
			{
				if (item != null && item.Length == 4)
				{
					IPAddress iPAddress = new IPAddress(item);
					if (!iPAddress.Equals(server))
					{
						list.Add(new IPEndPoint(iPAddress, udpPort));
					}
				}
			}
			return list.ToArray();
		}

		private void TcpReadLoop()
		{
			DisconnectReason disconnectReason = DisconnectReason.Timeout;
			string text = "Lost connection to the Fischyweb server";
			try
			{
				byte[] array = new byte[4];
				while (!_closed)
				{
					ReadExactly(array);
					int num = array[0] | (array[1] << 8) | (array[2] << 16) | (array[3] << 24);
					if (num <= 0 || num > 1048832)
					{
						throw new ProtocolException("bad frame length");
					}
					byte[] array2 = new byte[num];
					ReadExactly(array2);
					Interlocked.Add(ref BytesReceived, num + 4);
					byte[] array3 = _tcpRecv.Open(array, array2);
					if (array3 == null || array3.Length == 0)
					{
						throw new ProtocolException("frame failed authentication");
					}
					_lastTcpReceiveMs = MonoClock.Milliseconds;
					if (array3[0] == 193)
					{
						NetReader netReader = new NetReader(new ReadOnlySpan<byte>(array3, 1, array3.Length - 1));
						disconnectReason = (DisconnectReason)netReader.ReadByte();
						text = netReader.ReadString(1024);
						if (string.IsNullOrEmpty(text))
						{
							text = DisconnectReasonText.Describe(disconnectReason);
						}
						break;
					}
					Dispatch(array3, reliable: true);
				}
			}
			catch (ProtocolException ex)
			{
				disconnectReason = DisconnectReason.ProtocolError;
				text = "Protocol error: " + ex.Message;
			}
			catch (Exception)
			{
				if (_closed)
				{
					_readEnded.Set();
					return;
				}
			}
			_readEnded.Set();
			RaiseDisconnected(disconnectReason, text);
		}

		private void ReadExactly(byte[] buffer)
		{
			int num;
			for (int i = 0; i < buffer.Length; i += num)
			{
				num = _stream.Read(buffer, i, buffer.Length - i);
				if (num <= 0)
				{
					throw new EndOfStreamException();
				}
			}
		}

		private void TcpWriteLoop()
		{
			try
			{
				foreach (byte[] item in _outbox.GetConsumingEnumerable())
				{
					_stream.Write(item, 0, item.Length);
					Interlocked.Add(ref BytesSent, item.Length);
				}
			}
			catch (Exception)
			{
				if (!_closed)
				{
					RaiseDisconnected(DisconnectReason.Timeout, "Lost connection to the Fischyweb server");
				}
			}
		}

		private bool IsCandidate(IPEndPoint ep)
		{
			IPAddress obj = (ep.Address.IsIPv4MappedToIPv6 ? ep.Address.MapToIPv4() : ep.Address);
			IPEndPoint[] udpCandidates = _udpCandidates;
			foreach (IPEndPoint iPEndPoint in udpCandidates)
			{
				if (iPEndPoint.Address.Equals(obj) && iPEndPoint.Port == ep.Port)
				{
					return true;
				}
			}
			return false;
		}

		private void UdpReadLoop()
		{
			byte[] array = new byte[2048];
			byte[] array2 = new byte[1171];
			EndPoint endPoint = new IPEndPoint((_udp.AddressFamily == AddressFamily.InterNetworkV6) ? IPAddress.IPv6Any : IPAddress.Any, 0);
			while (!_closed)
			{
				EndPoint remoteEP = endPoint;
				int num;
				try
				{
					num = _udp.ReceiveFrom(array, ref remoteEP);
				}
				catch (SocketException)
				{
					continue;
				}
				catch (ObjectDisposedException)
				{
					break;
				}
				IPEndPoint iPEndPoint = (IPEndPoint)remoteEP;
				if (num <= 0 || !IsCandidate(iPEndPoint))
				{
					continue;
				}
				ReadOnlySpan<byte> datagram = new ReadOnlySpan<byte>(array, 0, num);
				if (!UdpCipher.TryPeekConnectionId(datagram, out var connectionId) || connectionId != _connectionId)
				{
					continue;
				}
				ulong packetNumber;
				int num2 = _udpCipher.Open(datagram, array2, out packetNumber);
				if (num2 <= 0)
				{
					continue;
				}
				if (_udpTarget == null)
				{
					_udpTarget = iPEndPoint;
					_options.Log($"UDP connected via {iPEndPoint}");
				}
				UdpBound = true;
				Interlocked.Add(ref BytesReceived, num);
				byte[] array3 = array2.AsSpan(0, num2).ToArray();
				if (array3[0] == 163)
				{
					continue;
				}
				if (array3[0] == 130)
				{
					HandlePong(array3);
					continue;
				}
				try
				{
					Dispatch(array3, reliable: false);
				}
				catch (ProtocolException)
				{
				}
			}
		}

		private void HandlePong(byte[] m)
		{
			NetReader netReader = new NetReader(new ReadOnlySpan<byte>(m, 1, m.Length - 1));
			netReader.ReadUInt32();
			uint b = netReader.ReadUInt32();
			RttMs = MonoClock.Diff(MonoClock.WireMs, b);
		}

		private void Dispatch(byte[] m, bool reliable)
		{
			RelayMessage relayMessage = (RelayMessage)m[0];
			NetReader netReader = new NetReader(new ReadOnlySpan<byte>(m, 1, m.Length - 1));
			switch (relayMessage)
			{
			case RelayMessage.Pong:
				HandlePong(m);
				break;
			case RelayMessage.FromMember:
			case RelayMessage.FromHost:
			{
				ulong sender = netReader.ReadUInt64();
				byte[] data = netReader.ReadBlob(1048576).ToArray();
				_events.Enqueue(new RelayEvent
				{
					Kind = ((relayMessage != RelayMessage.FromHost) ? RelayEventKind.FromMember : RelayEventKind.FromHost),
					Sender = sender,
					Data = data,
					Reliable = reliable
				});
				break;
			}
			case RelayMessage.ServerStarted:
				_events.Enqueue(new RelayEvent
				{
					Kind = RelayEventKind.ServerStarted,
					LobbyId = netReader.ReadUInt64()
				});
				break;
			case RelayMessage.LobbyList:
			{
				RelayEvent relayEvent2 = new RelayEvent
				{
					Kind = RelayEventKind.LobbyList,
					RequestId = netReader.ReadUInt32(),
					Lobbies = new List<LobbyEntry>()
				};
				int num = netReader.ReadCount16(1000, 10);
				for (int i = 0; i < num; i++)
				{
					LobbyEntry lobbyEntry = new LobbyEntry
					{
						LobbyId = netReader.ReadUInt64()
					};
					int num2 = netReader.ReadCount16(64, 4);
					for (int j = 0; j < num2; j++)
					{
						lobbyEntry.Metadata[netReader.ReadString(128)] = netReader.ReadString(61440);
					}
					relayEvent2.Lobbies.Add(lobbyEntry);
				}
				_events.Enqueue(relayEvent2);
				break;
			}
			case RelayMessage.JoinResult:
			{
				RelayEvent relayEvent = new RelayEvent
				{
					Kind = RelayEventKind.JoinResult,
					LobbyId = netReader.ReadUInt64(),
					Ok = netReader.ReadBool()
				};
				relayEvent.Reason = (DisconnectReason)netReader.ReadByte();
				relayEvent.Text = netReader.ReadString(1024);
				_events.Enqueue(relayEvent);
				break;
			}
			case RelayMessage.LobbyClosed:
			{
				ulong lobbyId = netReader.ReadUInt64();
				DisconnectReason disconnectReason = (DisconnectReason)netReader.ReadByte();
				string text = netReader.ReadString(1024);
				_events.Enqueue(new RelayEvent
				{
					Kind = RelayEventKind.LobbyClosed,
					LobbyId = lobbyId,
					Reason = disconnectReason,
					Text = (string.IsNullOrEmpty(text) ? DisconnectReasonText.Describe(disconnectReason) : text)
				});
				break;
			}
			case RelayMessage.MemberLeft:
				_events.Enqueue(new RelayEvent
				{
					Kind = RelayEventKind.MemberLeft,
					Sender = netReader.ReadUInt64()
				});
				break;
			case RelayMessage.Notice:
				netReader.ReadByte();
				_events.Enqueue(new RelayEvent
				{
					Kind = RelayEventKind.Notice,
					Text = netReader.ReadString(1024)
				});
				break;
			}
		}

		private void RaiseDisconnected(DisconnectReason reason, string text)
		{
			if (Interlocked.Exchange(ref _disconnectRaised, 1) == 0)
			{
				_events.Enqueue(new RelayEvent
				{
					Kind = RelayEventKind.Disconnected,
					Reason = reason,
					Text = text
				});
				Close();
			}
		}

		public bool TryGetEvent(out RelayEvent e)
		{
			return _events.TryDequeue(out e);
		}

		public void Update(double now)
		{
			if (_closed)
			{
				return;
			}
			if (_startedAt < 0.0)
			{
				_startedAt = now;
			}
			if (now - _lastPing >= 2.0)
			{
				_lastPing = now;
				NetWriter netWriter = new NetWriter(16);
				netWriter.WriteByte(2);
				netWriter.WriteUInt32(++_pingSeq);
				netWriter.WriteUInt32(MonoClock.WireMs);
				SendTcp(netWriter.Span);
			}
			if (MonoClock.Milliseconds - _lastTcpReceiveMs > (long)Math.Max(5, _options.ConnectionTimeoutSeconds) * 1000L)
			{
				RaiseDisconnected(DisconnectReason.Timeout, "The Fischyweb server stopped responding");
			}
			else
			{
				if (_udp == null)
				{
					return;
				}
				if (!UdpBound)
				{
					double num = ((now - _startedAt < 3.0) ? 0.25 : 1.0);
					if (now - _lastBind >= num)
					{
						_lastBind = now;
						SendUdp("#"u8);
					}
				}
				else if (now - _lastUdpPing >= 1.0)
				{
					_lastUdpPing = now;
					NetWriter netWriter2 = new NetWriter(16);
					netWriter2.WriteByte(2);
					netWriter2.WriteUInt32(++_pingSeq);
					netWriter2.WriteUInt32(MonoClock.WireMs);
					SendUdp(netWriter2.Span);
				}
			}
		}

		private void SendTcp(ReadOnlySpan<byte> plaintext)
		{
			if (_closed)
			{
				return;
			}
			lock (_tcpLock)
			{
				byte[] item = _tcpSend.Seal(plaintext);
				try
				{
					_outbox.Add(item);
				}
				catch (InvalidOperationException)
				{
				}
			}
		}

		private void SendUdp(ReadOnlySpan<byte> plaintext)
		{
			if (_udp == null || _closed)
			{
				return;
			}
			lock (_udpLock)
			{
				try
				{
					int num = _udpCipher.Seal(_connectionId, plaintext, _udpSendBuffer);
					IPEndPoint udpTarget = _udpTarget;
					if (udpTarget != null)
					{
						_udp.SendTo(_udpSendBuffer, 0, num, SocketFlags.None, udpTarget);
						Interlocked.Add(ref BytesSent, num);
						return;
					}
					IPEndPoint[] udpCandidates = _udpCandidates;
					foreach (IPEndPoint remoteEP in udpCandidates)
					{
						try
						{
							_udp.SendTo(_udpSendBuffer, 0, num, SocketFlags.None, remoteEP);
						}
						catch (SocketException)
						{
						}
						Interlocked.Add(ref BytesSent, num);
					}
				}
				catch (SocketException)
				{
				}
				catch (ObjectDisposedException)
				{
				}
			}
		}

		private void SendData(RelayMessage type, ulong? target, ReadOnlySpan<byte> data, bool reliable)
		{
			if (data.Length > 1048576)
			{
				throw new ArgumentException("message too large");
			}
			NetWriter netWriter = new NetWriter(data.Length + 16);
			netWriter.WriteByte((byte)type);
			if (target.HasValue)
			{
				netWriter.WriteUInt64(target.Value);
			}
			netWriter.WriteBlob(data);
			if (!reliable && UdpBound && data.Length <= 1158)
			{
				SendUdp(netWriter.Span);
			}
			else
			{
				SendTcp(netWriter.Span);
			}
		}

		public void SendToHost(ReadOnlySpan<byte> data, bool reliable)
		{
			SendData(RelayMessage.ToHost, null, data, reliable);
		}

		public void SendToMember(ulong member, ReadOnlySpan<byte> data, bool reliable)
		{
			SendData(RelayMessage.ToMember, member, data, reliable);
		}

		public void SendToAllMembers(ReadOnlySpan<byte> data, bool reliable)
		{
			SendData(RelayMessage.ToAllMembers, null, data, reliable);
		}

		public void StartServer()
		{
			SendTcp(new byte[1] { 16 });
		}

		public void Leave()
		{
			SendTcp(new byte[1] { 20 });
		}

		public void JoinServer(ulong lobbyId)
		{
			NetWriter netWriter = new NetWriter(16);
			netWriter.WriteByte(19);
			netWriter.WriteUInt64(lobbyId);
			SendTcp(netWriter.Span);
		}

		public void DisconnectUser(ulong platformId)
		{
			NetWriter netWriter = new NetWriter(16);
			netWriter.WriteByte(21);
			netWriter.WriteUInt64(platformId);
			SendTcp(netWriter.Span);
		}

		public void SetLobbyMetadata(string key, string value)
		{
			NetWriter netWriter = new NetWriter(64 + (value?.Length ?? 0) * 2);
			netWriter.WriteByte(17);
			netWriter.WriteString(key ?? "", 128);
			netWriter.WriteString(value ?? "", 61440);
			SendTcp(netWriter.Span);
		}

		public uint RequestLobbies(LobbyQuery q)
		{
			uint num = (uint)Interlocked.Increment(ref _requestSeq);
			NetWriter netWriter = new NetWriter();
			netWriter.WriteByte(18);
			netWriter.WriteUInt32(num);
			q.Write(netWriter);
			SendTcp(netWriter.Span);
			return num;
		}

		public void Disconnect()
		{
			if (!_closed)
			{
				Interlocked.Exchange(ref _disconnectRaised, 1);
				SendTcp(new byte[1] { 20 });
				try
				{
					_outbox.CompleteAdding();
				}
				catch (Exception)
				{
				}
				_writer?.Join(500);
				try
				{
					_tcp.Shutdown(SocketShutdown.Send);
				}
				catch (Exception)
				{
				}
				_readEnded.Wait(300);
				Close();
			}
		}

		private void Close()
		{
			if (_closed)
			{
				return;
			}
			_closed = true;
			try
			{
				_outbox.CompleteAdding();
			}
			catch (Exception)
			{
			}
			try
			{
				_stream.Dispose();
			}
			catch (Exception)
			{
			}
			try
			{
				_tcp.Dispose();
			}
			catch (Exception)
			{
			}
			try
			{
				_udp?.Dispose();
			}
			catch (Exception)
			{
			}
		}

		public void Dispose()
		{
			Close();
		}
	}
	public sealed class KnownServers
	{
		private readonly string _path;

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

		private readonly object _lock = new object();

		public KnownServers(string path)
		{
			_path = path;
			if (path == null || !File.Exists(path))
			{
				return;
			}
			try
			{
				foreach (KeyValuePair<string, object> item in MiniJson.ParseObject(File.ReadAllText(path)))
				{
					if (item.Value is string { Length: 64 } text)
					{
						_keys[item.Key] = text.ToLowerInvariant();
					}
				}
			}
			catch (Exception)
			{
			}
		}

		private static string KeyFor(string host, int port)
		{
			return $"{host.Trim().ToLowerInvariant()}:{port}";
		}

		public string Get(string host, int port)
		{
			lock (_lock)
			{
				string value;
				return _keys.TryGetValue(KeyFor(host, port), out value) ? value : null;
			}
		}

		public void Remember(string host, int port, string keyHex)
		{
			lock (_lock)
			{
				_keys[KeyFor(host, port)] = keyHex.ToLowerInvariant();
				if (_path == null)
				{
					return;
				}
				try
				{
					Dictionary<string, object> dictionary = new Dictionary<string, object>();
					foreach (KeyValuePair<string, string> key in _keys)
					{
						dictionary[key.Key] = key.Value;
					}
					Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(_path)) ?? ".");
					File.WriteAllText(_path, MiniJson.Serialize(dictionary));
				}
				catch (Exception)
				{
				}
			}
		}
	}
	public ref struct NetReader
	{
		private readonly ReadOnlySpan<byte> _data;

		private int _pos;

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

		public int Position => _pos;

		public int Remaining => _data.Length - _pos;

		public bool AtEnd => _pos >= _data.Length;

		public NetReader(ReadOnlySpan<byte> data)
		{
			_data = data;
			_pos = 0;
		}

		private void Need(int n)
		{
			if (n < 0 || _pos + n > _data.Length)
			{
				throw new ProtocolException("message truncated");
			}
		}

		public byte ReadByte()
		{
			Need(1);
			return _data[_pos++];
		}

		public bool ReadBool()
		{
			byte num = ReadByte();
			if (num > 1)
			{
				throw new ProtocolException("invalid bool");
			}
			return num == 1;
		}

		public ushort ReadUInt16()
		{
			Need(2);
			ushort result = (ushort)(_data[_pos] | (_data[_pos + 1] << 8));
			_pos += 2;
			return result;
		}

		public short ReadInt16()
		{
			return (short)ReadUInt16();
		}

		public uint ReadUInt32()
		{
			Need(4);
			int result = _data[_pos] | (_data[_pos + 1] << 8) | (_data[_pos + 2] << 16) | (_data[_pos + 3] << 24);
			_pos += 4;
			return (uint)result;
		}

		public int ReadInt32()
		{
			return (int)ReadUInt32();
		}

		public ulong ReadUInt64()
		{
			long num = ReadUInt32();
			ulong num2 = ReadUInt32();
			return (ulong)num | (num2 << 32);
		}

		public float ReadSingle()
		{
			float num = BitConverter.Int32BitsToSingle((int)ReadUInt32());
			if (float.IsNaN(num) || float.IsInfinity(num))
			{
				throw new ProtocolException("non-finite float");
			}
			return num;
		}

		public float ReadSingleBounded(float limit)
		{
			float num = ReadSingle();
			if (num > limit || num < 0f - limit)
			{
				throw new ProtocolException("float out of range");
			}
			return num;
		}

		public ReadOnlySpan<byte> ReadBytes(int count)
		{
			Need(count);
			ReadOnlySpan<byte> result = _data.Slice(_pos, count);
			_pos += count;
			return result;
		}

		public ReadOnlySpan<byte> ReadBlob(int maxBytes)
		{
			uint num = ReadUInt32();
			if (num > (uint)maxBytes)
			{
				throw new ProtocolException("blob too large");
			}
			return ReadBytes((int)num);
		}

		public string ReadShortString(int maxBytes = 255)
		{
			int num = ReadByte();
			if (num > maxBytes)
			{
				throw new ProtocolException("string too long");
			}
			return Decode(ReadBytes(num));
		}

		public string ReadString(int maxBytes = 4096)
		{
			int num = ReadUInt16();
			if (num > maxBytes)
			{
				throw new ProtocolException("string too long");
			}
			return Decode(ReadBytes(num));
		}

		private static string Decode(ReadOnlySpan<byte> bytes)
		{
			try
			{
				return StrictUtf8.GetString(bytes);
			}
			catch (ArgumentException)
			{
				throw new ProtocolException("invalid UTF-8");
			}
		}

		public int ReadCount(int max, int minBytesPerItem)
		{
			byte num = ReadByte();
			if (num > max)
			{
				throw new ProtocolException("count too large");
			}
			if (num * Math.Max(1, minBytesPerItem) > Remaining)
			{
				throw new ProtocolException("count exceeds payload");
			}
			return num;
		}

		public int ReadCount16(int max, int minBytesPerItem)
		{
			ushort num = ReadUInt16();
			if (num > max)
			{
				throw new ProtocolException("count too large");
			}
			if ((long)(int)num * (long)Math.Max(1, minBytesPerItem) > Remaining)
			{
				throw new ProtocolException("count exceeds payload");
			}
			return num;
		}

		public void ExpectEnd()
		{
			if (!AtEnd)
			{
				throw new ProtocolException("trailing bytes");
			}
		}
	}
	public sealed class ProtocolException : Exception
	{
		public ProtocolException(string message)
			: base(message)
		{
		}
	}
	public sealed class NetWriter
	{
		private byte[] _buffer;

		private int _length;

		public int Length => _length;

		public ReadOnlySpan<byte> Span => new ReadOnlySpan<byte>(_buffer, 0, _length);

		public ArraySegment<byte> Segment => new ArraySegment<byte>(_buffer, 0, _length);

		public NetWriter(int capacity = 256)
		{
			_buffer = new byte[Math.Max(16, capacity)];
		}

		public byte[] ToArray()
		{
			return Span.ToArray();
		}

		public void Reset()
		{
			_length = 0;
		}

		private void Ensure(int extra)
		{
			if (_length + extra > _buffer.Length)
			{
				int num;
				for (num = _buffer.Length * 2; num < _length + extra; num *= 2)
				{
				}
				Array.Resize(ref _buffer, num);
			}
		}

		public int Reserve(int count)
		{
			Ensure(count);
			int length = _length;
			_length += count;
			return length;
		}

		public void PatchUInt16(int offset, ushort value)
		{
			_buffer[offset] = (byte)value;
			_buffer[offset + 1] = (byte)(value >> 8);
		}

		public void WriteByte(byte v)
		{
			Ensure(1);
			_buffer[_length++] = v;
		}

		public void WriteBool(bool v)
		{
			WriteByte(v ? ((byte)1) : ((byte)0));
		}

		public void WriteSByte(sbyte v)
		{
			WriteByte((byte)v);
		}

		public void WriteUInt16(ushort v)
		{
			Ensure(2);
			_buffer[_length++] = (byte)v;
			_buffer[_length++] = (byte)(v >> 8);
		}

		public void WriteInt16(short v)
		{
			WriteUInt16((ushort)v);
		}

		public void WriteUInt32(uint v)
		{
			Ensure(4);
			_buffer[_length++] = (byte)v;
			_buffer[_length++] = (byte)(v >> 8);
			_buffer[_length++] = (byte)(v >> 16);
			_buffer[_length++] = (byte)(v >> 24);
		}

		public void WriteInt32(int v)
		{
			WriteUInt32((uint)v);
		}

		public void WriteUInt64(ulong v)
		{
			WriteUInt32((uint)v);
			WriteUInt32((uint)(v >> 32));
		}

		public void WriteInt64(long v)
		{
			WriteUInt64((ulong)v);
		}

		public void WriteSingle(float v)
		{
			WriteUInt32((uint)BitConverter.SingleToInt32Bits(v));
		}

		public void WriteBytes(ReadOnlySpan<byte> data)
		{
			Ensure(data.Length);
			data.CopyTo(new Span<byte>(_buffer, _length, data.Length));
			_length += data.Length;
		}

		public void WriteBlob(ReadOnlySpan<byte> data)
		{
			WriteUInt32((uint)data.Length);
			WriteBytes(data);
		}

		public void WriteShortString(string s, int maxBytes = 255)
		{
			byte[] array = Utf8Truncate(s ?? string.Empty, Math.Min(maxBytes, 255));
			WriteByte((byte)array.Length);
			WriteBytes(array);
		}

		public void WriteString(string s, int maxBytes = 4096)
		{
			byte[] array = Utf8Truncate(s ?? string.Empty, Math.Min(maxBytes, 65535));
			WriteUInt16((ushort)array.Length);
			WriteBytes(array);
		}

		internal static byte[] Utf8Truncate(string s, int maxBytes)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(s);
			if (bytes.Length <= maxBytes)
			{
				return bytes;
			}
			int num = maxBytes;
			while (num > 0 && (bytes[num] & 0xC0) == 128)
			{
				num--;
			}
			byte[] array = new byte[num];
			Array.Copy(bytes, array, num);
			return array;
		}

		internal static short Quantize(float value, float unit)
		{
			if (float.IsNaN(value))
			{
				return 0;
			}
			float num = value / unit;
			if (num > 32767f)
			{
				return short.MaxValue;
			}
			if (num < -32768f)
			{
				return short.MinValue;
			}
			return (short)Math.Round(num);
		}
	}
}
namespace FischywebNetworker.Shared.Crypto
{
	public sealed class ChaCha20Poly1305
	{
		public const int KeySize = 32;

		public const int NonceSize = 12;

		public const int TagSize = 16;

		private readonly uint[] _key = new uint[8];

		public ChaCha20Poly1305(ReadOnlySpan<byte> key)
		{
			if (key.Length != 32)
			{
				throw new ArgumentException("ChaCha20-Poly1305 key must be 32 bytes");
			}
			for (int i = 0; i < 8; i++)
			{
				_key[i] = LoadLE32(key, 4 * i);
			}
		}

		public void Encrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> plaintext, Span<byte> ciphertext, Span<byte> tag, ReadOnlySpan<byte> associatedData = default(ReadOnlySpan<byte>))
		{
			if (nonce.Length != 12)
			{
				throw new ArgumentException("nonce must be 12 bytes");
			}
			if (ciphertext.Length < plaintext.Length)
			{
				throw new ArgumentException("ciphertext too small");
			}
			Span<byte> output = stackalloc byte[64];
			Block(nonce, 0u, output);
			Xor(nonce, 1u, plaintext, ciphertext);
			ComputeTag(output.Slice(0, 32), associatedData, ciphertext.Slice(0, plaintext.Length), tag);
			output.Clear();
		}

		public bool Decrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> tag, Span<byte> plaintext, ReadOnlySpan<byte> associatedData = default(ReadOnlySpan<byte>))
		{
			if (nonce.Length != 12 || tag.Length != 16)
			{
				return false;
			}
			if (plaintext.Length < ciphertext.Length)
			{
				throw new ArgumentException("plaintext too small");
			}
			Span<byte> output = stackalloc byte[64];
			Block(nonce, 0u, output);
			Span<byte> span = stackalloc byte[16];
			ComputeTag(output.Slice(0, 32), associatedData, ciphertext, span);
			output.Clear();
			if (!CryptoUtil.FixedTimeEquals(span, tag))
			{
				return false;
			}
			Xor(nonce, 1u, ciphertext, plaintext);
			return true;
		}

		private static void ComputeTag(ReadOnlySpan<byte> polyKey, ReadOnlySpan<byte> ad, ReadOnlySpan<byte> ct, Span<byte> tag)
		{
			Poly1305 poly = new Poly1305(polyKey);
			Span<byte> span = stackalloc byte[16];
			span.Clear();
			poly.Update(ad);
			if ((ad.Length & 0xF) != 0)
			{
				poly.Update(span.Slice(0, 16 - (ad.Length & 0xF)));
			}
			poly.Update(ct);
			if ((ct.Length & 0xF) != 0)
			{
				poly.Update(span.Slice(0, 16 - (ct.Length & 0xF)));
			}
			Span<byte> span2 = stackalloc byte[16];
			StoreLE64(span2, 0, (ulong)ad.Length);
			StoreLE64(span2, 8, (ulong)ct.Length);
			poly.Update(span2);
			poly.Final(tag);
		}

		private void Xor(ReadOnlySpan<byte> nonce, uint counter, ReadOnlySpan<byte> input, Span<byte> output)
		{
			Span<byte> output2 = stackalloc byte[64];
			int num;
			for (int i = 0; i < input.Length; i += num)
			{
				Block(nonce, counter++, output2);
				num = Math.Min(64, input.Length - i);
				for (int j = 0; j < num; j++)
				{
					output[i + j] = (byte)(input[i + j] ^ output2[j]);
				}
			}
			output2.Clear();
		}

		public void Block(ReadOnlySpan<byte> nonce, uint counter, Span<byte> output)
		{
			Span<uint> span = stackalloc uint[16];
			span[0] = 1634760805u;
			span[1] = 857760878u;
			span[2] = 2036477234u;
			span[3] = 1797285236u;
			for (int i = 0; i < 8; i++)
			{
				span[4 + i] = _key[i];
			}
			span[12] = counter;
			span[13] = LoadLE32(nonce, 0);
			span[14] = LoadLE32(nonce, 4);
			span[15] = LoadLE32(nonce, 8);
			Span<uint> span2 = stackalloc uint[16];
			span.CopyTo(span2);
			for (int j = 0; j < 10; j++)
			{
				Quarter(span2, 0, 4, 8, 12);
				Quarter(span2, 1, 5, 9, 13);
				Quarter(span2, 2, 6, 10, 14);
				Quarter(span2, 3, 7, 11, 15);
				Quarter(span2, 0, 5, 10, 15);
				Quarter(span2, 1, 6, 11, 12);
				Quarter(span2, 2, 7, 8, 13);
				Quarter(span2, 3, 4, 9, 14);
			}
			for (int k = 0; k < 16; k++)
			{
				StoreLE32(output, 4 * k, span2[k] + span[k]);
			}
			span2.Clear();
			span.Clear();
		}

		private static uint Rotl(uint v, int c)
		{
			return (v << c) | (v >> 32 - c);
		}

		private static void Quarter(Span<uint> x, int a, int b, int c, int d)
		{
			x[a] += x[b];
			x[d] = Rotl(x[d] ^ x[a], 16);
			x[c] += x[d];
			x[b] = Rotl(x[b] ^ x[c], 12);
			x[a] += x[b];
			x[d] = Rotl(x[d] ^ x[a], 8);
			x[c] += x[d];
			x[b] = Rotl(x[b] ^ x[c], 7);
		}

		internal static uint LoadLE32(ReadOnlySpan<byte> b, int o)
		{
			return (uint)(b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24));
		}

		internal static void StoreLE32(Span<byte> b, int o, uint v)
		{
			b[o] = (byte)v;
			b[o + 1] = (byte)(v >> 8);
			b[o + 2] = (byte)(v >> 16);
			b[o + 3] = (byte)(v >> 24);
		}

		internal static void StoreLE64(Span<byte> b, int o, ulong v)
		{
			for (int i = 0; i < 8; i++)
			{
				b[o + i] = (byte)(v >> 8 * i);
			}
		}
	}
	public sealed class Poly1305
	{
		private readonly uint _r0;

		private readonly uint _r1;

		private readonly uint _r2;

		private readonly uint _r3;

		private readonly uint _r4;

		private readonly uint _s1;

		private readonly uint _s2;

		private readonly uint _s3;

		private readonly uint _s4;

		private readonly uint _pad0;

		private readonly uint _pad1;

		private readonly uint _pad2;

		private readonly uint _pad3;

		private uint _h0;

		private uint _h1;

		private uint _h2;

		private uint _h3;

		private uint _h4;

		private readonly byte[] _buffer = new byte[16];

		private int _leftover;

		public Poly1305(ReadOnlySpan<byte> key)
		{
			if (key.Length < 32)
			{
				throw new ArgumentException("Poly1305 key must be 32 bytes");
			}
			_r0 = ChaCha20Poly1305.LoadLE32(key, 0) & 0x3FFFFFF;
			_r1 = (ChaCha20Poly1305.LoadLE32(key, 3) >> 2) & 0x3FFFF03;
			_r2 = (ChaCha20Poly1305.LoadLE32(key, 6) >> 4) & 0x3FFC0FF;
			_r3 = (ChaCha20Poly1305.LoadLE32(key, 9) >> 6) & 0x3F03FFF;
			_r4 = (ChaCha20Poly1305.LoadLE32(key, 12) >> 8) & 0xFFFFF;
			_s1 = _r1 * 5;
			_s2 = _r2 * 5;
			_s3 = _r3 * 5;
			_s4 = _r4 * 5;
			_pad0 = ChaCha20Poly1305.LoadLE32(key, 16);
			_pad1 = ChaCha20Poly1305.LoadLE32(key, 20);
			_pad2 = ChaCha20Poly1305.LoadLE32(key, 24);
			_pad3 = ChaCha20Poly1305.LoadLE32(key, 28);
		}

		public void Update(ReadOnlySpan<byte> m)
		{
			if (_leftover > 0)
			{
				int num = Math.Min(16 - _leftover, m.Length);
				m.Slice(0, num).CopyTo(_buffer.AsSpan(_leftover));
				_leftover += num;
				m = m.Slice(num);
				if (_leftover < 16)
				{
					return;
				}
				Blocks(_buffer, 16777216u);
				_leftover = 0;
			}
			while (m.Length >= 16)
			{
				Blocks(m.Slice(0, 16), 16777216u);
				m = m.Slice(16);
			}
			if (m.Length > 0)
			{
				m.CopyTo(_buffer);
				_leftover = m.Length;
			}
		}

		private void Blocks(ReadOnlySpan<byte> m, uint hibit)
		{
			uint h = _h0;
			uint h2 = _h1;
			uint h3 = _h2;
			uint h4 = _h3;
			uint h5 = _h4;
			h += ChaCha20Poly1305.LoadLE32(m, 0) & 0x3FFFFFF;
			h2 += (ChaCha20Poly1305.LoadLE32(m, 3) >> 2) & 0x3FFFFFF;
			h3 += (ChaCha20Poly1305.LoadLE32(m, 6) >> 4) & 0x3FFFFFF;
			h4 += (ChaCha20Poly1305.LoadLE32(m, 9) >> 6) & 0x3FFFFFF;
			h5 += (ChaCha20Poly1305.LoadLE32(m, 12) >> 8) | hibit;
			ulong num = (ulong)((long)h * (long)_r0 + (long)h2 * (long)_s4 + (long)h3 * (long)_s3 + (long)h4 * (long)_s2 + (long)h5 * (long)_s1);
			ulong num2 = (ulong)((long)h * (long)_r1 + (long)h2 * (long)_r0 + (long)h3 * (long)_s4 + (long)h4 * (long)_s3 + (long)h5 * (long)_s2);
			ulong num3 = (ulong)((long)h * (long)_r2 + (long)h2 * (long)_r1 + (long)h3 * (long)_r0 + (long)h4 * (long)_s4 + (long)h5 * (long)_s3);
			ulong num4 = (ulong)((long)h * (long)_r3 + (long)h2 * (long)_r2 + (long)h3 * (long)_r1 + (long)h4 * (long)_r0 + (long)h5 * (long)_s4);
			long num5 = (long)h * (long)_r4 + (long)h2 * (long)_r3 + (long)h3 * (long)_r2 + (long)h4 * (long)_r1 + (long)h5 * (long)_r0;
			ulong num6 = num >> 26;
			h = (uint)((int)num & 0x3FFFFFF);
			num2 += num6;
			num6 = num2 >> 26;
			h2 = (uint)((int)num2 & 0x3FFFFFF);
			num3 += num6;
			num6 = num3 >> 26;
			h3 = (uint)((int)num3 & 0x3FFFFFF);
			num4 += num6;
			num6 = num4 >> 26;
			h4 = (uint)((int)num4 & 0x3FFFFFF);
			long num7 = num5 + (long)num6;
			num6 = (ulong)num7 >> 26;
			h5 = (uint)((int)num7 & 0x3FFFFFF);
			h += (uint)((int)num6 * 5);
			uint num8 = h >> 26;
			h &= 0x3FFFFFF;
			h2 += num8;
			_h0 = h;
			_h1 = h2;
			_h2 = h3;
			_h3 = h4;
			_h4 = h5;
		}

		public void Final(Span<byte> tag)
		{
			if (_leftover > 0)
			{
				_buffer[_leftover] = 1;
				for (int i = _leftover + 1; i < 16; i++)
				{
					_buffer[i] = 0;
				}
				Blocks(_buffer, 0u);
				_leftover = 0;
			}
			uint h = _h0;
			uint h2 = _h1;
			uint h3 = _h2;
			uint h4 = _h3;
			uint h5 = _h4;
			uint num = h2 >> 26;
			h2 &= 0x3FFFFFF;
			h3 += num;
			num = h3 >> 26;
			h3 &= 0x3FFFFFF;
			h4 += num;
			num = h4 >> 26;
			h4 &= 0x3FFFFFF;
			h5 += num;
			num = h5 >> 26;
			h5 &= 0x3FFFFFF;
			h += num * 5;
			num = h >> 26;
			h &= 0x3FFFFFF;
			h2 += num;
			uint num2 = h + 5;
			num = num2 >> 26;
			num2 &= 0x3FFFFFF;
			uint num3 = h2 + num;
			num = num3 >> 26;
			num3 &= 0x3FFFFFF;
			uint num4 = h3 + num;
			num = num4 >> 26;
			num4 &= 0x3FFFFFF;
			uint num5 = h4 + num;
			num = num5 >> 26;
			num5 &= 0x3FFFFFF;
			uint num6 = h5 + num - 67108864;
			uint num7 = (num6 >> 31) - 1;
			num2 &= num7;
			num3 &= num7;
			num4 &= num7;
			num5 &= num7;
			num6 &= num7;
			num7 = ~num7;
			h = (h & num7) | num2;
			h2 = (h2 & num7) | num3;
			h3 = (h3 & num7) | num4;
			h4 = (h4 & num7) | num5;
			h5 = (h5 & num7) | num6;
			h |= h2 << 26;
			h2 = (h2 >> 6) | (h3 << 20);
			h3 = (h3 >> 12) | (h4 << 14);
			h4 = (h4 >> 18) | (h5 << 8);
			ulong num8 = (ulong)h + (ulong)_pad0;
			h = (uint)num8;
			num8 = (ulong)((long)h2 + (long)_pad1) + (num8 >> 32);
			h2 = (uint)num8;
			num8 = (ulong)((long)h3 + (long)_pad2) + (num8 >> 32);
			h3 = (uint)num8;
			num8 = (ulong)((long)h4 + (long)_pad3) + (num8 >> 32);
			h4 = (uint)num8;
			ChaCha20Poly1305.StoreLE32(tag, 0, h);
			ChaCha20Poly1305.StoreLE32(tag, 4, h2);
			ChaCha20Poly1305.StoreLE32(tag, 8, h3);
			ChaCha20Poly1305.StoreLE32(tag, 12, h4);
			_h0 = (_h1 = (_h2 = (_h3 = (_h4 = 0u))));
		}
	}
	public static class CryptoUtil
	{
		public static bool FixedTimeEquals(ReadOnlySpan<byte> a, ReadOnlySpan<byte> b)
		{
			if (a.Length != b.Length)
			{
				return false;
			}
			int num = 0;
			for (int i = 0; i < a.Length; i++)
			{
				num |= a[i] ^ b[i];
			}
			return num == 0;
		}

		public static void Zero(Span<byte> data)
		{
			data.Clear();
		}

		public static string ToHex(ReadOnlySpan<byte> data)
		{
			char[] array = new char[data.Length * 2];
			for (int i = 0; i < data.Length; i++)
			{
				array[2 * i] = "0123456789abcdef"[data[i] >> 4];
				array[2 * i + 1] = "0123456789abcdef"[data[i] & 0xF];
			}
			return new string(array);
		}

		public static byte[] FromHex(string hex)
		{
			if (hex == null || (hex.Length & 1) != 0)
			{
				throw new FormatException("hex string must have even length");
			}
			byte[] array = new byte[hex.Length / 2];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = (byte)((Nibble(hex[2 * i]) << 4) | Nibble(hex[2 * i + 1]));
			}
			return array;
		}

		private static int Nibble(char c)
		{
			if (c >= '0' && c <= '9')
			{
				return c - 48;
			}
			if (c >= 'a' && c <= 'f')
			{
				return c - 97 + 10;
			}
			if (c >= 'A' && c <= 'F')
			{
				return c - 65 + 10;
			}
			throw new FormatException("invalid hex character");
		}

		public static string Fingerprint(ReadOnlySpan<byte> publicKey)
		{
			string text = ToHex(Sha256.Hash(publicKey).AsSpan(0, 16));
			string[] array = new string[8];
			for (int i = 0; i < 8; i++)
			{
				array[i] = text.Substring(i * 4, 4);
			}
			return string.Join(":", array);
		}
	}
	public static class SecureRandom
	{
		private static int _mode;

		private static readonly object Lock = new object();

		private static FileStream _urandom;

		public static void Fill(Span<byte> data)
		{
			if (_mode != 2)
			{
				try
				{
					RandomNumberGenerator.Fill(data);
					_mode = 1;
					return;
				}
				catch (Exception) when (_mode == 0)
				{
					_mode = 2;
				}
			}
			lock (Lock)
			{
				if (_urandom == null)
				{
					_urandom = new FileStream("/dev/urandom", FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1);
				}
				int num;
				for (int i = 0; i < data.Length; i += num)
				{
					num = _urandom.Read(data.Slice(i));
					if (num <= 0)
					{
						throw new IOException("/dev/urandom returned no data");
					}
				}
			}
		}

		public static byte[] Bytes(int count)
		{
			byte[] array = new byte[count];
			Fill(array);
			return array;
		}

		public static uint NextUInt32()
		{
			Span<byte> data = stackalloc byte[4];
			Fill(data);
			return (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24));
		}

		public static ulong NextUInt64()
		{
			Span<byte> data = stackalloc byte[8];
			Fill(data);
			ulong num = 0uL;
			for (int i = 0; i < 8; i++)
			{
				num |= (ulong)data[i] << 8 * i;
			}
			return num;
		}
	}
	public sealed class Sha256
	{
		public const int HashSize = 32;

		public const int BlockSize = 64;

		private static readonly uint[] K = new uint[64]
		{
			1116352408u, 1899447441u, 3049323471u, 3921009573u, 961987163u, 1508970993u, 2453635748u, 2870763221u, 3624381080u, 310598401u,
			607225278u, 1426881987u, 1925078388u, 2162078206u, 2614888103u, 3248222580u, 3835390401u, 4022224774u, 264347078u, 604807628u,
			770255983u, 1249150122u, 1555081692u, 1996064986u, 2554220882u, 2821834349u, 2952996808u, 3210313671u, 3336571891u, 3584528711u,
			113926993u, 338241895u, 666307205u, 773529912u, 1294757372u, 1396182291u, 1695183700u, 1986661051u, 2177026350u, 2456956037u,
			2730485921u, 2820302411u, 3259730800u, 3345764771u, 3516065817u, 3600352804u, 4094571909u, 275423344u, 430227734u, 506948616u,
			659060556u, 883997877u, 958139571u, 1322822218u, 1537002063u, 1747873779u, 1955562222u, 2024104815u, 2227730452u, 2361852424u,
			2428436474u, 2756734187u, 3204031479u, 3329325298u
		};

		private readonly uint[] _h = new uint[8];

		private readonly uint[] _w = new uint[64];

		private readonly byte[] _buffer = new byte[64];

		private int _bufferLength;

		private ulong _totalLength;

		public Sha256()
		{
			Reset();
		}

		public void Reset()
		{
			_h[0] = 1779033703u;
			_h[1] = 3144134277u;
			_h[2] = 1013904242u;
			_h[3] = 2773480762u;
			_h[4] = 1359893119u;
			_h[5] = 2600822924u;
			_h[6] = 528734635u;
			_h[7] = 1541459225u;
			_bufferLength = 0;
			_totalLength = 0uL;
		}

		public void Update(ReadOnlySpan<byte> data)
		{
			_totalLength += (ulong)data.Length;
			if (_bufferLength > 0)
			{
				int num = Math.Min(64 - _bufferLength, data.Length);
				data.Slice(0, num).CopyTo(_buffer.AsSpan(_bufferLength));
				_bufferLength += num;
				data = data.Slice(num);
				if (_bufferLength < 64)
				{
					return;
				}
				Compress(_buffer);
				_bufferLength = 0;
			}
			while (data.Length >= 64)
			{
				Compress(data.Slice(0, 64));
				data = data.Slice(64);
			}
			data.CopyTo(_buffer);
			_bufferLength = data.Length;
		}

		public void Final(Span<byte> output)
		{
			if (output.Length < 32)
			{
				throw new ArgumentException("output too small");
			}
			ulong num = _totalLength * 8;
			Span<byte> span = stackalloc byte[128];
			span.Clear();
			span[0] = 128;
			int num2 = ((_bufferLength < 56) ? (56 - _bufferLength) : (120 - _bufferLength));
			for (int i = 0; i < 8; i++)
			{
				span[num2 + i] = (byte)(num >> 56 - 8 * i);
			}
			ulong totalLength = _totalLength;
			Update(span.Slice(0, num2 + 8));
			_totalLength = totalLength;
			for (int j = 0; j < 8; j++)
			{
				output[4 * j] = (byte)(_h[j] >> 24);
				output[4 * j + 1] = (byte)(_h[j] >> 16);
				output[4 * j + 2] = (byte)(_h[j] >> 8);
				output[4 * j + 3] = (byte)_h[j];
			}
			Reset();
		}

		public static byte[] Hash(ReadOnlySpan<byte> data)
		{
			Sha256 sha = new Sha256();
			sha.Update(data);
			byte[] array = new byte[32];
			sha.Final(array);
			return array;
		}

		private static uint Rotr(uint x, int n)
		{
			return (x >> n) | (x << 32 - n);
		}

		private void Compress(ReadOnlySpan<byte> block)
		{
			uint[] w = _w;
			for (int i = 0; i < 16; i++)
			{
				w[i] = (uint)((block[4 * i] << 24) | (block[4 * i + 1] << 16) | (block[4 * i + 2] << 8) | block[4 * i + 3]);
			}
			for (int j = 16; j < 64; j++)
			{
				uint num = Rotr(w[j - 15], 7) ^ Rotr(w[j - 15], 18) ^ (w[j - 15] >> 3);
				uint num2 = Rotr(w[j - 2], 17) ^ Rotr(w[j - 2], 19) ^ (w[j - 2] >> 10);
				w[j] = w[j - 16] + num + w[j - 7] + num2;
			}
			uint num3 = _h[0];
			uint num4 = _h[1];
			uint num5 = _h[2];
			uint num6 = _h[3];
			uint num7 = _h[4];
			uint num8 = _h[5];
			uint num9 = _h[6];
			uint num10 = _h[7];
			for (int k = 0; k < 64; k++)
			{
				uint num11 = Rotr(num7, 6) ^ Rotr(num7, 11) ^ Rotr(num7, 25);
				uint num12 = (num7 & num8) ^ (~num7 & num9);
				uint num13 = num10 + num11 + num12 + K[k] + w[k];
				uint num14 = Rotr(num3, 2) ^ Rotr(num3, 13) ^ Rotr(num3, 22);
				uint num15 = (num3 & num4) ^ (num3 & num5) ^ (num4 & num5);
				uint num16 = num14 + num15;
				num10 = num9;
				num9 = num8;
				num8 = num7;
				num7 = num6 + num13;
				num6 = num5;
				num5 = num4;
				num4 = num3;
				num3 = num13 + num16;
			}
			_h[0] += num3;
			_h[1] += num4;
			_h[2] += num5;
			_h[3] += num6;
			_h[4] += num7;
			_h[5] += num8;
			_h[6] += num9;
			_h[7] += num10;
		}
	}
	public static class Hkdf
	{
		public static byte[] HmacSha256(ReadOnlySpan<byte> key, ReadOnlySpan<byte> message)
		{
			Span<byte> span = stackalloc byte[64];
			span.Clear();
			if (key.Length > 64)
			{
				Sha256.Hash(key).CopyTo(span);
			}
			else
			{
				key.CopyTo(span);
			}
			Span<byte> span2 = stackalloc byte[64];
			Sha256 sha = new Sha256();
			for (int i = 0; i < span2.Length; i++)
			{
				span2[i] = (byte)(span[i] ^ 0x36);
			}
			sha.Update(span2);
			sha.Update(message);
			Span<byte> span3 = stackalloc byte[32];
			sha.Final(span3);
			Sha256 sha2 = new Sha256();
			for (int j = 0; j < span2.Length; j++)
			{
				span2[j] = (byte)(span[j] ^ 0x5C);
			}
			sha2.Update(span2);
			sha2.Update(span3);
			byte[] array = new byte[32];
			sha2.Final(array);
			CryptoUtil.Zero(span);
			return array;
		}

		public static byte[] Extract(ReadOnlySpan<byte> salt, ReadOnlySpan<byte> ikm)
		{
			return HmacSha256((salt.Length == 0) ? ((ReadOnlySpan<byte>)new byte[32]) : salt, ikm);
		}

		public static byte[] Expand(ReadOnlySpan<byte> prk, ReadOnlySpan<byte> info, int length)
		{
			if (length <= 0 || length > 8160)
			{
				throw new ArgumentOutOfRangeException("length");
			}
			byte[] array = new byte[length];
			byte[] array2 = Array.Empty<byte>();
			int num = 0;
			byte b = 1;
			while (num < length)
			{
				byte[] array3 = new byte[array2.Length + info.Length + 1];
				array2.CopyTo(array3, 0);
				info.CopyTo(array3.AsSpan(array2.Length));
				array3[^1] = b;
				array2 = HmacSha256(prk, array3);
				int num2 = Math.Min(array2.Length, length - num);
				Array.Copy(array2, 0, array, num, num2);
				num += num2;
				b++;
			}
			return array;
		}

		public static byte[] DeriveKey(ReadOnlySpan<byte> salt, ReadOnlySpan<byte> ikm, ReadOnlySpan<byte> info, int length)
		{
			byte[] array = Extract(salt, ikm);
			try
			{
				return Expand(array, info, length);
			}
			finally
			{
				CryptoUtil.Zero(array);
			}
		}
	}
	public static class X25519
	{
		public const int KeySize = 32;

		private static readonly long[] A24 = new long[16]
		{
			56129L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L,
			0L, 0L, 0L, 0L, 0L, 0L
		};

		private static readonly byte[] BasePoint = CreateBasePoint();

		[ThreadStatic]
		private static long[] _mulScratch;

		private static byte[] CreateBasePoint()
		{
			byte[] array = new byte[32];
			array[0] = 9;
			return array;
		}

		public static void GeneratePrivateKey(Span<byte> privateKey)
		{
			SecureRandom.Fill(privateKey.Slice(0, 32));
			privateKey[0] &= 248;
			privateKey[31] &= 127;
			privateKey[31] |= 64;
		}

		public static byte[] GeneratePrivateKey()
		{
			byte[] array = new byte[32];
			GeneratePrivateKey(array);
			return array;
		}

		public static byte[] PublicKeyFromPrivate(ReadOnlySpan<byte> privateKey)
		{
			byte[] array = new byte[32];
			ScalarMult(array, privateKey, BasePoint);
			return array;
		}

		public static bool SharedSecret(Span<byte> output, ReadOnlySpan<byte> privateKey, ReadOnlySpan<byte> peerPublic)
		{
			if (peerPublic.Length != 32 || privateKey.Length != 32)
			{
				throw new ArgumentException("bad key size");
			}
			ScalarMult(output, privateKey, peerPublic);
			int num = 0;
			for (int i = 0; i < 32; i++)
			{
				num |= output[i];
			}
			if (num == 0)
			{
				output.Slice(0, 32).Clear();
				return false;
			}
			return true;
		}

		public static void ScalarMult(Span<byte> q, ReadOnlySpan<byte> n, ReadOnlySpan<byte> p)
		{
			Span<byte> span = stackalloc byte[32];
			long[] array = new long[80];
			long[] array2 = new long[16];
			long[] array3 = new long[16];
			long[] array4 = new long[16];
			long[] array5 = new long[16];
			long[] array6 = new long[16];
			long[] array7 = new long[16];
			for (int i = 0; i < 31; i++)
			{
				span[i] = n[i];
			}
			span[31] = (byte)((n[31] & 0x7F) | 0x40);
			span[0] &= 248;
			Unpack(array, 0, p);
			for (int j = 0; j < 16; j++)
			{
				array3[j] = array[j];
				array5[j] = (array2[j] = (array4[j] = 0L));
			}
			array2[0] = (array5[0] = 1L);
			long[] array8 = new long[16];
			Array.Copy(array, 0, array8, 0, 16);
			for (int num = 254; num >= 0; num--)
			{
				long b = (span[num >> 3] >> (num & 7)) & 1;
				Sel(array2, array3, b);
				Sel(array4, array5, b);
				Add(array6, array2, array4);
				Sub(array2, array2, array4);
				Add(array4, array3, array5);
				Sub(array3, array3, array5);
				Sqr(array5, array6);
				Sqr(array7, array2);
				Mul(array2, array4, array2);
				Mul(array4, array3, array6);
				Add(array6, array2, array4);
				Sub(array2, array2, array4);
				Sqr(array3, array2);
				Sub(array4, array5, array7);
				Mul(array2, array4, A24);
				Add(array2, array2, array5);
				Mul(array4, array4, array2);
				Mul(array2, array5, array7);
				Mul(array5, array3, array8);
				Sqr(array3, array6);
				Sel(array2, array3, b);
				Sel(array4, array5, b);
			}
			Inv(array4, array4);
			Mul(array2, array2, array4);
			Pack(q, array2);
			span.Clear();
			Array.Clear(array, 0, array.Length);
			Array.Clear(array2, 0, 16);
			Array.Clear(array3, 0, 16);
			Array.Clear(array4, 0, 16);
			Array.Clear(array5, 0, 16);
			Array.Clear(array6, 0, 16);
			Array.Clear(array7, 0, 16);
		}

		private static void Carry(long[] o)
		{
			for (int i = 0; i < 16; i++)
			{
				o[i] += 65536L;
				long num = o[i] >> 16;
				if (i < 15)
				{
					o[i + 1] += num - 1;
				}
				else
				{
					o[0] += 38 * (num - 1);
				}
				o[i] -= num << 16;
			}
		}

		private static void Sel(long[] p, long[] q, long b)
		{
			long num = ~(b - 1);
			for (int i = 0; i < 16; i++)
			{
				long num2 = num & (p[i] ^ q[i]);
				p[i] ^= num2;
				q[i] ^= num2;
			}
		}

		private static void Pack(Span<byte> o, long[] n)
		{
			long[] array = new long[16];
			long[] array2 = new long[16];
			Array.Copy(n, array2, 16);
			Carry(array2);
			Carry(array2);
			Carry(array2);
			for (int i = 0; i < 2; i++)
			{
				array[0] = array2[0] - 65517;
				for (int j = 1; j < 15; j++)
				{
					array[j] = array2[j] - 65535 - ((array[j - 1] >> 16) & 1);
					array[j - 1] &= 65535L;
				}
				array[15] = array2[15] - 32767 - ((array[14] >> 16) & 1);
				long num = (array[15] >> 16) & 1;
				array[14] &= 65535L;
				Sel(array2, array, 1 - num);
			}
			for (int k = 0; k < 16; k++)
			{
				o[2 * k] = (byte)(array2[k] & 0xFF);
				o[2 * k + 1] = (byte)(array2[k] >> 8);
			}
		}

		private static void Unpack(long[] o, int offset, ReadOnlySpan<byte> n)
		{
			for (int i = 0; i < 16; i++)
			{
				o[offset + i] = (long)(n[2 * i] + ((ulong)n[2 * i + 1] << 8));
			}
			o[offset + 15] &= 32767L;
		}

		private static void Add(long[] o, long[] a, long[] b)
		{
			for (int i = 0; i < 16; i++)
			{
				o[i] = a[i] + b[i];
			}
		}

		private static void Sub(long[] o, long[] a, long[] b)
		{
			for (int i = 0; i < 16; i++)
			{
				o[i] = a[i] - b[i];
			}
		}

		private static void Mul(long[] o, long[] a, long[] b)
		{
			long[] array = _mulScratch ?? (_mulScratch = new long[31]);
			Array.Clear(array, 0, 31);
			for (int i = 0; i < 16; i++)
			{
				long num = a[i];
				for (int j = 0; j < 16; j++)
				{
					array[i + j] += num * b[j];
				}
			}
			for (int k = 0; k < 15; k++)
			{
				array[k] += 38 * array[k + 16];
			}
			Array.Copy(array, o, 16);
			Carry(o);
			Carry(o);
		}

		private static void Sqr(long[] o, long[] a)
		{
			Mul(o, a, a);
		}

		private static void Inv(long[] o, long[] i)
		{
			long[] array = new long[16];
			Array.Copy(i, array, 16);
			for (int num = 253; num >= 0; num--)
			{
				Sqr(array, array);
				if (num != 2 && num != 4)
				{
					Mul(array, array, i);
				}
			}
			Array.Copy(array, o, 16);
		}
	}
}
namespace LabFusion.Network.Fischyweb
{
	public sealed class FischywebMatchmaker : IMatchmaker
	{
		private readonly FischywebNetworkLayer _layer;

		public FischywebMatchmaker(FischywebNetworkLayer layer)
		{
			_layer = layer;
		}

		public void RequestLobbies(Action<MatchmakerCallbackInfo> callback)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			RequestLobbies(MatchmakerFilters.Empty, callback);
		}

		public void RequestLobbies(MatchmakerFilters filters, Action<MatchmakerCallbackInfo> callback)
		{
			//IL_000b: 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)
			LobbyQuery lobbyQuery = AddPersistentFilters(new LobbyQuery());
			if (filters.FilterFull)
			{
				lobbyQuery.WithEqual("Full", bool.FalseString);
			}
			if (filters.FilterMismatchingVersions)
			{
				Version version = FusionMod.Version;
				lobbyQuery.WithEqual("VersionMajor", version.Major.ToString()).WithEqual("VersionMinor", version.Minor.ToString());
			}
			lobbyQuery.WithNotEqual("Privacy", 1.ToString()).WithNotEqual("Privacy", 3.ToString());
			Search(lobbyQuery, callback);
		}

		public void RequestLobbiesByCode(string code, Action<MatchmakerCallbackInfo> callback)
		{
			LobbyQuery query = AddPersistentFilters(new LobbyQuery()).WithEqual("LobbyCode", (code ?? string.Empty).Trim().ToUpperInvariant());
			Search(query, callback);
		}

		private static LobbyQuery AddPersistentFilters(LobbyQuery query)
		{
			return query.WithEqual("MarrowFusion", bool.TrueString).WithEqual("HasLobbyOpen", bool.TrueString).WithEqual("Game", GameInfo.GameName);
		}

		private void Search(LobbyQuery query, Action<MatchmakerCallbackInfo> callback)
		{
			_layer.RequestLobbies(query, delegate(List<LobbyEntry> entries)
			{
				//IL_0042: Unknown result type (might be due to invalid IL or missing references)
				//IL_0047: Unknown result type (might be due to invalid IL or missing references)
				//IL_0055: Unknown result type (might be due to invalid IL or missing references)
				//IL_0065: 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_0099: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
				List<LobbyInfo> list = new List<LobbyInfo>(entries.Count);
				foreach (LobbyEntry entry in entries)
				{
					if (entry.LobbyId != _layer.LocalPlatformID)
					{
						FischywebNetworkLobby fischywebNetworkLobby = new FischywebNetworkLobby(entry.Metadata);
						LobbyMetadataInfo metadata = LobbyMetadataSerializer.ReadInfo((INetworkLobby)(object)fischywebNetworkLobby);
						if (((LobbyMetadataInfo)(ref metadata)).HasLobbyOpen)
						{
							list.Add(new LobbyInfo
							{
								Lobby = (INetworkLobby)(object)fischywebNetworkLobby,
								Metadata = metadata
							});
						}
					}
				}
				callback?.Invoke(new MatchmakerCallbackInfo
				{
					Lobbies = list.ToArray()
				});
			});
		}
	}
	public class FischywebNetworkLayer : NetworkLayer
	{
		private struct LocalMessage
		{
			public byte[] Data;

			public bool IsServerHandled;
		}

		private class LobbyRequest
		{
			public Action<List<LobbyEntry>> Callback;

			public float TimeRemaining;
		}

		public const string LayerTitle = "Fischyweb";

		private const float LobbySearchTimeout = 20f;

		private FischywebNetworkLobby _currentLobby;

		private IVoiceManager _voiceManager;

		private IMatchmaker _matchmaker;

		private FischywebSettings _settings;

		private RelayClient _client;

		private bool _isServerActive;

		private bool _isConnectionActive;

		private ulong _hostId;

		private bool _wantOnline;

		private bool _loggedIn;

		private bool _connecting;

		private int _reconnectAttempt;

		private readonly Queue<LocalMessage> _localMessages = new Queue<LocalMessage>();

		private readonly Dictionary<uint, LobbyRequest> _lobbyRequests = new Dictionary<uint, LobbyRequest>();

		public static FischywebNetworkLayer Instance { get; private set; }

		public override string Title => "Fischyweb";

		public override string Platform => "Fischyweb";

		public override bool IsHost => _isServerActive;

		public override bool IsClient => _isConnectionActive;

		public override INetworkLobby Lobby => (INetworkLobby)(object)_currentLobby;

		public override IVoiceManager VoiceManager => _voiceManager;

		public override IMatchmaker Matchmaker => _matchmaker;

		public ulong LocalPlatformID { get; private set; }

		public bool IsRelayConnected
		{
			get
			{
				if (_client != null)
				{
					return !_client.IsClosed;
				}
				return false;
			}
		}

		public string ServerCode { get; private set; }

		public override bool CheckSupported()
		{
			return true;
		}

		public override bool CheckValidation()
		{
			return true;
		}

		public override void OnInitializeLayer()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)