Decompiled source of ValheimWebMap v2.1.5

plugins\WebMap\WebMap.dll

Decompiled a week 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.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using WebMap.Live;
using WebMap.Models;
using WebMap.Models.Unity;
using WebMap.Patches;
using WebMap.Tiles;
using WebMap.Util;
using WebMap.World;
using WebSocketSharp;
using WebSocketSharp.Net;
using WebSocketSharp.Server;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[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("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+b64fdabbea795fea34ff015a0130765278ffaec3")]
[assembly: AssemblyProduct("WebMap")]
[assembly: AssemblyTitle("Valheim WebMap")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace WebMap
{
	internal static class Announce
	{
		private const string TokenFile = "announce.token";

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

		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)
		{
			if (string.IsNullOrEmpty(text))
			{
				return;
			}
			lock (pending)
			{
				pending.Enqueue((text.Length > 300) ? text.Substring(0, 300) : text);
			}
		}

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

		private static void Send(string text)
		{
			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}\"");
				try
				{
					Events.Add("server", WebMapConfig.ANNOUNCE_NAME, text);
				}
				catch
				{
				}
			}
			catch (Exception ex)
			{
				ZLog.LogWarning((object)("WebMap: announce failed: " + ex));
			}
		}
	}
	internal static class WebMapConfig
	{
		public static int TEXTURE_SIZE = 2048;

		public static int PIXEL_SIZE = 12;

		public static float EXPLORE_RADIUS = 100f;

		public static bool REVEAL_VISITED = true;

		public static int REVEAL_VISITED_MARGIN = 3;

		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 bool WEB_PINS = true;

		public static bool WEBSOCKET_COMPRESSION = false;

		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 bool TEST = 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_INVITE_URL = "";

		public static string URL = "";

		public static string MAP_TITLE = "";

		public static int RENDER_THREADS = 1;

		public static int PRERENDER_ZOOM = 5;

		public static int MAX_RENDER_ZOOM = 7;

		public static int HEIGHT_MAX_ZOOM = 7;

		public static int MAIN_THREAD_ROWS_PER_FRAME = 24;

		public static float SWEEP_INTERVAL = 120f;

		public static float FIRST_SWEEP_DELAY = 30f;

		public static int SWEEP_ZDOS_PER_FRAME = 3000;

		public static bool SHOW_LAST_SEEN_POSITION = false;

		public static bool EVENT_LOG = true;

		public static float STATS_SAVE_INTERVAL = 60f;

		public static bool ENABLE_3D = true;

		public static bool LEGACY_MAP = true;

		public static bool REVEAL_ALL = false;

		public static bool EXPORT_MODELS = true;

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

		public static bool USE_TEXTURES = true;

		public static bool EXTRACT_MESHES = true;

		public static int TEXTURE_MAX_SIZE = 512;

		public static int MODEL_EXPORT_MS_PER_FRAME = 6;

		public static void ReadConfigFile(ConfigFile config)
		{
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Expected O, but got Unknown
			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;
			EXPLORE_RADIUS = config.Bind<float>("Texture", "explore_radius", EXPLORE_RADIUS, "A larger explore_radius reveals the map more quickly.").Value;
			REVEAL_VISITED = config.Bind<bool>("Texture", "reveal_visited", REVEAL_VISITED, "Lift the fog everywhere players have already been, including before the mod was installed. The world save remembers which 64 m zones the game generated; those only exist where someone stood nearby. The in-game map itself lives in each player's character file, which the server never sees, so this is the closest thing to it. Runs at start and after each world walk.").Value;
			REVEAL_VISITED_MARGIN = config.Bind<int>("Texture", "reveal_visited_margin", REVEAL_VISITED_MARGIN, new ConfigDescription("The game generates zones up to 5 away from a player. A zone only counts as visited when every zone within this many of it was generated too, so the edge of the reveal sits near where players actually saw. 3 = about 150 m from the path, 4 = about 100 m (the in-game radius), 0 = the whole generated area (about 320 m).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 5), Array.Empty<object>())).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;
			WEB_PINS = config.Bind<bool>("User", "web_pins", WEB_PINS, "Let people place and remove their own pins from the web page (right click or long press the map). Chat pins (!pin) only reach the server while two or more players are online, so this is the way that always works.").Value;
			WEBSOCKET_COMPRESSION = config.Bind<bool>("Server", "websocket_compression", WEBSOCKET_COMPRESSION, "Allow permessage-deflate on the live websocket. Off by default: some reverse proxies (IIS ARR) accept the handshake and then drop every frame.").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, "How zoomed in should the web map start at? Higher is more zoomed in.").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, "Output debugging information.").Value;
			TEST = config.Bind<bool>("Server", "test", TEST, "Enable test features (bugs).").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 URL").Value;
			DISCORD_INVITE_URL = config.Bind<string>("Server", "discord_invite_url", DISCORD_INVITE_URL, "Optional Discord invite URL to be added to the webpage.").Value;
			URL = config.Bind<string>("Server", "webmap_url", URL, "URL to view the web map.").Value;
			MAP_TITLE = config.Bind<string>("Server", "map_title", MAP_TITLE, "Title shown in the web map header. Empty = the server name.").Value;
			RENDER_THREADS = config.Bind<int>("Render", "render_threads", RENDER_THREADS, "Worker threads for rendering map tiles. 1 is right for most servers; 2 on a box with spare cores. 0 samples terrain on the game thread in small slices (slowest, safest).").Value;
			PRERENDER_ZOOM = config.Bind<int>("Render", "prerender_zoom", PRERENDER_ZOOM, "Render the whole world up to this zoom on first start (0 = 128 m/px ... 7 = 1 m/px). 5 (4 m/px, ~400 tiles) takes about a minute. Closer zooms are only rendered where players have explored.").Value;
			MAX_RENDER_ZOOM = config.Bind<int>("Render", "max_render_zoom", MAX_RENDER_ZOOM, "Closest zoom rendered over explored ground. 7 = 1 m/px (full detail), 6 = 2 m/px (a quarter of the disk and CPU).").Value;
			HEIGHT_MAX_ZOOM = config.Bind<int>("Render", "height_max_zoom", HEIGHT_MAX_ZOOM, "Closest zoom for height tiles (used by the 3D view). Lower it to save disk if 3D is off.").Value;
			MAIN_THREAD_ROWS_PER_FRAME = config.Bind<int>("Render", "main_thread_rows_per_frame", MAIN_THREAD_ROWS_PER_FRAME, "When sampling on the game thread, how many tile rows to sample per frame.").Value;
			SWEEP_INTERVAL = config.Bind<float>("Sweep", "sweep_interval", SWEEP_INTERVAL, "Seconds between walks over the world's objects (structures, trees, terraforming, portals).").Value;
			FIRST_SWEEP_DELAY = config.Bind<float>("Sweep", "first_sweep_delay", FIRST_SWEEP_DELAY, "Seconds after world load before the first sweep.").Value;
			SWEEP_ZDOS_PER_FRAME = config.Bind<int>("Sweep", "zdos_per_frame", SWEEP_ZDOS_PER_FRAME, "Objects inspected per game frame during a sweep. Lower is gentler on the game, higher finishes sooner.").Value;
			REVEAL_ALL = config.Bind<bool>("Markers", "reveal_all", REVEAL_ALL, "Ignore the fog of war entirely: no black veil, render and show the whole world, every build, tree and marker. Testing only. The full world at 1 m/px is ~6400 tiles and renders on demand as people browse.").Value;
			SHOW_LAST_SEEN_POSITION = config.Bind<bool>("User", "show_last_seen_position", SHOW_LAST_SEEN_POSITION, "Show where offline players were last seen in the stats panel.").Value;
			EVENT_LOG = config.Bind<bool>("Server", "event_log", EVENT_LOG, "Append joins, leaves, deaths, chat and pings to events.jsonl in the world's map data.").Value;
			STATS_SAVE_INTERVAL = config.Bind<float>("Interval", "stats_save_interval", STATS_SAVE_INTERVAL, "How often to save stats.json, in seconds.").Value;
			ENABLE_3D = config.Bind<bool>("Server", "enable_3d", ENABLE_3D, "Offer the 3D view in the web map.").Value;
			EXPORT_MODELS = config.Bind<bool>("Models", "export_models", EXPORT_MODELS, "Export the game's prefab meshes (buildings, trees, rocks, ruins) as glTF for the 3D view. Runs once per prefab on the game thread, a few milliseconds per frame, and is cached under map_data/models.").Value;
			OBJECT_CATEGORIES = config.Bind<string>("Models", "object_categories", OBJECT_CATEGORIES, "Which kinds of world object the 3D view gets, comma separated: piece (everything built), other (ruins, dungeon entrances, furniture, boats), rock, bush, tree. Trees and bushes are ~half the objects of a world; their leaves only show once the leaf textures are extracted, trunks always do.").Value;
			USE_TEXTURES = config.Bind<bool>("Models", "use_textures", USE_TEXTURES, "Texture the 3D models. The mod reads the textures its models need out of the game's own asset files on a background thread (a minute or two on first start, once per game version). Off: flat material colours; existing texture files are ignored.").Value;
			EXTRACT_MESHES = config.Bind<bool>("Models", "extract_meshes", EXTRACT_MESHES, "Read the meshes the engine keeps locked (most of them: carts, beehives, ruins, rocks...) out of the game's own asset files, so the 3D view shows the real shape instead of a box. Background thread, a few minutes on first start, once per game version. Off: locked meshes stay boxes.").Value;
			TEXTURE_MAX_SIZE = config.Bind<int>("Models", "texture_max_size", TEXTURE_MAX_SIZE, "Longest side of exported textures, in pixels. 256 is plenty for a map; 512 looks sharper up close.").Value;
			MODEL_EXPORT_MS_PER_FRAME = config.Bind<int>("Models", "export_ms_per_frame", MODEL_EXPORT_MS_PER_FRAME, "Game-thread time budget per frame for model export while the queue drains.").Value;
			LEGACY_MAP = config.Bind<bool>("Server", "legacy_map", LEGACY_MAP, "Build the single-image 2048px world render (map.png, served at /map and /map.jpg) if it does not exist yet. Takes a few seconds on the game thread at world load; the web map does not need it.").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,
				["web_pins"] = WEB_PINS,
				["max_pins_per_user"] = MAX_PINS_PER_USER,
				["always_map"] = ALWAYS_MAP,
				["always_visible"] = ALWAYS_VISIBLE,
				["title"] = ((!string.IsNullOrEmpty(MAP_TITLE)) ? MAP_TITLE : ((WebMap.serverInfo != null && WebMap.serverInfo.ContainsKey("serverName")) ? WebMap.serverInfo["serverName"].ToString() : "Valheim")),
				["discord_invite_url"] = DISCORD_INVITE_URL,
				["version"] = "2.1.5",
				["tile_size"] = 256,
				["max_zoom"] = 7,
				["max_render_zoom"] = MAX_RENDER_ZOOM,
				["height_max_zoom"] = HEIGHT_MAX_ZOOM,
				["world_size"] = 20480,
				["chunk_size"] = 256,
				["water_level"] = TileJob.WaterLevel,
				["enable_3d"] = ENABLE_3D,
				["show_last_seen_position"] = SHOW_LAST_SEEN_POSITION,
				["reveal_all"] = REVEAL_ALL,
				["models"] = EXPORT_MODELS
			});
		}

		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_0125: 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)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			MapDataServer instance = MapDataServer.getInstance();
			if (instance == null)
			{
				return;
			}
			ZDOID targetZDO = rpcData.m_targetZDO;
			long key = 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;
							key = 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(key))
			{
				return;
			}
			ZLog.Log((object)("WebMap: player " + text + " died"));
			float? x = null;
			float? z = null;
			try
			{
				ZDO val2 = (((ZDOID)(ref targetZDO)).IsNone() ? null : ZDOMan.instance.GetZDO(targetZDO));
				if (val2 != null)
				{
					Vector3 position = val2.GetPosition();
					x = position.x;
					z = position.z;
				}
			}
			catch
			{
			}
			Events.Add("death", text, "died", x, z);
			Stats.OnDeath(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;
		}
	}
	public class DiscordWebHook : IDisposable
	{
		private readonly WebClient webClient;

		private static readonly NameValueCollection values = new NameValueCollection();

		private readonly string webHookUrl;

		public DiscordWebHook(string url)
		{
			webHookUrl = url;
			webClient = new WebClient();
		}

		public void SendMessage(string msgSend)
		{
			values.Remove("content");
			values.Add("content", msgSend);
			if (Ext.IsNullOrEmpty(webHookUrl))
			{
				ZLog.Log((object)$"WebMap::DiscordWebHook::SendMessage: {values}");
			}
			else
			{
				webClient.UploadValues(webHookUrl, values);
			}
		}

		public void Dispose()
		{
			webClient.Dispose();
		}
	}
	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 Color32[] buf;

		private static volatile byte[] png;

		private static volatile bool pngStale = true;

		private static readonly object encodeLock = new object();

		private static 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 (buf == null)
			{
				int tEXTURE_SIZE = WebMapConfig.TEXTURE_SIZE;
				buf = (Color32[])(object)new Color32[tEXTURE_SIZE * tEXTURE_SIZE];
			}
		}

		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()
		{
			//IL_0268: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Unknown result type (might be due to invalid IL or missing references)
			if (buf == null)
			{
				return;
			}
			Array.Clear(buf, 0, buf.Length);
			int tEXTURE_SIZE = WebMapConfig.TEXTURE_SIZE;
			if (cells.Count == 0)
			{
				pngStale = true;
				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));
						buf[(m + num2) * tEXTURE_SIZE + (n + num)] = new Color32((byte)96, (byte)130, (byte)84, b);
					}
				}
			}
			pngStale = true;
			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();
			CultureInfo invariantCulture = CultureInfo.InvariantCulture;
			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", invariantCulture) + ",\"p90\":" + func(0.9f).ToString("0.00", invariantCulture) + ",\"p99\":" + func(0.99f).ToString("0.00", invariantCulture) + ",\"max\":" + v[v.Count - 1].ToString("0.00", invariantCulture) + "}";
		}

		public static string GetStats()
		{
			return statsJson;
		}

		public static byte[] GetPng()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			Color32[] array = buf;
			if (array == null)
			{
				return new byte[0];
			}
			if (!pngStale && png != null)
			{
				return png;
			}
			lock (encodeLock)
			{
				if (!pngStale && png != null)
				{
					return png;
				}
				int tEXTURE_SIZE = WebMapConfig.TEXTURE_SIZE;
				byte[] array2 = new byte[tEXTURE_SIZE * tEXTURE_SIZE * 4];
				for (int i = 0; i < tEXTURE_SIZE; i++)
				{
					int num = (tEXTURE_SIZE - 1 - i) * tEXTURE_SIZE;
					for (int j = 0; j < tEXTURE_SIZE; j++)
					{
						Color32 val = array[num + j];
						int num2 = (i * tEXTURE_SIZE + j) * 4;
						array2[num2] = val.r;
						array2[num2 + 1] = val.g;
						array2[num2 + 2] = val.b;
						array2[num2 + 3] = val.a;
					}
				}
				png = Png.Encode(array2, tEXTURE_SIZE, tEXTURE_SIZE, Png.Format.RGBA, fast: true);
				pngStale = false;
				return png;
			}
		}
	}
	internal static class ImageConv
	{
		private static readonly Type T = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule");

		private static readonly MethodInfo _load = T?.GetMethod("LoadImage", new Type[2]
		{
			typeof(Texture2D),
			typeof(byte[])
		}) ?? T?.GetMethod("LoadImage", new Type[3]
		{
			typeof(Texture2D),
			typeof(byte[]),
			typeof(bool)
		});

		private static readonly MethodInfo _enc = T?.GetMethod("EncodeToPNG", new Type[1] { typeof(Texture2D) });

		private static readonly MethodInfo _jpg = T?.GetMethod("EncodeToJPG", new Type[2]
		{
			typeof(Texture2D),
			typeof(int)
		});

		public static bool LoadImage(Texture2D tex, byte[] data)
		{
			object[] parameters = ((_load.GetParameters().Length != 3) ? new object[2] { tex, data } : new object[3] { tex, data, false });
			return (bool)_load.Invoke(null, parameters);
		}

		public static byte[] EncodeToPNG(Texture2D tex)
		{
			return (byte[])_enc.Invoke(null, new object[1] { tex });
		}

		public static byte[] EncodeToJPG(Texture2D tex, int quality)
		{
			return (byte[])_jpg.Invoke(null, new object[2] { tex, quality });
		}
	}
	public class WebSocketHandler : WebSocketBehavior
	{
		protected override void OnOpen()
		{
			string text = ((WebSocketBehavior)this).Context.Headers.Get("X-Forwarded-For");
			if (Ext.IsNullOrEmpty(text))
			{
				text = ((WebSocketBehavior)this).Context.UserEndPoint.ToString();
			}
			if (WebMapConfig.DEBUG)
			{
				ZLog.Log((object)("WebMap: new visitor connected from " + text));
			}
			MapDataServer instance = MapDataServer.getInstance();
			if (instance != null)
			{
				((WebSocketBehavior)this).Send(instance.HelloFrame());
				((WebSocketBehavior)this).Send("{\"t\":\"players\",\"data\":" + Players.Json + "}");
				((WebSocketBehavior)this).Send("{\"t\":\"events\",\"data\":" + Events.RecentJson + ",\"initial\":true}");
			}
			((WebSocketBehavior)this).OnOpen();
		}

		protected override void OnMessage(MessageEventArgs e)
		{
			if (e.Data == "players")
			{
				((WebSocketBehavior)this).Send("{\"t\":\"players\",\"data\":" + Players.Json + "}");
			}
			((WebSocketBehavior)this).OnMessage(e);
		}
	}
	public class MapDataServer
	{
		private static readonly Dictionary<string, string> contentTypes = new Dictionary<string, string>
		{
			{ "html", "text/html; charset=utf-8" },
			{ "js", "text/javascript; charset=utf-8" },
			{ "mjs", "text/javascript; charset=utf-8" },
			{ "css", "text/css; charset=utf-8" },
			{ "json", "application/json" },
			{ "png", "image/png" },
			{ "jpg", "image/jpeg" },
			{ "webp", "image/webp" },
			{ "svg", "image/svg+xml" },
			{ "ico", "image/x-icon" },
			{ "woff", "font/woff" },
			{ "woff2", "font/woff2" },
			{ "bin", "application/octet-stream" },
			{ "wasm", "application/wasm" },
			{ "map", "application/json" },
			{ "txt", "text/plain; charset=utf-8" },
			{ "webmanifest", "application/manifest+json" }
		};

		private readonly Timer broadcastTimer;

		private readonly ConcurrentDictionary<string, byte[]> fileCache = new ConcurrentDictionary<string, byte[]>();

		private readonly HttpServer httpServer;

		private readonly string publicRoot;

		private static Dictionary<string, string> embeddedWeb;

		private readonly WebSocketServiceHost wsHost;

		private readonly WebSocketServiceHost wsLegacyHost;

		private static MapDataServer __instance;

		public byte[] mapImageData;

		private byte[] mapJpgCache;

		public List<string> pins = new List<string>();

		public List<ZNetPeer> players = new List<ZNetPeer>();

		private string lastPlayersJson = "";

		private volatile bool forceReload;

		private volatile int worldRev;

		private volatile bool worldChanged;

		private static readonly Regex clientIdFilter = new Regex("[^A-Za-z0-9_-]", RegexOptions.Compiled);

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

		public MapDataServer()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			__instance = this;
			httpServer = new HttpServer(WebMapConfig.SERVER_PORT);
			httpServer.AddWebSocketService<WebSocketHandler>("/ws", (Action<WebSocketHandler>)delegate(WebSocketHandler ws)
			{
				((WebSocketBehavior)ws).IgnoreExtensions = !WebMapConfig.WEBSOCKET_COMPRESSION;
			});
			httpServer.AddWebSocketService<WebSocketHandler>("/", (Action<WebSocketHandler>)delegate(WebSocketHandler ws)
			{
				((WebSocketBehavior)ws).IgnoreExtensions = !WebMapConfig.WEBSOCKET_COMPRESSION;
			});
			httpServer.KeepClean = true;
			wsHost = httpServer.WebSocketServices["/ws"];
			wsLegacyHost = httpServer.WebSocketServices["/"];
			publicRoot = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty, "web"));
			broadcastTimer = new Timer(delegate
			{
				try
				{
					Broadcast();
				}
				catch (Exception ex)
				{
					if (WebMapConfig.DEBUG)
					{
						ZLog.LogWarning((object)("WebMap: broadcast failed: " + ex.Message));
					}
				}
			}, null, TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(WebMapConfig.PLAYER_UPDATE_INTERVAL));
			httpServer.OnGet += delegate(object sender, HttpRequestEventArgs e)
			{
				try
				{
					if (!Route(e, post: false))
					{
						ServeStatic(e);
					}
				}
				catch (Exception ex)
				{
					Fail(e, ex);
				}
			};
			httpServer.OnPost += delegate(object sender, HttpRequestEventArgs e)
			{
				try
				{
					if (!Route(e, post: true))
					{
						NotFound(e.Response);
					}
				}
				catch (Exception ex)
				{
					Fail(e, ex);
				}
			};
			httpServer.OnHead += delegate(object sender, HttpRequestEventArgs e)
			{
				try
				{
					if (!Route(e, post: false))
					{
						ServeStatic(e);
					}
				}
				catch (Exception ex)
				{
					Fail(e, ex);
				}
			};
		}

		public static MapDataServer getInstance()
		{
			return __instance;
		}

		private static void Fail(HttpRequestEventArgs e, Exception ex)
		{
			ZLog.LogWarning((object)("WebMap: request " + e.Request.RawUrl + " failed: " + ex));
			try
			{
				e.Response.StatusCode = 500;
				e.Response.Close();
			}
			catch
			{
			}
		}

		public string HelloFrame()
		{
			return "{\"t\":\"hello\",\"version\":\"2.1.5\",\"worldRev\":" + worldRev + ",\"config\":" + WebMapConfig.MakeClientConfigJson() + "}";
		}

		private void Send(string frame)
		{
			try
			{
				wsHost.Sessions.Broadcast(frame);
			}
			catch
			{
			}
			try
			{
				wsLegacyHost.Sessions.Broadcast(frame);
			}
			catch
			{
			}
		}

		private void Broadcast()
		{
			if (forceReload)
			{
				forceReload = false;
				Send("{\"t\":\"reload\"}");
				return;
			}
			string json = Players.Json;
			if (json != lastPlayersJson)
			{
				lastPlayersJson = json;
				Send("{\"t\":\"players\",\"data\":" + json + "}");
			}
			string text = Events.DrainPendingJson();
			if (text != null)
			{
				Send("{\"t\":\"events\",\"data\":" + text + "}");
			}
			List<string> list = TileStore.DrainNotifications();
			if (list != null)
			{
				JsonWriter jsonWriter = new JsonWriter(list.Count * 12 + 32);
				jsonWriter.BeginObject().Prop("t", "tiles").Key("keys")
					.BeginArray();
				foreach (string item in list)
				{
					jsonWriter.Value(item);
				}
				jsonWriter.End().Prop("status", TileStore.OnDisk + "/" + TileStore.QueueLength).End();
				Send(jsonWriter.ToString());
			}
			if (worldChanged)
			{
				worldChanged = false;
				Send("{\"t\":\"world\",\"rev\":" + worldRev + ",\"stats\":" + Stats.Json + "}");
			}
		}

		public void BroadcastWorldRevision()
		{
			worldRev++;
			worldChanged = true;
		}

		public void Reload()
		{
			forceReload = true;
		}

		public void BroadcastPing(long id, string name, Vector3 position)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			JsonWriter jsonWriter = new JsonWriter(128);
			jsonWriter.BeginObject().Prop("t", "ping").Prop("id", id)
				.Prop("name", name)
				.Prop("x", position.x, 1)
				.Prop("z", position.z, 1)
				.End();
			Send(jsonWriter.ToString());
			Events.Add("ping", name, "pinged the map", position.x, position.z);
		}

		public void AddPin(string id, string pinId, string type, string name, Vector3 position, string pinText)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: 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)
			lock (pins)
			{
				pins.Add(id + "," + pinId + "," + type + "," + name + "," + Fixed(position.x) + "," + Fixed(position.z) + "," + pinText);
			}
			JsonWriter jsonWriter = new JsonWriter(160);
			jsonWriter.BeginObject().Prop("t", "pin").Prop("owner", id)
				.Prop("id", pinId)
				.Prop("type", type)
				.Prop("name", name)
				.Prop("x", position.x, 1)
				.Prop("z", position.z, 1)
				.Prop("text", pinText)
				.End();
			Send(jsonWriter.ToString());
			Events.Add("pin", name, "placed a pin" + ((pinText.Length > 0) ? (": " + pinText) : ""), position.x, position.z);
		}

		public void RemovePin(int idx)
		{
			string[] array;
			lock (pins)
			{
				array = pins[idx].Split(new char[1] { ',' });
				pins.RemoveAt(idx);
			}
			Send("{\"t\":\"rmpin\",\"id\":\"" + array[1] + "\"}");
		}

		public string PinsJson()
		{
			JsonWriter jsonWriter = new JsonWriter(1024);
			jsonWriter.BeginArray();
			lock (pins)
			{
				foreach (string pin in pins)
				{
					string[] array = pin.Split(new char[1] { ',' });
					if (array.Length >= 7)
					{
						jsonWriter.BeginObject().Prop("owner", array[0]).Prop("id", array[1])
							.Prop("type", array[2])
							.Prop("name", array[3]);
						float.TryParse(array[4], NumberStyles.Float, CultureInfo.InvariantCulture, out var result);
						float.TryParse(array[5], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2);
						jsonWriter.Prop("x", result, 1).Prop("z", result2, 1).Prop("text", string.Join(",", array, 6, array.Length - 6))
							.End();
					}
				}
			}
			jsonWriter.End();
			return jsonWriter.ToString();
		}

		public void AddMessage(long id, int type, string name, string message)
		{
			Events.Add(type switch
			{
				0 => "whisper", 
				2 => "shout", 
				_ => (name == "Server") ? "server" : "chat", 
			}, name, message);
		}

		private static string Fixed(float f)
		{
			return f.ToString("F2", CultureInfo.InvariantCulture);
		}

		public void BuildMapJpg()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			if (mapJpgCache != null || mapImageData == null || mapImageData.Length == 0)
			{
				return;
			}
			try
			{
				Texture2D val = new Texture2D(WebMapConfig.TEXTURE_SIZE, WebMapConfig.TEXTURE_SIZE, (TextureFormat)4, false);
				if (ImageConv.LoadImage(val, mapImageData))
				{
					mapJpgCache = ImageConv.EncodeToJPG(val, 85);
					Object.Destroy((Object)(object)val);
				}
			}
			catch (Exception ex)
			{
				ZLog.LogWarning((object)("WebMap: jpeg encode failed: " + ex.Message));
			}
		}

		public void ListenAsync()
		{
			httpServer.Start();
			if (httpServer.IsListening)
			{
				ZLog.Log((object)$"WebMap: HTTP server listening on port {WebMapConfig.SERVER_PORT}");
			}
			else
			{
				ZLog.LogError((object)"WebMap: HTTP server failed to start");
			}
		}

		public void Stop()
		{
			broadcastTimer.Dispose();
			try
			{
				httpServer.Stop();
			}
			catch
			{
			}
		}

		private bool Route(HttpRequestEventArgs e, bool post)
		{
			//IL_090a: Unknown result type (might be due to invalid IL or missing references)
			HttpListenerRequest request = e.Request;
			_ = e.Response;
			string absolutePath = request.Url.AbsolutePath;
			if (absolutePath.StartsWith("/tiles/"))
			{
				if (!post)
				{
					return ServeTile(e, absolutePath);
				}
				return false;
			}
			if (absolutePath.StartsWith("/data/"))
			{
				if (!post)
				{
					return ServeData(e, absolutePath);
				}
				return false;
			}
			if (absolutePath.StartsWith("/models/"))
			{
				if (!post)
				{
					return ServeModel(e, absolutePath);
				}
				return false;
			}
			switch (absolutePath)
			{
			case "/config":
				return Text(e, WebMapConfig.MakeClientConfigJson(), "application/json", nocache: true);
			case "/api/status":
			{
				JsonWriter jsonWriter = new JsonWriter(512);
				jsonWriter.BeginObject().PropRaw("tiles", TileStore.StatusJson()).Prop("sweeps", WorldSweep.Sweeps)
					.Prop("lastSweepSeconds", WorldSweep.LastSweepSeconds, 1)
					.Prop("objects", WorldSweep.LastScanned)
					.Prop("structures", Structures.Total)
					.Prop("worldRev", worldRev)
					.Prop("version", "2.1.5")
					.End();
				return Text(e, jsonWriter.ToString(), "application/json", nocache: true);
			}
			case "/api/sweep":
				if (!post)
				{
					return false;
				}
				WorldSweep.RefreshRequested = true;
				return Text(e, "{\"queued\":true}", "application/json", nocache: true, 202);
			case "/api/reexport":
				if (!post)
				{
					return false;
				}
				if (!Authorized(request))
				{
					return Text(e, "{\"error\":\"forbidden\"}", "application/json", nocache: true, 403);
				}
				return Text(e, "{\"queued\":" + ModelStore.ReexportAll() + "}", "application/json", nocache: true, 202);
			case "/api/rerender":
			{
				if (!post)
				{
					return false;
				}
				if (!Authorized(request))
				{
					return Text(e, "{\"error\":\"forbidden\"}", "application/json", nocache: true, 403);
				}
				int.TryParse(request.QueryString["zoom"] ?? "0", out var result);
				return Text(e, "{\"queued\":" + TileStore.Rerender(result) + "}", "application/json", nocache: true, 202);
			}
			case "/api/reload":
			{
				if (!post)
				{
					return false;
				}
				if (!Authorized(request))
				{
					return Text(e, "{\"error\":\"forbidden\"}", "application/json", nocache: true, 403);
				}
				int count = fileCache.Count;
				fileCache.Clear();
				Reload();
				ZLog.Log((object)("WebMap: web files reloaded (" + count + " cached files dropped), browsers told to refresh"));
				return Text(e, "{\"dropped\":" + count + ",\"browsers\":" + (wsHost.Sessions.Count + wsLegacyHost.Sessions.Count) + "}", "application/json", nocache: true);
			}
			case "/map":
				if (mapImageData == null)
				{
					return Text(e, "not built", "text/plain", nocache: false, 503);
				}
				return Bytes(e, mapImageData, "application/octet-stream", "public, max-age=604800, immutable");
			case "/map.jpg":
				if (mapJpgCache == null)
				{
					return Text(e, "not built", "text/plain", nocache: false, 503);
				}
				return Bytes(e, mapJpgCache, "image/jpeg", "public, max-age=604800, immutable");
			case "/fog":
				return Bytes(e, Fog.Png(), "image/png", "no-cache");
			case "/players":
				return Text(e, Players.Json, "application/json", nocache: true);
			case "/messages":
				return Text(e, Events.RecentJson, "application/json", nocache: true);
			case "/pins":
			{
				string text2;
				lock (pins)
				{
					text2 = string.Join("\n", pins);
				}
				return Text(e, text2, "text/csv", nocache: true);
			}
			case "/structures":
				return Bytes(e, StructureMap.GetPng(), "image/png", "no-cache");
			case "/structures/stats":
				return Text(e, Structures.StatsJson, "application/json", nocache: true);
			case "/structures/refresh":
				WorldSweep.RefreshRequested = true;
				return Text(e, "{\"queued\":true}", "application/json", nocache: true, 202);
			case "/forest":
				return Bytes(e, ForestMap.GetPng(), "image/png", "no-cache");
			case "/forest/stats":
				return Text(e, ForestMap.GetStats(), "application/json", nocache: true);
			case "/vehicles":
				return Text(e, Vehicles.GetJson(), "application/json", nocache: true);
			case "/api/pin":
			{
				if (!post)
				{
					return false;
				}
				if (!WebMapConfig.WEB_PINS && !Authorized(request))
				{
					return Text(e, "{\"error\":\"web pins are off\"}", "application/json", nocache: true, 403);
				}
				string text5;
				using (StreamReader streamReader2 = new StreamReader(request.InputStream, Encoding.UTF8))
				{
					text5 = streamReader2.ReadToEnd();
				}
				Dictionary<string, object> dictionary;
				try
				{
					dictionary = JsonParser.Parse(text5) as Dictionary<string, object>;
				}
				catch
				{
					dictionary = null;
				}
				if (dictionary == null || !dictionary.TryGetValue("x", out var value) || !dictionary.TryGetValue("z", out var value2))
				{
					return Text(e, "{\"error\":\"need x and z\"}", "application/json", nocache: true, 400);
				}
				float num = Convert.ToSingle(value, CultureInfo.InvariantCulture);
				float num2 = Convert.ToSingle(value2, CultureInfo.InvariantCulture);
				float num3 = 10240f;
				if (float.IsNaN(num) || float.IsNaN(num2) || Mathf.Abs(num) > num3 || Mathf.Abs(num2) > num3)
				{
					return Text(e, "{\"error\":\"off the map\"}", "application/json", nocache: true, 400);
				}
				string text6 = WebOwner(request, dictionary);
				if (!PinRateOk(text6))
				{
					return Text(e, "{\"error\":\"slow down\"}", "application/json", nocache: true, 429);
				}
				object value3;
				string text7 = WebMap.CleanPinText(dictionary.TryGetValue("name", out value3) ? (value3 as string) : null, 16);
				if (text7.Length == 0)
				{
					text7 = "web";
				}
				object value4;
				object value5;
				string text8 = WebMap.PlacePin(text6, dictionary.TryGetValue("type", out value4) ? (value4 as string) : "dot", text7, new Vector3(num, 0f, num2), dictionary.TryGetValue("text", out value5) ? (value5 as string) : "");
				return Text(e, "{\"id\":\"" + text8 + "\",\"owner\":\"" + text6 + "\"}", "application/json", nocache: true);
			}
			case "/api/unpin":
			{
				if (!post)
				{
					return false;
				}
				string text3 = request.QueryString["id"] ?? "";
				if (text3.Length == 0 || text3.Contains(","))
				{
					return Text(e, "{\"error\":\"need id\"}", "application/json", nocache: true, 400);
				}
				string text4 = (Authorized(request) ? "" : WebOwner(request, null));
				if (!WebMapConfig.WEB_PINS && text4.Length > 0)
				{
					return Text(e, "{\"error\":\"web pins are off\"}", "application/json", nocache: true, 403);
				}
				bool flag = WebMap.DeletePinById(text4, text3);
				return Text(e, flag ? "{\"removed\":true}" : "{\"error\":\"not yours\"}", "application/json", nocache: true, flag ? 200 : 404);
			}
			case "/announce":
			{
				if (!post)
				{
					return false;
				}
				if (!Authorized(request))
				{
					return Text(e, "{\"error\":\"forbidden\"}", "application/json", nocache: true, 403);
				}
				string text;
				using (StreamReader streamReader = new StreamReader(request.InputStream, Encoding.UTF8))
				{
					text = streamReader.ReadToEnd();
				}
				text = (text ?? "").Trim();
				if (text.Length == 0)
				{
					return Text(e, "{\"error\":\"empty\"}", "application/json", nocache: true, 400);
				}
				Announce.Enqueue(text);
				return Text(e, "{\"queued\":true}", "application/json", nocache: true, 202);
			}
			default:
				return false;
			}
		}

		private static string WebOwner(HttpListenerRequest req, Dictionary<string, object> body)
		{
			string text = req.Headers["X-WebMap-Client"];
			if (string.IsNullOrEmpty(text) && body != null && body.TryGetValue("client", out var value))
			{
				text = value as string;
			}
			text = clientIdFilter.Replace(text ?? "", "");
			if (text.Length > 40)
			{
				text = text.Substring(0, 40);
			}
			if (text.Length == 0)
			{
				text = "anon";
			}
			return "web:" + text;
		}

		private static bool PinRateOk(string owner)
		{
			float num = (float)(DateTime.UtcNow - new DateTime(2020, 1, 1)).TotalSeconds;
			lock (pinLast)
			{
				if (pinLast.TryGetValue(owner, out var value) && num - value < 2f)
				{
					return false;
				}
				pinLast[owner] = num;
				if (pinLast.Count > 512)
				{
					pinLast.Clear();
				}
			}
			return true;
		}

		private static bool Authorized(HttpListenerRequest req)
		{
			string token = Announce.Token;
			string text = req.Headers["X-Announce-Token"] ?? req.Headers["X-WebMap-Token"] ?? "";
			if (token != null)
			{
				return text == token;
			}
			return false;
		}

		private bool ServeTile(HttpRequestEventArgs e, string path)
		{
			HttpListenerResponse response = e.Response;
			string[] array = path.Split(new char[1] { '/' });
			if (array.Length != 6 || !array[5].EndsWith(".png"))
			{
				NotFound(response);
				return true;
			}
			string text = array[2];
			if (text != "map" && text != "height" && text != "veg")
			{
				NotFound(response);
				return true;
			}
			if (!int.TryParse(array[3], out var result) || !int.TryParse(array[4], out var result2) || !int.TryParse(array[5].Substring(0, array[5].Length - 4), out var result3))
			{
				NotFound(response);
				return true;
			}
			string etag;
			byte[] array2 = TileStore.Get(text, result, result2, result3, out etag);
			if (array2 == null)
			{
				((NameValueCollection)(object)response.Headers).Add("X-WebMap-Tile", "pending");
				response.Headers.Add((HttpResponseHeader)0, "no-store");
				response.StatusCode = 404;
				response.Close();
				return true;
			}
			string text2 = e.Request.Headers["If-None-Match"];
			if (text2 != null && text2 == etag)
			{
				((NameValueCollection)(object)response.Headers).Add("ETag", etag);
				response.Headers.Add((HttpResponseHeader)0, "no-cache");
				response.StatusCode = 304;
				response.Close();
				return true;
			}
			((NameValueCollection)(object)response.Headers).Add("ETag", etag);
			return Bytes(e, array2, "image/png", "no-cache");
		}

		private bool ServeData(HttpRequestEventArgs e, string path)
		{
			HttpListenerResponse response = e.Response;
			string text = path.Substring("/data/".Length);
			switch (text)
			{
			case "players.json":
				return Text(e, Players.Json, "application/json", nocache: true);
			case "stats.json":
				return Text(e, Stats.Json, "application/json", nocache: true);
			case "events.json":
				return Text(e, Events.RecentJson, "application/json", nocache: true);
			case "pins.json":
				return Text(e, PinsJson(), "application/json", nocache: true);
			case "markers.json":
				return Text(e, Markers.Json, "application/json", nocache: true);
			case "fog.png":
				return Bytes(e, Fog.Png(), "image/png", "no-cache");
			case "structures/index.json":
				return Text(e, Structures.IndexJson, "application/json", nocache: true);
			case "objects/index.json":
				return Text(e, WorldObjects.IndexJson, "application/json", nocache: true);
			case "prefabs.json":
				return Text(e, ModelStore.PrefabsJson, "application/json", nocache: true);
			default:
				if (text.StartsWith("objects/") && text.EndsWith(".bin"))
				{
					if (!ParseChunk(text.Substring(8, text.Length - 12), out var cx, out var cz))
					{
						NotFound(response);
						return true;
					}
					float num = TileMath.ChunkMin(cx);
					float num2 = TileMath.ChunkMin(cz);
					if (!WebMapConfig.REVEAL_ALL && !Fog.AnyExplored(num, num2, num + 256f, num2 + 256f))
					{
						NotFound(response);
						return true;
					}
					byte[] array = WorldObjects.ChunkBytes(cx, cz);
					if (array == null)
					{
						NotFound(response);
						return true;
					}
					return Bytes(e, array, "application/octet-stream", "no-cache", compressible: true);
				}
				if (text.StartsWith("structures/") && text.EndsWith(".json"))
				{
					if (!ParseChunk(text.Substring("structures/".Length, text.Length - "structures/".Length - 5), out var cx2, out var cz2))
					{
						NotFound(response);
						return true;
					}
					string text2 = Structures.ChunkJson(cx2, cz2);
					if (text2 == null)
					{
						text2 = "{\"cx\":" + cx2 + ",\"cz\":" + cz2 + ",\"rev\":0,\"count\":0,\"pieces\":[],\"prefabs\":[]}";
					}
					return Text(e, text2, "application/json", nocache: true);
				}
				if (text.StartsWith("veg/") && text.EndsWith(".bin"))
				{
					if (!ParseChunk(text.Substring(4, text.Length - 8), out var cx3, out var cz3))
					{
						NotFound(response);
						return true;
					}
					float num3 = TileMath.ChunkMin(cx3);
					float num4 = TileMath.ChunkMin(cz3);
					if (!WebMapConfig.REVEAL_ALL && !Fog.AnyExplored(num3, num4, num3 + 256f, num4 + 256f))
					{
						NotFound(response);
						return true;
					}
					return Bytes(e, Vegetation.Chunk(cx3, cz3), "application/octet-stream", "no-cache");
				}
				NotFound(response);
				return true;
			}
		}

		private bool ServeModel(HttpRequestEventArgs e, string path)
		{
			HttpListenerResponse response = e.Response;
			string text = path.Substring("/models/".Length);
			if (text.Length == 0 || text.Contains("/") || text.Contains("..") || text.Contains("\\"))
			{
				NotFound(response);
				return true;
			}
			string root = ModelStore.Root;
			if (root == null)
			{
				NotFound(response);
				return true;
			}
			string path2 = Path.Combine(root, text);
			if (!File.Exists(path2))
			{
				NotFound(response);
				return true;
			}
			byte[] array;
			try
			{
				array = File.ReadAllBytes(path2);
			}
			catch
			{
				NotFound(response);
				return true;
			}
			string text2 = "\"" + array.Length.ToString("x") + "-" + Fnv(array).ToString("x") + "\"";
			if (e.Request.Headers["If-None-Match"] == text2)
			{
				((NameValueCollection)(object)response.Headers).Add("ETag", text2);
				response.Headers.Add((HttpResponseHeader)0, "no-cache");
				response.StatusCode = 304;
				response.Close();
				return true;
			}
			((NameValueCollection)(object)response.Headers).Add("ETag", text2);
			bool flag = text.EndsWith(".glb");
			return Bytes(e, array, flag ? "model/gltf-binary" : "image/png", "no-cache", flag);
		}

		private static bool ParseChunk(string s, out int cx, out int cz)
		{
			cx = (cz = 0);
			int num = s.IndexOf('_');
			if (num < 0)
			{
				return false;
			}
			if (int.TryParse(s.Substring(0, num), out cx) && int.TryParse(s.Substring(num + 1), out cz) && cx >= 0 && cz >= 0 && cx < TileMath.ChunksPerSide)
			{
				return cz < TileMath.ChunksPerSide;
			}
			return false;
		}

		private void ServeStatic(HttpRequestEventArgs e)
		{
			HttpListenerRequest request = e.Request;
			HttpListenerResponse response = e.Response;
			string text = request.Url.AbsolutePath;
			if (text == "/")
			{
				text = "/index.html";
			}
			string text2 = text.TrimStart(new char[1] { '/' });
			if (text2.Length == 0 || text2.Contains("..") || text2.Contains("\\") || text2.Contains(":"))
			{
				NotFound(response);
				return;
			}
			string text3 = Path.GetExtension(text2).TrimStart(new char[1] { '.' }).ToLowerInvariant();
			if (!contentTypes.TryGetValue(text3, out var value))
			{
				NotFound(response);
				return;
			}
			if (!fileCache.TryGetValue(text2, out var value2))
			{
				string fullPath = Path.GetFullPath(Path.Combine(publicRoot, text2.Replace('/', Path.DirectorySeparatorChar)));
				if (fullPath.StartsWith(publicRoot, StringComparison.Ordinal) && File.Exists(fullPath))
				{
					try
					{
						value2 = File.ReadAllBytes(fullPath);
					}
					catch (Exception ex)
					{
						ZLog.LogError((object)("WebMap: failed to read " + text2 + ": " + ex.Message));
						NotFound(response);
						return;
					}
				}
				else
				{
					value2 = EmbeddedWebFile(text2);
					if (value2 == null)
					{
						NotFound(response);
						return;
					}
				}
				if (WebMapConfig.CACHE_SERVER_FILES)
				{
					fileCache[text2] = value2;
				}
			}
			string text4 = (text2.StartsWith("vendor/") ? "public, max-age=2592000, immutable" : "no-cache");
			string text5 = "\"" + value2.Length.ToString("x") + "-" + Fnv(value2).ToString("x") + "\"";
			if (request.Headers["If-None-Match"] == text5)
			{
				((NameValueCollection)(object)response.Headers).Add("ETag", text5);
				response.Headers.Add((HttpResponseHeader)0, text4);
				response.StatusCode = 304;
				response.Close();
				return;
			}
			((NameValueCollection)(object)response.Headers).Add("ETag", text5);
			byte[] data = value2;
			string ctype = value;
			int compressible;
			switch (text3)
			{
			default:
				compressible = ((text3 == "svg") ? 1 : 0);
				break;
			case "html":
			case "js":
			case "mjs":
			case "css":
			case "json":
				compressible = 1;
				break;
			}
			Bytes(e, data, ctype, text4, (byte)compressible != 0);
		}

		private byte[] EmbeddedWebFile(string rel)
		{
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			if (embeddedWeb == null)
			{
				Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
				string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
				foreach (string text in manifestResourceNames)
				{
					if (text.StartsWith("web/") || text.StartsWith("web\\"))
					{
						dictionary[text.Substring(4).Replace('\\', '/')] = text;
					}
				}
				embeddedWeb = dictionary;
				if (!Directory.Exists(publicRoot))
				{
					ZLog.LogWarning((object)("WebMap: no web folder next to WebMap.dll, serving the copy built into the DLL (" + dictionary.Count + " files)"));
				}
			}
			if (!embeddedWeb.TryGetValue(rel, out var value))
			{
				return null;
			}
			using Stream stream = executingAssembly.GetManifestResourceStream(value);
			if (stream == null)
			{
				return null;
			}
			using MemoryStream memoryStream = new MemoryStream();
			stream.CopyTo(memoryStream);
			return memoryStream.ToArray();
		}

		private static uint Fnv(byte[] d)
		{
			uint num = 2166136261u;
			int num2 = Math.Max(1, d.Length / 4096);
			for (int i = 0; i < d.Length; i += num2)
			{
				num ^= d[i];
				num *= 16777619;
			}
			return num;
		}

		private static bool Text(HttpRequestEventArgs e, string text, string ctype, bool nocache = false, int status = 200)
		{
			return Bytes(e, Encoding.UTF8.GetBytes(text ?? ""), ctype, nocache ? "no-cache" : null, compressible: true, status);
		}

		private static bool Bytes(HttpRequestEventArgs e, byte[] data, string ctype, string cache, bool compressible = false, int status = 200)
		{
			HttpListenerResponse response = e.Response;
			if (cache != null)
			{
				response.Headers.Add((HttpResponseHeader)0, cache);
			}
			((NameValueCollection)(object)response.Headers).Add("Access-Control-Allow-Origin", "*");
			response.ContentType = ctype;
			response.StatusCode = status;
			if (compressible && data.Length > 1400 && (e.Request.Headers["Accept-Encoding"] ?? "").Contains("gzip"))
			{
				using (MemoryStream memoryStream = new MemoryStream(data.Length / 3 + 64))
				{
					using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true))
					{
						gZipStream.Write(data, 0, data.Length);
					}
					data = memoryStream.ToArray();
				}
				response.Headers.Add((HttpResponseHeader)13, "gzip");
				response.Headers.Add((HttpResponseHeader)28, "Accept-Encoding");
			}
			response.ContentLength64 = data.Length;
			if (e.Request.HttpMethod == "HEAD")
			{
				response.Close();
				return true;
			}
			response.Close(data, true);
			return true;
		}

		private static void NotFound(HttpListenerResponse res)
		{
			res.StatusCode = 404;
			res.Close();
		}
	}
	internal static class StructureMap
	{
		private struct Cell
		{
			public int n;

			public int r;

			public int g;

			public int b;
		}

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

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

		private static byte[] rgba;

		private static volatile byte[] png;

		private static volatile bool pngStale = true;

		private static readonly object encodeLock = new object();

		public static volatile bool RefreshRequested;

		public static int LastCount { get; private set; }

		public static int LastScanned { get; set; }

		private static Color32 MaterialOf(int prefabHash)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			if (paletteCache.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
			{
			}
			Color32 val2 = default(Color32);
			if (text == null)
			{
				((Color32)(ref val2))..ctor((byte)150, (byte)120, (byte)90, byte.MaxValue);
			}
			else if (text.Contains("portal"))
			{
				((Color32)(ref val2))..ctor((byte)90, (byte)200, (byte)210, byte.MaxValue);
			}
			else if (text.Contains("blackmarble"))
			{
				((Color32)(ref val2))..ctor((byte)70, (byte)70, (byte)85, byte.MaxValue);
			}
			else if (text.Contains("stone") || text.Contains("grausten"))
			{
				((Color32)(ref val2))..ctor((byte)150, (byte)150, (byte)145, byte.MaxValue);
			}
			else if (text.Contains("iron") || text.Contains("metal"))
			{
				((Color32)(ref val2))..ctor((byte)120, (byte)130, (byte)145, byte.MaxValue);
			}
			else if (text.Contains("darkwood"))
			{
				((Color32)(ref val2))..ctor((byte)90, (byte)66, (byte)46, byte.MaxValue);
			}
			else if (text.Contains("roof") || text.Contains("straw") || text.Contains("thatch"))
			{
				((Color32)(ref val2))..ctor((byte)196, (byte)160, (byte)86, byte.MaxValue);
			}
			else if (text.Contains("fire") || text.Contains("hearth") || text.Contains("forge"))
			{
				((Color32)(ref val2))..ctor((byte)214, (byte)122, (byte)58, byte.MaxValue);
			}
			else
			{
				((Color32)(ref val2))..ctor((byte)150, (byte)108, (byte)66, byte.MaxValue);
			}
			paletteCache[prefabHash] = val2;
			return val2;
		}

		public static void Begin()
		{
			cells.Clear();
			LastCount = 0;
		}

		public static void Observe(int prefabHash, int idx)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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_004c: Unknown result type (might be due to invalid IL or missing references)
			Color32 val = MaterialOf(prefabHash);
			cells.TryGetValue(idx, out var value);
			value.n++;
			value.r += val.r;
			value.g += val.g;
			value.b += val.b;
			cells[idx] = value;
			LastCount++;
		}

		public static void Finish()
		{
			int tEXTURE_SIZE = WebMapConfig.TEXTURE_SIZE;
			byte[] array = new byte[tEXTURE_SIZE * tEXTURE_SIZE * 4];
			foreach (KeyValuePair<int, Cell> cell in cells)
			{
				Cell value = cell.Value;
				if (value.n > 0)
				{
					Put(array, tEXTURE_SIZE, cell.Key, (byte)(value.r / value.n), (byte)(value.g / value.n), (byte)(value.b / value.n), (byte)Mathf.Clamp(120 + value.n * 20, 120, 255));
				}
			}
			foreach (KeyValuePair<int, Cell> cell2 in cells)
			{
				Cell value2 = cell2.Value;
				byte b = (byte)Mathf.Clamp(55 + value2.n * 10, 55, 140);
				int key = cell2.Key;
				int[] array2 = new int[4]
				{
					key - 1,
					key + 1,
					key - tEXTURE_SIZE,
					key + tEXTURE_SIZE
				};
				foreach (int num in array2)
				{
					if (num >= 0 && num < tEXTURE_SIZE * tEXTURE_SIZE && !cells.ContainsKey(num))
					{
						int num2 = Offset(tEXTURE_SIZE, num);
						if (array[num2 + 3] < b)
						{
							Put(array, tEXTURE_SIZE, num, (byte)(value2.r / value2.n), (byte)(value2.g / value2.n), (byte)(value2.b / value2.n), b);
						}
					}
				}
			}
			rgba = array;
			pngStale = true;
		}

		private static int Offset(int size, int idx)
		{
			int num = idx / size;
			int num2 = idx % size;
			return ((size - 1 - num) * size + num2) * 4;
		}

		private static void Put(byte[] buf, int size, int idx, byte r, byte g, byte b, byte a)
		{
			int num = Offset(size, idx);
			buf[num] = r;
			buf[num + 1] = g;
			buf[num + 2] = b;
			buf[num + 3] = a;
		}

		public static string GetStats()
		{
			return Structures.StatsJson;
		}

		public static byte[] GetPng()
		{
			byte[] array = rgba;
			if (array == null)
			{
				return new byte[0];
			}
			if (!pngStale && png != null)
			{
				return png;
			}
			lock (encodeLock)
			{
				if (!pngStale && png != null)
				{
					return png;
				}
				png = Png.Encode(array, WebMapConfig.TEXTURE_SIZE, WebMapConfig.TEXTURE_SIZE, Png.Format.RGBA, fast: true);
				pngStale = false;
				return png;
			}
		}
	}
	internal static class Vehicles
	{
		internal enum Kind
		{
			None,
			Boat,
			Cart
		}

		private struct Entry
		{
			public Kind kind;

			public string name;

			public float x;

			public float z;
		}

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

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

		private static readonly List<Entry> found = new List<Entry>();

		private static string json = "{\"boats\":0,\"carts\":0,\"vehicles\":[]}";

		private static volatile string markersJson = "[]";

		public static Kind Classify(int prefabHash)
		{
			if (kindCache.TryGetValue(prefabHash, out var value))
			{
				return value;
			}
			Kind kind = Kind.None;
			string text = null;
			try
			{
				GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefabHash) : null);
				if ((Object)(object)val != (Object)null)
				{
					if ((Object)(object)val.GetComponent<Ship>() != (Object)null)
					{
						kind = Kind.Boat;
					}
					else if ((Object)(object)val.GetComponent<Vagon>() != (Object)null)
					{
						kind = Kind.Cart;
					}
					if (kind != Kind.None)
					{
						text = ((Object)val).name;
					}
				}
			}
			catch
			{
			}
			kindCache[prefabHash] = kind;
			nameCache[prefabHash] = text ?? "";
			return kind;
		}

		public static void Begin()
		{
			found.Clear();
		}

		public static void Observe(int prefabHash, Kind kind, Vector3 pos)
		{
			//IL_0036: 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)
			nameCache.TryGetValue(prefabHash, out var value);
			found.Add(new Entry
			{
				kind = kind,
				name = (value ?? ""),
				x = pos.x,
				z = pos.z
			});
		}

		private static bool Explored(float x, float z)
		{
			if (!WebMapConfig.REVEAL_ALL)
			{
				return Fog.IsExplored(x, z);
			}
			return true;
		}

		public static void Finish()
		{
			int num = 0;
			int num2 = 0;
			StringBuilder stringBuilder = new StringBuilder();
			StringBuilder stringBuilder2 = new StringBuilder("[");
			stringBuilder.Append("{\"vehicles\":[");
			bool flag = true;
			for (int i = 0; i < found.Count; i++)
			{
				Entry entry = found[i];
				if (WebMapConfig.SHOW_VEHICLES && Explored(entry.x, entry.z))
				{
					if (entry.kind == Kind.Boat)
					{
						num++;
					}
					else
					{
						num2++;
					}
					if (!flag)
					{
						stringBuilder.Append(",");
					}
					flag = false;
					string text = ((entry.kind == Kind.Boat) ? "boat" : "cart");
					string text2 = entry.name.Replace("\"", "");
					stringBuilder.Append(FormattableString.Invariant($"{{\"kind\":\"{text}\",\"name\":\"{text2}\",\"x\":{entry.x:0.#},\"z\":{entry.z:0.#}}}"));
					if (stringBuilder2.Length > 1)
					{
						stringBuilder2.Append(",");
					}
					stringBuilder2.Append(FormattableString.Invariant($"{{\"x\":{entry.x:0.#},\"z\":{entry.z:0.#},\"cat\":\"{text}\",\"icon\":\"{text}\",\"label\":\"{PrettyName(text2)}\"}}"));
				}
			}
			stringBuilder2.Append("]");
			markersJson = stringBuilder2.ToString();
			stringBuilder.Append("],\"boats\":").Append(num).Append(",\"carts\":")
				.Append(num2)
				.Append("}");
			json = stringBuilder.ToString();
		}

		public static string GetJson()
		{
			return json;
		}

		public static string GetMarkersJson()
		{
			return markersJson;
		}

		private static string PrettyName(string prefab)
		{
			return prefab.ToLowerInvariant() switch
			{
				"raft" => "Raft", 
				"karve" => "Karve", 
				"vikingship" => "Longship", 
				"vikingship_ashlands" => "Drakkar", 
				"trailership" => "Trailer ship", 
				"cart" => "Cart", 
				_ => prefab.Replace("_", " "), 
			};
		}
	}
	[BepInPlugin("com.valheimwebmap.server", "WebMap", "2.1.5")]
	public class WebMap : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(ZoneSystem), "Start")]
		private class ZoneSystemPatch
		{
			private static readonly Color DeepWaterColor = new Color(0.36105883f, 0.36105883f, 22f / 51f);

			private static readonly Color ShallowWaterColor = new Color(0.574f, 0.50709206f, 0.47892025f);

			private static readonly Color ShoreColor = new Color(21f / 106f, 0.12241901f, 0.1503943f);

			private static Color GetPixelColor(Biome biome)
			{
				//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_00ab: 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_0033: Invalid comparison between Unknown and I4
				//IL_0051: 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)
				//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
				//IL_006c: Unknown result type (might be due to invalid IL or missing references)
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				//IL_001f: Invalid comparison between Unknown and I4
				//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
				//IL_0035: Unknown result type (might be due to invalid IL or missing references)
				//IL_003b: Invalid comparison between Unknown and I4
				//IL_0081: 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_00c6: Unknown result type (might be due to invalid IL or missing references)
				//IL_0096: Unknown result type (might be due to invalid IL or missing references)
				if ((int)biome <= 16)
				{
					switch (biome - 1)
					{
					default:
						if ((int)biome != 8)
						{
							if ((int)biome != 16)
							{
								break;
							}
							return new Color(0.906f, 0.671f, 0.47f);
						}
						return new Color(0.42f, 0.455f, 0.247f);
					case 0:
						return new Color(0.573f, 0.655f, 0.361f);
					case 1:
						return new Color(0.639f, 0.447f, 0.345f);
					case 3:
						return Color.white;
					case 2:
						break;
					}
				}
				else
				{
					if ((int)biome == 32)
					{
						return new Color(0.69f, 0.192f, 0.192f);
					}
					if ((int)biome == 64)
					{
						return Color.white;
					}
					if ((int)biome == 512)
					{
						return new Color(0.36f, 0.22f, 0.4f);
					}
				}
				return Color.white;
			}

			private static void Postfix(ZoneSystem __instance)
			{
				//IL_008c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0091: Unknown result type (might be due to invalid IL or missing references)
				//IL_0098: 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_00b3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b8: 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_02a0: Unknown result type (might be due to invalid IL or missing references)
				//IL_02a5: Unknown result type (might be due to invalid IL or missing references)
				//IL_02ad: Unknown result type (might be due to invalid IL or missing references)
				//IL_02b3: Expected O, but got Unknown
				//IL_02ba: Expected O, but got Unknown
				//IL_0179: Unknown result type (might be due to invalid IL or missing references)
				//IL_017e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0182: Unknown result type (might be due to invalid IL or missing references)
				//IL_019c: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
				//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
				//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
				//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
				//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
				//IL_021e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0226: Unknown result type (might be due to invalid IL or missing references)
				//IL_022b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0232: Unknown result type (might be due to invalid IL or missing references)
				//IL_0237: Unknown result type (might be due to invalid IL or missing references)
				//IL_0239: Unknown result type (might be due to invalid IL or missing references)
				//IL_023e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0242: Unknown result type (might be due to invalid IL or missing references)
				//IL_0247: Unknown result type (might be due to invalid IL or missing references)
				//IL_0249: Unknown result type (might be due to invalid IL or missing references)
				//IL_024e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0252: Unknown result type (might be due to invalid IL or missing references)
				//IL_0257: Unknown result type (might be due to invalid IL or missing references)
				//IL_025d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0267: Unknown result type (might be due to invalid IL or missing references)
				//IL_0271: Unknown result type (might be due to invalid IL or missing references)
				//IL_027b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0282: Unknown result type (might be due to invalid IL or missing references)
				//IL_0287: Unknown result type (might be due to invalid IL or missing references)
				instance.NewWorld();
				if (mapDataServer.mapImageData != null || !WebMapConfig.LEGACY_MAP)
				{
					return;
				}
				ZLog.Log((object)"WebMap: building legacy world render (once)");
				int tEXTURE_SIZE = WebMapConfig.TEXTURE_SIZE;
				int num = tEXTURE_SIZE / 2;
				float num2 = (float)WebMapConfig.PIXEL_SIZE / 2f;
				Color32[] array = (Color32[])(object)new Color32[tEXTURE_SIZE * tEXTURE_SIZE];
				float[] array2 = new float[tEXTURE_SIZE * tEXTURE_SIZE];
				Color val = default(Color);
				for (int i = 0; i < tEXTURE_SIZE; i++)
				{
					for (int j = 0; j < tEXTURE_SIZE; j++)
					{
						float num3 = (float)((j - num) * WebMapConfig.PIXEL_SIZE) + num2;
						float num4 = (float)((i - num) * WebMapConfig.PIXEL_SIZE) + num2;
						Biome biome = WorldGenerator.instance.GetBiome(num3, num4, 0.02f, false);
						float biomeHeight = WorldGenerator.instance.GetBiomeHeight(biome, num3, num4, ref val, false, true);
						array[i * tEXTURE_SIZE + j] = Color32.op_Implicit(GetPixelColor(biome));
						array2[i * tEXTURE_SIZE + j] = biomeHeight;
					}
				}
				float waterLevel = ZoneSystem.instance.m_waterLevel;
				Vector3 val2 = default(Vector3);
				((Vector3)(ref val2))..ctor(-0.57735f, 0.57735f, 0.57735f);
				Color[] array3 = (Color[])(object)new Color[array.Length];
				for (int k = 0; k < array.Length; k++)
				{
					float num5 = array2[k];
					int num6 = k - tEXTURE_SIZE;
					if (num6 < 0)
					{
						num6 = k;
					}
					int num7 = k + tEXTURE_SIZE;
					if (num7 > array.Length - 1)
					{
						num7 = k;
					}
					int num8 = k + 1;
					if (num8 > array.Length - 1)
					{
						num8 = k;
					}
					int num9 = k - 1;
					if (num9 < 0)
					{
						num9 = k;
					}
					Vector3 val3 = new Vector3(2f, 0f, array2[num8] - array2[num9]);
					Vector3 normalized = ((Vector3)(ref val3)).normalized;
					val3 = new Vector3(0f, 2f, array2[num6] - array2[num7]);
					Vector3 normalized2 = ((Vector3)(ref val3)).normalized;
					float num10 = Vector3.Dot(Vector3.Cross(normalized, normalized2), val2) * 0.25f + 0.75f;
					float num11 = Mathf.Clamp(num5 - waterLevel, 0f, 1f);
					float num12 = Mathf.Clamp((num5 - waterLevel + 2.5f) * 0.5f, 0f, 1f);
					float num13 = Mathf.Clamp((num5 - waterLevel + 12.5f) * 0.1f, 0f, 1f);
					Color val4 = Color.Lerp(ShoreColor, Color32.op_Implicit(array[k]), num11);
					val4 = Color.Lerp(ShallowWaterColor, val4, num12);
					val4 = Color.Lerp(DeepWaterColor, val4, num13);
					array3[k] = new Color(val4.r * num10, val4.g * num10, val4.b * num10, val4.a);
				}
				Texture2D val5 = new Texture2D(tEXTURE_SIZE, tEXTURE_SIZE, (TextureFormat)4, false);
				val5.SetPixels(array3);
				byte[] array4 = ImageConv.EncodeToPNG(val5);
				Object.Destroy((Object)val5);
				mapDataServer.mapImageData = array4;
				mapDataServer.BuildMapJpg();
				try
				{
					File.WriteAllBytes(Path.Combine(worldDataPath, "map.png"), array4);
				}
				catch (Exception ex)
				{
					ZLog.LogError((object)("WebMap: FAILED TO WRITE MAP FILE! " + ex.Message));
				}
			}
		}

		[HarmonyPatch(typeof(ZoneSystem), "Load")]
		private class ZoneSystemLoadPatch
		{
			private unsafe static void Postfix()
			{
				//IL_000a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0018: 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_001e: 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)
				LocationInstance val = default(LocationInstance);
				if (ZoneSystem.instance.FindClosestLocation("StartTemple", Vector3.zero, ref val))
				{
					WebMapConfig.WORLD_START_POS = val.m_position;
					Vector3 wORLD_START_POS = WebMapConfig.WORLD_START_POS;
					ZLog.Log((object)("WebMap: starting point " + ((object)(*(Vector3*)(&wORLD_START_POS))/*cast due to .constrained prefix*/).ToString()));
				}
				else
				{
					ZLog.LogWarning((object)"WebMap: failed to find starting point");
				}
				instance.Online();
				mapDataServer.ListenAsync();
				try
				{
					Fog.RevealVisitedZones();
				}
				catch (Exception ex)
				{
					ZLog.LogWarning((object)("WebMap: visited-zone reveal failed: " + ex.Message));
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "Start")]
		private class ZNetPatchStart
		{
			private static void Postfix(List<ZNetPeer> ___m_peers)
			{
				mapDataServer.players = ___m_peers;
			}
		}

		[HarmonyPatch(typeof(ZNet), "Shutdown")]
		private class ZNetPatchShutdown
		{
			private static void Postfix()
			{
				try
				{
					Stats.Save(force: true);
				}
				catch
				{
				}
				try
				{
					if (Fog.Dirty)
					{
						Fog.Save(Path.Combine(worldDataPath, "fog.png"));
					}
				}
				catch
				{
				}
				TileStore.Stop();
				mapDataServer.Stop();
				instance.NotifyOffline();
			}
		}

		[HarmonyPatch(typeof(ZNet), "SetServer")]
		private class ZNetPatchSetServer
		{
			private static void Postfix(bool server, bool openServer, bool publicServer, string serverName, string password, World world)
			{
				instance.SetServerInfo(openServer, publicServer, serverName, password, world.m_name);
			}
		}

		[HarmonyPatch(typeof(ZNet), "Disconnect")]
		private class ZNetPatchDisconnect
		{
			private static void Prefix(ref ZNetPeer peer)
			{
				if (!peer.m_server && !string.IsNullOrEmpty(peer.m_playerName))
				{
					instance.NotifyLeave(peer);
				}
			}
		}

		[HarmonyPatch(typeof(ZRoutedRpc), "AddPeer")]
		private class ZRoutedRpcAddPeerPatch
		{
			private static void Postfix(ZNetPeer peer)
			{
				if (!peer.m_server && !string.IsNullOrEmpty(peer.m_playerName))
				{
					instance.NotifyJoin(peer);
				}
			}
		}

		[HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")]
		private class ZRoutedRpcRoutePatch
		{
			private static readonly Dictionary<string, float> recent = new Dictionary<string, float>();

			private static void Prefix(ref ZRoutedRpc __instance, RoutedRPCData rpcData)
			{
				if (rpcData == null || rpcData.m_targetPeerID == 0L)
				{
					return;
				}
				try
				{
					if (!IsDuplicate(rpcData))
					{
						RoutedRPCData data = rpcData;
						ZRoutedRpcPatch.Observe(ref __instance, ref data);
					}
				}
				catch (Exception ex)
				{
					ZLog.LogWarning((object)("WebMap: failed observing a routed rpc: " + ex));
				}
			}

			private static bool IsDuplicate(RoutedRPCData d)
			{
				byte[] array = ((d.m_parameters != null) ? d.m_parameters.GetArray() : null);
				uint num = 2166136261u;
				if (array != null)
				{
					byte[] array2 = array;
					foreach (byte b in array2)
					{
						num ^= b;
						num *= 16777619;
					}
				}
				string key = d.m_senderPeerID + ":" + d.m_methodHash + ":" + num;
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (recent.TryGetValue(key, out var value) && realtimeSinceStartup - value < 2f)
				{
					return true;
				}
				recent[key] = realtimeSinceStartup;
				if (recent.Count > 256)
				{
					List<string> list = new List<string>();
					foreach (KeyValuePair<string, float> item in recent)
					{
						if (realtimeSinceStartup - item.Value > 10f)
						{
							list.Add(item.Key);
						}
					}
					foreach (string item2 in list)
					{
						recent.Remove(item2);
					}
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(ZRoutedRpc), "HandleRoutedRPC")]
		private class ZRoutedRpcPatch
		{
			private static readonly string[] ignoreRpc = new string[4] { "DestroyZDO", "SetEvent", "OnTargeted", "Step" };

			private static void Postfix(ref ZRoutedRpc __instance, ref RoutedRPCData data)
			{
				Observe(ref __instance, ref data);
			}

			internal static void Observe(ref ZRoutedRpc __instance, ref RoutedRPCData data)
			{
				//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
				//IL_01f6: Expected O, but got Unknown
				//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
				//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
				//IL_0208: Unknown result type (might be due to invalid IL or missing references)
				//IL_020f: Expected O, but got Unknown
				//IL_0230: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
				//IL_0259: 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_010c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0135: 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_014a: Expected O, but got Unknown
				//IL_0153: Unknown result type (might be due to invalid IL or missing references)
				//IL_015a: Expected O, but got Unknown
				//IL_0127: Unknown result type (might be due to invalid IL or missing references)
				//IL_011f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0183: Unknown result type (might be due to invalid IL or missing references)
				int num = data?.m_methodHash ?? 0;
				if (num == 0)
				{
					return;
				}
				bool flag = num == sayMethodHash || num == StringExtensionMethods.GetStableHashCode("Say");
				bool flag2 = num == chatMessageMethodHash || num == StringExtensionMethods.GetStableHashCode("ChatMessage");
				if (!flag && !flag2)
				{
					if (WebMapConfig.DEBUG)
					{
						string other = StringExtensionMethods_Patch.GetStableHashName(num);
						if (!Array.Exists(ignoreRpc, (string x) => x == other))
						{
							ZLog.Log((object)("RoutedRPC: " + other));
						}
					}
					return;
				}
				ZNetPeer peer = ZNet.instance.GetPeer(data.m_senderPeerID);
				string owner = "";
				if (peer != null)
				{
					try
					{
						owner = peer.m_rpc.GetSocket().GetHostName();
					}
					catch
					{
					}
				}
				if (flag)
				{
					sayMethodHash = data.m_methodHash;
					try
					{
						ZDO val = ((!((ZDOID)(ref data.m_targetZDO)).IsNone()) ? ZDOMan.instance.GetZDO(data.m_targetZDO) : null);
						if (val == null && peer != null)
						{
							val = ZDOMan.instance.GetZDO(peer.m_characterID);
						}
						Vector3 pos = ((val != null) ? val.GetPosition() : (peer?.m_refPos ?? Vector3.zero));
						ZPackage val2 = new ZPackage(data.m_parameters.GetArray());
						int num2 = val2.ReadInt();
						UserInfo val3 = new UserInfo();
						val3.Deserialize(ref val2);
						string message = (val2.ReadString() ?? "").Trim();
						if (!HandleChatCommand(owner, val3.Name, pos, message) && num2 != 0)
						{
							mapDataServer.AddMessage(data.m_senderPeerID, num2, val3.Name, message);
						}
						return;
					}
					catch (Exception ex)
					{
						ZLog.LogWarning((object)("WebMap: failed handling a chat message: " + ex));
						return;
					}
				}
				chatMessageMethodHash = data.m_methodHash;
				try
				{
					ZPackage val4 = new ZPackage(data.m_parameters.GetArray());
					Vector3 val5 = val4.ReadVector3();
					int num3 = val4.ReadInt();
					UserInfo val6 = new UserInfo();
					val6.Deserialize(ref val4);
					if (num3 == 3)
					{
						mapDataServer.BroadcastPing(data.m_senderPeerID, val6.Name, val5);
						return;
					}
					string message2 = (val4.ReadString() ?? "").Trim();
					if (!HandleChatCommand(owner, val6.Name, val5, message2))
					{
						mapDataServer.AddMessage(data.m_senderPeerID, num3, val6.Name, message2);
					}
				}
				catch (Exception ex2)
				{
					if (WebMapConfig.DEBUG)
					{
						ZLog.LogError((object)ex2.ToString());
					}
				}
			}
		}

		public const string GUID = "com.valheimwebmap.server";

		public const string NAME = "WebMap";

		public const string VERSION = "2.1.5";

		private static readonly string[] ALLOWED_PINS = new string[5] { "dot", "fire", "mine", "house", "cave" };

		public DiscordWebHook discordWebHook;

		public static MapDataServer mapDataServer;

		public static string worldDataPath;

		public static string mapDataPath;

		public static string pluginPath;

		public static int sayMethodHash = 0;

		public static int chatMessageMethodHash = 0;

		public static string currentWorldName;

		public static Dictionary<string, object> serverInfo;

		private static Harmony harmony;

		public static WebMap instance;

		public static readonly Regex PinTextFilter = new Regex("[^a-zA-Z0-9 ]", RegexOptions.Compiled);

		public void Awake()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Expected O, but got Unknown
			instance = this;
			harmony = new Harmony("com.valheimwebmap.server");
			harmony.PatchAll(Assembly.GetExecutingAssembly());
			pluginPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			mapDataPath = Path.Combine(pluginPath ?? string.Empty, "map_data");
			Directory.CreateDirectory(mapDataPath);
			WebMapConfig.ReadConfigFile(((BaseUnityPlugin)this).Config);
			discordWebHook = new DiscordWebHook(WebMapConfig.DISCORD_WEBHOOK);
			ZLog.Log((object)"WebMap 2.1.5 loaded");
		}

		public void OnDestroy()
		{
		}

		public void SetServerInfo(bool openServer, bool publicServer, string serverName, string password, string worldName)
		{
			serverInfo = new Dictionary<string, object>
			{
				["openServer"] = openServer,
				["publicServer"] = publicServer,
				["serverName"] = serverName,
				["password"] = password,
				["worldName"] = worldName
			};
		}

		public void NewWorld()
		{
			string worldName = WebMapConfig.GetWorldName();
			bool flag = currentWorldName != null && currentWorldName != worldName;
			worldDataPath = Path.Combine(mapDataPath, worldName);
			Directory.CreateDirectory(worldDataPath);
			if (mapDataServer == null)
			{
				ZLog.Log((object)("WebMap: loading world '" + worldName + "'"));
				mapDataServer = new MapDataServer();
			}
			else if (flag)
			{
				ZLog.Log((object)("WebMap: switching world from '" + currentWorldName + "' to '" + worldName + "'"));
			}
			currentWorldName = worldName;
			try
			{
				string path = Path.Combine(worldDataPath, "map.png");
				if (File.Exists(path))
				{
					mapDataServer.mapImageData = File.ReadAllBytes(path);
					mapDataServer.BuildMapJpg();
				}
			}
			catch (Exception ex)
			{
				ZLog.LogWarning((object)("WebMap: legacy map.png not readable: " + ex.Message));
			}
			Fog.Init(WebMapConfig.TEXTURE_SIZE, WebMapConfig.PIXEL_SIZE);
			string path2 = Path.Combine(worldDataPath, "fog.png");
			if (!Fog.Load(path2))
			{
				ZLog.Log((object)"WebMap: starting a fresh fog of war");
				Fog.Save(path2);
			}
			try
			{
				string path3 = Path.Combine(worldDataPath, "pins.csv");
				if (File.Exists(path3))
				{
					mapDataServer.pins = new List<string>(File.ReadAllLines(path3));
				}
			}
			catch (Exception ex2)
			{
				ZLog.LogWarning((object)("WebMap: pins.csv not readable: " + ex2.Message));
			}
			Stats.Load();
			Events.LoadTail();
			TileStore.Init(worldDataPath);
			ModelStore.Init(mapDataPath);
			if (flag)
			{
				mapDataServer.Reload();
			}
		}

		public void Online()
		{
			StaticCoroutine.Start(PlayerSnapshotLoop());
			StaticCoroutine.Start(UpdateFogLoop());
			StaticCoroutine.Start(SaveLoop());
			StaticCoroutine.Start(WorldSweep.Loop());
			StaticCoroutine.Start(Announce.Pump());
			StaticCoroutine.Start(TileStore.MainThreadPump());
			StaticCoroutine.Start(ModelStore.Pump());
			TileStore.Start();
			int stride = Math.Max(1, (int)(TileMath.TileSpanMeters(WebMapConfig.PRERENDER_ZOOM + 1) / (float)WebMapConfig.PIXEL_SIZE / 2f));
			Fog.ForEachExplored(TileStore.OnExplored, stride);
			Events.Add("server", "Server", "online");
			NotifyOnline();
		}

		public void NotifyOnline()
		{
			try
			{
				string text = AccessTools.Method(typeof(ZNet), "GetServerIP", (Type[])null, (Type[])null)?.Invoke(ZNet.instance, new object[0])?.ToString() ?? "";
				discordWebHook.SendMessage(string.Format("\ud83c\udfae **{0}** is *online* \ud83d\udfe2\n\ud83d\udcbb {1}:{2}\n\ud83d\udd11 {3}\n\ud83d\uddfa {4}", serverInfo["serverName"], text, ZNet.instance.GetHostPort(), serverInfo["password"], WebMapConfig.URL));
			}
			catch (Exception ex)
			{
				if (WebMapConfig.DEBUG)
				{
					ZLog.LogWarning((object)("WebMap: online notice failed: " + ex.Message));
				}
			}
		}

		public void NotifyOffline()
		{
			try
			{
				discordWebHook.SendMessage(string.Format("\ud83c\udfae **{0}** is *offline* \ud83d\udd34", serverInfo["serverName"]));
			}
			catch
			{
			}
		}

		public void NotifyJoin(ZNetPeer peer)
		{
			string arg = "player _" + peer.m_playerName + "_ joined";
			discordWebHook.SendMessage(string.Format("\ud83c\udfae **{0}** {1}", serverInfo["serverName"], arg));
			Events.Add("join", peer.m_playerName, "joined the server");
			Stats.OnJoin(Players.KeyOf(peer), peer.m_playerName);
		}

		public void NotifyLeave(ZNetPeer peer)
		{
			string text = "player _" + peer.m_playerName + "_ left";
			discordWebHook.SendMessage(string.Format("\ud83c\udfae **{0}** {1}", serverInfo["serverName"], text));
			Announce.Enqueue(text);
			Events.Add("leave", peer.m_playerName, "left the server");
			Stats.OnLeave(Players.KeyOf(peer), peer.m_playerName);
			Players.Forget(peer);
		}

		public IEnumerator PlayerSnapshotLoop()
		{
			while (true)
			{
				try
				{
					Players.Refresh(mapDataServer.players);
					Stats.OnTick(Players.Current);
				}
				catch (Exception ex)
				{
					if (WebMapConfig.DEBUG)
					{
						ZLog.LogWarning((object)("WebMap: player snapshot failed: " + ex.Message));
					}
				}
				yield return (object)new WaitForSeconds(WebMapConfig.PLAYER_UPDATE_INTERVAL);
			}
		}

		public IEnumerator UpdateFogLoop()
		{
			float visitedT = 0f;
			while (true)
			{
				yield return (object)new WaitForSeconds(WebMapConfig.UPDATE_FOG_TEXTURE_INTERVAL);
				try
				{
					foreach (Players.Snapshot item in Players.Current)
					{
						if (item.tracked && !item.dead)
						{
							int num = Fog.Reveal(item.x, item.z, WebMapConfig.EXPLORE_RADIUS);
							if (num > 0)
							{
								Stats.OnRevealed(item.key, item.name, num);
							}
						}
					}
					visitedT += WebMapConfig.UPDATE_FOG_TEXTURE_INTERVAL;
					if (visitedT >= 60f)
					{
						visitedT = 0f;
						Fog.RevealVisitedZones();
					}
				}
				catch (Exception ex)
				{
					if (WebMapConfig.DEBUG)
					{
						ZLog.LogWarning((object)("WebMap: fog update failed: " + ex.Message));
					}
				}
			}
		}

		public IEnumerator SaveLoop()
		{
			float fogT = 0f;
			float statsT = 0f;
			while (true)
			{
				yield return (object)new WaitForSeconds(5f);
				fogT += 5f;
				statsT += 5f;
				if (fogT >= WebMapConfig.SAVE_FOG_TEXTURE_INTERVAL)
				{
					fogT = 0f;
					if (Fog.Dirty)
					{
						Fog.Save(Path.Combine(worldDataPath, "fog.png"));
					}
				}
				if (statsT >= WebMapConfig.STATS_SAVE_INTERVAL)
				{
					statsT = 0f;
					Stats.Save();
				}
			}
		}

		public static void SavePins()
		{
			try
			{
				lock (mapDataServer.pins)
				{
					File.WriteAllLines(Path.Combine(worldDataPath, "pins.csv"), mapDataServer.pins);
				}
			}
			catch (Exception ex)
			{
				ZLog.Log((object)("WebMap: FAILED TO WRITE PINS FILE! " + ex.Message));
			}
		}

		public static string CleanPinText(string text, int max = 20)
		{
			text = PinTextFilter.Replace((text ?? "").Trim(), "");
			if (text.Length <= max)
			{
				return text;
			}
			return text.Substring(0, max);
		}

		public static string CleanPinType(string type)
		{
			if (!Array.Exists(ALLOWED_PINS, (string e) => e == (type ?? "").ToLower()))
			{
				return "dot";
			}
			return type.ToLower();
		}

		public static string PlacePin(string owner, string type, string name, Vector3 pos, string text)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			long num = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds();
			string text2 = $"{num}-{Random.Range(1000, 9999)}";
			mapDataServer.AddPin(owner, text2, CleanPinType(type), name, pos, CleanPinText(text));
			int num2;
			lock (mapDataServer.pins)
			{
				num2 = mapDataServer.pins.FindAll((string pin) => pin.StartsWith(owner + ",")).Count - WebMapConfig.MAX_PINS_PER_USER;
			}
			for (int num3 = num2; num3 > 0; num3--)
			{
				int num4;
				lock (mapDataServer.pins)
				{
					num4 = mapDataServer.pins.FindIndex((string pin) => pin.StartsWith(owner + ","));
				}
				if (num4 > -1)
				{
					mapDataServer.RemovePin(num4);
				}
			}
			SavePins();
			return text2;
		}

		public static bool UndoPin(string owner)
		{
			int num;
			lock (mapDataServer.pins)
			{
				num = mapDataServer.pins.FindLastIndex((string pin) => pin.StartsWith(owner + ","));
			}
			if (num < 0)
			{
				return false;
			}
			mapDataServer.RemovePin(num);
			SavePins();
			return true;
		}

		public static bool DeletePinByText(string owner, string text)
		{
			int num;
			lock (mapDataServer.pins)
			{
				num = mapDataServer.pins.FindLastIndex(delegate(string pin)
				{
					string[] array = pin.Split(new char[1] { ',' });
					return array[0] == owner && array[^1] == text;
				});
			}
			if (num < 0)
			{
				return false;
			}
			mapDataServer.RemovePin(num);
			SavePins();
			return true;
		}

		public static bool DeletePinById(string owner, string pinId)
		{
			int num;
			lock (mapDataServer.pins)
			{
				num = mapDataServer.pins.FindIndex(delegate(string pin)
				{
					string[] array = pin.Split(new char[1] { ',' });
					return array.Length > 1 && array[1] == pinId && (owner.Length == 0 || array[0] == owner);
				});
			}
			if (num < 0)
			{
				return false;
			}
			mapDataServer.RemovePin(num);
			SavePins();
			return true;
		}

		private static bool HandleChatCommand(string owner, string name, Vector3 pos, string message)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			string text = message.ToUpper();
			if (text.StartsWith("!PIN"))
			{
				string[] parts = message.Split(new char[1] { ' ' });
				string type = "dot";
				int num = 1;
				if (parts.Length > 1 && Array.Exists(ALLOWED_PINS, (string e) => e == parts[1].ToLower()))
				{
					type = parts[1].ToLower();
					num = 2;
				}
				string text2 = ((num < parts.Length) ? string.Join(" ", parts, num, parts.Length - num) : "");
				PlacePin(owner, type, name, pos, text2);
				return true;
			}
			if (text.StartsWith("!UNDOPIN"))
			{
				UndoPin(owner);
				return true;
			}
			if (text.StartsWith("!DELETEPIN"))
			{
				string[] array = message.Split(new char[1] { ' ' });
				DeletePinByText(owner, (array.Length > 1) ? string.Join(" ", array, 1, array.Length - 1) : "");
				return true;
			}
			return false;
		}
	}
	public class StaticCoroutine
	{
		private class StaticCoroutineRunner : MonoBehaviour
		{
		}

		private static StaticCoroutineRunner runner;

		public static Coroutine Start(IEnumerator coroutine)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)runner == (Object)null)
			{
				runner = new GameObject("[WebMap Coroutines]").AddComponent<StaticCoroutineRunner>();
				Object.DontDestroyOnLoad((Object)(object)((Component)runner).gameObject);
			}
			return ((MonoBehaviour)runner).StartCoroutine(coroutine);
		}
	}
}
namespace WebMap.World
{
	internal static class Fog
	{
		private static byte[] mask;

