Decompiled source of WebMap v2.16.1

websocket-sharp.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Timers;
using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("websocket-sharp")]
[assembly: AssemblyDescription("A C# implementation of the WebSocket protocol client and server")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("websocket-sharp.dll")]
[assembly: AssemblyCopyright("sta.blockhead")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("1.0.2.29017")]
namespace WebSocketSharp
{
	public static class Ext
	{
		private static readonly byte[] _last = new byte[1];

		private static readonly int _retry = 5;

		private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";

		private static byte[] compress(this byte[] data)
		{
			if (data.LongLength == 0)
			{
				return data;
			}
			using MemoryStream stream = new MemoryStream(data);
			return stream.compressToArray();
		}

		private static MemoryStream compress(this Stream stream)
		{
			MemoryStream memoryStream = new MemoryStream();
			if (stream.Length == 0)
			{
				return memoryStream;
			}
			stream.Position = 0L;
			using DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress, leaveOpen: true);
			CopyTo(stream, deflateStream, 1024);
			deflateStream.Close();
			memoryStream.Write(_last, 0, 1);
			memoryStream.Position = 0L;
			return memoryStream;
		}

		private static byte[] compressToArray(this Stream stream)
		{
			using MemoryStream memoryStream = stream.compress();
			memoryStream.Close();
			return memoryStream.ToArray();
		}

		private static byte[] decompress(this byte[] data)
		{
			if (data.LongLength == 0)
			{
				return data;
			}
			using MemoryStream stream = new MemoryStream(data);
			return stream.decompressToArray();
		}

		private static MemoryStream decompress(this Stream stream)
		{
			MemoryStream memoryStream = new MemoryStream();
			if (stream.Length == 0)
			{
				return memoryStream;
			}
			stream.Position = 0L;
			using DeflateStream source = new DeflateStream(stream, CompressionMode.Decompress, leaveOpen: true);
			CopyTo(source, memoryStream, 1024);
			memoryStream.Position = 0L;
			return memoryStream;
		}

		private static byte[] decompressToArray(this Stream stream)
		{
			using MemoryStream memoryStream = stream.decompress();
			memoryStream.Close();
			return memoryStream.ToArray();
		}

		private static bool isHttpMethod(this string value)
		{
			int result;
			switch (value)
			{
			default:
				result = ((value == "TRACE") ? 1 : 0);
				break;
			case "GET":
			case "HEAD":
			case "POST":
			case "PUT":
			case "DELETE":
			case "CONNECT":
			case "OPTIONS":
				result = 1;
				break;
			}
			return (byte)result != 0;
		}

		private static bool isHttpMethod10(this string value)
		{
			return value == "GET" || value == "HEAD" || value == "POST";
		}

		internal static byte[] Append(this ushort code, string reason)
		{
			byte[] array = code.InternalToByteArray(ByteOrder.Big);
			if (reason == null || reason.Length == 0)
			{
				return array;
			}
			List<byte> list = new List<byte>(array);
			list.AddRange(Encoding.UTF8.GetBytes(reason));
			return list.ToArray();
		}

		internal static byte[] Compress(this byte[] data, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? data.compress() : data;
		}

		internal static Stream Compress(this Stream stream, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? stream.compress() : stream;
		}

		internal static byte[] CompressToArray(this Stream stream, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? stream.compressToArray() : stream.ToByteArray();
		}

		internal static bool Contains(this string value, params char[] anyOf)
		{
			return anyOf != null && anyOf.Length != 0 && value.IndexOfAny(anyOf) > -1;
		}

		internal static bool Contains(this NameValueCollection collection, string name)
		{
			return collection[name] != null;
		}

		internal static bool Contains(this NameValueCollection collection, string name, string value, StringComparison comparisonTypeForValue)
		{
			string text = collection[name];
			if (text == null)
			{
				return false;
			}
			string[] array = text.Split(new char[1] { ',' });
			foreach (string text2 in array)
			{
				if (text2.Trim().Equals(value, comparisonTypeForValue))
				{
					return true;
				}
			}
			return false;
		}

		internal static bool Contains<T>(this IEnumerable<T> source, Func<T, bool> condition)
		{
			foreach (T item in source)
			{
				if (condition(item))
				{
					return true;
				}
			}
			return false;
		}

		internal static bool ContainsTwice(this string[] values)
		{
			int len = values.Length;
			int end = len - 1;
			Func<int, bool> seek = null;
			seek = delegate(int idx)
			{
				if (idx == end)
				{
					return false;
				}
				string text = values[idx];
				for (int i = idx + 1; i < len; i++)
				{
					if (values[i] == text)
					{
						return true;
					}
				}
				return seek(++idx);
			};
			return seek(0);
		}

		internal static T[] Copy<T>(this T[] source, int length)
		{
			T[] array = new T[length];
			Array.Copy(source, 0, array, 0, length);
			return array;
		}

		internal static T[] Copy<T>(this T[] source, long length)
		{
			T[] array = new T[length];
			Array.Copy(source, 0L, array, 0L, length);
			return array;
		}

		internal static void CopyTo(this Stream source, Stream destination, int bufferLength)
		{
			byte[] buffer = new byte[bufferLength];
			int num = 0;
			while (true)
			{
				num = source.Read(buffer, 0, bufferLength);
				if (num <= 0)
				{
					break;
				}
				destination.Write(buffer, 0, num);
			}
		}

		internal static void CopyToAsync(this Stream source, Stream destination, int bufferLength, Action completed, Action<Exception> error)
		{
			byte[] buff = new byte[bufferLength];
			AsyncCallback callback = null;
			callback = delegate(IAsyncResult ar)
			{
				try
				{
					int num = source.EndRead(ar);
					if (num <= 0)
					{
						if (completed != null)
						{
							completed();
						}
					}
					else
					{
						destination.Write(buff, 0, num);
						source.BeginRead(buff, 0, bufferLength, callback, null);
					}
				}
				catch (Exception obj2)
				{
					if (error != null)
					{
						error(obj2);
					}
				}
			};
			try
			{
				source.BeginRead(buff, 0, bufferLength, callback, null);
			}
			catch (Exception obj)
			{
				if (error != null)
				{
					error(obj);
				}
			}
		}

		internal static byte[] Decompress(this byte[] data, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? data.decompress() : data;
		}

		internal static Stream Decompress(this Stream stream, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? stream.decompress() : stream;
		}

		internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method)
		{
			return (method == CompressionMethod.Deflate) ? stream.decompressToArray() : stream.ToByteArray();
		}

		internal static void Emit(this EventHandler eventHandler, object sender, EventArgs e)
		{
			eventHandler?.Invoke(sender, e);
		}

		internal static void Emit<TEventArgs>(this EventHandler<TEventArgs> eventHandler, object sender, TEventArgs e) where TEventArgs : EventArgs
		{
			eventHandler?.Invoke(sender, e);
		}

		internal static bool EqualsWith(this int value, char c, Action<int> action)
		{
			action(value);
			return value == c;
		}

		internal static string GetAbsolutePath(this Uri uri)
		{
			if (uri.IsAbsoluteUri)
			{
				return uri.AbsolutePath;
			}
			string originalString = uri.OriginalString;
			if (originalString[0] != '/')
			{
				return null;
			}
			int num = originalString.IndexOfAny(new char[2] { '?', '#' });
			return (num > 0) ? originalString.Substring(0, num) : originalString;
		}

		internal static WebSocketSharp.Net.CookieCollection GetCookies(this NameValueCollection headers, bool response)
		{
			string text = headers[response ? "Set-Cookie" : "Cookie"];
			return (text != null) ? WebSocketSharp.Net.CookieCollection.Parse(text, response) : new WebSocketSharp.Net.CookieCollection();
		}

		internal static string GetDnsSafeHost(this Uri uri, bool bracketIPv6)
		{
			return (bracketIPv6 && uri.HostNameType == UriHostNameType.IPv6) ? uri.Host : uri.DnsSafeHost;
		}

		internal static string GetMessage(this CloseStatusCode code)
		{
			return code switch
			{
				CloseStatusCode.TlsHandshakeFailure => "An error has occurred during a TLS handshake.", 
				CloseStatusCode.ServerError => "WebSocket server got an internal error.", 
				CloseStatusCode.MandatoryExtension => "WebSocket client didn't receive expected extension(s).", 
				CloseStatusCode.TooBig => "A too big message has been received.", 
				CloseStatusCode.PolicyViolation => "A policy violation has occurred.", 
				CloseStatusCode.InvalidData => "Invalid data has been received.", 
				CloseStatusCode.Abnormal => "An exception has occurred.", 
				CloseStatusCode.UnsupportedData => "Unsupported data has been received.", 
				CloseStatusCode.ProtocolError => "A WebSocket protocol error has occurred.", 
				_ => string.Empty, 
			};
		}

		internal static string GetName(this string nameAndValue, char separator)
		{
			int num = nameAndValue.IndexOf(separator);
			return (num > 0) ? nameAndValue.Substring(0, num).Trim() : null;
		}

		internal static string GetUTF8DecodedString(this byte[] bytes)
		{
			return Encoding.UTF8.GetString(bytes);
		}

		internal static byte[] GetUTF8EncodedBytes(this string s)
		{
			return Encoding.UTF8.GetBytes(s);
		}

		internal static string GetValue(this string nameAndValue, char separator)
		{
			return nameAndValue.GetValue(separator, unquote: false);
		}

		internal static string GetValue(this string nameAndValue, char separator, bool unquote)
		{
			int num = nameAndValue.IndexOf(separator);
			if (num < 0 || num == nameAndValue.Length - 1)
			{
				return null;
			}
			string text = nameAndValue.Substring(num + 1).Trim();
			return unquote ? text.Unquote() : text;
		}

		internal static byte[] InternalToByteArray(this ushort value, ByteOrder order)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			if (!order.IsHostOrder())
			{
				Array.Reverse((Array)bytes);
			}
			return bytes;
		}

		internal static byte[] InternalToByteArray(this ulong value, ByteOrder order)
		{
			byte[] bytes = BitConverter.GetBytes(value);
			if (!order.IsHostOrder())
			{
				Array.Reverse((Array)bytes);
			}
			return bytes;
		}

		internal static bool IsCompressionExtension(this string value, CompressionMethod method)
		{
			return value.StartsWith(method.ToExtensionString());
		}

		internal static bool IsControl(this byte opcode)
		{
			return opcode > 7 && opcode < 16;
		}

		internal static bool IsControl(this Opcode opcode)
		{
			return (int)opcode >= 8;
		}

		internal static bool IsData(this byte opcode)
		{
			return opcode == 1 || opcode == 2;
		}

		internal static bool IsData(this Opcode opcode)
		{
			return opcode == Opcode.Text || opcode == Opcode.Binary;
		}

		internal static bool IsHttpMethod(this string value, Version version)
		{
			return (version == WebSocketSharp.Net.HttpVersion.Version10) ? value.isHttpMethod10() : value.isHttpMethod();
		}

		internal static bool IsPortNumber(this int value)
		{
			return value > 0 && value < 65536;
		}

		internal static bool IsReserved(this ushort code)
		{
			return code == 1004 || code == 1005 || code == 1006 || code == 1015;
		}

		internal static bool IsReserved(this CloseStatusCode code)
		{
			return code == CloseStatusCode.Undefined || code == CloseStatusCode.NoStatus || code == CloseStatusCode.Abnormal || code == CloseStatusCode.TlsHandshakeFailure;
		}

		internal static bool IsSupported(this byte opcode)
		{
			return Enum.IsDefined(typeof(Opcode), opcode);
		}

		internal static bool IsText(this string value)
		{
			int length = value.Length;
			for (int i = 0; i < length; i++)
			{
				char c = value[i];
				if (c < ' ')
				{
					if ("\r\n\t".IndexOf(c) == -1)
					{
						return false;
					}
					if (c == '\n')
					{
						i++;
						if (i == length)
						{
							break;
						}
						c = value[i];
						if (" \t".IndexOf(c) == -1)
						{
							return false;
						}
					}
				}
				else if (c == '\u007f')
				{
					return false;
				}
			}
			return true;
		}

		internal static bool IsToken(this string value)
		{
			foreach (char c in value)
			{
				if (c < ' ')
				{
					return false;
				}
				if (c > '~')
				{
					return false;
				}
				if ("()<>@,;:\\\"/[]?={} \t".IndexOf(c) > -1)
				{
					return false;
				}
			}
			return true;
		}

		internal static bool KeepsAlive(this NameValueCollection headers, Version version)
		{
			StringComparison comparisonTypeForValue = StringComparison.OrdinalIgnoreCase;
			return (version < WebSocketSharp.Net.HttpVersion.Version11) ? headers.Contains("Connection", "keep-alive", comparisonTypeForValue) : (!headers.Contains("Connection", "close", comparisonTypeForValue));
		}

		internal static string Quote(this string value)
		{
			return string.Format("\"{0}\"", value.Replace("\"", "\\\""));
		}

		internal static byte[] ReadBytes(this Stream stream, int length)
		{
			byte[] array = new byte[length];
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			while (length > 0)
			{
				num3 = stream.Read(array, num, length);
				if (num3 <= 0)
				{
					if (num2 >= _retry)
					{
						return array.SubArray(0, num);
					}
					num2++;
				}
				else
				{
					num2 = 0;
					num += num3;
					length -= num3;
				}
			}
			return array;
		}

		internal static byte[] ReadBytes(this Stream stream, long length, int bufferLength)
		{
			using MemoryStream memoryStream = new MemoryStream();
			byte[] buffer = new byte[bufferLength];
			int num = 0;
			int num2 = 0;
			while (length > 0)
			{
				if (length < bufferLength)
				{
					bufferLength = (int)length;
				}
				num2 = stream.Read(buffer, 0, bufferLength);
				if (num2 <= 0)
				{
					if (num >= _retry)
					{
						break;
					}
					num++;
				}
				else
				{
					num = 0;
					memoryStream.Write(buffer, 0, num2);
					length -= num2;
				}
			}
			memoryStream.Close();
			return memoryStream.ToArray();
		}

		internal static void ReadBytesAsync(this Stream stream, int length, Action<byte[]> completed, Action<Exception> error)
		{
			byte[] buff = new byte[length];
			int offset = 0;
			int retry = 0;
			AsyncCallback callback = null;
			callback = delegate(IAsyncResult ar)
			{
				try
				{
					int num = stream.EndRead(ar);
					if (num <= 0)
					{
						if (retry < _retry)
						{
							retry++;
							stream.BeginRead(buff, offset, length, callback, null);
						}
						else if (completed != null)
						{
							completed(buff.SubArray(0, offset));
						}
					}
					else if (num == length)
					{
						if (completed != null)
						{
							completed(buff);
						}
					}
					else
					{
						retry = 0;
						offset += num;
						length -= num;
						stream.BeginRead(buff, offset, length, callback, null);
					}
				}
				catch (Exception obj2)
				{
					if (error != null)
					{
						error(obj2);
					}
				}
			};
			try
			{
				stream.BeginRead(buff, offset, length, callback, null);
			}
			catch (Exception obj)
			{
				if (error != null)
				{
					error(obj);
				}
			}
		}

		internal static void ReadBytesAsync(this Stream stream, long length, int bufferLength, Action<byte[]> completed, Action<Exception> error)
		{
			MemoryStream dest = new MemoryStream();
			byte[] buff = new byte[bufferLength];
			int retry = 0;
			Action<long> read = null;
			read = delegate(long len)
			{
				if (len < bufferLength)
				{
					bufferLength = (int)len;
				}
				stream.BeginRead(buff, 0, bufferLength, delegate(IAsyncResult ar)
				{
					try
					{
						int num = stream.EndRead(ar);
						if (num <= 0)
						{
							if (retry < _retry)
							{
								int num2 = retry;
								retry = num2 + 1;
								read(len);
							}
							else
							{
								if (completed != null)
								{
									dest.Close();
									completed(dest.ToArray());
								}
								dest.Dispose();
							}
						}
						else
						{
							dest.Write(buff, 0, num);
							if (num == len)
							{
								if (completed != null)
								{
									dest.Close();
									completed(dest.ToArray());
								}
								dest.Dispose();
							}
							else
							{
								retry = 0;
								read(len - num);
							}
						}
					}
					catch (Exception obj2)
					{
						dest.Dispose();
						if (error != null)
						{
							error(obj2);
						}
					}
				}, null);
			};
			try
			{
				read(length);
			}
			catch (Exception obj)
			{
				dest.Dispose();
				if (error != null)
				{
					error(obj);
				}
			}
		}

		internal static T[] Reverse<T>(this T[] array)
		{
			int num = array.Length;
			T[] array2 = new T[num];
			int num2 = num - 1;
			for (int i = 0; i <= num2; i++)
			{
				array2[i] = array[num2 - i];
			}
			return array2;
		}

		internal static IEnumerable<string> SplitHeaderValue(this string value, params char[] separators)
		{
			int len = value.Length;
			int end = len - 1;
			StringBuilder buff = new StringBuilder(32);
			bool escaped = false;
			bool quoted = false;
			for (int i = 0; i <= end; i++)
			{
				char c = value[i];
				buff.Append(c);
				switch (c)
				{
				case '"':
					if (escaped)
					{
						escaped = false;
					}
					else
					{
						quoted = !quoted;
					}
					continue;
				case '\\':
					if (i == end)
					{
						break;
					}
					if (value[i + 1] == '"')
					{
						escaped = true;
					}
					continue;
				default:
					if (Array.IndexOf(separators, c) > -1 && !quoted)
					{
						buff.Length--;
						yield return buff.ToString();
						buff.Length = 0;
					}
					continue;
				}
				break;
			}
			yield return buff.ToString();
		}

		internal static byte[] ToByteArray(this Stream stream)
		{
			using MemoryStream memoryStream = new MemoryStream();
			stream.Position = 0L;
			CopyTo(stream, memoryStream, 1024);
			memoryStream.Close();
			return memoryStream.ToArray();
		}

		internal static CompressionMethod ToCompressionMethod(this string value)
		{
			Array values = Enum.GetValues(typeof(CompressionMethod));
			foreach (CompressionMethod item in values)
			{
				if (item.ToExtensionString() == value)
				{
					return item;
				}
			}
			return CompressionMethod.None;
		}

		internal static string ToExtensionString(this CompressionMethod method, params string[] parameters)
		{
			if (method == CompressionMethod.None)
			{
				return string.Empty;
			}
			string text = $"permessage-{method.ToString().ToLower()}";
			return (parameters != null && parameters.Length != 0) ? string.Format("{0}; {1}", text, parameters.ToString("; ")) : text;
		}

		internal static IPAddress ToIPAddress(this string value)
		{
			if (value == null || value.Length == 0)
			{
				return null;
			}
			if (IPAddress.TryParse(value, out IPAddress address))
			{
				return address;
			}
			try
			{
				IPAddress[] hostAddresses = Dns.GetHostAddresses(value);
				return hostAddresses[0];
			}
			catch
			{
				return null;
			}
		}

		internal static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
		{
			return new List<TSource>(source);
		}

		internal static string ToString(this IPAddress address, bool bracketIPv6)
		{
			return (bracketIPv6 && address.AddressFamily == AddressFamily.InterNetworkV6) ? $"[{address.ToString()}]" : address.ToString();
		}

		internal static ushort ToUInt16(this byte[] source, ByteOrder sourceOrder)
		{
			return BitConverter.ToUInt16(source.ToHostOrder(sourceOrder), 0);
		}

		internal static ulong ToUInt64(this byte[] source, ByteOrder sourceOrder)
		{
			return BitConverter.ToUInt64(source.ToHostOrder(sourceOrder), 0);
		}

		internal static IEnumerable<string> TrimEach(this IEnumerable<string> source)
		{
			foreach (string elm in source)
			{
				yield return elm.Trim();
			}
		}

		internal static string TrimSlashFromEnd(this string value)
		{
			string text = value.TrimEnd(new char[1] { '/' });
			return (text.Length > 0) ? text : "/";
		}

		internal static string TrimSlashOrBackslashFromEnd(this string value)
		{
			string text = value.TrimEnd('/', '\\');
			return (text.Length > 0) ? text : value[0].ToString();
		}

		internal static bool TryCreateVersion(this string versionString, out Version result)
		{
			result = null;
			try
			{
				result = new Version(versionString);
			}
			catch
			{
				return false;
			}
			return true;
		}

		internal static bool TryCreateWebSocketUri(this string uriString, out Uri result, out string message)
		{
			result = null;
			message = null;
			Uri uri = uriString.ToUri();
			if (uri == null)
			{
				message = "An invalid URI string.";
				return false;
			}
			if (!uri.IsAbsoluteUri)
			{
				message = "A relative URI.";
				return false;
			}
			string scheme = uri.Scheme;
			if (!(scheme == "ws") && !(scheme == "wss"))
			{
				message = "The scheme part is not 'ws' or 'wss'.";
				return false;
			}
			int port = uri.Port;
			if (port == 0)
			{
				message = "The port part is zero.";
				return false;
			}
			if (uri.Fragment.Length > 0)
			{
				message = "It includes the fragment component.";
				return false;
			}
			result = ((port != -1) ? uri : new Uri(string.Format("{0}://{1}:{2}{3}", scheme, uri.Host, (scheme == "ws") ? 80 : 443, uri.PathAndQuery)));
			return true;
		}

		internal static bool TryGetUTF8DecodedString(this byte[] bytes, out string s)
		{
			s = null;
			try
			{
				s = Encoding.UTF8.GetString(bytes);
			}
			catch
			{
				return false;
			}
			return true;
		}

		internal static bool TryGetUTF8EncodedBytes(this string s, out byte[] bytes)
		{
			bytes = null;
			try
			{
				bytes = Encoding.UTF8.GetBytes(s);
			}
			catch
			{
				return false;
			}
			return true;
		}

		internal static bool TryOpenRead(this FileInfo fileInfo, out FileStream fileStream)
		{
			fileStream = null;
			try
			{
				fileStream = fileInfo.OpenRead();
			}
			catch
			{
				return false;
			}
			return true;
		}

		internal static string Unquote(this string value)
		{
			int num = value.IndexOf('"');
			if (num == -1)
			{
				return value;
			}
			int num2 = value.LastIndexOf('"');
			if (num2 == num)
			{
				return value;
			}
			int num3 = num2 - num - 1;
			return (num3 > 0) ? value.Substring(num + 1, num3).Replace("\\\"", "\"") : string.Empty;
		}

		internal static bool Upgrades(this NameValueCollection headers, string protocol)
		{
			StringComparison comparisonTypeForValue = StringComparison.OrdinalIgnoreCase;
			return headers.Contains("Upgrade", protocol, comparisonTypeForValue) && headers.Contains("Connection", "Upgrade", comparisonTypeForValue);
		}

		internal static string UrlDecode(this string value, Encoding encoding)
		{
			return HttpUtility.UrlDecode(value, encoding);
		}

		internal static string UrlEncode(this string value, Encoding encoding)
		{
			return HttpUtility.UrlEncode(value, encoding);
		}

		internal static void WriteBytes(this Stream stream, byte[] bytes, int bufferLength)
		{
			using MemoryStream source = new MemoryStream(bytes);
			CopyTo(source, stream, bufferLength);
		}

		internal static void WriteBytesAsync(this Stream stream, byte[] bytes, int bufferLength, Action completed, Action<Exception> error)
		{
			MemoryStream src = new MemoryStream(bytes);
			src.CopyToAsync(stream, bufferLength, delegate
			{
				if (completed != null)
				{
					completed();
				}
				src.Dispose();
			}, delegate(Exception ex)
			{
				src.Dispose();
				if (error != null)
				{
					error(ex);
				}
			});
		}

		public static string GetDescription(this WebSocketSharp.Net.HttpStatusCode code)
		{
			return ((int)code).GetStatusDescription();
		}

		public static string GetStatusDescription(this int code)
		{
			return code switch
			{
				100 => "Continue", 
				101 => "Switching Protocols", 
				102 => "Processing", 
				200 => "OK", 
				201 => "Created", 
				202 => "Accepted", 
				203 => "Non-Authoritative Information", 
				204 => "No Content", 
				205 => "Reset Content", 
				206 => "Partial Content", 
				207 => "Multi-Status", 
				300 => "Multiple Choices", 
				301 => "Moved Permanently", 
				302 => "Found", 
				303 => "See Other", 
				304 => "Not Modified", 
				305 => "Use Proxy", 
				307 => "Temporary Redirect", 
				400 => "Bad Request", 
				401 => "Unauthorized", 
				402 => "Payment Required", 
				403 => "Forbidden", 
				404 => "Not Found", 
				405 => "Method Not Allowed", 
				406 => "Not Acceptable", 
				407 => "Proxy Authentication Required", 
				408 => "Request Timeout", 
				409 => "Conflict", 
				410 => "Gone", 
				411 => "Length Required", 
				412 => "Precondition Failed", 
				413 => "Request Entity Too Large", 
				414 => "Request-Uri Too Long", 
				415 => "Unsupported Media Type", 
				416 => "Requested Range Not Satisfiable", 
				417 => "Expectation Failed", 
				422 => "Unprocessable Entity", 
				423 => "Locked", 
				424 => "Failed Dependency", 
				500 => "Internal Server Error", 
				501 => "Not Implemented", 
				502 => "Bad Gateway", 
				503 => "Service Unavailable", 
				504 => "Gateway Timeout", 
				505 => "Http Version Not Supported", 
				507 => "Insufficient Storage", 
				_ => string.Empty, 
			};
		}

		public static bool IsCloseStatusCode(this ushort value)
		{
			return value > 999 && value < 5000;
		}

		public static bool IsEnclosedIn(this string value, char c)
		{
			if (value == null)
			{
				return false;
			}
			int length = value.Length;
			if (length < 2)
			{
				return false;
			}
			return value[0] == c && value[length - 1] == c;
		}

		public static bool IsHostOrder(this ByteOrder order)
		{
			return BitConverter.IsLittleEndian == (order == ByteOrder.Little);
		}

		public static bool IsLocal(this IPAddress address)
		{
			if (address == null)
			{
				throw new ArgumentNullException("address");
			}
			if (address.Equals(IPAddress.Any))
			{
				return true;
			}
			if (address.Equals(IPAddress.Loopback))
			{
				return true;
			}
			if (Socket.OSSupportsIPv6)
			{
				if (address.Equals(IPAddress.IPv6Any))
				{
					return true;
				}
				if (address.Equals(IPAddress.IPv6Loopback))
				{
					return true;
				}
			}
			string hostName = Dns.GetHostName();
			IPAddress[] hostAddresses = Dns.GetHostAddresses(hostName);
			IPAddress[] array = hostAddresses;
			foreach (IPAddress obj in array)
			{
				if (address.Equals(obj))
				{
					return true;
				}
			}
			return false;
		}

		public static bool IsNullOrEmpty(this string value)
		{
			return value == null || value.Length == 0;
		}

		public static bool IsPredefinedScheme(this string value)
		{
			if (value == null || value.Length < 2)
			{
				return false;
			}
			switch (value[0])
			{
			case 'h':
				return value == "http" || value == "https";
			case 'w':
				return value == "ws" || value == "wss";
			case 'f':
				return value == "file" || value == "ftp";
			case 'g':
				return value == "gopher";
			case 'm':
				return value == "mailto";
			case 'n':
			{
				char c = value[1];
				return (c != 'e') ? (value == "nntp") : (value == "news" || value == "net.pipe" || value == "net.tcp");
			}
			default:
				return false;
			}
		}

		public static bool MaybeUri(this string value)
		{
			if (value == null)
			{
				return false;
			}
			if (value.Length == 0)
			{
				return false;
			}
			int num = value.IndexOf(':');
			if (num == -1)
			{
				return false;
			}
			if (num >= 10)
			{
				return false;
			}
			string value2 = value.Substring(0, num);
			return value2.IsPredefinedScheme();
		}

		public static T[] SubArray<T>(this T[] array, int startIndex, int length)
		{
			if (array == null)
			{
				throw new ArgumentNullException("array");
			}
			int num = array.Length;
			if (num == 0)
			{
				if (startIndex != 0)
				{
					throw new ArgumentOutOfRangeException("startIndex");
				}
				if (length != 0)
				{
					throw new ArgumentOutOfRangeException("length");
				}
				return array;
			}
			if (startIndex < 0 || startIndex >= num)
			{
				throw new ArgumentOutOfRangeException("startIndex");
			}
			if (length < 0 || length > num - startIndex)
			{
				throw new ArgumentOutOfRangeException("length");
			}
			if (length == 0)
			{
				return new T[0];
			}
			if (length == num)
			{
				return array;
			}
			T[] array2 = new T[length];
			Array.Copy(array, startIndex, array2, 0, length);
			return array2;
		}

		public static T[] SubArray<T>(this T[] array, long startIndex, long length)
		{
			if (array == null)
			{
				throw new ArgumentNullException("array");
			}
			long num = array.LongLength;
			if (num == 0)
			{
				if (startIndex != 0)
				{
					throw new ArgumentOutOfRangeException("startIndex");
				}
				if (length != 0)
				{
					throw new ArgumentOutOfRangeException("length");
				}
				return array;
			}
			if (startIndex < 0 || startIndex >= num)
			{
				throw new ArgumentOutOfRangeException("startIndex");
			}
			if (length < 0 || length > num - startIndex)
			{
				throw new ArgumentOutOfRangeException("length");
			}
			if (length == 0)
			{
				return new T[0];
			}
			if (length == num)
			{
				return array;
			}
			T[] array2 = new T[length];
			Array.Copy(array, startIndex, array2, 0L, length);
			return array2;
		}

		public static void Times(this int n, Action action)
		{
			if (n > 0 && action != null)
			{
				for (int i = 0; i < n; i++)
				{
					action();
				}
			}
		}

		public static void Times(this long n, Action action)
		{
			if (n > 0 && action != null)
			{
				for (long num = 0L; num < n; num++)
				{
					action();
				}
			}
		}

		public static void Times(this uint n, Action action)
		{
			if (n != 0 && action != null)
			{
				for (uint num = 0u; num < n; num++)
				{
					action();
				}
			}
		}

		public static void Times(this ulong n, Action action)
		{
			if (n != 0 && action != null)
			{
				for (ulong num = 0uL; num < n; num++)
				{
					action();
				}
			}
		}

		public static void Times(this int n, Action<int> action)
		{
			if (n > 0 && action != null)
			{
				for (int i = 0; i < n; i++)
				{
					action(i);
				}
			}
		}

		public static void Times(this long n, Action<long> action)
		{
			if (n > 0 && action != null)
			{
				for (long num = 0L; num < n; num++)
				{
					action(num);
				}
			}
		}

		public static void Times(this uint n, Action<uint> action)
		{
			if (n != 0 && action != null)
			{
				for (uint num = 0u; num < n; num++)
				{
					action(num);
				}
			}
		}

		public static void Times(this ulong n, Action<ulong> action)
		{
			if (n != 0 && action != null)
			{
				for (ulong num = 0uL; num < n; num++)
				{
					action(num);
				}
			}
		}

		[Obsolete("This method will be removed.")]
		public static T To<T>(this byte[] source, ByteOrder sourceOrder) where T : struct
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}
			if (source.Length == 0)
			{
				return default(T);
			}
			Type typeFromHandle = typeof(T);
			byte[] value = source.ToHostOrder(sourceOrder);
			return ((object)typeFromHandle == typeof(bool)) ? ((T)(object)BitConverter.ToBoolean(value, 0)) : (((object)typeFromHandle == typeof(char)) ? ((T)(object)BitConverter.ToChar(value, 0)) : (((object)typeFromHandle == typeof(double)) ? ((T)(object)BitConverter.ToDouble(value, 0)) : (((object)typeFromHandle == typeof(short)) ? ((T)(object)BitConverter.ToInt16(value, 0)) : (((object)typeFromHandle == typeof(int)) ? ((T)(object)BitConverter.ToInt32(value, 0)) : (((object)typeFromHandle == typeof(long)) ? ((T)(object)BitConverter.ToInt64(value, 0)) : (((object)typeFromHandle == typeof(float)) ? ((T)(object)BitConverter.ToSingle(value, 0)) : (((object)typeFromHandle == typeof(ushort)) ? ((T)(object)BitConverter.ToUInt16(value, 0)) : (((object)typeFromHandle == typeof(uint)) ? ((T)(object)BitConverter.ToUInt32(value, 0)) : (((object)typeFromHandle == typeof(ulong)) ? ((T)(object)BitConverter.ToUInt64(value, 0)) : default(T))))))))));
		}

		[Obsolete("This method will be removed.")]
		public static byte[] ToByteArray<T>(this T value, ByteOrder order) where T : struct
		{
			Type typeFromHandle = typeof(T);
			byte[] array = (((object)typeFromHandle == typeof(bool)) ? BitConverter.GetBytes((bool)(object)value) : (((object)typeFromHandle != typeof(byte)) ? (((object)typeFromHandle == typeof(char)) ? BitConverter.GetBytes((char)(object)value) : (((object)typeFromHandle == typeof(double)) ? BitConverter.GetBytes((double)(object)value) : (((object)typeFromHandle == typeof(short)) ? BitConverter.GetBytes((short)(object)value) : (((object)typeFromHandle == typeof(int)) ? BitConverter.GetBytes((int)(object)value) : (((object)typeFromHandle == typeof(long)) ? BitConverter.GetBytes((long)(object)value) : (((object)typeFromHandle == typeof(float)) ? BitConverter.GetBytes((float)(object)value) : (((object)typeFromHandle == typeof(ushort)) ? BitConverter.GetBytes((ushort)(object)value) : (((object)typeFromHandle == typeof(uint)) ? BitConverter.GetBytes((uint)(object)value) : (((object)typeFromHandle == typeof(ulong)) ? BitConverter.GetBytes((ulong)(object)value) : WebSocket.EmptyBytes))))))))) : new byte[1] { (byte)(object)value }));
			if (array.Length > 1 && !order.IsHostOrder())
			{
				Array.Reverse((Array)array);
			}
			return array;
		}

		public static byte[] ToHostOrder(this byte[] source, ByteOrder sourceOrder)
		{
			if (source == null)
			{
				throw new ArgumentNullException("source");
			}
			if (source.Length < 2)
			{
				return source;
			}
			if (sourceOrder.IsHostOrder())
			{
				return source;
			}
			return source.Reverse();
		}

		public static string ToString<T>(this T[] array, string separator)
		{
			if (array == null)
			{
				throw new ArgumentNullException("array");
			}
			int num = array.Length;
			if (num == 0)
			{
				return string.Empty;
			}
			if (separator == null)
			{
				separator = string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(64);
			int num2 = num - 1;
			for (int i = 0; i < num2; i++)
			{
				stringBuilder.AppendFormat("{0}{1}", array[i], separator);
			}
			stringBuilder.Append(array[num2].ToString());
			return stringBuilder.ToString();
		}

		public static Uri ToUri(this string value)
		{
			Uri.TryCreate(value, value.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out Uri result);
			return result;
		}

		[Obsolete("This method will be removed.")]
		public static void WriteContent(this WebSocketSharp.Net.HttpListenerResponse response, byte[] content)
		{
			if (response == null)
			{
				throw new ArgumentNullException("response");
			}
			if (content == null)
			{
				throw new ArgumentNullException("content");
			}
			long num = content.LongLength;
			if (num == 0)
			{
				response.Close();
				return;
			}
			response.ContentLength64 = num;
			Stream outputStream = response.OutputStream;
			if (num <= int.MaxValue)
			{
				outputStream.Write(content, 0, (int)num);
			}
			else
			{
				outputStream.WriteBytes(content, 1024);
			}
			outputStream.Close();
		}
	}
	public class MessageEventArgs : EventArgs
	{
		private string _data;

		private bool _dataSet;

		private Opcode _opcode;

		private byte[] _rawData;

		internal Opcode Opcode => _opcode;

		public string Data
		{
			get
			{
				setData();
				return _data;
			}
		}

		public bool IsBinary => _opcode == Opcode.Binary;

		public bool IsPing => _opcode == Opcode.Ping;

		public bool IsText => _opcode == Opcode.Text;

		public byte[] RawData
		{
			get
			{
				setData();
				return _rawData;
			}
		}

		internal MessageEventArgs(WebSocketFrame frame)
		{
			_opcode = frame.Opcode;
			_rawData = frame.PayloadData.ApplicationData;
		}

		internal MessageEventArgs(Opcode opcode, byte[] rawData)
		{
			if ((ulong)rawData.LongLength > PayloadData.MaxLength)
			{
				throw new WebSocketException(CloseStatusCode.TooBig);
			}
			_opcode = opcode;
			_rawData = rawData;
		}

		private void setData()
		{
			if (_dataSet)
			{
				return;
			}
			if (_opcode == Opcode.Binary)
			{
				_dataSet = true;
				return;
			}
			if (_rawData.TryGetUTF8DecodedString(out var s))
			{
				_data = s;
			}
			_dataSet = true;
		}
	}
	public class CloseEventArgs : EventArgs
	{
		private bool _clean;

		private PayloadData _payloadData;

		public ushort Code => _payloadData.Code;

		public string Reason => _payloadData.Reason;

		public bool WasClean => _clean;

		internal CloseEventArgs(PayloadData payloadData, bool clean)
		{
			_payloadData = payloadData;
			_clean = clean;
		}

		internal CloseEventArgs(ushort code, string reason, bool clean)
		{
			_payloadData = new PayloadData(code, reason);
			_clean = clean;
		}
	}
	public enum ByteOrder
	{
		Little,
		Big
	}
	public class ErrorEventArgs : EventArgs
	{
		private Exception _exception;

		private string _message;

		public Exception Exception => _exception;

		public string Message => _message;

		internal ErrorEventArgs(string message)
			: this(message, null)
		{
		}

		internal ErrorEventArgs(string message, Exception exception)
		{
			_message = message;
			_exception = exception;
		}
	}
	public class WebSocket : IDisposable
	{
		private AuthenticationChallenge _authChallenge;

		private string _base64Key;

		private bool _client;

		private Action _closeContext;

		private CompressionMethod _compression;

		private WebSocketContext _context;

		private WebSocketSharp.Net.CookieCollection _cookies;

		private WebSocketSharp.Net.NetworkCredential _credentials;

		private bool _emitOnPing;

		private bool _enableRedirection;

		private string _extensions;

		private bool _extensionsRequested;

		private object _forMessageEventQueue;

		private object _forPing;

		private object _forSend;

		private object _forState;

		private MemoryStream _fragmentsBuffer;

		private bool _fragmentsCompressed;

		private Opcode _fragmentsOpcode;

		private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

		private Func<WebSocketContext, string> _handshakeRequestChecker;

		private bool _ignoreExtensions;

		private bool _inContinuation;

		private volatile bool _inMessage;

		private volatile Logger _logger;

		private static readonly int _maxRetryCountForConnect;

		private Action<MessageEventArgs> _message;

		private Queue<MessageEventArgs> _messageEventQueue;

		private uint _nonceCount;

		private string _origin;

		private ManualResetEvent _pongReceived;

		private bool _preAuth;

		private string _protocol;

		private string[] _protocols;

		private bool _protocolsRequested;

		private WebSocketSharp.Net.NetworkCredential _proxyCredentials;

		private Uri _proxyUri;

		private volatile WebSocketState _readyState;

		private ManualResetEvent _receivingExited;

		private int _retryCountForConnect;

		private bool _secure;

		private ClientSslConfiguration _sslConfig;

		private Stream _stream;

		private TcpClient _tcpClient;

		private Uri _uri;

		private const string _version = "13";

		private TimeSpan _waitTime;

		internal static readonly byte[] EmptyBytes;

		internal static readonly int FragmentLength;

		internal static readonly RandomNumberGenerator RandomNumber;

		internal WebSocketSharp.Net.CookieCollection CookieCollection => _cookies;

		internal Func<WebSocketContext, string> CustomHandshakeRequestChecker
		{
			get
			{
				return _handshakeRequestChecker;
			}
			set
			{
				_handshakeRequestChecker = value;
			}
		}

		internal bool HasMessage
		{
			get
			{
				lock (_forMessageEventQueue)
				{
					return _messageEventQueue.Count > 0;
				}
			}
		}

		internal bool IgnoreExtensions
		{
			get
			{
				return _ignoreExtensions;
			}
			set
			{
				_ignoreExtensions = value;
			}
		}

		internal bool IsConnected => _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing;

		public CompressionMethod Compression
		{
			get
			{
				return _compression;
			}
			set
			{
				string text = null;
				if (!_client)
				{
					text = "This instance is not a client.";
					throw new InvalidOperationException(text);
				}
				if (!canSet(out text))
				{
					_logger.Warn(text);
					return;
				}
				lock (_forState)
				{
					if (!canSet(out text))
					{
						_logger.Warn(text);
					}
					else
					{
						_compression = value;
					}
				}
			}
		}

		public IEnumerable<WebSocketSharp.Net.Cookie> Cookies
		{
			get
			{
				lock (_cookies.SyncRoot)
				{
					foreach (WebSocketSharp.Net.Cookie cookie in _cookies)
					{
						yield return cookie;
					}
				}
			}
		}

		public WebSocketSharp.Net.NetworkCredential Credentials => _credentials;

		public bool EmitOnPing
		{
			get
			{
				return _emitOnPing;
			}
			set
			{
				_emitOnPing = value;
			}
		}

		public bool EnableRedirection
		{
			get
			{
				return _enableRedirection;
			}
			set
			{
				string text = null;
				if (!_client)
				{
					text = "This instance is not a client.";
					throw new InvalidOperationException(text);
				}
				if (!canSet(out text))
				{
					_logger.Warn(text);
					return;
				}
				lock (_forState)
				{
					if (!canSet(out text))
					{
						_logger.Warn(text);
					}
					else
					{
						_enableRedirection = value;
					}
				}
			}
		}

		public string Extensions => _extensions ?? string.Empty;

		public bool IsAlive => ping(EmptyBytes);

		public bool IsSecure => _secure;

		public Logger Log
		{
			get
			{
				return _logger;
			}
			internal set
			{
				_logger = value;
			}
		}

		public string Origin
		{
			get
			{
				return _origin;
			}
			set
			{
				string text = null;
				if (!_client)
				{
					text = "This instance is not a client.";
					throw new InvalidOperationException(text);
				}
				if (!value.IsNullOrEmpty())
				{
					if (!Uri.TryCreate(value, UriKind.Absolute, out Uri result))
					{
						text = "Not an absolute URI string.";
						throw new ArgumentException(text, "value");
					}
					if (result.Segments.Length > 1)
					{
						text = "It includes the path segments.";
						throw new ArgumentException(text, "value");
					}
				}
				if (!canSet(out text))
				{
					_logger.Warn(text);
					return;
				}
				lock (_forState)
				{
					if (!canSet(out text))
					{
						_logger.Warn(text);
						return;
					}
					_origin = ((!value.IsNullOrEmpty()) ? value.TrimEnd(new char[1] { '/' }) : value);
				}
			}
		}

		public string Protocol
		{
			get
			{
				return _protocol ?? string.Empty;
			}
			internal set
			{
				_protocol = value;
			}
		}

		public WebSocketState ReadyState => _readyState;

		public ClientSslConfiguration SslConfiguration
		{
			get
			{
				if (!_client)
				{
					string text = "This instance is not a client.";
					throw new InvalidOperationException(text);
				}
				if (!_secure)
				{
					string text2 = "This instance does not use a secure connection.";
					throw new InvalidOperationException(text2);
				}
				return getSslConfiguration();
			}
		}

		public Uri Url => _client ? _uri : _context.RequestUri;

		public TimeSpan WaitTime
		{
			get
			{
				return _waitTime;
			}
			set
			{
				if (value <= TimeSpan.Zero)
				{
					throw new ArgumentOutOfRangeException("value", "Zero or less.");
				}
				if (!canSet(out var text))
				{
					_logger.Warn(text);
					return;
				}
				lock (_forState)
				{
					if (!canSet(out text))
					{
						_logger.Warn(text);
					}
					else
					{
						_waitTime = value;
					}
				}
			}
		}

		public event EventHandler<CloseEventArgs> OnClose;

		public event EventHandler<ErrorEventArgs> OnError;

		public event EventHandler<MessageEventArgs> OnMessage;

		public event EventHandler OnOpen;

		static WebSocket()
		{
			_maxRetryCountForConnect = 10;
			EmptyBytes = new byte[0];
			FragmentLength = 1016;
			RandomNumber = new RNGCryptoServiceProvider();
		}

		internal WebSocket(HttpListenerWebSocketContext context, string protocol)
		{
			_context = context;
			_protocol = protocol;
			_closeContext = context.Close;
			_logger = context.Log;
			_message = messages;
			_secure = context.IsSecureConnection;
			_stream = context.Stream;
			_waitTime = TimeSpan.FromSeconds(1.0);
			init();
		}

		internal WebSocket(TcpListenerWebSocketContext context, string protocol)
		{
			_context = context;
			_protocol = protocol;
			_closeContext = context.Close;
			_logger = context.Log;
			_message = messages;
			_secure = context.IsSecureConnection;
			_stream = context.Stream;
			_waitTime = TimeSpan.FromSeconds(1.0);
			init();
		}

		public WebSocket(string url, params string[] protocols)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}
			if (url.Length == 0)
			{
				throw new ArgumentException("An empty string.", "url");
			}
			if (!url.TryCreateWebSocketUri(out _uri, out var text))
			{
				throw new ArgumentException(text, "url");
			}
			if (protocols != null && protocols.Length != 0)
			{
				if (!checkProtocols(protocols, out text))
				{
					throw new ArgumentException(text, "protocols");
				}
				_protocols = protocols;
			}
			_base64Key = CreateBase64Key();
			_client = true;
			_logger = new Logger();
			_message = messagec;
			_secure = _uri.Scheme == "wss";
			_waitTime = TimeSpan.FromSeconds(5.0);
			init();
		}

		private bool accept()
		{
			if (_readyState == WebSocketState.Open)
			{
				string text = "The handshake request has already been accepted.";
				_logger.Warn(text);
				return false;
			}
			lock (_forState)
			{
				if (_readyState == WebSocketState.Open)
				{
					string text2 = "The handshake request has already been accepted.";
					_logger.Warn(text2);
					return false;
				}
				if (_readyState == WebSocketState.Closing)
				{
					string text3 = "The close process has set in.";
					_logger.Error(text3);
					text3 = "An interruption has occurred while attempting to accept.";
					error(text3, null);
					return false;
				}
				if (_readyState == WebSocketState.Closed)
				{
					string text4 = "The connection has been closed.";
					_logger.Error(text4);
					text4 = "An interruption has occurred while attempting to accept.";
					error(text4, null);
					return false;
				}
				try
				{
					if (!acceptHandshake())
					{
						return false;
					}
				}
				catch (Exception ex)
				{
					_logger.Fatal(ex.Message);
					_logger.Debug(ex.ToString());
					string text5 = "An exception has occurred while attempting to accept.";
					fatal(text5, ex);
					return false;
				}
				_readyState = WebSocketState.Open;
				return true;
			}
		}

		private bool acceptHandshake()
		{
			_logger.Debug($"A handshake request from {_context.UserEndPoint}:\n{_context}");
			if (!checkHandshakeRequest(_context, out var text))
			{
				_logger.Error(text);
				refuseHandshake(CloseStatusCode.ProtocolError, "A handshake error has occurred while attempting to accept.");
				return false;
			}
			if (!customCheckHandshakeRequest(_context, out text))
			{
				_logger.Error(text);
				refuseHandshake(CloseStatusCode.PolicyViolation, "A handshake error has occurred while attempting to accept.");
				return false;
			}
			_base64Key = _context.Headers["Sec-WebSocket-Key"];
			if (_protocol != null)
			{
				IEnumerable<string> secWebSocketProtocols = _context.SecWebSocketProtocols;
				processSecWebSocketProtocolClientHeader(secWebSocketProtocols);
			}
			if (!_ignoreExtensions)
			{
				string value = _context.Headers["Sec-WebSocket-Extensions"];
				processSecWebSocketExtensionsClientHeader(value);
			}
			return sendHttpResponse(createHandshakeResponse());
		}

		private bool canSet(out string message)
		{
			message = null;
			if (_readyState == WebSocketState.Open)
			{
				message = "The connection has already been established.";
				return false;
			}
			if (_readyState == WebSocketState.Closing)
			{
				message = "The connection is closing.";
				return false;
			}
			return true;
		}

		private bool checkHandshakeRequest(WebSocketContext context, out string message)
		{
			message = null;
			if (!context.IsWebSocketRequest)
			{
				message = "Not a handshake request.";
				return false;
			}
			if (context.RequestUri == null)
			{
				message = "It specifies an invalid Request-URI.";
				return false;
			}
			NameValueCollection headers = context.Headers;
			string text = headers["Sec-WebSocket-Key"];
			if (text == null)
			{
				message = "It includes no Sec-WebSocket-Key header.";
				return false;
			}
			if (text.Length == 0)
			{
				message = "It includes an invalid Sec-WebSocket-Key header.";
				return false;
			}
			string text2 = headers["Sec-WebSocket-Version"];
			if (text2 == null)
			{
				message = "It includes no Sec-WebSocket-Version header.";
				return false;
			}
			if (text2 != "13")
			{
				message = "It includes an invalid Sec-WebSocket-Version header.";
				return false;
			}
			string text3 = headers["Sec-WebSocket-Protocol"];
			if (text3 != null && text3.Length == 0)
			{
				message = "It includes an invalid Sec-WebSocket-Protocol header.";
				return false;
			}
			if (!_ignoreExtensions)
			{
				string text4 = headers["Sec-WebSocket-Extensions"];
				if (text4 != null && text4.Length == 0)
				{
					message = "It includes an invalid Sec-WebSocket-Extensions header.";
					return false;
				}
			}
			return true;
		}

		private bool checkHandshakeResponse(HttpResponse response, out string message)
		{
			message = null;
			if (response.IsRedirect)
			{
				message = "Indicates the redirection.";
				return false;
			}
			if (response.IsUnauthorized)
			{
				message = "Requires the authentication.";
				return false;
			}
			if (!response.IsWebSocketResponse)
			{
				message = "Not a WebSocket handshake response.";
				return false;
			}
			NameValueCollection headers = response.Headers;
			if (!validateSecWebSocketAcceptHeader(headers["Sec-WebSocket-Accept"]))
			{
				message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value.";
				return false;
			}
			if (!validateSecWebSocketProtocolServerHeader(headers["Sec-WebSocket-Protocol"]))
			{
				message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value.";
				return false;
			}
			if (!validateSecWebSocketExtensionsServerHeader(headers["Sec-WebSocket-Extensions"]))
			{
				message = "Includes an invalid Sec-WebSocket-Extensions header.";
				return false;
			}
			if (!validateSecWebSocketVersionServerHeader(headers["Sec-WebSocket-Version"]))
			{
				message = "Includes an invalid Sec-WebSocket-Version header.";
				return false;
			}
			return true;
		}

		private static bool checkProtocols(string[] protocols, out string message)
		{
			message = null;
			Func<string, bool> condition = (string protocol) => protocol.IsNullOrEmpty() || !protocol.IsToken();
			if (protocols.Contains(condition))
			{
				message = "It contains a value that is not a token.";
				return false;
			}
			if (protocols.ContainsTwice())
			{
				message = "It contains a value twice.";
				return false;
			}
			return true;
		}

		private bool checkReceivedFrame(WebSocketFrame frame, out string message)
		{
			message = null;
			bool isMasked = frame.IsMasked;
			if (_client && isMasked)
			{
				message = "A frame from the server is masked.";
				return false;
			}
			if (!_client && !isMasked)
			{
				message = "A frame from a client is not masked.";
				return false;
			}
			if (_inContinuation && frame.IsData)
			{
				message = "A data frame has been received while receiving continuation frames.";
				return false;
			}
			if (frame.IsCompressed && _compression == CompressionMethod.None)
			{
				message = "A compressed frame has been received without any agreement for it.";
				return false;
			}
			if (frame.Rsv2 == Rsv.On)
			{
				message = "The RSV2 of a frame is non-zero without any negotiation for it.";
				return false;
			}
			if (frame.Rsv3 == Rsv.On)
			{
				message = "The RSV3 of a frame is non-zero without any negotiation for it.";
				return false;
			}
			return true;
		}

		private void close(ushort code, string reason)
		{
			if (_readyState == WebSocketState.Closing)
			{
				_logger.Info("The closing is already in progress.");
				return;
			}
			if (_readyState == WebSocketState.Closed)
			{
				_logger.Info("The connection has already been closed.");
				return;
			}
			if (code == 1005)
			{
				close(PayloadData.Empty, send: true, receive: true, received: false);
				return;
			}
			bool receive = !code.IsReserved();
			close(new PayloadData(code, reason), receive, receive, received: false);
		}

		private void close(PayloadData payloadData, bool send, bool receive, bool received)
		{
			lock (_forState)
			{
				if (_readyState == WebSocketState.Closing)
				{
					_logger.Info("The closing is already in progress.");
					return;
				}
				if (_readyState == WebSocketState.Closed)
				{
					_logger.Info("The connection has already been closed.");
					return;
				}
				send = send && _readyState == WebSocketState.Open;
				receive = send && receive;
				_readyState = WebSocketState.Closing;
			}
			_logger.Trace("Begin closing the connection.");
			bool clean = closeHandshake(payloadData, send, receive, received);
			releaseResources();
			_logger.Trace("End closing the connection.");
			_readyState = WebSocketState.Closed;
			CloseEventArgs e = new CloseEventArgs(payloadData, clean);
			try
			{
				this.OnClose.Emit(this, e);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.Message);
				_logger.Debug(ex.ToString());
			}
		}

		private void closeAsync(ushort code, string reason)
		{
			if (_readyState == WebSocketState.Closing)
			{
				_logger.Info("The closing is already in progress.");
				return;
			}
			if (_readyState == WebSocketState.Closed)
			{
				_logger.Info("The connection has already been closed.");
				return;
			}
			if (code == 1005)
			{
				closeAsync(PayloadData.Empty, send: true, receive: true, received: false);
				return;
			}
			bool receive = !code.IsReserved();
			closeAsync(new PayloadData(code, reason), receive, receive, received: false);
		}

		private void closeAsync(PayloadData payloadData, bool send, bool receive, bool received)
		{
			Action<PayloadData, bool, bool, bool> closer = close;
			closer.BeginInvoke(payloadData, send, receive, received, delegate(IAsyncResult ar)
			{
				closer.EndInvoke(ar);
			}, null);
		}

		private bool closeHandshake(byte[] frameAsBytes, bool receive, bool received)
		{
			bool flag = frameAsBytes != null && sendBytes(frameAsBytes);
			if (!received && flag && receive && _receivingExited != null)
			{
				received = _receivingExited.WaitOne(_waitTime);
			}
			bool flag2 = flag && received;
			_logger.Debug($"Was clean?: {flag2}\n  sent: {flag}\n  received: {received}");
			return flag2;
		}

		private bool closeHandshake(PayloadData payloadData, bool send, bool receive, bool received)
		{
			bool flag = false;
			if (send)
			{
				WebSocketFrame webSocketFrame = WebSocketFrame.CreateCloseFrame(payloadData, _client);
				flag = sendBytes(webSocketFrame.ToArray());
				if (_client)
				{
					webSocketFrame.Unmask();
				}
			}
			if (!received && flag && receive && _receivingExited != null)
			{
				received = _receivingExited.WaitOne(_waitTime);
			}
			bool flag2 = flag && received;
			_logger.Debug($"Was clean?: {flag2}\n  sent: {flag}\n  received: {received}");
			return flag2;
		}

		private bool connect()
		{
			if (_readyState == WebSocketState.Open)
			{
				string text = "The connection has already been established.";
				_logger.Warn(text);
				return false;
			}
			lock (_forState)
			{
				if (_readyState == WebSocketState.Open)
				{
					string text2 = "The connection has already been established.";
					_logger.Warn(text2);
					return false;
				}
				if (_readyState == WebSocketState.Closing)
				{
					string text3 = "The close process has set in.";
					_logger.Error(text3);
					text3 = "An interruption has occurred while attempting to connect.";
					error(text3, null);
					return false;
				}
				if (_retryCountForConnect > _maxRetryCountForConnect)
				{
					string text4 = "An opportunity for reconnecting has been lost.";
					_logger.Error(text4);
					text4 = "An interruption has occurred while attempting to connect.";
					error(text4, null);
					return false;
				}
				_readyState = WebSocketState.Connecting;
				try
				{
					doHandshake();
				}
				catch (Exception ex)
				{
					_retryCountForConnect++;
					_logger.Fatal(ex.Message);
					_logger.Debug(ex.ToString());
					string text5 = "An exception has occurred while attempting to connect.";
					fatal(text5, ex);
					return false;
				}
				_retryCountForConnect = 1;
				_readyState = WebSocketState.Open;
				return true;
			}
		}

		private string createExtensions()
		{
			StringBuilder stringBuilder = new StringBuilder(80);
			if (_compression != 0)
			{
				string arg = _compression.ToExtensionString("server_no_context_takeover", "client_no_context_takeover");
				stringBuilder.AppendFormat("{0}, ", arg);
			}
			int length = stringBuilder.Length;
			if (length > 2)
			{
				stringBuilder.Length = length - 2;
				return stringBuilder.ToString();
			}
			return null;
		}

		private HttpResponse createHandshakeFailureResponse(WebSocketSharp.Net.HttpStatusCode code)
		{
			HttpResponse httpResponse = HttpResponse.CreateCloseResponse(code);
			httpResponse.Headers["Sec-WebSocket-Version"] = "13";
			return httpResponse;
		}

		private HttpRequest createHandshakeRequest()
		{
			HttpRequest httpRequest = HttpRequest.CreateWebSocketRequest(_uri);
			NameValueCollection headers = httpRequest.Headers;
			if (!_origin.IsNullOrEmpty())
			{
				headers["Origin"] = _origin;
			}
			headers["Sec-WebSocket-Key"] = _base64Key;
			_protocolsRequested = _protocols != null;
			if (_protocolsRequested)
			{
				headers["Sec-WebSocket-Protocol"] = _protocols.ToString(", ");
			}
			_extensionsRequested = _compression != CompressionMethod.None;
			if (_extensionsRequested)
			{
				headers["Sec-WebSocket-Extensions"] = createExtensions();
			}
			headers["Sec-WebSocket-Version"] = "13";
			AuthenticationResponse authenticationResponse = null;
			if (_authChallenge != null && _credentials != null)
			{
				authenticationResponse = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount);
				_nonceCount = authenticationResponse.NonceCount;
			}
			else if (_preAuth)
			{
				authenticationResponse = new AuthenticationResponse(_credentials);
			}
			if (authenticationResponse != null)
			{
				headers["Authorization"] = authenticationResponse.ToString();
			}
			if (_cookies.Count > 0)
			{
				httpRequest.SetCookies(_cookies);
			}
			return httpRequest;
		}

		private HttpResponse createHandshakeResponse()
		{
			HttpResponse httpResponse = HttpResponse.CreateWebSocketResponse();
			NameValueCollection headers = httpResponse.Headers;
			headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key);
			if (_protocol != null)
			{
				headers["Sec-WebSocket-Protocol"] = _protocol;
			}
			if (_extensions != null)
			{
				headers["Sec-WebSocket-Extensions"] = _extensions;
			}
			if (_cookies.Count > 0)
			{
				httpResponse.SetCookies(_cookies);
			}
			return httpResponse;
		}

		private bool customCheckHandshakeRequest(WebSocketContext context, out string message)
		{
			message = null;
			if (_handshakeRequestChecker == null)
			{
				return true;
			}
			message = _handshakeRequestChecker(context);
			return message == null;
		}

		private MessageEventArgs dequeueFromMessageEventQueue()
		{
			lock (_forMessageEventQueue)
			{
				return (_messageEventQueue.Count > 0) ? _messageEventQueue.Dequeue() : null;
			}
		}

		private void doHandshake()
		{
			setClientStream();
			HttpResponse httpResponse = sendHandshakeRequest();
			if (!checkHandshakeResponse(httpResponse, out var text))
			{
				throw new WebSocketException(CloseStatusCode.ProtocolError, text);
			}
			if (_protocolsRequested)
			{
				_protocol = httpResponse.Headers["Sec-WebSocket-Protocol"];
			}
			if (_extensionsRequested)
			{
				processSecWebSocketExtensionsServerHeader(httpResponse.Headers["Sec-WebSocket-Extensions"]);
			}
			processCookies(httpResponse.Cookies);
		}

		private void enqueueToMessageEventQueue(MessageEventArgs e)
		{
			lock (_forMessageEventQueue)
			{
				_messageEventQueue.Enqueue(e);
			}
		}

		private void error(string message, Exception exception)
		{
			try
			{
				this.OnError.Emit(this, new ErrorEventArgs(message, exception));
			}
			catch (Exception ex)
			{
				_logger.Error(ex.Message);
				_logger.Debug(ex.ToString());
			}
		}

		private void fatal(string message, Exception exception)
		{
			CloseStatusCode code = ((exception is WebSocketException) ? ((WebSocketException)exception).Code : CloseStatusCode.Abnormal);
			fatal(message, (ushort)code);
		}

		private void fatal(string message, ushort code)
		{
			PayloadData payloadData = new PayloadData(code, message);
			close(payloadData, !code.IsReserved(), receive: false, received: false);
		}

		private void fatal(string message, CloseStatusCode code)
		{
			fatal(message, (ushort)code);
		}

		private ClientSslConfiguration getSslConfiguration()
		{
			if (_sslConfig == null)
			{
				_sslConfig = new ClientSslConfiguration(_uri.DnsSafeHost);
			}
			return _sslConfig;
		}

		private void init()
		{
			_compression = CompressionMethod.None;
			_cookies = new WebSocketSharp.Net.CookieCollection();
			_forPing = new object();
			_forSend = new object();
			_forState = new object();
			_messageEventQueue = new Queue<MessageEventArgs>();
			_forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
			_readyState = WebSocketState.Connecting;
		}

		private void message()
		{
			MessageEventArgs obj = null;
			lock (_forMessageEventQueue)
			{
				if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
				{
					return;
				}
				_inMessage = true;
				obj = _messageEventQueue.Dequeue();
			}
			_message(obj);
		}

		private void messagec(MessageEventArgs e)
		{
			while (true)
			{
				try
				{
					this.OnMessage.Emit(this, e);
				}
				catch (Exception ex)
				{
					_logger.Error(ex.ToString());
					error("An error has occurred during an OnMessage event.", ex);
				}
				lock (_forMessageEventQueue)
				{
					if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
					{
						_inMessage = false;
						break;
					}
					e = _messageEventQueue.Dequeue();
				}
				bool flag = true;
			}
		}

		private void messages(MessageEventArgs e)
		{
			try
			{
				this.OnMessage.Emit(this, e);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.ToString());
				error("An error has occurred during an OnMessage event.", ex);
			}
			lock (_forMessageEventQueue)
			{
				if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
				{
					_inMessage = false;
					return;
				}
				e = _messageEventQueue.Dequeue();
			}
			ThreadPool.QueueUserWorkItem(delegate
			{
				messages(e);
			});
		}

		private void open()
		{
			_inMessage = true;
			startReceiving();
			try
			{
				this.OnOpen.Emit(this, EventArgs.Empty);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.ToString());
				error("An error has occurred during the OnOpen event.", ex);
			}
			MessageEventArgs obj = null;
			lock (_forMessageEventQueue)
			{
				if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
				{
					_inMessage = false;
					return;
				}
				obj = _messageEventQueue.Dequeue();
			}
			_message.BeginInvoke(obj, delegate(IAsyncResult ar)
			{
				_message.EndInvoke(ar);
			}, null);
		}

		private bool ping(byte[] data)
		{
			if (_readyState != WebSocketState.Open)
			{
				return false;
			}
			ManualResetEvent pongReceived = _pongReceived;
			if (pongReceived == null)
			{
				return false;
			}
			lock (_forPing)
			{
				try
				{
					pongReceived.Reset();
					if (!send(Fin.Final, Opcode.Ping, data, compressed: false))
					{
						return false;
					}
					return pongReceived.WaitOne(_waitTime);
				}
				catch (ObjectDisposedException)
				{
					return false;
				}
			}
		}

		private bool processCloseFrame(WebSocketFrame frame)
		{
			PayloadData payloadData = frame.PayloadData;
			close(payloadData, !payloadData.HasReservedCode, receive: false, received: true);
			return false;
		}

		private void processCookies(WebSocketSharp.Net.CookieCollection cookies)
		{
			if (cookies.Count != 0)
			{
				_cookies.SetOrRemove(cookies);
			}
		}

		private bool processDataFrame(WebSocketFrame frame)
		{
			enqueueToMessageEventQueue(frame.IsCompressed ? new MessageEventArgs(frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression)) : new MessageEventArgs(frame));
			return true;
		}

		private bool processFragmentFrame(WebSocketFrame frame)
		{
			if (!_inContinuation)
			{
				if (frame.IsContinuation)
				{
					return true;
				}
				_fragmentsOpcode = frame.Opcode;
				_fragmentsCompressed = frame.IsCompressed;
				_fragmentsBuffer = new MemoryStream();
				_inContinuation = true;
			}
			_fragmentsBuffer.WriteBytes(frame.PayloadData.ApplicationData, 1024);
			if (frame.IsFinal)
			{
				using (_fragmentsBuffer)
				{
					byte[] rawData = (_fragmentsCompressed ? _fragmentsBuffer.DecompressToArray(_compression) : _fragmentsBuffer.ToArray());
					enqueueToMessageEventQueue(new MessageEventArgs(_fragmentsOpcode, rawData));
				}
				_fragmentsBuffer = null;
				_inContinuation = false;
			}
			return true;
		}

		private bool processPingFrame(WebSocketFrame frame)
		{
			_logger.Trace("A ping was received.");
			WebSocketFrame webSocketFrame = WebSocketFrame.CreatePongFrame(frame.PayloadData, _client);
			lock (_forState)
			{
				if (_readyState != WebSocketState.Open)
				{
					_logger.Error("The connection is closing.");
					return true;
				}
				if (!sendBytes(webSocketFrame.ToArray()))
				{
					return false;
				}
			}
			_logger.Trace("A pong to this ping has been sent.");
			if (_emitOnPing)
			{
				if (_client)
				{
					webSocketFrame.Unmask();
				}
				enqueueToMessageEventQueue(new MessageEventArgs(frame));
			}
			return true;
		}

		private bool processPongFrame(WebSocketFrame frame)
		{
			_logger.Trace("A pong was received.");
			try
			{
				_pongReceived.Set();
			}
			catch (NullReferenceException ex)
			{
				_logger.Error(ex.Message);
				_logger.Debug(ex.ToString());
				return false;
			}
			catch (ObjectDisposedException ex2)
			{
				_logger.Error(ex2.Message);
				_logger.Debug(ex2.ToString());
				return false;
			}
			_logger.Trace("It has been signaled.");
			return true;
		}

		private bool processReceivedFrame(WebSocketFrame frame)
		{
			if (!checkReceivedFrame(frame, out var text))
			{
				throw new WebSocketException(CloseStatusCode.ProtocolError, text);
			}
			frame.Unmask();
			return frame.IsFragment ? processFragmentFrame(frame) : (frame.IsData ? processDataFrame(frame) : (frame.IsPing ? processPingFrame(frame) : (frame.IsPong ? processPongFrame(frame) : (frame.IsClose ? processCloseFrame(frame) : processUnsupportedFrame(frame)))));
		}

		private void processSecWebSocketExtensionsClientHeader(string value)
		{
			if (value == null)
			{
				return;
			}
			StringBuilder stringBuilder = new StringBuilder(80);
			bool flag = false;
			foreach (string item in value.SplitHeaderValue(','))
			{
				string text = item.Trim();
				if (text.Length != 0 && !flag && text.IsCompressionExtension(CompressionMethod.Deflate))
				{
					_compression = CompressionMethod.Deflate;
					stringBuilder.AppendFormat("{0}, ", _compression.ToExtensionString("client_no_context_takeover", "server_no_context_takeover"));
					flag = true;
				}
			}
			int length = stringBuilder.Length;
			if (length > 2)
			{
				stringBuilder.Length = length - 2;
				_extensions = stringBuilder.ToString();
			}
		}

		private void processSecWebSocketExtensionsServerHeader(string value)
		{
			if (value == null)
			{
				_compression = CompressionMethod.None;
			}
			else
			{
				_extensions = value;
			}
		}

		private void processSecWebSocketProtocolClientHeader(IEnumerable<string> values)
		{
			if (!values.Contains((string val) => val == _protocol))
			{
				_protocol = null;
			}
		}

		private bool processUnsupportedFrame(WebSocketFrame frame)
		{
			_logger.Fatal("An unsupported frame:" + frame.PrintToString(dumped: false));
			fatal("There is no way to handle it.", CloseStatusCode.PolicyViolation);
			return false;
		}

		private void refuseHandshake(CloseStatusCode code, string reason)
		{
			_readyState = WebSocketState.Closing;
			HttpResponse response = createHandshakeFailureResponse(WebSocketSharp.Net.HttpStatusCode.BadRequest);
			sendHttpResponse(response);
			releaseServerResources();
			_readyState = WebSocketState.Closed;
			CloseEventArgs e = new CloseEventArgs((ushort)code, reason, clean: false);
			try
			{
				this.OnClose.Emit(this, e);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.Message);
				_logger.Debug(ex.ToString());
			}
		}

		private void releaseClientResources()
		{
			if (_stream != null)
			{
				_stream.Dispose();
				_stream = null;
			}
			if (_tcpClient != null)
			{
				_tcpClient.Close();
				_tcpClient = null;
			}
		}

		private void releaseCommonResources()
		{
			if (_fragmentsBuffer != null)
			{
				_fragmentsBuffer.Dispose();
				_fragmentsBuffer = null;
				_inContinuation = false;
			}
			if (_pongReceived != null)
			{
				_pongReceived.Close();
				_pongReceived = null;
			}
			if (_receivingExited != null)
			{
				_receivingExited.Close();
				_receivingExited = null;
			}
		}

		private void releaseResources()
		{
			if (_client)
			{
				releaseClientResources();
			}
			else
			{
				releaseServerResources();
			}
			releaseCommonResources();
		}

		private void releaseServerResources()
		{
			if (_closeContext != null)
			{
				_closeContext();
				_closeContext = null;
				_stream = null;
				_context = null;
			}
		}

		private bool send(Opcode opcode, Stream stream)
		{
			lock (_forSend)
			{
				Stream stream2 = stream;
				bool flag = false;
				bool flag2 = false;
				try
				{
					if (_compression != 0)
					{
						stream = stream.Compress(_compression);
						flag = true;
					}
					flag2 = send(opcode, stream, flag);
					if (!flag2)
					{
						error("A send has been interrupted.", null);
					}
				}
				catch (Exception ex)
				{
					_logger.Error(ex.ToString());
					error("An error has occurred during a send.", ex);
				}
				finally
				{
					if (flag)
					{
						stream.Dispose();
					}
					stream2.Dispose();
				}
				return flag2;
			}
		}

		private bool send(Opcode opcode, Stream stream, bool compressed)
		{
			long length = stream.Length;
			if (length == 0)
			{
				return send(Fin.Final, opcode, EmptyBytes, compressed: false);
			}
			long num = length / FragmentLength;
			int num2 = (int)(length % FragmentLength);
			byte[] array = null;
			switch (num)
			{
			case 0L:
				array = new byte[num2];
				return stream.Read(array, 0, num2) == num2 && send(Fin.Final, opcode, array, compressed);
			case 1L:
				if (num2 == 0)
				{
					array = new byte[FragmentLength];
					return stream.Read(array, 0, FragmentLength) == FragmentLength && send(Fin.Final, opcode, array, compressed);
				}
				break;
			}
			array = new byte[FragmentLength];
			if (stream.Read(array, 0, FragmentLength) != FragmentLength || !send(Fin.More, opcode, array, compressed))
			{
				return false;
			}
			long num3 = ((num2 == 0) ? (num - 2) : (num - 1));
			for (long num4 = 0L; num4 < num3; num4++)
			{
				if (stream.Read(array, 0, FragmentLength) != FragmentLength || !send(Fin.More, Opcode.Cont, array, compressed: false))
				{
					return false;
				}
			}
			if (num2 == 0)
			{
				num2 = FragmentLength;
			}
			else
			{
				array = new byte[num2];
			}
			return stream.Read(array, 0, num2) == num2 && send(Fin.Final, Opcode.Cont, array, compressed: false);
		}

		private bool send(Fin fin, Opcode opcode, byte[] data, bool compressed)
		{
			lock (_forState)
			{
				if (_readyState != WebSocketState.Open)
				{
					_logger.Error("The connection is closing.");
					return false;
				}
				WebSocketFrame webSocketFrame = new WebSocketFrame(fin, opcode, data, compressed, _client);
				return sendBytes(webSocketFrame.ToArray());
			}
		}

		private void sendAsync(Opcode opcode, Stream stream, Action<bool> completed)
		{
			Func<Opcode, Stream, bool> sender = send;
			sender.BeginInvoke(opcode, stream, delegate(IAsyncResult ar)
			{
				try
				{
					bool obj = sender.EndInvoke(ar);
					if (completed != null)
					{
						completed(obj);
					}
				}
				catch (Exception ex)
				{
					_logger.Error(ex.ToString());
					error("An error has occurred during the callback for an async send.", ex);
				}
			}, null);
		}

		private bool sendBytes(byte[] bytes)
		{
			try
			{
				_stream.Write(bytes, 0, bytes.Length);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.Message);
				_logger.Debug(ex.ToString());
				return false;
			}
			return true;
		}

		private HttpResponse sendHandshakeRequest()
		{
			HttpRequest httpRequest = createHandshakeRequest();
			HttpResponse httpResponse = sendHttpRequest(httpRequest, 90000);
			if (httpResponse.IsUnauthorized)
			{
				string text = httpResponse.Headers["WWW-Authenticate"];
				_logger.Warn($"Received an authentication requirement for '{text}'.");
				if (text.IsNullOrEmpty())
				{
					_logger.Error("No authentication challenge is specified.");
					return httpResponse;
				}
				_authChallenge = AuthenticationChallenge.Parse(text);
				if (_authChallenge == null)
				{
					_logger.Error("An invalid authentication challenge is specified.");
					return httpResponse;
				}
				if (_credentials != null && (!_preAuth || _authChallenge.Scheme == WebSocketSharp.Net.AuthenticationSchemes.Digest))
				{
					if (httpResponse.HasConnectionClose)
					{
						releaseClientResources();
						setClientStream();
					}
					AuthenticationResponse authenticationResponse = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount);
					_nonceCount = authenticationResponse.NonceCount;
					httpRequest.Headers["Authorization"] = authenticationResponse.ToString();
					httpResponse = sendHttpRequest(httpRequest, 15000);
				}
			}
			if (httpResponse.IsRedirect)
			{
				string text2 = httpResponse.Headers["Location"];
				_logger.Warn($"Received a redirection to '{text2}'.");
				if (_enableRedirection)
				{
					if (text2.IsNullOrEmpty())
					{
						_logger.Error("No url to redirect is located.");
						return httpResponse;
					}
					if (!text2.TryCreateWebSocketUri(out var result, out var text3))
					{
						_logger.Error("An invalid url to redirect is located: " + text3);
						return httpResponse;
					}
					releaseClientResources();
					_uri = result;
					_secure = result.Scheme == "wss";
					setClientStream();
					return sendHandshakeRequest();
				}
			}
			return httpResponse;
		}

		private HttpResponse sendHttpRequest(HttpRequest request, int millisecondsTimeout)
		{
			_logger.Debug("A request to the server:\n" + request.ToString());
			HttpResponse response = request.GetResponse(_stream, millisecondsTimeout);
			_logger.Debug("A response to this request:\n" + response.ToString());
			return response;
		}

		private bool sendHttpResponse(HttpResponse response)
		{
			_logger.Debug($"A response to {_context.UserEndPoint}:\n{response}");
			return sendBytes(response.ToByteArray());
		}

		private void sendProxyConnectRequest()
		{
			HttpRequest httpRequest = HttpRequest.CreateConnectRequest(_uri);
			HttpResponse httpResponse = sendHttpRequest(httpRequest, 90000);
			if (httpResponse.IsProxyAuthenticationRequired)
			{
				string text = httpResponse.Headers["Proxy-Authenticate"];
				_logger.Warn($"Received a proxy authentication requirement for '{text}'.");
				if (text.IsNullOrEmpty())
				{
					throw new WebSocketException("No proxy authentication challenge is specified.");
				}
				AuthenticationChallenge authenticationChallenge = AuthenticationChallenge.Parse(text);
				if (authenticationChallenge == null)
				{
					throw new WebSocketException("An invalid proxy authentication challenge is specified.");
				}
				if (_proxyCredentials != null)
				{
					if (httpResponse.HasConnectionClose)
					{
						releaseClientResources();
						_tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
						_stream = _tcpClient.GetStream();
					}
					AuthenticationResponse authenticationResponse = new AuthenticationResponse(authenticationChallenge, _proxyCredentials, 0u);
					httpRequest.Headers["Proxy-Authorization"] = authenticationResponse.ToString();
					httpResponse = sendHttpRequest(httpRequest, 15000);
				}
				if (httpResponse.IsProxyAuthenticationRequired)
				{
					throw new WebSocketException("A proxy authentication is required.");
				}
			}
			if (httpResponse.StatusCode[0] != '2')
			{
				throw new WebSocketException("The proxy has failed a connection to the requested host and port.");
			}
		}

		private void setClientStream()
		{
			if (_proxyUri != null)
			{
				_tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
				_stream = _tcpClient.GetStream();
				sendProxyConnectRequest();
			}
			else
			{
				_tcpClient = new TcpClient(_uri.DnsSafeHost, _uri.Port);
				_stream = _tcpClient.GetStream();
			}
			if (_secure)
			{
				ClientSslConfiguration sslConfiguration = getSslConfiguration();
				string targetHost = sslConfiguration.TargetHost;
				if (targetHost != _uri.DnsSafeHost)
				{
					throw new WebSocketException(CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified.");
				}
				try
				{
					SslStream sslStream = new SslStream(_stream, leaveInnerStreamOpen: false, sslConfiguration.ServerCertificateValidationCallback, sslConfiguration.ClientCertificateSelectionCallback);
					sslStream.AuthenticateAsClient(targetHost, sslConfiguration.ClientCertificates, sslConfiguration.EnabledSslProtocols, sslConfiguration.CheckCertificateRevocation);
					_stream = sslStream;
				}
				catch (Exception innerException)
				{
					throw new WebSocketException(CloseStatusCode.TlsHandshakeFailure, innerException);
				}
			}
		}

		private void startReceiving()
		{
			if (_messageEventQueue.Count > 0)
			{
				_messageEventQueue.Clear();
			}
			_pongReceived = new ManualResetEvent(initialState: false);
			_receivingExited = new ManualResetEvent(initialState: false);
			Action receive = null;
			receive = delegate
			{
				WebSocketFrame.ReadFrameAsync(_stream, unmask: false, delegate(WebSocketFrame frame)
				{
					if (!processReceivedFrame(frame) || _readyState == WebSocketState.Closed)
					{
						_receivingExited?.Set();
					}
					else
					{
						receive();
						if (!_inMessage && HasMessage && _readyState == WebSocketState.Open)
						{
							message();
						}
					}
				}, delegate(Exception ex)
				{
					_logger.Fatal(ex.ToString());
					fatal("An exception has occurred while receiving.", ex);
				});
			};
			receive();
		}

		private bool validateSecWebSocketAcceptHeader(string value)
		{
			return value != null && value == CreateResponseKey(_base64Key);
		}

		private bool validateSecWebSocketExtensionsServerHeader(string value)
		{
			if (value == null)
			{
				return true;
			}
			if (value.Length == 0)
			{
				return false;
			}
			if (!_extensionsRequested)
			{
				return false;
			}
			bool flag = _compression != CompressionMethod.None;
			foreach (string item in value.SplitHeaderValue(','))
			{
				string text = item.Trim();
				if (flag && text.IsCompressionExtension(_compression))
				{
					if (!text.Contains("server_no_context_takeover"))
					{
						_logger.Error("The server hasn't sent back 'server_no_context_takeover'.");
						return false;
					}
					if (!text.Contains("client_no_context_takeover"))
					{
						_logger.Warn("The server hasn't sent back 'client_no_context_takeover'.");
					}
					string method = _compression.ToExtensionString();
					if (text.SplitHeaderValue(';').Contains(delegate(string t)
					{
						t = t.Trim();
						return t != method && t != "server_no_context_takeover" && t != "client_no_context_takeover";
					}))
					{
						return false;
					}
					continue;
				}
				return false;
			}
			return true;
		}

		private bool validateSecWebSocketProtocolServerHeader(string value)
		{
			if (value == null)
			{
				return !_protocolsRequested;
			}
			if (value.Length == 0)
			{
				return false;
			}
			return _protocolsRequested && _protocols.Contains((string p) => p == value);
		}

		private bool validateSecWebSocketVersionServerHeader(string value)
		{
			return value == null || value == "13";
		}

		internal void Close(HttpResponse response)
		{
			_readyState = WebSocketState.Closing;
			sendHttpResponse(response);
			releaseServerResources();
			_readyState = WebSocketState.Closed;
		}

		internal void Close(WebSocketSharp.Net.HttpStatusCode code)
		{
			Close(createHandshakeFailureResponse(code));
		}

		internal void Close(PayloadData payloadData, byte[] frameAsBytes)
		{
			lock (_forState)
			{
				if (_readyState == WebSocketState.Closing)
				{
					_logger.Info("The closing is already in progress.");
					return;
				}
				if (_readyState == WebSocketState.Closed)
				{
					_logger.Info("The connection has already been closed.");
					return;
				}
				_readyState = WebSocketState.Closing;
			}
			_logger.Trace("Begin closing the connection.");
			bool flag = frameAsBytes != null && sendBytes(frameAsBytes);
			bool flag2 = flag && _receivingExited != null && _receivingExited.WaitOne(_waitTime);
			bool flag3 = flag && flag2;
			_logger.Debug($"Was clean?: {flag3}\n  sent: {flag}\n  received: {flag2}");
			releaseServerResources();
			releaseCommonResources();
			_logger.Trace("End closing the connection.");
			_readyState = WebSocketState.Closed;
			CloseEventArgs e = new CloseEventArgs(payloadData, flag3);
			try
			{
				this.OnClose.Emit(this, e);
			}
			catch (Exception ex)
			{
				_logger.Error(ex.Message);
				_logger.Debug(ex.ToString());
			}
		}

		internal static string CreateBase64Key()
		{
			byte[] array = new byte[16];
			RandomNumber.GetBytes(array);
			return Convert.ToBase64String(array);
		}

		internal static string CreateResponseKey(string base64Key)
		{
			StringBuilder stringBuilder = new StringBuilder(base64Key, 64);
			stringBuilder.Append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
			SHA1 sHA = new SHA1CryptoServiceProvider();
			byte[] inArray = sHA.ComputeHash(stringBuilder.ToString().GetUTF8EncodedBytes());
			return Convert.ToBase64String(inArray);
		}

		internal void InternalAccept()
		{
			try
			{
				if (!acceptHandshake())
				{
					return;
				}
			}
			catch (Exception ex)
			{
				_logger.Fatal(ex.Message);
				_logger.Debug(ex.ToString());
				string text = "An exception has occurred while attempting to accept.";
				fatal(text, ex);
				return;
			}
			_readyState = WebSocketState.Open;
			open();
		}

		internal bool Ping(byte[] frameAsBytes, TimeSpan timeout)
		{
			if (_readyState != WebSocketState.Open)
			{
				return false;
			}
			ManualResetEvent pongReceived = _pongReceived;
			if (pongReceived == null)
			{
				return false;
			}
			lock (_forPing)
			{
				try
				{
					pongReceived.Reset();
					lock (_forState)
					{
						if (_readyState != WebSocketState.Open)
						{
							return false;
						}
						if (!sendBytes(frameAsBytes))
						{
							return false;
						}
					}
					return pongReceived.WaitOne(timeout);
				}
				catch (ObjectDisposedException)
				{
					return false;
				}
			}
		}

		internal void Send(Opcode opcode, byte[] data, Dictionary<CompressionMethod, byte[]> cache)
		{
			lock (_forSend)
			{
				lock (_forState)
				{
					if (_readyState != WebSocketState.Open)
					{
						_logger.Error("The connection is closing.");
						return;
					}
					if (!cache.TryGetValue(_compression, out var value))
					{
						value = new WebSocketFrame(Fin.Final, opcode, data.Compress(_compression), _compression != CompressionMethod.None, mask: false).ToArray();
						cache.Add(_compression, value);
					}
					sendBytes(value);
				}
			}
		}

		internal void Send(Opcode opcode, Stream stream, Dictionary<CompressionMethod, Stream> cache)
		{
			lock (_forSend)
			{
				if (!cache.TryGetValue(_compression, out var value))
				{
					value = stream.Compress(_compression);
					cache.Add(_compression, value);
				}
				else
				{
					value.Position = 0L;
				}
				send(opcode, value, _compression != CompressionMethod.None);
			}
		}

		public void Accept()
		{
			if (_client)
			{
				string text = "This instance is a client.";
				throw new InvalidOperationException(text);
			}
			if (_readyState == WebSocketState.Closing)
			{
				string text2 = "The close process is in progress.";
				throw new InvalidOperationException(text2);
			}
			if (_readyState == WebSocketState.Closed)
			{
				string text3 = "The connection has already been closed.";
				throw new InvalidOperationException(text3);
			}
			if (accept())
			{
				open();
			}
		}

		public void AcceptAsync()
		{
			if (_client)
			{
				string text = "This instance is a client.";
				throw new InvalidOperationException(text);
			}
			if (_readyState == WebSocketState.Closing)
			{
				string text2 = "The close process is in progress.";
				throw new InvalidOperationException(text2);
			}
			if (_readyState == WebSocketState.Closed)
			{
				string text3 = "The connection has already been closed.";
				throw new InvalidOperationException(text3);
			}
			Func<bool> acceptor = accept;
			acceptor.BeginInvoke(delegate(IAsyncResult ar)
			{
				if (acceptor.EndInvoke(ar))
				{
					open();
				}
			}, null);
		}

		public void Close()
		{
			close(1005, string.Empty);
		}

		public void Close(ushort code)
		{
			if (!code.IsCloseStatusCode())
			{
				string text = "Less than 1000 or greater than 4999.";
				throw new ArgumentOutOfRangeException("code", text);
			}
			if (_client && code == 1011)
			{
				string text2 = "1011 cannot be used.";
				throw new ArgumentException(text2, "code");
			}
			if (!_client && code == 1010)
			{
				string text3 = "1010 cannot be used.";
				throw new ArgumentException(text3, "code");
			}
			close(code, string.Empty);
		}

		public void Close(CloseStatusCode code)
		{
			if (_client && code == CloseStatusCode.ServerError)
			{
				string text = "ServerError cannot be used.";
				throw new ArgumentException(text, "code");
			}
			if (!_client && code == CloseStatusCode.MandatoryExtension)
			{
				string text2 = "MandatoryExtension cannot be used.";
				throw new ArgumentException(text2, "code");
			}
			close((ushort)code, string.Empty);
		}

		public void Close(ushort code, string reason)
		{
			if (!code.IsCloseStatusCode())
			{
				string text = "Less than 1000 or greater than 4999.";
				throw new ArgumentOutOfRangeException("code", text);
			}
			if (_client && code == 1011)
			{
				string text2 = "1011 cannot be used.";
				throw new ArgumentException(text2, "code");
			}
			if (!_client && code == 1010)
			{
				string text3 = "1010 cannot be used.";
				throw new ArgumentException(text3, "code");
			}
			if (reason.IsNullOrEmpty())
			{
				close(code, string.Empty);
				return;
			}
			if (code == 1005)
			{
				string text4 = "1005 cannot be used.";
				throw new ArgumentException(text4, "code");
			}
			if (!reason.TryGetUTF8EncodedBytes(out var bytes))
			{
				string text5 = "It could not be UTF-8-encoded.";
				throw new ArgumentException(text5, "reason");
			}
			if (bytes.Length > 123)
			{
				string text6 = "Its size is greater than 123 bytes.";
				throw new ArgumentOutOfRangeException("reason", text6);
			}
			close(code, reason);
		}

		public void Close(CloseStatusCode code, string reason)
		{
			if (_client && code == CloseStatusCode.ServerError)
			{
				string text = "ServerError cannot be used.";
				throw new ArgumentException(text, "code");
			}
			if (!_client && code == CloseStatusCode.MandatoryExtension)
			{
				string text2 = "MandatoryExtension cannot be used.";
				throw new ArgumentException(text2, "code");
			}
			if (reason.IsNullOrEmpty())
			{
				close((ushort)code, string.Empty);
				return;
			}
			if (code == CloseStatusCode.NoStatus)
			{
				string text3 = "NoStatus cannot be used.";
				throw new ArgumentException(text3, "code");
			}
			if (!reason.TryGetUTF8EncodedBytes(out var bytes))
			{
				string text4 = "It could not be UTF-8-encoded.";
				throw new ArgumentException(text4, "reason");
			}
			if (bytes.Length > 123)
			{
				string text5 = "Its size is greater than 123 bytes.";
				throw new ArgumentOutOfRangeException("reason", text5);
			}
			close((ushort)code, reason);
		}

		public void CloseAsync()
		{
			closeAsync(1005, string.Empty);
		}

		public void CloseAsync(ushort code)
		{
			if (!code.IsCloseStatusCode())
			{
				string text = "Less than 1000 or greater than 4999.";
				throw new ArgumentOutOfRangeException("code", text);
			}
			if (_client && code == 1011)
			{
				string text2 = "1011 cannot be used.";
				throw new ArgumentException(text2, "code");
			}
			if (!_client && code == 1010)
			{
				string text3 = "1010 cannot be used.";
				throw new ArgumentException(text3, "code");
			}
			closeAsync(code, string.Empty);
		}

		public void CloseAsync(CloseStatusCode code)
		{
			if (_client && code == CloseStatusCode.ServerError)
			{
				string text = "ServerError cannot be used.";
				throw new ArgumentException(text, "code");
			}
			if (!_client && code == CloseStatusCode.MandatoryExtension)
			{
				string text2 = "MandatoryExtension cannot be used.";
				throw new ArgumentException(text2, "code");
			}
			closeAsync((ushort)code, string.Empty);
		}

		public void CloseAsync(ushort code, string reason)
		{
			if (!code.IsCloseStatusCode())
			{
				string text = "Less than 1000 or greater than 4999.";
				throw new ArgumentOutOfRangeException("code", text);
			}
			if (_client && code == 1011)
			{
				string text2 = "1011 cannot be used.";
				throw new ArgumentException(text2, "code");
			}
			if (!_client && code == 1010)
			{
				string text3 = "1010 cannot be used.";
				throw new ArgumentException(text3, "code");
			}
			if (reason.IsNullOrEmpty())
			{
				closeAsync(code, string.Empty);
				return;
			}
			if (code == 1005)
			{
				string text4 = "1005 cannot be used.";
				throw new ArgumentException(text4, "code");
			}
			if (!reason.TryGetUTF8EncodedBytes(out var bytes))
			{
				string text5 = "It could not be UTF-8-encoded.";
				throw new ArgumentException(text5, "reason");
			}
			if (bytes.Length > 123)
			{
				string text6 = "Its size is greater than 123 bytes.";
				throw new ArgumentOutOfRangeException("reason", text6);
			}
			closeAsync(code, reason);
		}

		public void CloseAsync(CloseStatusCode code, string reason)
		{
			if (_client && code == CloseStatusCode.ServerError)
			{
				string text = "ServerError cannot be used.";
				throw new ArgumentException(text, "code");
			}
			if (!_client && code == CloseStatusCode.MandatoryExtension)
			{
				string text2 = "MandatoryExtension cannot be used.";
				throw new ArgumentException(text2, "code");
			}
			if (reason.IsNullOrEmpty())
			{
				closeAsync((ushort)code, string.Empty);
				return;
			}
			if (code == CloseStatusCode.NoStatus)
			{
				string text3 = "NoStatus cannot be used.";
				throw new ArgumentException(text3, "code");
			}
			if (!reason.TryGetUTF8EncodedBytes(out var bytes))
			{
				string text4 = "It could not be UTF-8-encoded.";
				throw new ArgumentException(text4, "reason");
			}
			if (bytes.Length > 123)
			{
				string text5 = "Its size is greater than 123 bytes.";
				throw new ArgumentOutOfRangeException("reason", text5);
			}
			closeAsync((ushort)code, reason);
		}

		public void Connect()
		{
			if (!_client)
			{
				string text = "This instance is not a client.";
				throw new InvalidOperationException(text);
			}
			if (_readyState == WebSocketState.Closing)
			{
				string text2 = "The close process is in progress.";
				throw new InvalidOperationException(text2);
			}
			if (_retryCountForConnect > _maxRetryCountForConnect)
			{
				string text3 = "A series of reconnecting has failed.";
				throw new InvalidOperationException(text3);
			}
			if (connect())
			{
				open();
			}
		}

		public void ConnectAsync()
		{
			if (!_client)
			{
				string text = "This instance is not a client.";
				throw new InvalidOperationException(text);
			}
			if (_readyState == WebSocketState.Closing)
			{
				string text2 = "The close process is in progress.";
				throw new InvalidOperationException(text2);
			}
			if (_retryCountForConnect > _maxRetryCountForConnect)
			{
				string text3 = "A series of reconnecting has failed.";
				throw new InvalidOperationException(text3);
			}
			Func<bool> connector = connect;
			connector.BeginInvoke(delegate(IAsyncResult ar)
			{
				if (connector.EndInvoke(ar))
				{
					open();
				}
			}, null);
		}

		public bool Ping()
		{
			return ping(EmptyBytes);
		}

		public bool Ping(string message)
		{
			if (message.IsNullOrEmpty())
			{
				return ping(EmptyBytes);
			}
			if (!message.TryGetUTF8EncodedBytes(out var bytes))
			{
				string text = "It could not be UTF-8-encoded.";
				throw new ArgumentException(text, "message");
			}
			if (bytes.Length > 125)
			{
				string text2 = "Its size is greater than 125 bytes.";
				throw new ArgumentOutOfRangeException("message", text2);
			}
			return ping(bytes);
		}

		public void Send(byte[] data)
		{
			if (_readyState != WebSocketState.Open)
			{
				string text = "The current state of the connection is not Open.";
				throw new InvalidOperationException(text);
			}
			if (data == null)
			{
				throw new ArgumentNullException("data");
			}
			send(Opcode.Binary, new MemoryStream(data));
		}

		public void Send(FileInfo fileInfo)
		{
			if (_readyState != WebSocketState.Open)
			{
				string text = "The current state of the connection is not Open.";
				throw new InvalidOperationException(text);
			}
			if (fileInfo == null)
			{
				throw new ArgumentNullException("fileInfo");
			}
			if (!fileInfo.Exists)
			{
				string text2 = "The file does not exist.";
				throw new ArgumentException(text2, "fileInfo");
			}
			if (!fileInfo.TryOpenRead(out var fileStream))
			{
				string text3 = "The file could not be opened.";
				throw new ArgumentException(text3, "fileInfo");
			}
			send(Opcode.Binary, fileStream);
		}

		public void Send(string data)
		{
			if (_readyState != WebSocketState.Open)
			{
				string text = "The current state of the connection is not Open.";
				throw new InvalidOperationException(text);
			}
			if (data == null)
			{
				throw new ArgumentNullException("data");
			}
			if (!data.TryGetUTF8EncodedBytes(out var bytes))
			{
				string text2 = "It could not be UTF-8-encoded.";
				throw new ArgumentException(text2, "data");
			}
			send(Opcode.Text, new MemoryStream(bytes));
		}

		public void Send(Stream stream, int length)
		{
			if (_readyState != WebSocketState.Open)
			{
				string text = "The current state of the connection is not Open.";
				throw new InvalidOperationException(text);
			}
			if (stream == null)
			{
				throw new ArgumentNullException("stream");
			}
			if (!stream.CanRead)
			{
				string text2 = "It cannot be read.";
				throw new ArgumentException(text2, "stream");
			}
			if (length < 1)
			{
				string text3 = "Less than 1.";
				throw new ArgumentException(text3, "length");
			}
			byte[] array = stream.ReadBytes(length);
			int num = array.Length;
			if (num == 0)
			{
				string text4 = "No data could be read from it.";
				throw new ArgumentException(text4, "stream");
			}
			if (num < length)
			{
				_logger.Warn($"Only {num} byte(s) of data could be read from the stream.");
			}
			send(Opcode.Binary, new MemoryStream(array));
		}

		public void SendAsync(byte[] data, Action<bool> completed)
		{
			if (_readyState != WebSocketState.Open)
			{
				string text = "The current state of the connection is not Open.";
				throw new InvalidOperationException(text);
			}
			if (data == null)
			{
				throw new ArgumentNullException("data");
			}
			sendAsync(Opcode.Binary, new MemoryStream(data), completed);
		}

		public void SendAsync(FileInfo fileInfo, Action<bool> completed)
		{
			if (_readyState != WebSocketState.Open)
			{
				string text = "The current state of the connection is not Open.";
				throw new InvalidOperationException(text);
			}
			if (fileInfo == null)
			{
				throw new ArgumentNullException("fileInfo");
			}
			if (!fileInfo.Exists)
			{
				string text2 = "The file does not exist.";
				throw new ArgumentException(text2, "fileInfo");
			}
			if (!fileInfo.TryOpenRead(out var fileStream))
			{
				string text3 = "The file could not be opened.";
				throw new ArgumentException(text3, "fileInfo");
			}
			sendAsync(Opcode.Binary, fileStream, completed);
		}

		public void SendAsync(string data, Action<bool> completed)
		{
			if (_readyState != WebSocketState.Open)
			{
				string text = "The current state of the connection is not Open.";
				throw new InvalidOperationException(text);
			}
			if (data == null)
			{
				throw new ArgumentNullException("data");
			}
			if (!data.TryGetUTF8EncodedBytes(out var bytes))
			{
				string text2 = "It could not be UTF-8-encoded.";
				throw new ArgumentException(text2, "data");
			}
			sendAsync(Opcode.Text, new MemoryStream(bytes), completed);
		}

		public void SendAsync(Stream stream, int length, Action<bool> completed)
		{
			if (_readyState != WebSocketState.Open)
			{
				string text = "The current state of the connection is not Open.";
				throw new InvalidOperationException(text);
			}
			if (stream == null)
			{
				throw new ArgumentNullException("stream");
			}
			if (!stream.CanRead)
			{
				string text2 = "It cannot be read.";
				throw new ArgumentException(text2, "stream");
			}
			if (length < 1)
			{
				string text3 = "Less than 1.";
				throw new ArgumentException(text3, "length");
			}
			byte[] array = stream.ReadBytes(length);
			int num = array.Length;
			if (num == 0)
			{
				string text4 = "No data could be read from it.";
				throw new ArgumentException(text4, "stream");
			}
			if (num < length)
			{
				_logger.Warn($"Only {num} byte(s) of data could be read from the stream.");
			}
			sendAsync(Opcode.Binary, new MemoryStream(array), completed);
		}

		public void SetCookie(WebSocketSharp.Net.Cookie cookie)
		{
			string text = null;
			if (!_client)
			{
				text = "This instance is not a client.";
				throw new InvalidOperationException(text);
			}
			if (cookie == null)
			{
				throw new ArgumentNullException("cookie");
			}
			if (!canSet(out text))
			{
				_logger.Warn(text);
				return;
			}
			lock (_forState)
			{
				if (!canSet(out text))
				{
					_logger.Warn(text);
					return;
				}
				lock (_cookies.SyncRoot)
				{
					_cookies.SetOrRemove(cookie);
				}
			}
		}

		public void SetCredentials(string username, string password, bool preAuth)
		{
			string text = null;
			if (!_client)
			{
				text = "This instance is not a client.";
				throw new InvalidOperationException(text);
			}
			if (!username.IsNullOrEmpty() && (Ext.Contains(username, ':') || !username.IsText()))
			{
				text = "It contains an invalid character.";
				throw new ArgumentException(text, "username");
			}
			if (!password.IsNullOrEmpty() && !password.IsText())
			{
				text = "It contains an invalid character.";
				throw new ArgumentException(text, "password");
			}
			if (!canSet(out text))
			{
				_logger.Warn(text);
				return;
			}
			lock (_forState)
			{
				if (!canSet(out text))
				{
					_logger.Warn(text);
				}
				else if (username.IsNullOrEmpty())
				{
					_credentials = null;
					_preAuth = false;
				}
				else
				{
					_credentials = new WebSocketSharp.Net.NetworkCredential(username, password, _uri.PathAndQuery);
					_preAuth = preAuth;
				}
			}
		}

		public void SetProxy(string url, string username, string password)
		{
			string text = null;
			if (!_client)
			{
				text = "This instance is not a client.";
				throw new InvalidOperationException(text);
			}
			Uri result = null;
			if (!url.IsNullOrEmpty())
			{
				if (!Uri.TryCreate(url, UriKind.Absolute, out result))
				{
					text = "Not an absolute URI string.";
					throw new ArgumentException(text, "url");
				}
				if (result.Scheme != "http")
				{
					text = "The scheme part is not http.";
					throw new ArgumentException(text, "url");
				}
				if (result.Segments.Length > 1)
				{
					text = "It includes the path segments.";
					throw new ArgumentException(text, "url");
				}
			}
			if (!username.IsNullOrEmpty() && (Ext.Contains(username, ':') || !username.IsText()))
			{
				text = "It contains an invalid character.";
				throw new ArgumentException(text, "username");
			}
			if (!password.IsNullOrEmpty() && !password.IsText())
			{
				text = "It contains an invalid character.";
				throw new ArgumentException(text, "password");
			}
			if (!canSet(out text))
			{
				_logger.Warn(text);
				return;
			}
			lock (_forState)
			{
				if (!canSet(out text))
				{
					_logger.Warn(text);
		

WebMap.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
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.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Steamworks;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Playables;
using WebMap.Models;
using WebMap.Models.Unity;
using WebMap.Util;
using WebSocketSharp;
using WebSocketSharp.Net;
using WebSocketSharp.Server;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("WebMap.Tests")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: AssemblyCompany("WebMap")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("(c) 2025 Various Authors")]
[assembly: AssemblyFileVersion("2.16.1.0")]
[assembly: AssemblyInformationalVersion("2.16.1+c032c9a3c629970214dac82a2f400331b6b72716")]
[assembly: AssemblyProduct("WebMap")]
[assembly: AssemblyTitle("Valheim WebMap")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("2.16.1.0")]
[module: UnverifiableCode]
namespace WebMap
{
	internal static class Announce
	{
		private struct Pending
		{
			public string text;

			public bool feed;
		}

		private const string TokenFile = "announce.token";

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

		private static string tokenCache;

		private static DateTime tokenStamp;

		public static string Token
		{
			get
			{
				try
				{
					string path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", "announce.token");
					if (!File.Exists(path))
					{
						return null;
					}
					DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(path);
					if (tokenCache == null || lastWriteTimeUtc != tokenStamp)
					{
						tokenCache = File.ReadAllText(path).Trim();
						tokenStamp = lastWriteTimeUtc;
					}
					return string.IsNullOrEmpty(tokenCache) ? null : tokenCache;
				}
				catch
				{
					return null;
				}
			}
		}

		public static void Enqueue(string text, bool feed = true)
		{
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			lock (pending)
			{
				pending.Enqueue(new Pending
				{
					text = ((text.Length > 300) ? text.Substring(0, 300) : text),
					feed = feed
				});
			}
		}

		public static IEnumerator Pump()
		{
			while (true)
			{
				Pending pending = default(Pending);
				lock (Announce.pending)
				{
					if (Announce.pending.Count > 0)
					{
						pending = Announce.pending.Dequeue();
					}
				}
				if (pending.text != null)
				{
					Send(pending.text, pending.feed);
				}
				yield return (object)new WaitForSeconds(0.5f);
			}
		}

		private static void Send(string text, bool feed)
		{
			try
			{
				if (ZRoutedRpc.instance == null || (Object)(object)ZNet.instance == (Object)null)
				{
					return;
				}
				ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text });
				int num = 0;
				try
				{
					num = ZNet.instance.GetPeers()?.Count ?? (-1);
				}
				catch
				{
					num = -2;
				}
				ZLog.Log((object)$"WebMap: announced (peers seen: {num}): \"{text}\"");
				if (!feed)
				{
					return;
				}
				try
				{
					WebMap.mapDataServer?.AddMessage(0L, 2, WebMapConfig.ANNOUNCE_NAME, text);
				}
				catch
				{
				}
			}
			catch (Exception ex)
			{
				ZLog.LogWarning((object)("WebMap: announce failed: " + ex));
			}
		}
	}
	internal static class Auth
	{
		internal sealed class Session
		{
			public string Id;

			public string Name;

			public string Avatar;

			public string Perms;

			public bool Owner;

			public List<string> Roles = new List<string>();
		}

		internal const string Broker = "https://valheim-proxy.hunterjsb.workers.dev";

		internal const string BrokerKey = "vXDqIj4PivgOvGg/uNIisLfE2tMp1iF/TtLHn6Y2VwLmygV2LoO/G1P7hUsuRMjJKz+fWPZir8m255XlDdE1Xs1Lzj+a74fenDx6C3Db02Y2qgCHDik2vOKblbEsu1jkHc53PqERO7SO1vcKeimHugtCulJKWhKj1EfjACNVLNunu3pfaXC4PwZ+Y+w7Bww6g1XwCgS59echk38CxyiF+GqXEEHoMI+3yJQ08GVOMaHedRX3cZC4n3aR7M0e7K4rI/XMuHhxuRqSJS8AUj+jKGd2vAG2C/nSyRVgQ/+dGmkYS7iyoMU2A50fxGTYtwLh4akMLJ3IVS9eg6tT8v4WYw==.AQAB";

		private static readonly Regex TokenShape = new Regex("^([A-Za-z0-9_-]+)\\.([A-Za-z0-9_-]+)$");

		private static readonly Regex Quoted = new Regex("\"((?:[^\"\\\\]|\\\\.)*)\"");

		internal static RSAParameters? Key(string text)
		{
			string[] array = (text ?? "").Trim().Split(new char[1] { '.' });
			if (array.Length != 2)
			{
				return null;
			}
			byte[] array2 = B64(array[0]);
			byte[] array3 = B64(array[1]);
			if (array2 == null || array3 == null || array3.Length == 0)
			{
				return null;
			}
			int i;
			for (i = 0; i < array2.Length - 1 && array2[i] == 0; i++)
			{
			}
			if (array2.Length - i < 256)
			{
				return null;
			}
			byte[] array4 = new byte[array2.Length - i];
			Array.Copy(array2, i, array4, 0, array4.Length);
			return new RSAParameters
			{
				Modulus = array4,
				Exponent = array3
			};
		}

		internal static Session Verify(string token, string key, string guild, ICollection<string> origins, long now, long signedOutBefore = 0L)
		{
			Match match = TokenShape.Match(token ?? "");
			RSAParameters? rSAParameters = Key(key);
			if (!match.Success || !rSAParameters.HasValue || string.IsNullOrEmpty(guild))
			{
				return null;
			}
			string value = match.Groups[1].Value;
			string text;
			try
			{
				byte[] array = B64(match.Groups[2].Value);
				if (array == null)
				{
					return null;
				}
				using (RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider())
				{
					rSACryptoServiceProvider.ImportParameters(rSAParameters.Value);
					if (!rSACryptoServiceProvider.VerifyData(Encoding.ASCII.GetBytes(value), CryptoConfig.MapNameToOID("SHA256"), array))
					{
						return null;
					}
				}
				text = Encoding.UTF8.GetString(B64(value));
			}
			catch
			{
				return null;
			}
			if (!(Body.Num(text, "exp") > (double)now))
			{
				return null;
			}
			if (!(Body.Num(text, "iat") >= (double)signedOutBefore))
			{
				return null;
			}
			if (Body.Str(text, "guild") != guild)
			{
				return null;
			}
			string text2 = Origin(Body.Str(text, "aud"));
			if (text2 == null || !origins.Contains(text2))
			{
				return null;
			}
			Session session = new Session
			{
				Id = Body.Str(text, "id"),
				Name = (Body.Str(text, "name") ?? ""),
				Avatar = Body.Str(text, "avatar"),
				Perms = Body.Str(text, "perms"),
				Owner = Body.Bool(text, "owner")
			};
			if (string.IsNullOrEmpty(session.Id))
			{
				return null;
			}
			foreach (Match item in Quoted.Matches(Body.Arr(text, "roles") ?? ""))
			{
				session.Roles.Add(item.Groups[1].Value);
			}
			return session;
		}

		internal static bool IsAdmin(bool owner, string perms, IList<string> roles, string adminRole)
		{
			if (owner)
			{
				return true;
			}
			if (!string.IsNullOrEmpty(perms) && Regex.IsMatch(perms, "^\\d+$") && (int.Parse((perms.Length > 4) ? perms.Substring(perms.Length - 4) : perms, CultureInfo.InvariantCulture) & 8) != 0)
			{
				return true;
			}
			if (!string.IsNullOrEmpty(adminRole) && roles != null)
			{
				return roles.Contains(adminRole);
			}
			return false;
		}

		internal static string Origin(string url)
		{
			if (string.IsNullOrEmpty(url) || !Uri.TryCreate(url, UriKind.Absolute, out Uri result))
			{
				return null;
			}
			if ((result.Scheme != "http" && result.Scheme != "https") || result.UserInfo.Length > 0 || result.Host.Length == 0)
			{
				return null;
			}
			return result.Scheme + "://" + result.Host.ToLowerInvariant() + (result.IsDefaultPort ? "" : (":" + result.Port.ToString(CultureInfo.InvariantCulture)));
		}

		internal static List<string> Origins(string host, IPAddress peer, string publicUrl, string publicIp, int port)
		{
			List<string> list = new List<string>();
			Add(Origin(publicUrl));
			if (IPAddress.TryParse(publicIp ?? "", out IPAddress address))
			{
				Add(Origin("http://" + ((address.AddressFamily == AddressFamily.InterNetworkV6) ? ("[" + address?.ToString() + "]") : address.ToString()) + ":" + port.ToString(CultureInfo.InvariantCulture)));
			}
			string text = (string.IsNullOrEmpty(host) ? null : Origin("http://" + host));
			if (text != null && Private(peer) && Uri.TryCreate(text, UriKind.Absolute, out Uri result) && Local(result.Host))
			{
				Add(text);
			}
			return list;
			void Add(string o)
			{
				if (o != null && !list.Contains(o))
				{
					list.Add(o);
				}
			}
		}

		internal static bool Private(IPAddress a)
		{
			if (a == null)
			{
				return false;
			}
			if (a.IsIPv4MappedToIPv6)
			{
				a = a.MapToIPv4();
			}
			if (IPAddress.IsLoopback(a))
			{
				return true;
			}
			byte[] addressBytes = a.GetAddressBytes();
			if (a.AddressFamily == AddressFamily.InterNetwork)
			{
				if (addressBytes[0] != 10 && (addressBytes[0] != 172 || (addressBytes[1] & 0xF0) != 16))
				{
					if (addressBytes[0] == 192)
					{
						return addressBytes[1] == 168;
					}
					return false;
				}
				return true;
			}
			if (a.AddressFamily == AddressFamily.InterNetworkV6)
			{
				return (addressBytes[0] & 0xFE) == 252;
			}
			return false;
		}

		private static bool Local(string host)
		{
			if (!(host == "localhost"))
			{
				if (IPAddress.TryParse(host.Trim('[', ']'), out IPAddress address))
				{
					return Private(address);
				}
				return false;
			}
			return true;
		}

		internal static bool Same(string secret, string given)
		{
			if (string.IsNullOrEmpty(secret) || given == null)
			{
				return false;
			}
			int num = secret.Length ^ given.Length;
			for (int i = 0; i < secret.Length; i++)
			{
				num |= secret[i] ^ ((given.Length > 0) ? given[i % given.Length] : '\0');
			}
			return num == 0;
		}

		internal static string LoginUrl(string broker, string to, string guild, ICollection<string> origins)
		{
			string text = Origin(to);
			if (text == null || to.Length > 2000 || !origins.Contains(text) || string.IsNullOrEmpty(guild))
			{
				return null;
			}
			return (string.IsNullOrEmpty(broker) ? "https://valheim-proxy.hunterjsb.workers.dev" : broker).TrimEnd(new char[1] { '/' }) + "/auth/login?to=" + Uri.EscapeDataString(to) + "&guild=" + Uri.EscapeDataString(guild) + "&aud=" + Uri.EscapeDataString(text);
		}

		private static byte[] B64(string s)
		{
			s = s.Replace('-', '+').Replace('_', '/');
			s += new string('=', (4 - s.Length % 4) % 4);
			try
			{
				return Convert.FromBase64String(s);
			}
			catch (FormatException)
			{
				return null;
			}
		}
	}
	internal static class Body
	{
		public static string Str(string body, string key)
		{
			Match match = Regex.Match(body ?? "", "\"" + key + "\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"");
			if (!match.Success)
			{
				return null;
			}
			return Regex.Replace(match.Groups[1].Value, "\\\\(u[0-9a-fA-F]{4}|.)", delegate(Match mm)
			{
				string value = mm.Groups[1].Value;
				return (value[0] == 'u') ? ((char)Convert.ToInt32(value.Substring(1), 16)).ToString() : (value switch
				{
					"n" => "\n", 
					"t" => "\t", 
					"r" => "\r", 
					"b" => "\b", 
					"f" => "\f", 
					_ => value, 
				});
			});
		}

		public static double Num(string body, string key)
		{
			Match match = Regex.Match(body ?? "", "\"" + key + "\"\\s*:\\s*(-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)");
			if (!match.Success || !double.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return double.NaN;
			}
			return result;
		}

		public static bool Has(string body, string key)
		{
			return Regex.IsMatch(body ?? "", "\"" + key + "\"\\s*:");
		}

		public static bool Bool(string body, string key)
		{
			return Regex.IsMatch(body ?? "", "\"" + key + "\"\\s*:\\s*true");
		}

		public static List<string> Items(string json)
		{
			List<string> list = new List<string>();
			json = (json ?? "").Trim();
			if (json.Length < 2 || json[0] != '[')
			{
				return list;
			}
			int num = 0;
			int num2 = -1;
			bool flag = false;
			bool flag2 = false;
			for (int i = 1; i < json.Length - 1; i++)
			{
				char c = json[i];
				if (flag2)
				{
					flag2 = false;
				}
				else if (c == '\\' && flag)
				{
					flag2 = true;
				}
				else if (c == '"')
				{
					flag = !flag;
				}
				else
				{
					if (flag)
					{
						continue;
					}
					switch (c)
					{
					case '[':
					case '{':
						if (num == 0)
						{
							num2 = i;
						}
						num++;
						break;
					case ']':
					case '}':
						num--;
						if (num == 0 && num2 >= 0)
						{
							list.Add(json.Substring(num2, i - num2 + 1));
							num2 = -1;
						}
						break;
					}
				}
			}
			return list;
		}

		public static string Arr(string json, string key)
		{
			Match match = Regex.Match(json ?? "", "\"" + key + "\"\\s*:\\s*\\[");
			if (!match.Success)
			{
				return null;
			}
			int num = match.Index + match.Length - 1;
			int num2 = 0;
			bool flag = false;
			bool flag2 = false;
			for (int i = num; i < json.Length; i++)
			{
				char c = json[i];
				if (flag2)
				{
					flag2 = false;
				}
				else if (c == '\\' && flag)
				{
					flag2 = true;
				}
				else if (c == '"')
				{
					flag = !flag;
				}
				else
				{
					if (flag)
					{
						continue;
					}
					switch (c)
					{
					case '[':
						num2++;
						break;
					case ']':
						num2--;
						if (num2 == 0)
						{
							return json.Substring(num, i - num + 1);
						}
						break;
					}
				}
			}
			return null;
		}

		public static string Obj(string json, string key)
		{
			Match match = Regex.Match(json ?? "", "\"" + key + "\"\\s*:\\s*\\{");
			if (!match.Success)
			{
				return null;
			}
			int num = match.Index + match.Length - 1;
			int num2 = 0;
			bool flag = false;
			bool flag2 = false;
			for (int i = num; i < json.Length; i++)
			{
				char c = json[i];
				if (flag2)
				{
					flag2 = false;
				}
				else if (c == '\\' && flag)
				{
					flag2 = true;
				}
				else if (c == '"')
				{
					flag = !flag;
				}
				else
				{
					if (flag)
					{
						continue;
					}
					switch (c)
					{
					case '{':
						num2++;
						break;
					case '}':
						num2--;
						if (num2 == 0)
						{
							return json.Substring(num, i - num + 1);
						}
						break;
					}
				}
			}
			return null;
		}
	}
	internal static class Chart
	{
		private const string FileName = "chart.v2.png";

		private static volatile byte[] png;

		private static volatile bool building;

		public static volatile int Rev;

		private static readonly Color32 Water = new Color32((byte)43, (byte)79, (byte)122, byte.MaxValue);

		private const int Classes = 33;

		private static Color32 Albedo(Biome b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected I4, but got Unknown
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Invalid comparison between Unknown and I4
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Invalid comparison between Unknown and I4
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			if ((int)b <= 16)
			{
				switch (b - 1)
				{
				default:
					if ((int)b != 8)
					{
						if ((int)b != 16)
						{
							break;
						}
						return new Color32((byte)231, (byte)171, (byte)120, byte.MaxValue);
					}
					return new Color32((byte)107, (byte)116, (byte)63, byte.MaxValue);
				case 0:
					return new Color32((byte)146, (byte)167, (byte)92, byte.MaxValue);
				case 1:
					return new Color32((byte)163, (byte)114, (byte)88, byte.MaxValue);
				case 3:
					return new Color32((byte)236, (byte)238, (byte)240, byte.MaxValue);
				case 2:
					break;
				}
			}
			else
			{
				if ((int)b == 32)
				{
					return new Color32((byte)176, (byte)49, (byte)49, byte.MaxValue);
				}
				if ((int)b == 64)
				{
					return new Color32((byte)230, (byte)236, (byte)242, byte.MaxValue);
				}
				if ((int)b == 512)
				{
					return new Color32((byte)92, (byte)56, (byte)102, byte.MaxValue);
				}
			}
			return Water;
		}

		public static void Load(string worldDataPath)
		{
			png = null;
			string path = Path.Combine(worldDataPath, "chart.v2.png");
			try
			{
				if (File.Exists(path))
				{
					Rev = Fnv.Of(png = File.ReadAllBytes(path));
				}
			}
			catch
			{
			}
			try
			{
				string path2 = Path.Combine(worldDataPath, "chart.png");
				if (File.Exists(path2))
				{
					File.Delete(path2);
				}
			}
			catch
			{
			}
			if (png == null)
			{
				StaticCoroutine.Start(Build(worldDataPath));
			}
		}

		private static IEnumerator Build(string worldDataPath)
		{
			if (building)
			{
				yield break;
			}
			building = true;
			int size = WebMapConfig.TEXTURE_SIZE;
			int half = size / 2;
			float ps = WebMapConfig.PIXEL_SIZE;
			float halfPx = ps / 2f;
			byte[] cls = new byte[size * size];
			float water = ZoneSystem.instance.m_waterLevel;
			Color val = default(Color);
			for (int y = 0; y < size; y++)
			{
				yield return null;
				float num = (float)(y - half) * ps + halfPx;
				for (int i = 0; i < size; i++)
				{
					float num2 = (float)(i - half) * ps + halfPx;
					Biome biome = WorldGenerator.instance.GetBiome(num2, num, 0.02f, false);
					float biomeHeight = WorldGenerator.instance.GetBiomeHeight(biome, num2, num, ref val, false, true);
					cls[y * size + i] = (byte)((!(biomeHeight < water)) ? ((byte)(Index(biome) + 1)) : 0);
				}
			}
			ThreadPool.QueueUserWorkItem(delegate
			{
				//IL_0137: Unknown result type (might be due to invalid IL or missing references)
				//IL_012b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0130: Unknown result type (might be due to invalid IL or missing references)
				//IL_013c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0147: Unknown result type (might be due to invalid IL or missing references)
				//IL_0154: Unknown result type (might be due to invalid IL or missing references)
				//IL_0161: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					byte[] array = new byte[cls.Length];
					int[] array2 = new int[33];
					for (int j = 0; j < size; j++)
					{
						for (int k = 0; k < size; k++)
						{
							Array.Clear(array2, 0, array2.Length);
							for (int l = -1; l <= 1; l++)
							{
								int num3 = j + l;
								if (num3 >= 0 && num3 < size)
								{
									for (int m = -1; m <= 1; m++)
									{
										int num4 = k + m;
										if (num4 >= 0 && num4 < size)
										{
											array2[cls[num3 * size + num4]]++;
										}
									}
								}
							}
							int num5 = cls[j * size + k];
							for (int n = 0; n < 33; n++)
							{
								if (array2[n] > array2[num5])
								{
									num5 = n;
								}
							}
							array[j * size + k] = (byte)num5;
						}
					}
					byte[] array3 = new byte[size * size * 4];
					for (int num6 = 0; num6 < array.Length; num6++)
					{
						Color32 val2 = ((array[num6] == 0) ? Water : Albedo(BiomeAt(array[num6] - 1)));
						int num7 = num6 * 4;
						array3[num7] = val2.r;
						array3[num7 + 1] = val2.g;
						array3[num7 + 2] = val2.b;
						array3[num7 + 3] = byte.MaxValue;
					}
					byte[] array4 = (png = ImageConv.EncodeRgbaToPNG(array3, size, size));
					Rev = Fnv.Of(array4);
					File.WriteAllBytes(Path.Combine(worldDataPath, "chart.v2.png"), array4);
					ZLog.Log((object)$"WebMap: chart built, {array4.Length} bytes");
				}
				catch (Exception ex)
				{
					ZLog.LogWarning((object)("WebMap: chart not written: " + ex.Message));
				}
				finally
				{
					building = false;
				}
			});
		}

		private static int Index(Biome b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Expected I4, but got Unknown
			int num = (int)b;
			int num2 = 0;
			while (num > 1 && num2 < 31)
			{
				num >>= 1;
				num2++;
			}
			return num2;
		}

		private static Biome BiomeAt(int index)
		{
			return (Biome)(1 << index);
		}

		public static byte[] GetPng()
		{
			return png ?? new byte[0];
		}
	}
	internal static class ChatRelay
	{
		private const int PollMs = 3000;

		internal const int Cap = 5;

		private static Timer timer;

		private static int polling;

		private static string afterId;

		private static string seededChannel;

		private static int warned;

		public static void Start()
		{
			if (timer != null)
			{
				return;
			}
			timer = new Timer(delegate
			{
				if (Interlocked.CompareExchange(ref polling, 1, 0) != 0)
				{
					return;
				}
				try
				{
					Poll();
				}
				catch (Exception ex)
				{
					WarnOnce(ex);
				}
				finally
				{
					polling = 0;
				}
			}, null, 3000, 3000);
		}

		private static void Poll()
		{
			string dISCORD_CHAT_CHANNEL = WebMapConfig.DISCORD_CHAT_CHANNEL;
			if (string.IsNullOrEmpty(WebMapConfig.DISCORD_BOT_TOKEN) || string.IsNullOrEmpty(dISCORD_CHAT_CHANNEL) || !WebMapConfig.CHAT_RELAY)
			{
				seededChannel = null;
				return;
			}
			if (seededChannel != dISCORD_CHAT_CHANNEL || afterId == null)
			{
				afterId = Discord.LatestMessageId(dISCORD_CHAT_CHANNEL);
				seededChannel = dISCORD_CHAT_CHANNEL;
				return;
			}
			List<DiscordMessage> list = Discord.MessagesAfter(dISCORD_CHAT_CHANNEL, afterId, 50);
			if (list.Count == 0)
			{
				return;
			}
			afterId = Newest(list, afterId);
			foreach (string item in LinesToSpeak(list))
			{
				Announce.Enqueue(item);
			}
		}

		internal static string Newest(List<DiscordMessage> msgs, string current)
		{
			ulong result = 0uL;
			string result2 = current;
			ulong.TryParse(current ?? "", out result);
			foreach (DiscordMessage msg in msgs)
			{
				if (ulong.TryParse(msg.Id ?? "", out var result3) && result3 > result)
				{
					result = result3;
					result2 = msg.Id;
				}
			}
			return result2;
		}

		internal static List<string> LinesToSpeak(List<DiscordMessage> newestFirst)
		{
			List<string> list = new List<string>();
			int num = newestFirst.Count - 1;
			while (num >= 0 && list.Count < 5)
			{
				DiscordMessage discordMessage = newestFirst[num];
				if (!discordMessage.Bot && !discordMessage.Webhook && !string.IsNullOrEmpty(discordMessage.Content))
				{
					list.Add("[Discord] " + discordMessage.DisplayName + ": " + discordMessage.Content);
				}
				num--;
			}
			return list;
		}

		private static void WarnOnce(Exception ex)
		{
			if (Interlocked.CompareExchange(ref warned, 1, 0) == 0)
			{
				ZLog.LogWarning((object)("WebMap: discord chat relay failed, will keep trying quietly: " + ex.Message));
			}
		}
	}
	internal static class WebMapConfig
	{
		public static int TEXTURE_SIZE = 2048;

		public static int PIXEL_SIZE = 12;

		public static int RENDER_SIZE = 2048;

		public static float EXPLORE_RADIUS = 100f;

		public static float UPDATE_FOG_TEXTURE_INTERVAL = 2f;

		public static float SAVE_FOG_TEXTURE_INTERVAL = 30f;

		public static int MAX_PINS_PER_USER = 50;

		public static int MAX_MESSAGES = 100;

		public static bool ALWAYS_MAP = true;

		public static bool ALWAYS_VISIBLE = false;

		public static bool DEBUG = false;

		public static int SERVER_PORT = 3000;

		public static float PLAYER_UPDATE_INTERVAL = 1f;

		public static bool CACHE_SERVER_FILES = true;

		public static string WORLD_NAME = "";

		public static Vector3 WORLD_START_POS = Vector3.zero;

		public static int DEFAULT_ZOOM = 100;

		public static bool SHOW_VEHICLES = true;

		public static string ANNOUNCE_NAME = "Server";

		public static string DISCORD_WEBHOOK = "";

		public static string DISCORD_BOT_TOKEN = "";

		public static string DISCORD_GUILD = "";

		public static string DISCORD_LOG_CHANNEL = "";

		public static string DISCORD_CHAT_CHANNEL = "";

		public static bool CHAT_RELAY = true;

		public static string DISCORD_ADMIN_ROLE = "";

		public static string PUBLIC_URL = "";

		public static string AUTH_URL = "";

		public static string AUTH_PUBLIC_KEY = "";

		public static long SIGN_OUT_BEFORE = 0L;

		public static string URL = "";

		public static bool EXPORT_MODELS = true;

		public static bool EXTRACT_TEXTURES = true;

		public static bool EXTRACT_MESHES = true;

		public static int EXPORT_MS_PER_FRAME = 6;

		public static int TEXTURE_MAX_SIZE = 512;

		public static string OBJECT_CATEGORIES = "piece,other,rock,bush,tree";

		public static void ReadConfigFile(ConfigFile config)
		{
			TEXTURE_SIZE = config.Bind<int>("Texture", "texture_size", TEXTURE_SIZE, "How large is the map texture? Probably dont change this.").Value;
			PIXEL_SIZE = config.Bind<int>("Texture", "pixel_size", PIXEL_SIZE, "How many in game units does a map pixel represent? Probably dont change this.").Value;
			RENDER_SIZE = config.Bind<int>("Texture", "render_size", RENDER_SIZE, "Pixels across the world render (the terrain picture). Covers the same area as texture_size, just sharper: 4096 halves the metres per pixel. Costs a one-time render and a bigger download; the overlays stay at texture_size, where extra resolution buys nothing.").Value;
			if (RENDER_SIZE < TEXTURE_SIZE)
			{
				RENDER_SIZE = TEXTURE_SIZE;
			}
			EXPLORE_RADIUS = config.Bind<float>("Texture", "explore_radius", EXPLORE_RADIUS, "A larger explore_radius reveals the map more quickly.").Value;
			UPDATE_FOG_TEXTURE_INTERVAL = config.Bind<float>("Interval", "update_fog_texture_interval", UPDATE_FOG_TEXTURE_INTERVAL, "How often do we update the fog texture on the server in seconds.").Value;
			SAVE_FOG_TEXTURE_INTERVAL = config.Bind<float>("Interval", "save_fog_texture_interval", SAVE_FOG_TEXTURE_INTERVAL, "How often do we save the fog texture in seconds.").Value;
			MAX_PINS_PER_USER = config.Bind<int>("User", "max_pins_per_user", MAX_PINS_PER_USER, "How many pins each client is allowed to make before old ones start being deleted.").Value;
			SERVER_PORT = config.Bind<int>("Server", "server_port", SERVER_PORT, "HTTP port for the website. The map will be display on this site.").Value;
			PLAYER_UPDATE_INTERVAL = config.Bind<float>("Interval", "player_update_interval", PLAYER_UPDATE_INTERVAL, "How often do we send position data to web browsers in seconds.").Value;
			CACHE_SERVER_FILES = config.Bind<bool>("Server", "cache_server_files", CACHE_SERVER_FILES, "Should the server cache web files to be more performant?").Value;
			DEFAULT_ZOOM = config.Bind<int>("Texture", "default_zoom", DEFAULT_ZOOM, "Kept for front-ends of your own; the bundled viewer ignores it.").Value;
			MAX_MESSAGES = config.Bind<int>("Server", "max_messages", MAX_MESSAGES, "How many messages to keep buffered and display to client.").Value;
			ALWAYS_MAP = config.Bind<bool>("User", "always_map", ALWAYS_MAP, "Update the map to show where hidden players have traveled.").Value;
			ALWAYS_VISIBLE = config.Bind<bool>("User", "always_visible", ALWAYS_VISIBLE, "Completely ignore the players preference to be hidden.").Value;
			DEBUG = config.Bind<bool>("Server", "debug", DEBUG, "Extra log lines: fog saves, routed RPC hashes, chat and pin diagnostics.").Value;
			SHOW_VEHICLES = config.Bind<bool>("Server", "show_vehicles", SHOW_VEHICLES, "Report boats and carts at /vehicles. They are only ever reported in territory players have already explored, but turning this off stops the endpoint reporting anything at all.").Value;
			DISCORD_WEBHOOK = config.Bind<string>("Server", "discord_webhook", DISCORD_WEBHOOK, "Discord webhook that gets the server coming on and off line, joins and leaves.").Value;
			DISCORD_BOT_TOKEN = config.Bind<string>("Server", "discord_bot_token", DISCORD_BOT_TOKEN, "Discord bot token (Bot ...). Empty turns off the audit log, the chat relay and the settings picker; the webhook above keeps working either way.").Value;
			DISCORD_GUILD = config.Bind<string>("Server", "discord_guild", DISCORD_GUILD, "Your Discord server's id (Developer Mode on, right-click the server, Copy Server ID). Its members can sign in to the map, and its owner and administrators are the map's admins; the settings picker lists its channels. Empty: no sign-in.").Value;
			DISCORD_ADMIN_ROLE = config.Bind<string>("Server", "discord_admin_role", DISCORD_ADMIN_ROLE, "A role id whose members are the map's admins too, beside the server's owner and administrators.").Value;
			PUBLIC_URL = config.Bind<string>("Server", "public_url", PUBLIC_URL, "The map's address when visitors reach it through a proxy or a name (https://map.example.com). Empty: whatever address it is visited at.").Value;
			AUTH_URL = config.Bind<string>("Server", "auth_url", AUTH_URL, "The sign-in service. Empty: the project's own, https://valheim-proxy.hunterjsb.workers.dev. Only for running your own.").Value;
			AUTH_PUBLIC_KEY = config.Bind<string>("Server", "auth_public_key", AUTH_PUBLIC_KEY, "The public key that service signs sessions with, as <modulus base64>.<exponent base64>. Empty: the project's own.").Value;
			if (AUTH_PUBLIC_KEY.Length > 0 && !Auth.Key(AUTH_PUBLIC_KEY).HasValue)
			{
				ZLog.LogWarning((object)"WebMap: auth_public_key is not an RSA key of 2048 bits or more; nobody can sign in");
			}
			SIGN_OUT_BEFORE = config.Bind<long>("Server", "sign_out_before", SIGN_OUT_BEFORE, "Unix seconds: sessions signed in before this are refused, so everyone signs in again. The settings page's Sign everyone out sets it to now.").Value;
			DISCORD_LOG_CHANNEL = config.Bind<string>("Server", "discord_log_channel", DISCORD_LOG_CHANNEL, "Channel that gets what people do on the site: names, pins, settings changes.").Value;
			DISCORD_CHAT_CHANNEL = config.Bind<string>("Server", "discord_chat_channel", DISCORD_CHAT_CHANNEL, "Channel relayed both ways with in-game chat.").Value;
			CHAT_RELAY = config.Bind<bool>("Server", "chat_relay", CHAT_RELAY, "Relay chat between the game and discord_chat_channel.").Value;
			URL = config.Bind<string>("Server", "webmap_url", URL, "URL to view the web map.").Value;
			EXPORT_MODELS = config.Bind<bool>("Models", "export_models", EXPORT_MODELS, "Export each prefab the world holds as a glTF model for the 3D view, a few a frame, into map_data/models. Once per prefab; kept across restarts.").Value;
			EXTRACT_TEXTURES = config.Bind<bool>("Models", "extract_textures", EXTRACT_TEXTURES, "Read the models' textures out of the game's own asset files on a background thread, once per game version (the dedicated server cannot read them any other way). Off: flat colours.").Value;
			EXTRACT_MESHES = config.Bind<bool>("Models", "extract_meshes", EXTRACT_MESHES, "Read the meshes the engine keeps locked (most of them) out of the game's asset files the same way. Off: those models are left out or drawn as boxes.").Value;
			EXPORT_MS_PER_FRAME = config.Bind<int>("Models", "export_ms_per_frame", EXPORT_MS_PER_FRAME, "Game-thread milliseconds a frame the model export may take.").Value;
			TEXTURE_MAX_SIZE = config.Bind<int>("Models", "texture_max_size", TEXTURE_MAX_SIZE, "Longest edge of an extracted texture, in pixels.").Value;
			OBJECT_CATEGORIES = config.Bind<string>("Models", "object_categories", OBJECT_CATEGORIES, "What the 3D view is sent, of piece, other, rock, bush, tree (comma separated).").Value;
		}

		public static string GetWorldName()
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				WORLD_NAME = ZNet.instance.GetWorldName();
			}
			else
			{
				string[] commandLineArgs = Environment.GetCommandLineArgs();
				string wORLD_NAME = "";
				for (int i = 0; i < commandLineArgs.Length; i++)
				{
					if (commandLineArgs[i] == "-world")
					{
						wORLD_NAME = commandLineArgs[i + 1];
						break;
					}
				}
				WORLD_NAME = wORLD_NAME;
			}
			return WORLD_NAME;
		}

		public static string MakeClientConfigJson()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			return DictionaryToJson(new Dictionary<string, object>
			{
				["world_name"] = GetWorldName(),
				["world_start_pos"] = WORLD_START_POS,
				["default_zoom"] = DEFAULT_ZOOM,
				["texture_size"] = TEXTURE_SIZE,
				["pixel_size"] = PIXEL_SIZE,
				["update_interval"] = PLAYER_UPDATE_INTERVAL,
				["explore_radius"] = EXPLORE_RADIUS,
				["max_messages"] = MAX_MESSAGES,
				["always_map"] = ALWAYS_MAP,
				["always_visible"] = ALWAYS_VISIBLE
			});
		}

		private static string DictionaryToJson(Dictionary<string, object> dict)
		{
			IEnumerable<string> values = dict.Select(delegate(KeyValuePair<string, object> d)
			{
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				object value = d.Value;
				if (value is float num)
				{
					return "\"" + d.Key + "\": " + num.ToString("F2", CultureInfo.InvariantCulture);
				}
				if (value is double num2)
				{
					return "\"" + d.Key + "\": " + num2.ToString("F2", CultureInfo.InvariantCulture);
				}
				if (value is string text)
				{
					return "\"" + d.Key + "\": \"" + text + "\"";
				}
				if (value is bool flag)
				{
					return "\"" + d.Key + "\": " + flag.ToString().ToLower();
				}
				return (value is Vector3 val) ? ("\"" + d.Key + "\": \"" + val.x.ToString("F2", CultureInfo.InvariantCulture) + "," + val.y.ToString("F2", CultureInfo.InvariantCulture) + "," + val.z.ToString("F2", CultureInfo.InvariantCulture) + "\"") : $"\"{d.Key}\": {d.Value}";
			});
			return "{\n    " + string.Join(",\n    ", values) + "\n}\n";
		}
	}
	[HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")]
	internal class DeathWatch
	{
		private const double DUPLICATE_WINDOW = 5.0;

		private static int deathRpcHash;

		private static bool deathRpcHashReady;

		private static readonly Dictionary<long, DateTime> lastDeath = new Dictionary<long, DateTime>();

		private static void Postfix(RoutedRPCData rpcData)
		{
			try
			{
				if (rpcData != null)
				{
					if (!deathRpcHashReady)
					{
						deathRpcHash = StringExtensionMethods.GetStableHashCode("OnDeath");
						deathRpcHashReady = true;
					}
					if (rpcData.m_methodHash == deathRpcHash)
					{
						Announce(rpcData);
					}
				}
			}
			catch (Exception ex)
			{
				ZLog.LogWarning((object)("WebMap: death watch failed: " + ex.Message));
			}
		}

		private static void Announce(RoutedRPCData rpcData)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			MapDataServer instance = MapDataServer.getInstance();
			if (instance == null)
			{
				return;
			}
			ZDOID targetZDO = rpcData.m_targetZDO;
			long num = rpcData.m_senderPeerID;
			string text = null;
			List<ZNetPeer> players = instance.players;
			if (players != null)
			{
				foreach (ZNetPeer item in players)
				{
					if (item != null && !item.m_server)
					{
						if (!((ZDOID)(ref targetZDO)).IsNone() && item.m_characterID == targetZDO)
						{
							text = item.m_playerName;
							num = item.m_uid;
							break;
						}
						if (text == null && item.m_uid == rpcData.m_senderPeerID)
						{
							text = item.m_playerName;
						}
					}
				}
			}
			if (string.IsNullOrEmpty(text) && !((ZDOID)(ref targetZDO)).IsNone())
			{
				ZDO val = null;
				try
				{
					val = ZDOMan.instance.GetZDO(targetZDO);
				}
				catch
				{
				}
				if (val != null)
				{
					text = val.GetString(ZDOVars.s_playerName, "");
				}
			}
			if (!string.IsNullOrEmpty(text) && ShouldAnnounce(num))
			{
				ZLog.Log((object)("WebMap: player " + text + " died"));
				instance.AddMessage(num, 1, "Server", "player _" + text + "_ died", ev: true);
				Stats.Death(text);
			}
		}

		private static bool ShouldAnnounce(long key)
		{
			DateTime utcNow = DateTime.UtcNow;
			if (lastDeath.TryGetValue(key, out var value) && (utcNow - value).TotalSeconds < 5.0)
			{
				return false;
			}
			if (lastDeath.Count > 64)
			{
				List<long> list = new List<long>();
				foreach (KeyValuePair<long, DateTime> item in lastDeath)
				{
					if ((utcNow - item.Value).TotalSeconds >= 5.0)
					{
						list.Add(item.Key);
					}
				}
				list.ForEach(delegate(long k)
				{
					lastDeath.Remove(k);
				});
			}
			lastDeath[key] = utcNow;
			return true;
		}
	}
	internal static class Deeds
	{
		public enum Kind
		{
			Kill,
			Tree,
			Rock
		}

		private struct Last
		{
			public string who;

			public float t;
		}

		private const float WindowS = 10f;

		private static readonly Dictionary<ZDOID, Last> last = new Dictionary<ZDOID, Last>();

		private static float pruned;

		public static void Hit(ZDOID target, string player, float now)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(player))
			{
				return;
			}
			last[target] = new Last
			{
				who = player,
				t = now
			};
			if (now - pruned < 10f)
			{
				return;
			}
			pruned = now;
			List<ZDOID> list = new List<ZDOID>();
			foreach (KeyValuePair<ZDOID, Last> item in last)
			{
				if (now - item.Value.t > 10f)
				{
					list.Add(item.Key);
				}
			}
			foreach (ZDOID item2 in list)
			{
				last.Remove(item2);
			}
		}

		public static void AreaBroken(ZDOID target, float now, string owner = null)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			Credit(Who(target, now, owner), Kind.Rock);
		}

		public static void Gone(ZDOID target, Kind kind, float now, string owner = null)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			Credit(Who(target, now, owner), kind);
			last.Remove(target);
		}

		private static string Who(ZDOID target, float now, string owner)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			if (!last.TryGetValue(target, out var value) || !(now - value.t <= 10f))
			{
				return owner;
			}
			return value.who;
		}

		private static void Credit(string who, Kind kind)
		{
			if (!string.IsNullOrEmpty(who))
			{
				Stats.Deed(who, kind);
			}
		}

		internal static void ResetForTests()
		{
			last.Clear();
			pruned = 0f;
		}

		internal static string OwnerBeside(long owner, Vector3 at, float m)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetPeer(owner) : null);
			if (val == null || string.IsNullOrEmpty(val.m_playerName))
			{
				return null;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(val.m_characterID);
			if (zDO == null)
			{
				return null;
			}
			Vector3 position = zDO.GetPosition();
			float num = position.x - at.x;
			float num2 = position.z - at.z;
			if (!(num * num + num2 * num2 <= m * m))
			{
				return null;
			}
			return val.m_playerName;
		}
	}
	[HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")]
	internal class DeedsRoutePatch
	{
		private static readonly int DamageHash = StringExtensionMethods.GetStableHashCode("RPC_Damage");

		private static readonly int HitHash = StringExtensionMethods.GetStableHashCode("Hit");

		private static readonly int AreaHash = StringExtensionMethods.GetStableHashCode("RPC_SetAreaHealth");

		private static readonly int HideHash = StringExtensionMethods.GetStableHashCode("Hide");

		private static readonly int AddItemHash = StringExtensionMethods.GetStableHashCode("RPC_AddItem");

		private static readonly int SlotHash = StringExtensionMethods.GetStableHashCode("RPC_SetSlotVisual");

		private const float AreaM = 32f;

		private static bool warned;

		private static void Prefix(RoutedRPCData rpcData)
		{
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Expected O, but got Unknown
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			if (rpcData == null)
			{
				return;
			}
			int methodHash = rpcData.m_methodHash;
			if (methodHash != DamageHash && methodHash != HitHash && methodHash != AreaHash && methodHash != HideHash && methodHash != AddItemHash && methodHash != SlotHash)
			{
				return;
			}
			ZPackage parameters = rpcData.m_parameters;
			if (parameters == null)
			{
				return;
			}
			int pos = parameters.GetPos();
			try
			{
				parameters.SetPos(0);
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (methodHash == AddItemHash || methodHash == SlotHash)
				{
					string text = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetPeer(rpcData.m_senderPeerID) : null)?.m_playerName;
					if (methodHash == AddItemHash)
					{
						Kitchen.Asked(rpcData.m_targetZDO, text, realtimeSinceStartup);
						return;
					}
					int slot = parameters.ReadInt();
					Kitchen.Shown(rpcData.m_targetZDO, text, slot, parameters.ReadString(), realtimeSinceStartup);
				}
				else if (methodHash == DamageHash || methodHash == HitHash)
				{
					HitData val = new HitData();
					val.Deserialize(ref parameters);
					if (!((ZDOID)(ref val.m_attacker)).IsNone())
					{
						ZDO zDO = ZDOMan.instance.GetZDO(val.m_attacker);
						string text2 = ((zDO != null) ? zDO.GetString(ZDOVars.s_playerName, "") : "");
						Deeds.Hit(rpcData.m_targetZDO, text2, realtimeSinceStartup);
						if (methodHash == DamageHash && text2.Length > 0)
						{
							Gear.Struck(text2, rpcData.m_targetZDO, val);
						}
					}
				}
				else
				{
					parameters.ReadInt();
					if (methodHash != AreaHash || !(parameters.ReadSingle() > 0f))
					{
						ZDO zDO2 = ZDOMan.instance.GetZDO(rpcData.m_targetZDO);
						string owner = ((zDO2 != null) ? Deeds.OwnerBeside(rpcData.m_senderPeerID, zDO2.GetPosition(), 32f) : null);
						Deeds.AreaBroken(rpcData.m_targetZDO, realtimeSinceStartup, owner);
					}
				}
			}
			catch (Exception ex)
			{
				if (!warned)
				{
					warned = true;
					ZLog.LogWarning((object)("WebMap: deeds: reading a forwarded RPC failed: " + ex.Message));
				}
			}
			finally
			{
				parameters.SetPos(pos);
			}
		}
	}
	[HarmonyPatch(typeof(ZDOMan), "HandleDestroyedZDO")]
	internal class DeedsGonePatch
	{
		private const float BesideM = 16f;

		private static readonly Dictionary<int, int> kinds = new Dictionary<int, int>();

		private static bool warned;

		private static void Prefix(ZDOID uid)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				ZDO zDO = ZDOMan.instance.GetZDO(uid);
				if (zDO == null)
				{
					return;
				}
				int num = KindOf(zDO.GetPrefab());
				if (num < 0)
				{
					return;
				}
				Deeds.Kind kind = (Deeds.Kind)num;
				string text;
				if (kind == Deeds.Kind.Kill)
				{
					if (zDO.GetBool(ZDOVars.s_tamed, false))
					{
						return;
					}
					text = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetPeer(zDO.GetOwner()) : null)?.m_playerName;
					if (!string.IsNullOrEmpty(text))
					{
						int s_attackers = ZDOVars.s_attackers;
						if (!zDO.GetBool(s_attackers + text, false))
						{
							text = null;
						}
					}
				}
				else
				{
					text = Deeds.OwnerBeside(zDO.GetOwner(), zDO.GetPosition(), 16f);
				}
				Deeds.Gone(uid, kind, Time.realtimeSinceStartup, text);
			}
			catch (Exception ex)
			{
				if (!warned)
				{
					warned = true;
					ZLog.LogWarning((object)("WebMap: deeds: reading a destroy failed: " + ex.Message));
				}
			}
		}

		internal static int KindOf(int prefab)
		{
			if (kinds.TryGetValue(prefab, out var value))
			{
				return value;
			}
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return -1;
			}
			value = -1;
			GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab);
			if ((Object)(object)prefab2 != (Object)null)
			{
				if ((Object)(object)prefab2.GetComponent<Character>() != (Object)null)
				{
					if ((Object)(object)prefab2.GetComponent<Player>() == (Object)null)
					{
						value = 0;
					}
				}
				else if ((Object)(object)prefab2.GetComponent<TreeBase>() != (Object)null)
				{
					value = 1;
				}
				else if ((Object)(object)prefab2.GetComponent<Destructible>() != (Object)null && WorldObjects.ClassifyName(((Object)prefab2).name.ToLowerInvariant()) == WorldObjects.Cat.Rock)
				{
					value = 2;
				}
			}
			kinds[prefab] = value;
			return value;
		}
	}
	internal struct DiscordMessage
	{
		public string Id;

		public string Content;

		public string DisplayName;

		public bool Bot;

		public bool Webhook;
	}
	internal struct DiscordRef
	{
		public string Id;

		public string Name;
	}
	internal static class Discord
	{
		internal struct Result
		{
			public int Status;

			public string Body;

			public double RetryAfter;
		}

		private const string Api = "https://discord.com/api/v10";

		private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

		internal static Func<string, string, string, Result> Transport = DefaultTransport;

		private static int warnedPost;

		private static int warnedGuilds;

		private static int warnedChannels;

		private static int warnedMessages;

		private static Dictionary<string, string> roleNames;

		private static DateTime rolesAt;

		private static string Token => WebMapConfig.DISCORD_BOT_TOKEN;

		public static void Tell(string line)
		{
			Post(WebMapConfig.DISCORD_LOG_CHANNEL, line);
		}

		public static void PostChat(string name, string text)
		{
			if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(name) && !(name == WebMapConfig.ANNOUNCE_NAME) && WebMapConfig.CHAT_RELAY)
			{
				Post(WebMapConfig.DISCORD_CHAT_CHANNEL, "**" + name + "**: " + text);
			}
		}

		public static void Post(string channelId, string content)
		{
			if (string.IsNullOrEmpty(Token) || string.IsNullOrEmpty(channelId) || string.IsNullOrEmpty(content))
			{
				return;
			}
			ThreadPool.QueueUserWorkItem(delegate
			{
				try
				{
					string body = "{\"content\":\"" + JsonEsc(Clip(content, 1900)) + "\",\"allowed_mentions\":{\"parse\":[]}}";
					Call("POST", "/channels/" + channelId + "/messages", body);
				}
				catch (Exception ex)
				{
					WarnOnce(ref warnedPost, "post a message", ex);
				}
			});
		}

		public static List<DiscordRef> Guilds()
		{
			List<DiscordRef> list = new List<DiscordRef>();
			if (string.IsNullOrEmpty(Token))
			{
				return list;
			}
			try
			{
				foreach (string item in Body.Items(Call("GET", "/users/@me/guilds", null).Body))
				{
					list.Add(new DiscordRef
					{
						Id = Body.Str(item, "id"),
						Name = Body.Str(item, "name")
					});
				}
			}
			catch (Exception ex)
			{
				WarnOnce(ref warnedGuilds, "list guilds", ex);
			}
			return list;
		}

		public static List<DiscordRef> Channels(string guildId)
		{
			List<DiscordRef> list = new List<DiscordRef>();
			if (string.IsNullOrEmpty(Token) || string.IsNullOrEmpty(guildId))
			{
				return list;
			}
			try
			{
				foreach (string item in Body.Items(Call("GET", "/guilds/" + guildId + "/channels", null).Body))
				{
					double num = Body.Num(item, "type");
					if (num == 0.0 || num == 5.0)
					{
						list.Add(new DiscordRef
						{
							Id = Body.Str(item, "id"),
							Name = Body.Str(item, "name")
						});
					}
				}
			}
			catch (Exception ex)
			{
				WarnOnce(ref warnedChannels, "list channels", ex);
			}
			return list;
		}

		public static List<DiscordMessage> MessagesAfter(string channelId, string afterId, int limit)
		{
			List<DiscordMessage> list = new List<DiscordMessage>();
			if (string.IsNullOrEmpty(Token) || string.IsNullOrEmpty(channelId))
			{
				return list;
			}
			try
			{
				string path = "/channels/" + channelId + "/messages?limit=" + limit.ToString(Inv) + (string.IsNullOrEmpty(afterId) ? "" : ("&after=" + afterId));
				foreach (string item in Body.Items(Call("GET", path, null).Body))
				{
					list.Add(Parse(item));
				}
			}
			catch (Exception ex)
			{
				WarnOnce(ref warnedMessages, "read messages", ex);
			}
			return list;
		}

		public static string LatestMessageId(string channelId)
		{
			if (string.IsNullOrEmpty(Token) || string.IsNullOrEmpty(channelId))
			{
				return null;
			}
			try
			{
				List<string> list = Body.Items(Call("GET", "/channels/" + channelId + "/messages?limit=1", null).Body);
				return (list.Count > 0) ? Body.Str(list[0], "id") : null;
			}
			catch (Exception ex)
			{
				WarnOnce(ref warnedMessages, "read messages", ex);
				return null;
			}
		}

		internal static string Plain(string content, string item)
		{
			if (string.IsNullOrEmpty(content))
			{
				return content;
			}
			Dictionary<string, string> names = new Dictionary<string, string>();
			foreach (string item2 in Body.Items(Body.Arr(item ?? "", "mentions") ?? "[]"))
			{
				string text = Body.Str(item2, "id");
				if (text != null)
				{
					names[text] = Body.Str(item2, "global_name") ?? Body.Str(item2, "username") ?? "someone";
				}
			}
			content = Regex.Replace(content, "<@!?(\\d+)>", (Match m) => "@" + (names.TryGetValue(m.Groups[1].Value, out var value) ? value : "someone"));
			content = Regex.Replace(content, "<@&(\\d+)>", (Match m) => "@" + RoleName(m.Groups[1].Value));
			content = Regex.Replace(content, "<#(\\d+)>", "#channel");
			content = Regex.Replace(content, "<a?:(\\w+):\\d+>", ":$1:");
			return content;
		}

		private static string RoleName(string id)
		{
			try
			{
				if (roleNames == null || (DateTime.UtcNow - rolesAt).TotalMinutes > 10.0)
				{
					Dictionary<string, string> dictionary = new Dictionary<string, string>();
					string dISCORD_GUILD = WebMapConfig.DISCORD_GUILD;
					if (!string.IsNullOrEmpty(dISCORD_GUILD))
					{
						foreach (string item in Body.Items(Call("GET", "/guilds/" + dISCORD_GUILD + "/roles", null).Body))
						{
							string text = Body.Str(item, "id");
							string text2 = Body.Str(item, "name");
							if (text != null && text2 != null)
							{
								dictionary[text] = text2;
							}
						}
					}
					roleNames = dictionary;
					rolesAt = DateTime.UtcNow;
				}
				string value;
				return roleNames.TryGetValue(id, out value) ? value : "role";
			}
			catch
			{
				return "role";
			}
		}

		private static DiscordMessage Parse(string item)
		{
			string body = Body.Obj(item, "author") ?? "{}";
			string text = Body.Obj(item, "member");
			string text2 = ((text != null) ? Body.Str(text, "nick") : null);
			string text3 = Body.Str(body, "global_name");
			string text4 = Body.Str(body, "username") ?? "someone";
			return new DiscordMessage
			{
				Id = Body.Str(item, "id"),
				Content = Plain(Body.Str(item, "content") ?? "", item),
				Bot = Body.Bool(body, "bot"),
				Webhook = Body.Has(item, "webhook_id"),
				DisplayName = ((!string.IsNullOrEmpty(text2)) ? text2 : ((!string.IsNullOrEmpty(text3)) ? text3 : text4))
			};
		}

		internal static Result Call(string method, string path, string body)
		{
			Result result = Transport(method, path, body);
			if (result.Status == 429)
			{
				Thread.Sleep((int)Math.Ceiling(result.RetryAfter * 1000.0) + 50);
				result = Transport(method, path, body);
			}
			if (result.Status >= 300)
			{
				throw new Exception($"discord {method} {path}: HTTP {result.Status}");
			}
			return result;
		}

		private static Result DefaultTransport(string method, string path, string body)
		{
			HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("https://discord.com/api/v10" + path);
			httpWebRequest.Method = method;
			httpWebRequest.Headers["Authorization"] = "Bot " + Token;
			httpWebRequest.UserAgent = "WebMap (https://github.com/hunter-jsb/valheim-webmap, 1.0)";
			httpWebRequest.Timeout = 10000;
			if (body != null)
			{
				httpWebRequest.ContentType = "application/json";
				byte[] bytes = Encoding.UTF8.GetBytes(body);
				using Stream stream = httpWebRequest.GetRequestStream();
				stream.Write(bytes, 0, bytes.Length);
			}
			HttpWebResponse httpWebResponse2 = default(HttpWebResponse);
			try
			{
				using HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
				return new Result
				{
					Status = (int)httpWebResponse.StatusCode,
					Body = ReadBody(httpWebResponse),
					RetryAfter = 0.0
				};
			}
			catch (WebException ex) when (((Func<bool>)delegate
			{
				// Could not convert BlockContainer to single expression
				httpWebResponse2 = ex.Response as HttpWebResponse;
				return httpWebResponse2 != null;
			}).Invoke())
			{
				string body2 = ReadBody(httpWebResponse2);
				double result = Body.Num(body2, "retry_after");
				if (double.IsNaN(result))
				{
					double.TryParse(httpWebResponse2.Headers["Retry-After"], NumberStyles.Float, Inv, out result);
				}
				return new Result
				{
					Status = (int)httpWebResponse2.StatusCode,
					Body = body2,
					RetryAfter = result
				};
			}
		}

		private static string ReadBody(HttpWebResponse resp)
		{
			using Stream stream = resp.GetResponseStream();
			using StreamReader streamReader = new StreamReader(stream ?? Stream.Null, Encoding.UTF8);
			return streamReader.ReadToEnd();
		}

		private static string Clip(string s, int max)
		{
			if (s.Length <= max)
			{
				return s;
			}
			return s.Substring(0, max);
		}

		private static string JsonEsc(string s)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (char c in s)
			{
				if (c == '"' || c == '\\')
				{
					stringBuilder.Append('\\').Append(c);
				}
				else if (c == '\n')
				{
					stringBuilder.Append("\\n");
				}
				else if (c == '\r')
				{
					stringBuilder.Append("\\r");
				}
				else if (c < ' ')
				{
					stringBuilder.Append(' ');
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}

		private static void WarnOnce(ref int flag, string what, Exception ex)
		{
			if (Interlocked.CompareExchange(ref flag, 1, 0) == 0)
			{
				ZLog.LogWarning((object)("WebMap: discord " + what + " failed, will keep trying quietly: " + ex.Message));
			}
		}
	}
	public class DiscordWebHook
	{
		private readonly string webHookUrl;

		public DiscordWebHook(string url)
		{
			webHookUrl = url;
		}

		public void SendMessage(string msgSend)
		{
			if (Ext.IsNullOrEmpty(webHookUrl))
			{
				return;
			}
			ThreadPool.QueueUserWorkItem(delegate
			{
				try
				{
					NameValueCollection data = new NameValueCollection { { "content", msgSend } };
					using WebClient webClient = new WebClient();
					webClient.UploadValues(webHookUrl, data);
				}
				catch (Exception ex)
				{
					ZLog.LogWarning((object)("WebMap: discord webhook failed: " + ex.Message));
				}
			});
		}
	}
	internal static class Features
	{
		private class Grids
		{
			public ushort[] land;

			public ushort[] region;

			public ushort[] water;

			public ushort[] range;

			public byte[] cls;

			public float[] hgt;

			public float cell;
		}

		private class Named
		{
			public string name;

			public string by;

			public long t;
		}

		private class Feature
		{
			public string id;

			public string kind;

			public float x;

			public float z;

			public float w;

			public float h;

			public float area;

			public bool hasPeak;

			public float px;

			public float pz;

			public float py;

			public bool hasElev;

			public float hmin;

			public float hmax;

			public float hmean;

			public List<float[]> line;
		}

		private const string NamesFile = "names.tsv";

		private const int N = 1024;

		private const float Edge = 10500f;

		public static volatile int Rev;

		private static volatile string json = "{\"features\":[],\"count\":0,\"rev\":0}";

		private static volatile bool building;

		private static string dir;

		private static readonly object gate = new object();

		private static readonly Dictionary<string, Named> names = new Dictionary<string, Named>();

		private static Feature[] found = new Feature[0];

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

		private static volatile Grids grids;

		private static readonly string[] Stems = new string[113]
		{
			"Ask", "Bjarn", "Brim", "Dag", "Eld", "Fenn", "Frost", "Gard", "Grim", "Gull",
			"Hav", "Hjal", "Isa", "Jarn", "Kald", "Lang", "Nord", "Orm", "Ragn", "Sten",
			"Torn", "Ulf", "Var", "Vind", "Ygg", "Aud", "Ein", "Skal", "Hrim", "Sval",
			"Ravn", "Bjork", "Eik", "Lind", "Hol", "Sol", "Mor", "Raud", "Gra", "Svart",
			"Hvit", "Blak", "Kol", "Sig", "Thor", "Odd", "Rune", "Hild", "Alf", "Ger",
			"Gunn", "Ing", "Ketil", "Odin", "Tyr", "Vale", "Agn", "Arn", "Bal", "Berg",
			"Bod", "Dyr", "Egg", "Eir", "Fal", "Fjor", "Galt", "Geir", "Hakon", "Hall",
			"Har", "Heid", "Helg", "Hjort", "Hrafn", "Ivar", "Jor", "Kar", "Knut", "Leif",
			"Loft", "Magn", "Mjol", "Njal", "Ol", "Ott", "Rag", "Rand", "Rolf", "Saem",
			"Sand", "Sindr", "Skeg", "Snor", "Stein", "Styr", "Sun", "Svein", "Tind", "Tofa",
			"Trygg", "Ulfr", "Una", "Vig", "Vott", "Yng", "Yr", "Aesir", "Bragi", "Freyr",
			"Idun", "Loki", "Skadi"
		};

		private static readonly string[] Links = new string[10] { "", "", "s", "s", "ar", "a", "e", "i", "u", "en" };

		private const float HubReach = 150f;

		internal const int Cells = 1024;

		private static string Inv(FormattableString f)
		{
			return f.ToString(CultureInfo.InvariantCulture);
		}

		public static void Load(string worldDataPath)
		{
			dir = worldDataPath;
			lock (gate)
			{
				LoadNames();
			}
			StaticCoroutine.Start(Build());
		}

		private static void LoadNames()
		{
			names.Clear();
			try
			{
				string path = Path.Combine(dir, "names.tsv");
				if (!File.Exists(path))
				{
					return;
				}
				string[] array = File.ReadAllLines(path);
				for (int i = 0; i < array.Length; i++)
				{
					string[] array2 = array[i].Split(new char[1] { '\t' });
					if (array2.Length >= 2 && array2[0].Length != 0)
					{
						long result = 0L;
						if (array2.Length > 3)
						{
							long.TryParse(array2[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
						}
						names[array2[0]] = new Named
						{
							name = array2[1],
							by = ((array2.Length > 2) ? array2[2] : ""),
							t = result
						};
					}
				}
			}
			catch (Exception ex)
			{
				ZLog.LogWarning((object)("WebMap: names not loaded: " + ex.Message));
			}
		}

		private static IEnumerator Build()
		{
			if (building)
			{
				yield break;
			}
			building = true;
			float cell = (float)(WebMapConfig.TEXTURE_SIZE * WebMapConfig.PIXEL_SIZE) / 1024f;
			float half = 512f;
			byte[] cls = new byte[1048576];
			float[] hgt = new float[1048576];
			float water = ZoneSystem.instance.m_waterLevel;
			WorldGenerator wg = WorldGenerator.instance;
			Color val = default(Color);
			for (int y = 0; y < 1024; y++)
			{
				yield return null;
				float num = ((float)y - half) * cell + cell / 2f;
				for (int i = 0; i < 1024; i++)
				{
					float num2 = ((float)i - half) * cell + cell / 2f;
					int num3 = y * 1024 + i;
					if (num2 * num2 + num * num > 110250000f)
					{
						cls[num3] = 0;
						hgt[num3] = 0f;
						continue;
					}
					Biome biome = wg.GetBiome(num2, num, 0.02f, false);
					float biomeHeight = wg.GetBiomeHeight(biome, num2, num, ref val, false, true);
					hgt[num3] = biomeHeight;
					cls[num3] = (byte)((!(biomeHeight < water)) ? ((byte)(Index(biome) + 1)) : 0);
				}
			}
			List<List<Vector2>> rivers = new List<List<Vector2>>();
			try
			{
				foreach (River river in wg.GetRivers())
				{
					rivers.Add(RiverPath(river));
				}
			}
			catch (Exception ex)
			{
				ZLog.LogWarning((object)("WebMap: rivers not read: " + ex.Message));
			}
			string seed = "";
			try
			{
				seed = ((wg.m_world != null) ? (wg.m_world.m_seedName ?? "") : "");
			}
			catch
			{
			}
			ThreadPool.QueueUserWorkItem(delegate
			{
				try
				{
					Analyse(cls, hgt, rivers, cell, seed);
				}
				catch (Exception ex2)
				{
					ZLog.LogWarning((object)("WebMap: geography not read: " + ex2.Message + "\n" + ex2.StackTrace));
				}
				finally
				{
					building = false;
				}
			});
		}

		private static List<Vector2> RiverPath(River r)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			List<Vector2> list = new List<Vector2>();
			float num = r.widthMin / 8f;
			Vector2 val = r.p1 - r.p0;
			Vector2 normalized = ((Vector2)(ref val)).normalized;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0f - normalized.y, normalized.x);
			float num2 = Vector2.Distance(r.p0, r.p1);
			for (float num3 = 0f; num3 <= num2; num3 += num)
			{
				float num4 = num3 / r.curveWavelength;
				float num5 = (float)(Math.Sin(num4) * Math.Sin((double)num4 * 0.634119987487793) * Math.Sin((double)num4 * 0.3341200053691864) * (double)r.curveWidth);
				list.Add(r.p0 + normalized * num3 + val2 * num5);
			}
			return list;
		}

		private static int Index(Biome b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Expected I4, but got Unknown
			int num = (int)b;
			int num2 = 0;
			while (num > 1 && num2 < 31)
			{
				num >>= 1;
				num2++;
			}
			return num2;
		}

		private static byte Class(Biome b)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return (byte)(Index(b) + 1);
		}

		private static void Analyse(byte[] cls, float[] hgt, List<List<Vector2>> rivers, float cell, string seed)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_0564: Unknown result type (might be due to invalid IL or missing references)
			//IL_056d: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_05f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0727: Unknown result type (might be due to invalid IL or missing references)
			//IL_073a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0610: Unknown result type (might be due to invalid IL or missing references)
			//IL_0619: Unknown result type (might be due to invalid IL or missing references)
			//IL_0650: Unknown result type (might be due to invalid IL or missing references)
			//IL_0666: Unknown result type (might be due to invalid IL or missing references)
			//IL_06bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_06d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0700: Unknown result type (might be due to invalid IL or missing references)
			//IL_0691: Unknown result type (might be due to invalid IL or missing references)
			//IL_069e: Unknown result type (might be due to invalid IL or missing references)
			//IL_06ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b3: Unknown result type (might be due to invalid IL or missing references)
			List<Feature> list = new List<Feature>();
			int num = 1048576;
			float cellKm2 = cell * cell / 1000000f;
			bool[] array = new bool[num];
			foreach (List<Vector2> river in rivers)
			{
				float num2 = 4f;
				foreach (Vector2 item2 in river)
				{
					int num3 = (int)Math.Floor(item2.x / cell + 512f);
					int num4 = (int)Math.Floor(item2.y / cell + 512f);
					for (int i = -(int)num2; (float)i <= num2; i++)
					{
						for (int j = -(int)num2; (float)j <= num2; j++)
						{
							int num5 = num3 + j;
							int num6 = num4 + i;
							if (num5 >= 0 && num6 >= 0 && num5 < 1024 && num6 < 1024 && !((float)(j * j + i * i) > num2 * num2))
							{
								array[num6 * 1024 + num5] = true;
							}
						}
					}
				}
			}
			Grids grids = new Grids
			{
				land = new ushort[num],
				region = new ushort[num],
				water = new ushort[num],
				range = new ushort[num],
				cls = cls,
				hgt = hgt,
				cell = cell
			};
			bool[] array2 = new bool[num];
			for (int k = 0; k < num; k++)
			{
				array2[k] = cls[k] != 0;
			}
			Regions(array2, cell, 20, (int num25) => (!((float)num25 * cellKm2 >= 2f)) ? ((!((float)num25 * cellKm2 >= 0.1f)) ? "holm" : "island") : "continent", list, hgt, withPeak: false, grids.land);
			bool[] array3 = new bool[num];
			for (int num7 = 0; num7 < num; num7++)
			{
				array3[num7] = !array2[num7] && !array[num7];
			}
			int count;
			int[] area;
			int[] array4 = Label(array3, out count, out area);
			bool[] array5 = new bool[count + 1];
			for (int num8 = 0; num8 < 1024; num8++)
			{
				array5[array4[num8]] = true;
				array5[array4[1047552 + num8]] = true;
			}
			for (int num9 = 0; num9 < 1024; num9++)
			{
				array5[array4[num9 * 1024]] = true;
				array5[array4[num9 * 1024 + 1024 - 1]] = true;
			}
			bool[] array6 = new bool[num];
			for (int num10 = 0; num10 < num; num10++)
			{
				array6[num10] = array3[num10] && !array5[array4[num10]];
			}
			Regions(array6, cell, 12, (int _) => "lake", list, null, withPeak: false, grids.water);
			bool[] array7 = new bool[num];
			for (int num11 = 0; num11 < num; num11++)
			{
				array7[num11] = !array2[num11] && array5[array4[num11]];
			}
			bool[] array8 = Erode(Dilate(array2, 8), 8);
			bool[] array9 = new bool[num];
			for (int num12 = 0; num12 < num; num12++)
			{
				array9[num12] = array8[num12] && array7[num12];
			}
			Regions(array9, cell, 40, (int _) => "bay", list, null, withPeak: false, grids.water);
			bool[] array10 = new bool[num];
			byte b = Class((Biome)4);
			for (int num13 = 0; num13 < num; num13++)
			{
				array10[num13] = cls[num13] == b;
			}
			Regions(array10, cell, 40, (int _) => "range", list, hgt, withPeak: true, grids.range);
			(Biome, string)[] array11 = new(Biome, string)[7]
			{
				((Biome)8, "forest"),
				((Biome)2, "swamp"),
				((Biome)16, "plains"),
				((Biome)512, "mistlands"),
				((Biome)1, "meadows"),
				((Biome)32, "ashlands"),
				((Biome)64, "north")
			};
			for (int num14 = 0; num14 < array11.Length; num14++)
			{
				(Biome, string) tuple = array11[num14];
				Biome item = tuple.Item1;
				string kind = tuple.Item2;
				byte b2 = Class(item);
				bool[] array12 = new bool[num];
				for (int num15 = 0; num15 < num; num15++)
				{
					array12[num15] = cls[num15] == b2;
				}
				Regions(array12, cell, (int)(0.5f / cellKm2), (int _) => kind, list, hgt, withPeak: false, grids.region);
			}
			foreach (List<Vector2> river2 in rivers)
			{
				if (river2.Count < 2)
				{
					continue;
				}
				float num16 = 0f;
				for (int num17 = 1; num17 < river2.Count; num17++)
				{
					num16 += Vector2.Distance(river2[num17 - 1], river2[num17]);
				}
				if (num16 < 300f)
				{
					continue;
				}
				Feature feature = new Feature
				{
					kind = "river",
					line = new List<float[]>(),
					area = 0f
				};
				float num18 = 0f;
				float num19 = 0f;
				float num20 = float.MaxValue;
				float num21 = float.MaxValue;
				float num22 = float.MinValue;
				float num23 = float.MinValue;
				Vector2 val = river2[river2.Count / 2];
				for (int num24 = 0; num24 < river2.Count; num24++)
				{
					if (num24 > 0)
					{
						num18 += Vector2.Distance(river2[num24 - 1], river2[num24]);
					}
					if (num24 == 0 || num24 == river2.Count - 1 || num18 >= num19)
					{
						feature.line.Add(new float[2]
						{
							R1(river2[num24].x),
							R1(river2[num24].y)
						});
						num19 = num18 + 50f;
					}
					if (num18 >= num16 / 2f && val == river2[river2.Count / 2])
					{
						val = river2[num24];
					}
					num20 = Math.Min(num20, river2[num24].x);
					num22 = Math.Max(num22, river2[num24].x);
					num21 = Math.Min(num21, river2[num24].y);
					num23 = Math.Max(num23, river2[num24].y);
				}
				feature.x = R1(val.x);
				feature.z = R1(val.y);
				feature.w = R1(num22 - num20);
				feature.h = R1(num23 - num21);
				feature.area = R1(num16);
				feature.id = Id(feature.kind, feature.x, feature.z);
				list.Add(feature);
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			HashSet<string> used = new HashSet<string>();
			foreach (Feature item3 in list)
			{
				dictionary[item3.id] = Generate(seed, item3, used);
			}
			lock (gate)
			{
				found = list.ToArray();
				Features.grids = grids;
				generated.Clear();
				foreach (KeyValuePair<string, string> item4 in dictionary)
				{
					generated[item4.Key] = item4.Value;
				}
				Rebuild();
			}
			Dictionary<string, int> dictionary2 = new Dictionary<string, int>();
			foreach (Feature item5 in list)
			{
				dictionary2[item5.kind] = ((!dictionary2.TryGetValue(item5.kind, out var value)) ? 1 : (value + 1));
			}
			List<string> list2 = new List<string>();
			foreach (KeyValuePair<string, int> item6 in dictionary2)
			{
				list2.Add(item6.Value + " " + item6.Key);
			}
			ZLog.Log((object)("WebMap: geography read: " + string.Join(", ", list2.ToArray())));
		}

		private static float R1(float v)
		{
			return (float)Math.Round(v, 1);
		}

		private static string Id(string kind, float x, float z)
		{
			return kind + "@" + ((int)Math.Round(x / 48f) * 48).ToString(CultureInfo.InvariantCulture) + "," + ((int)Math.Round(z / 48f) * 48).ToString(CultureInfo.InvariantCulture);
		}

		private static void Regions(bool[] mask, float cell, int minCells, Func<int, string> kindOf, List<Feature> feats, float[] hgt, bool withPeak, ushort[] into)
		{
			int num = 1048576;
			int count;
			int[] area;
			int[] array = Label(mask, out count, out area);
			if (count == 0)
			{
				return;
			}
			int[] array2 = Depth(mask);
			int[] array3 = new int[count + 1];
			int[] array4 = new int[count + 1];
			int[] array5 = new int[count + 1];
			int[] array6 = new int[count + 1];
			int[] array7 = new int[count + 1];
			int[] array8 = new int[count + 1];
			int[] array9 = new int[count + 1];
			float[] array10 = new float[count + 1];
			float[] array11 = new float[count + 1];
			double[] array12 = new double[count + 1];
			for (int i = 1; i <= count; i++)
			{
				array5[i] = (array6[i] = 1024);
				array7[i] = (array8[i] = -1);
				array4[i] = -1;
				array10[i] = float.MinValue;
				array11[i] = float.MaxValue;
			}
			for (int j = 0; j < num; j++)
			{
				int num2 = array[j];
				if (num2 == 0)
				{
					continue;
				}
				int num3 = j % 1024;
				int num4 = j / 1024;
				if (array2[j] > array4[num2])
				{
					array4[num2] = array2[j];
					array3[num2] = j;
				}
				if (num3 < array5[num2])
				{
					array5[num2] = num3;
				}
				if (num3 > array7[num2])
				{
					array7[num2] = num3;
				}
				if (num4 < array6[num2])
				{
					array6[num2] = num4;
				}
				if (num4 > array8[num2])
				{
					array8[num2] = num4;
				}
				if (hgt != null)
				{
					if (hgt[j] > array10[num2])
					{
						array10[num2] = hgt[j];
						array9[num2] = j;
					}
					if (hgt[j] < array11[num2])
					{
						array11[num2] = hgt[j];
					}
					array12[num2] += hgt[j];
				}
			}
			float num5 = cell * cell / 1000000f;
			ushort[] array13 = new ushort[count + 1];
			for (int k = 1; k <= count; k++)
			{
				if (area[k] < minCells)
				{
					continue;
				}
				Feature feature = new Feature
				{
					kind = kindOf(area[k]),
					area = R1((float)area[k] * num5 * 100f) / 100f
				};
				feature.x = R1(World(array3[k] % 1024, cell));
				feature.z = R1(World(array3[k] / 1024, cell));
				feature.w = R1((float)(array7[k] - array5[k] + 1) * cell);
				feature.h = R1((float)(array8[k] - array6[k] + 1) * cell);
				if (hgt != null)
				{
					feature.hasElev = true;
					feature.hmin = R1(array11[k]);
					feature.hmax = R1(array10[k]);
					feature.hmean = R1((float)(array12[k] / (double)area[k]));
					if (withPeak)
					{
						feature.hasPeak = true;
						feature.px = R1(World(array9[k] % 1024, cell));
						feature.pz = R1(World(array9[k] / 1024, cell));
						feature.py = R1(array10[k]);
					}
				}
				feature.id = Id(feature.kind, feature.x, feature.z);
				if (feats.Count < 65535)
				{
					array13[k] = (ushort)(feats.Count + 1);
				}
				feats.Add(feature);
			}
			if (into == null)
			{
				return;
			}
			for (int l = 0; l < num; l++)
			{
				if (array[l] != 0 && array13[array[l]] != 0)
				{
					into[l] = array13[array[l]];
				}
			}
		}

		private static float World(int c, float cell)
		{
			return ((float)c - 512f) * cell + cell / 2f;
		}

		private static int[] Label(bool[] mask, out int count, out int[] area)
		{
			int num = 1048576;
			int[] array = new int[num];
			List<int> list = new List<int> { 0 };
			int[] array2 = new int[num];
			count = 0;
			for (int i = 0; i < num; i++)
			{
				if (!mask[i] || array[i] != 0)
				{
					continue;
				}
				int num2 = ++count;
				int num3 = 0;
				int num4 = 0;
				int num5 = 0;
				array2[num4++] = i;
				array[i] = num2;
				while (num3 < num4)
				{
					int num6 = array2[num3++];
					num5++;
					int num7 = num6 % 1024;
					int num8 = num6 / 1024;
					if (num7 > 0 && mask[num6 - 1] && array[num6 - 1] == 0)
					{
						array[num6 - 1] = num2;
						array2[num4++] = num6 - 1;
					}
					if (num7 < 1023 && mask[num6 + 1] && array[num6 + 1] == 0)
					{
						array[num6 + 1] = num2;
						array2[num4++] = num6 + 1;
					}
					if (num8 > 0 && mask[num6 - 1024] && array[num6 - 1024] == 0)
					{
						array[num6 - 1024] = num2;
						array2[num4++] = num6 - 1024;
					}
					if (num8 < 1023 && mask[num6 + 1024] && array[num6 + 1024] == 0)
					{
						array[num6 + 1024] = num2;
						array2[num4++] = num6 + 1024;
					}
				}
				list.Add(num5);
			}
			area = list.ToArray();
			return array;
		}

		private static int[] Depth(bool[] mask)
		{
			int num = 1048576;
			int[] array = new int[num];
			int[] array2 = new int[num];
			int num2 = 0;
			int num3 = 0;
			for (int i = 0; i < num; i++)
			{
				array[i] = -1;
				if (mask[i])
				{
					int num4 = i % 1024;
					int num5 = i / 1024;
					if (num4 == 0 || num5 == 0 || num4 == 1023 || num5 == 1023 || !mask[i - 1] || !mask[i + 1] || !mask[i - 1024] || !mask[i + 1024])
					{
						array[i] = 0;
						array2[num3++] = i;
					}
				}
			}
			while (num2 < num3)
			{
				int num6 = array2[num2++];
				int num7 = num6 % 1024;
				int num8 = num6 / 1024;
				int num9 = array[num6] + 1;
				if (num7 > 0 && mask[num6 - 1] && array[num6 - 1] < 0)
				{
					array[num6 - 1] = num9;
					array2[num3++] = num6 - 1;
				}
				if (num7 < 1023 && mask[num6 + 1] && array[num6 + 1] < 0)
				{
					array[num6 + 1] = num9;
					array2[num3++] = num6 + 1;
				}
				if (num8 > 0 && mask[num6 - 1024] && array[num6 - 1024] < 0)
				{
					array[num6 - 1024] = num9;
					array2[num3++] = num6 - 1024;
				}
				if (num8 < 1023 && mask[num6 + 1024] && array[num6 + 1024] < 0)
				{
					array[num6 + 1024] = num9;
					array2[num3++] = num6 + 1024;
				}
			}
			return array;
		}

		private static bool[] Dilate(bool[] a, int r)
		{
			return Pass(Pass(a, r, rows: true, any: true), r, rows: false, any: true);
		}

		private static bool[] Erode(bool[] a, int r)
		{
			return Pass(Pass(a, r, rows: true, any: false), r, rows: false, any: false);
		}

		private static bool[] Pass(bool[] a, int r, bool rows, bool any)
		{
			bool[] array = new bool[a.Length];
			for (int i = 0; i < 1024; i++)
			{
				for (int j = 0; j < 1024; j++)
				{
					bool flag = !any;
					for (int k = -r; k <= r; k++)
					{
						int num = (rows ? (j + k) : j);
						int num2 = (rows ? i : (i + k));
						bool flag2 = num >= 0 && num2 >= 0 && num < 1024 && num2 < 1024 && a[num2 * 1024 + num];
						if (any ? flag2 : (!flag2))
						{
							flag = any;
							break;
						}
					}
					array[i * 1024 + j] = flag;
				}
			}
			return array;
		}

		private static string Suffix(string kind)
		{
			return kind switch
			{
				"continent" => "land", 
				"island" => "ey", 
				"holm" => "holm", 
				"range" => "fjell", 
				"lake" => "vatn", 
				"river" => "á", 
				"bay" => "vik", 
				"forest" => "mork", 
				"swamp" => "myr", 
				"plains" => "vellir", 
				"meadows" => "eng", 
				"mistlands" => "dimma", 
				"ashlands" => "brand", 
				"north" => "frost", 
				_ => "stad", 
			};
		}

		private static string Generate(string seed, Feature f, HashSet<string> used)
		{
			string text = null;
			string text2 = ((f.kind == "continent") ? ("island" + f.id.Substring(f.id.IndexOf('@'))) : f.id);
			for (int i = 0; i < 24; i++)
			{
				uint num = (uint)Fnv.Of(seed + "|" + text2 + "|" + i);
				string text3 = Stems[num % (uint)Stems.Length];
				string text4 = Suffix(f.kind);
				bool flag = "aeiouy".IndexOf(char.ToLowerInvariant(text3[text3.Length - 1])) >= 0;
				bool flag2 = "aeiouyá".IndexOf(text4[0]) >= 0;
				if (flag && flag2)
				{
					text3 = text3.Substring(0, text3.Length - 1);
					flag = false;
				}
				string text5 = (flag ? "" : Links[(num >> 8) % (uint)Links.Length]);
				if (flag2 && text5.Length > 0 && "aeiou".IndexOf(text5[text5.Length - 1]) >= 0)
				{
					text5 = "";
				}
				text = text3 + text5 + text4;
				if (used.Add(text))
				{
					return text;
				}
			}
			return text;
		}

		private static string Alt(string id)
		{
			int num = id.IndexOf('@');
			if (num < 0)
			{
				return null;
			}
			string text = id.Substring(0, num);
			if (!(text == "continent"))
			{
				if (!(text == "island"))
				{
					return null;
				}
				return "continent" + id.Substring(num);
			}
			return "island" + id.Substring(num);
		}

		private static bool Given(string id, out Named nm)
		{
			if (names.TryGetValue(id, out nm) && !string.IsNullOrEmpty(nm.name))
			{
				return true;
			}
			string text = Alt(id);
			if (text != null && names.TryGetValue(text, out nm))
			{
				return !string.IsNullOrEmpty(nm.name);
			}
			return false;
		}

		private static void Open(StringBuilder sb, Feature f)
		{
			Named nm;
			bool flag = Given(f.id, out nm);
			string value;
			string s = (flag ? nm.name : (generated.TryGetValue(f.id, out value) ? value : f.kind));
			sb.Append(Inv($"{{\"id\":\"{Esc(f.id)}\",\"kind\":\"{f.kind}\",\"name\":\"{Esc(s)}\",\"x\":{f.x:0.#},\"z\":{f.z:0.#},\"w\":{f.w:0.#},\"h\":{f.h:0.#},\"area\":{f.area:0.##}"));
			if (flag)
			{
				sb.Append(Inv($",\"by\":\"{Esc(nm.by)}\",\"t\":{nm.t}"));
			}
			if (f.hasPeak)
			{
				sb.Append(Inv($",\"peak\":{{\"x\":{f.px:0.#},\"z\":{f.pz:0.#},\"y\":{f.py:0.#}}}"));
			}
			if (f.hasElev)
			{
				sb.Append(Inv($",\"elev\":{{\"min\":{f.hmin:0.#},\"max\":{f.hmax:0.#},\"mean\":{f.hmean:0.#}}}"));
			}
			if (f.line == null)
			{
				return;
			}
			sb.Append(",\"line\":[");
			for (int i = 0; i < f.line.Count; i++)
			{
				if (i > 0)
				{
					sb.Append(',');
				}
				sb.Append(Inv($"[{f.line[i][0]:0.#},{f.line[i][1]:0.#}]"));
			}
			sb.Append(']');
		}

		private static void Rebuild()
		{
			StringBuilder stringBuilder = new StringBuilder("{\"features\":[");
			int value = 0;
			Feature[] array = found;
			foreach (Feature f in array)
			{
				if (value++ > 0)
				{
					stringBuilder.Append(',');
				}
				Open(stringBuilder, f);
				stringBuilder.Append('}');
			}
			stringBuilder.Append("],\"count\":").Append(value);
			int num = Fnv.Of(stringBuilder.ToString());
			stringBuilder.Append(",\"rev\":").Append(num).Append('}');
			json = stringBuilder.ToString();
			Rev = num;
		}

		private static string Esc(string s)
		{
			StringBuilder stringBuilder = new StringBuilder();
			string text = s ?? "";
			foreach (char c in text)
			{
				if (c == '"' || c == '\\')
				{
					stringBuilder.Append('\\').Append(c);
				}
				else if (c < ' ')
				{
					stringBuilder.Append(' ');
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}

		public static string Json()
		{
			return json;
		}

		public static string At(float x, float z)
		{
			Grids grids = Features.grids;
			if (grids == null)
			{
				return null;
			}
			int num = (int)Math.Floor(x / grids.cell + 512f);
			int num2 = (int)Math.Floor(z / grids.cell + 512f);
			if (num < 0 || num2 < 0 || num >= 1024 || num2 >= 1024)
			{
				return null;
			}
			if (!MapFog.Explored(x, z))
			{
				return null;
			}
			int num3 = num2 * 1024 + num;
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(Inv($"{{\"x\":{x:0.#},\"z\":{z:0.#},\"biome\":\"{BiomeName(grids.cls[num3])}\",\"height\":{grids.hgt[num3]:0.#},\"here\":["));
			int num4 = 0;
			lock (gate)
			{
				ushort[][] array = new ushort[4][] { grids.water, grids.range, grids.region, grids.land };
				foreach (ushort[] array2 in array)
				{
					int num5 = array2[num3];
					if (num5 != 0 && num5 <= found.Length)
					{
						Feature f = found[num5 - 1];
						if (num4++ > 0)
						{
							stringBuilder.Append(',');
						}
						Open(stringBuilder, f);
						Measure(grids, array2, (ushort)num5, f, out var walked, out var builds, out var portals);
						int num6 = Count(grids, array2, (ushort)num5, Stats.DeathPositions);
						stringBuilder.Append(Inv($",\"explored\":{walked:0.###},\"builds\":{builds},\"portals\":{portals},\"deaths\":{num6}}}"));
					}
				}
			}
			stringBuilder.Append("]}");
			return stringBuilder.ToString();
		}

		private static void Measure(Grids g, ushort[] layer, ushort k, Feature f, out float walked, out int builds, out int portals)
		{
			int num = 0;
			int num2 = 0;
			byte[] array = ((WebMap.mapDataServer != null) ? WebMap.mapDataServer.fogRgba : null);
			int tEXTURE_SIZE = WebMapConfig.TEXTURE_SIZE;
			int num3 = tEXTURE_SIZE / 1024;
			for (int i = 0; i < 1024; i++)
			{
				int num4 = i * 1024;
				for (int j = 0; j < 1024; j++)
				{
					if (layer[num4 + j] != k)
					{
						continue;
					}
					num++;
					if (array == null)
					{
						num2++;
						continue;
					}
					int num5 = j * num3 + num3 / 2;
					int num6 = i * num3 + num3 / 2;
					if (array[(num6 * tEXTURE_SIZE + num5) * 4] > 127)
					{
						num2++;
					}
				}
			}
			walked = ((num == 0) ? 0f : ((float)num2 / (float)num));
			builds = Count(g, layer, k, Pieces.Positions);
			portals = Count(g, layer, k, Portals.Positions);
		}

		private static int Count(Grids g, ushort[] layer, ushort k, float[] pos)
		{
			int num = 0;
			if (pos == null)
			{
				return 0;
			}
			for (int i = 0; i + 1 < pos.Length; i += 2)
			{
				int num2 = (int)Math.Floor(pos[i] / g.cell + 512f);
				int num3 = (int)Math.Floor(pos[i + 1] / g.cell + 512f);
				if (num2 >= 0 && num3 >= 0 && num2 < 1024 && num3 < 1024 && layer[num3 * 1024 + num2] == k)
				{
					num++;
				}
			}
			return num;
		}

		public static int ClassAt(float x, float z)
		{
			Grids grids = Features.grids;
			if (grids == null)
			{
				return -1;
			}
			int num = (int)Math.Floor(x / grids.cell + 512f);
			int num2 = (int)Math.Floor(z / grids.cell + 512f);
			if (num < 0 || num2 < 0 || num >= 1024 || num2 >= 1024)
			{
				return -1;
			}
			return grids.cls[num2 * 1024 + num];
		}

		public static string BiomeName(byte c)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Invalid comparison between Unknown and I4
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Invalid comparison between Unknown and I4
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected I4, but got Unknown
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Invalid comparison between Unknown and I4
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Invalid comparison between Unknown and I4
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Invalid comparison between Unknown and I4
			if (c == 0)
			{
				return "Ocean";
			}
			Biome val = (Biome)(1 << c - 1);
			if ((int)val <= 16)
			{
				switch (val - 1)
				{
				default:
					if ((int)val != 8)
					{
						if ((int)val != 16)
						{
							break;
						}
						return "Plains";
					}
					return "Black Forest";
				case 0:
					return "Meadows";
				case 1:
					return "Swamp";
				case 3:
					return "Mountain";
				case 2:
					break;
				}
			}
			else
			{
				if ((int)val == 32)
				{
					return "Ashlands";
				}
				if ((int)val == 64)
				{
					return "Deep North";
				}
				if ((int)val == 512)
				{
					return "Mistlands";
				}
			}
			return "Ocean";
		}

		public static string SetName(string id, string name, string by)
		{
			string log;
			return SetName(id, name, by, out log);
		}

		public static string SetName(string id, string name, string by, out string log)
		{
			log = null;
			id = (id ?? "").Trim();
			by = (by ?? "").Trim();
			name = Regex.Replace(name ?? "", "[\\p{C}]", "").Trim();
			if (name.Length > 40)
			{
				return "a name is at most 40 characters";
			}
			if (id.StartsWith("hub@"))
			{
				return SetHubName(id, name, by, out log);
			}
			lock (gate)
			{
				Feature feature = null;
				Feature[] array = found;
				foreach (Feature feature2 in array)
				{
					if (feature2.id == id)
					{
						feature = feature2;
						break;
					}
				}
				if (feature == null)
				{
					return "no such place";
				}
				Named nm;
				string value;
				string text = (Given(id, out nm) ? nm.name : (generated.TryGetValue(id, out value) ? value : ""));
				string text2 = Alt(id);
				if (text2 != null)
				{
					names.Remove(text2);
				}
				if (name.Length == 0)
				{
					names.Remove(id);
				}
				else
				{
					names[id] = new Named
					{
						name = name,
						by = by,
						t = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
					};
				}
				Rebuild();
				Save();
				log = ((by.Length > 0) ? by : "someone") + " named the " + feature.kind + " '" + text + "' " + ((name.Length == 0) ? "back to its own name" : ("'" + name + "'"));
				ZLog.Log((object)("WebMap: " + log));
				Discord.Tell(log);
			}
			return null;
		}

		private static bool ParseHub(string id, out float x, out float z)
		{
			x = (z = 0f);
			Match match = Regex.Match(id, "^hub@(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)$");
			if (match.Success && float.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out x))
			{
				return float.TryParse(match.Groups[2].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out z);
			}
			return false;
		}

		private static string SetHubName(string id, string name, string by, out string log)
		{
			log = null;
			if (!ParseHub(id, out var x, out var z))
			{
				return "no such place";
			}
			float[] positions = Portals.Positions;
			bool flag = false;
			for (int i = 0; i + 1 < positions.Length; i += 2)
			{
				if (Math.Abs(positions[i] - x) < 150f && Math.Abs(positions[i + 1] - z) < 150f && Math.Sqrt((positions[i] - x) * (positions[i] - x) + (positions[i + 1] - z) * (positions[i + 1] - z)) < 150.0)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				return "no portal stands there";
			}
			string key = Inv($"hub@{Math.Round(x)},{Math.Round(z)}");
			lock (gate)
			{
				string text = "";
				foreach (string item in new List<string>(names.Keys))
				{
					if (item.StartsWith("hub@") && ParseHub(item, out var x2, out var z2) && Math.Sqrt((x2 - x) * (x2 - x) + (z2 - z) * (z2 - z)) < 150.0)
					{
						text = names[item].name;
						names.Remove(item);
					}
				}
				if (name.Length > 0)
				{
					names[key] = new Named
					{
						name = name,
						by = by,
						t = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
					};
				}
				Save();
				log = ((by.Length > 0) ? by : "someone") + " named the hub " + ((text.Length > 0) ? ("'" + text + "' ") : "") + ((name.Length == 0) ? "back to its gates" : ("'" + name + "'"));
				ZLog.Log((object)("WebMap: " + log));
				Discord.Tell(log);
			}
			return null;
		}

		public static string HubNamesJson()
		{
			StringBuilder stringBuilder = new StringBuilder("[");
			int num = 0;
			lock (gate)
			{
				foreach (KeyValuePair<string, Named> name in names)
				{
					if (name.Key.StartsWith("hub@") && ParseHub(name.Key, out var x, out var z))
					{
						if (num++ > 0)
						{
							stringBuilder.Append(',');
						}
						stringBuilder.Append(Inv($"{{\"id\":\"{Esc(name.Key)}\",\"x\":{x:0.#},\"z\":{z:0.#},\"name\":\"{Esc(name.Value.name)}\",\"by\":\"{Esc(name.Value.by)}\"}}"));
					}
				}
			}
			return stringBuilder.Append(']').ToString();
		}

		private static void Save()
		{
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				foreach (KeyValuePair<string, Named> name in names)
				{
					stringBuilder.Append(name.Key).Append('\t').Append(name.Value.name.Replace('\t', ' '))
						.Append('\t')
						.Append((name.Value.by ?? "").Replace('\t', ' '))
						.Append('\t')
						.Append(name.Value.t.ToString(CultureInfo.InvariantCulture))
						.Append('\n');
				}
				string text = Path.Combine(dir, "names.tsv");
				string text2 = text + ".new";
				File.WriteAllText(text2, stringBuilder.ToString());
				if (File.Exists(text))
				{
					File.Replace(text2, text, null);
				}
				else
				{
					File.Move(text2, text);
				}
			}
			catch (Exception ex)
			{
				ZLog.LogWarning((object)("WebMap: names not saved: " + ex.Message));
			}
		}

		public static bool ParseBody(string body, out string id, out string name, string a = "id", string b = "name")
		{
			id = Body.Str(body, a);
			name = Body.Str(body, b);
			if (id != null)
			{
				return name != null;
			}
			return false;
		}

		internal static void ResetForTests(string worldDataPath)
		{
			lock (gate)
			{
				generated.Clear();
				found = new Feature[0];
				grids = null;
				dir = worldDataPath;
				LoadNames();
			}
		}

		internal static void AnalyseForTests(byte[] cls, float[] hgt, List<List<Vector2>> rivers, string seed)
		{
			Analyse(cls, hgt, rivers, (float)(WebMapConfig.TEXTURE_SIZE * WebMapConfig.PIXEL_SIZE) / 1024f, seed);
		}
	}
	internal static class ForestMap
	{
		private enum Kind
		{
			Other,
			Tree,
			Stump
		}

		private struct Cell
		{
			public int trees;

			public int stumps;
		}

		private const float Steepness = 225f;

		private const float HalfShade = 2f;

		private const float MaxShade = 200f;

		private static readonly Dictionary<int, Cell> cells = new Dictionary<int, Cell>();

		private static readonly Dictionary<int, Kind> kindCache = new Dictionary<int, Kind>();

		private static byte[] rgba;

		private static volatile byte[] png;

		public static volatile int Rev;

		private static volatile string statsJson = "{\"trees\":0,\"stumps\":0}";

		public static int LastTrees { get; private set; }

		public static int LastStumps { get; private set; }

		private static Kind Classify(int prefabHash)
		{
			if (kindCache.TryGetValue(prefabHash, out var value))
			{
				return value;
			}
			string text = null;
			try
			{
				GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefabHash) : null);
				if ((Object)(object)val != (Object)null)
				{
					text = ((Object)val).name.ToLowerInvariant();
				}
			}
			catch
			{
			}
			Kind kind = ((text != null) ? (text.Contains("_stub") ? Kind.Stump : ((!text.Contains("_log") && !text.Contains("logs")) ? ((text.Contains("tree") || text.Contains("beech") || text.Contains("birch") || text.Contains("oak") || text.Contains("yggashoot")) ? Kind.Tree : Kind.Other) : Kind.Stump)) : Kind.Other);
			kindCache[prefabHash] = kind;
			return kind;
		}

		public static void Begin()
		{
			cells.Clear();
			LastTrees = 0;
			LastStumps = 0;
			if (rgba == null)
			{
				int tEXTURE_SIZE = WebMapConfig.TEXTURE_SIZE;
				rgba = new byte[tEXTURE_SIZE * tEXTURE_SIZE * 4];
			}
		}

		public static void Observe(int prefabHash, int idx)
		{
			Kind kind = Classify(prefabHash);
			if (kind != Kind.Other)
			{
				cells.TryGetValue(idx, out var value);
				if (kind == Kind.Tree)
				{
					value.trees++;
					LastTrees++;
				}
				else
				{
					value.stumps++;
					LastStumps++;
				}
				cells[idx] = value;
			}
		}

		public static void Finish()
		{
			if (rgba == null)
			{
				return;
			}
			Array.Clear(rgba, 0, rgba.Length);
			int tEXTURE_SIZE = WebMapConfig.TEXTURE_SIZE;
			if (cells.Count == 0)
			{
				Rev = Fnv.Of(png = ImageConv.EncodeRgbaToPNG(rgba, tEXTURE_SIZE, tEXTURE_SIZE));
				return;
			}
			int num = tEXTURE_SIZE;
			int num2 = tEXTURE_SIZE;
			int num3 = 0;
			int num4 = 0;
			foreach (KeyValuePair<int, Cell> cell in cells)
			{
				int num5 = cell.Key % tEXTURE_SIZE;
				int num6 = cell.Key / tEXTURE_SIZE;
				if (num5 < num)
				{
					num = num5;
				}
				if (num5 > num3)
				{
					num3 = num5;
				}
				if (num6 < num2)
				{
					num2 = num6;
				}
				if (num6 > num4)
				{
					num4 = num6;
				}
			}
			num = Mathf.Max(0, num - 2);
			num2 = Mathf.Max(0, num2 - 2);
			num3 = Mathf.Min(tEXTURE_SIZE - 1, num3 + 2);
			num4 = Mathf.Min(tEXTURE_SIZE - 1, num4 + 2);
			int num7 = num3 - num + 1;
			int num8 = num4 - num2 + 1;
			float[] array = new float[num7 * num8];
			foreach (KeyValuePair<int, Cell> cell2 in cells)
			{
				int num9 = cell2.Key % tEXTURE_SIZE - num;
				int num10 = cell2.Key / tEXTURE_SIZE - num2;
				if (num9 >= 0 && num10 >= 0 && num9 < num7 && num10 < num8)
				{
					array[num10 * num7 + num9] = cell2.Value.trees;
				}
			}
			float[] array2 = new float[num7 * num8];
			float num11 = 25f;
			for (int i = 0; i < num8; i++)
			{
				for (int j = 0; j < num7; j++)
				{
					float num12 = 0f;
					for (int k = -2; k <= 2; k++)
					{
						int num13 = i + k;
						if (num13 < 0 || num13 >= num8)
						{
							continue;
						}
						for (int l = -2; l <= 2; l++)
						{
							int num14 = j + l;
							if (num14 >= 0 && num14 < num7)
							{
								num12 += array[num13 * num7 + num14];
							}
						}
					}
					array2[i * num7 + j] = num12 / num11;
				}
			}
			for (int m = 0; m < num8; m++)
			{
				for (int n = 0; n < num7; n++)
				{
					float num15 = array2[m * num7 + n];
					if (!(num15 <= 0.02f))
					{
						byte b = (byte)Mathf.Min(200f, 225f * num15 / (num15 + 2f));
						int num16 = ((m + num2) * tEXTURE_SIZE + (n + num)) * 4;
						rgba[num16] = 96;
						rgba[num16 + 1] = 130;
						rgba[num16 + 2] = 84;
						rgba[num16 + 3] = b;
					}
				}
			}
			Rev = Fnv.Of(png = ImageConv.EncodeRgbaToPNG(rgba, tEXTURE_SIZE, tEXTURE_SIZE));
			statsJson = "{\"trees\":" + LastTrees + ",\"stumps\":" + LastStumps + ",\"cells\":" + cells.Count + ",\"density\":" + Percentiles(array2) + "}";
		}

		private static string Percentiles(float[] blur)
		{
			List<float> v = new List<float>();
			foreach (float num in blur)
			{
				if (num > 0.02f)
				{
					v.Add(num);
				}
			}
			if (v.Count == 0)
			{
				return "{}";
			}
			v.Sort();
			Func<float, float> func = (float p) => v[Mathf.Clamp((int)(p * (float)v.Count), 0, v.Count - 1)];
			return "{\"p50\":" + func(0.5f).ToString("0.00", CultureInfo.InvariantCulture) + ",\"p90\":" + func(0.9f).ToString("0.00", CultureInfo.InvariantCulture) + ",\"p99\":" + func(0.99f).ToString("0.00", CultureInfo.InvariantCulture) + ",\"max\":" + v[v.Count - 1].ToString("0.00", CultureInfo.InvariantCulture) + "}";
		}

		public static string GetStats()
		{
			return statsJson;
		}

		public static byte[] GetPng()
		{
			return png ?? new byte[0];
		}
	}
	internal static class Gear
	{
		public enum Hand
		{
			None,
			Bow,
			Crossbow,
			Staff,
			Shield,
			Knife,
			Sword,
			Axe,
			Mace,
			Spear,
			Polearm,
			Hammer,
			Hoe,
			Cultivator,
			Pickaxe,
			Torch,
			Fishing,
			Tool,
			TwoHanded
		}

		public enum Armor
		{
			None,
			Light,
			Medium,
			Heavy,
			Mage
		}

		public enum Hit
		{
			Melee,
			Ranged,
			Magic,
			Backstab
		}

		public enum Diet
		{
			None,
			Hearty,
			Quick,
			Eitr,
			Balanced
		}

		private struct Item
		{
			public string name;

			public Hand hand;

			public bool twoHanded;

			public Armor armor;
		}

		private struct Peak
		{
			public float st;

			public float eitr;
		}

		public static readonly string[] HandNames = Lower(typeof(Hand));

		public static readonly string[] ArmorNames = Lower(typeof(Armor));

		public static readonly string[] HitNames = Lower(typeof(Hit));

		public static readonly string[] DietNames = Lower(typeof(Diet));

		public static readonly string[] Slots = new string[8] { "right", "left", "chest", "legs", "helmet", "shoulder", "rightBack", "leftBack" };

		private const float LightMove = -0.01f;

		private const float MediumMove = -0.035f;

		private static readonly Dictionary<int, Item> items = new Dictionary<int, Item>();

		private static bool warned;

		private const float BaseHp = 25f;

		private const float BaseStamina = 75f;

		private const float PeakFadePerSecond = 0.5f;

		private static readonly Dictionary<string, Peak> peaks = new Dictionary<string, Peak>();

		private static string[] Lower(Type e)
		{
			return Array.ConvertAll(Enum.GetNames(e), (string n) => n.ToLowerInvariant());
		}

		public static Han