		private static int size;

		private static int pixelSize;

		private static int half;

		private static int exploredCount;

		private static volatile byte[] pngCache;

		private static byte[] revealedPng;

		private static readonly HashSet<Vector2s> visitedDone = new HashSet<Vector2s>();

		public static bool Dirty { get; private set; }

		public static int Size => size;

		public static int PixelSize => pixelSize;

		public static int ExploredCells => exploredCount;

		public static void Init(int textureSize, int pixel)
		{
			size = textureSize;
			pixelSize = pixel;
			half = size / 2;
			mask = new byte[size * size];
			exploredCount = 0;
			pngCache = null;
			Dirty = false;
		}

		public static bool Load(string path)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			try
			{
				if (!File.Exists(path))
				{
					return false;
				}
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				if (!ImageConv.LoadImage(val, File.ReadAllBytes(path)))
				{
					return false;
				}
				if (((Texture)val).width != size || ((Texture)val).height != size)
				{
					ZLog.LogWarning((object)$"WebMap: fog.png is {((Texture)val).width}x{((Texture)val).height}, expected {size}x{size}; starting a fresh fog");
					Object.Destroy((Object)(object)val);
					return false;
				}
				Color32[] pixels = val.GetPixels32();
				int num = 0;
				for (int i = 0; i < pixels.Length; i++)
				{
					bool flag = pixels[i].r > 127;
					mask[i] = (byte)(flag ? byte.MaxValue : 0);
					if (flag)
					{
						num++;
					}
				}
				exploredCount = num;
				Object.Destroy((Object)(object)val);
				pngCache = null;
				return true;
			}
			catch (Exception ex)
			{
				ZLog.LogWarning((ob

plugins\WebMap\websocket-sharp.dll

Decompiled a week 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);