Index: binary-improvements2/WebServer/src/WebAPI/APIs/Animal.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/Animal.cs	(revision 432)
+++ 	(revision )
@@ -1,47 +1,0 @@
-﻿using System.Collections.Generic;
-using JetBrains.Annotations;
-using Utf8Json;
-using Webserver.LiveData;
-
-namespace Webserver.WebAPI.APIs {
-	[UsedImplicitly]
-	internal class Animal : AbsRestApi {
-		private readonly List<EntityAnimal> entities = new List<EntityAnimal> ();
-
-		private static readonly byte[] jsonKeyId = JsonWriter.GetEncodedPropertyNameWithBeginObject ("id");
-		private static readonly byte[] jsonKeyName = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("name");
-		private static readonly byte[] jsonKeyPosition = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("position");
-
-		protected override void HandleRestGet (RequestContext _context) {
-			PrepareEnvelopedResult (out JsonWriter writer);
-			writer.WriteBeginArray ();
-			
-			lock (entities) {
-				Animals.Instance.Get (entities);
-				
-				for (int i = 0; i < entities.Count; i++) {
-					if (i > 0) {
-						writer.WriteValueSeparator ();
-					}
-					
-					EntityAlive entity = entities [i];
-					Vector3i position = new Vector3i (entity.GetPosition ());
-					
-					writer.WriteRaw (jsonKeyId);
-					writer.WriteInt32 (entity.entityId);
-					
-					writer.WriteRaw (jsonKeyName);
-					writer.WriteString (!string.IsNullOrEmpty (entity.EntityName) ? entity.EntityName : $"animal class #{entity.entityClass}");
-					
-					writer.WriteRaw (jsonKeyPosition);
-					JsonCommons.WritePositionObject (ref writer, position);
-
-					writer.WriteEndObject ();
-				}
-			}
-			
-			writer.WriteEndArray ();
-			SendEnvelopedResult (_context, ref writer);
-		}
-	}
-}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/GameData/Item.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/GameData/Item.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/GameData/Item.cs	(revision 433)
@@ -0,0 +1,63 @@
+﻿using JetBrains.Annotations;
+using Utf8Json;
+using Webserver.Permissions;
+
+namespace Webserver.WebAPI.APIs {
+	[UsedImplicitly]
+	internal class Item : AbsRestApi {
+		private static readonly byte[] jsonKeyName = JsonWriter.GetEncodedPropertyNameWithBeginObject ("name");
+		private static readonly byte[] jsonKeyLocalizedName = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("localizedName");
+		private static readonly byte[] jsonKeyIsBlock = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("isBlock");
+
+		private readonly byte[] allItemsSerialized;
+
+		public Item (Web _parent) {
+			JsonWriter writer = new JsonWriter ();
+			
+			writer.WriteBeginArray ();
+
+			int written = 0;
+			for (int i = 0; i < ItemClass.list.Length; i++) {
+				ItemClass item = ItemClass.list [i];
+				if (item == null) {
+					continue;
+				}
+				
+				if (written > 0) {
+					writer.WriteValueSeparator ();
+				}
+
+				written++;
+
+				string name = item.Name;
+				string localizedName = item.GetLocalizedItemName ();
+				bool isBlock = item.IsBlock ();
+				
+				writer.WriteRaw (jsonKeyName);
+				writer.WriteString (name);
+
+				writer.WriteRaw (jsonKeyLocalizedName);
+				writer.WriteString (localizedName);
+
+				writer.WriteRaw (jsonKeyIsBlock);
+				writer.WriteBoolean (isBlock);
+				
+				writer.WriteEndObject ();
+			}
+
+			writer.WriteEndArray ();
+
+			allItemsSerialized = writer.ToUtf8ByteArray ();
+		}
+
+		protected override void HandleRestGet (RequestContext _context) {
+			PrepareEnvelopedResult (out JsonWriter writer);
+			writer.WriteRaw (allItemsSerialized);
+			SendEnvelopedResult (_context, ref writer);
+		}
+
+		public override int DefaultPermissionLevel () {
+			return AdminWebModules.PermissionLevelGuest;
+		}
+	}
+}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/GameData/Mods.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/GameData/Mods.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/GameData/Mods.cs	(revision 433)
@@ -0,0 +1,91 @@
+using JetBrains.Annotations;
+using Utf8Json;
+using Webserver.Permissions;
+
+namespace Webserver.WebAPI.APIs {
+	[UsedImplicitly]
+	public class Mods : AbsRestApi {
+		private readonly byte[] loadedWebMods;
+
+		public Mods (Web _parent) {
+			JsonWriter writer = new JsonWriter ();
+			writer.WriteBeginArray ();
+
+			for (int i = 0; i < _parent.webMods.Count; i++) {
+				WebMod webMod = _parent.webMods [i];
+
+				if (i > 0) {
+					writer.WriteValueSeparator ();
+				}
+				
+				writer.WriteBeginObject ();
+
+				writeModJson (ref writer, webMod);
+
+				if (webMod.ReactBundle != null || webMod.CssPath != null) {
+					writer.WriteValueSeparator ();
+
+					writer.WritePropertyName ("web");
+					writer.WriteBeginObject ();
+					
+					string webModReactBundle = webMod.ReactBundle;
+					if (webModReactBundle != null) {
+						writer.WritePropertyName ("bundle");
+						writer.WriteString (webModReactBundle);
+					}
+
+					string webModCssFile = webMod.CssPath;
+					if (webModCssFile != null) {
+						if (webModReactBundle != null) {
+							writer.WriteValueSeparator ();
+						}
+
+						writer.WritePropertyName ("css");
+						writer.WriteString (webModCssFile);
+					}
+					
+					writer.WriteEndObject ();
+				}
+
+				writer.WriteEndObject ();
+			}
+
+			writer.WriteEndArray ();
+
+			loadedWebMods = writer.ToUtf8ByteArray ();
+		}
+
+		private void writeModJson (ref JsonWriter _writer, WebMod _webMod) {
+			_writer.WritePropertyName ("name");
+			_writer.WriteString (_webMod.ParentMod.Name);
+			
+			_writer.WriteValueSeparator ();
+			_writer.WritePropertyName ("displayName");
+			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.DisplayName);
+
+			_writer.WriteValueSeparator ();
+			_writer.WritePropertyName ("description");
+			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.Description);
+
+			_writer.WriteValueSeparator ();
+			_writer.WritePropertyName ("author");
+			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.Author);
+
+			_writer.WriteValueSeparator ();
+			_writer.WritePropertyName ("version");
+			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.VersionString);
+
+			_writer.WriteValueSeparator ();
+			_writer.WritePropertyName ("website");
+			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.Website);
+		}
+
+		protected override void HandleRestGet (RequestContext _context) {
+			PrepareEnvelopedResult (out JsonWriter writer);
+			writer.WriteRaw (loadedWebMods);
+			SendEnvelopedResult (_context, ref writer);
+		}
+
+		public override int DefaultPermissionLevel () => AdminWebModules.PermissionLevelGuest;
+	}
+}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/GetLandClaims.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/GetLandClaims.cs	(revision 432)
+++ 	(revision )
@@ -1,78 +1,0 @@
-// using System.Collections.Generic;
-// using System.Net;
-// using AllocsFixes;
-// using AllocsFixes.PersistentData;
-// using JetBrains.Annotations;
-//
-// namespace Webserver.WebAPI.APIs {
-// 	[UsedImplicitly]
-// 	public class GetLandClaims : AbsWebAPI {
-// 		public override void HandleRequest (RequestContext _context) {
-// 			PlatformUserIdentifierAbs requestedUserId = null;
-// 			if (_context.Request.QueryString ["userid"] != null) {
-// 				if (!PlatformUserIdentifierAbs.TryFromCombinedString (_context.Request.QueryString ["userid"], out requestedUserId)) {
-// 					WebUtils.WriteText (_context.Response, "Invalid user id given", HttpStatusCode.BadRequest);
-// 					return;
-// 				}
-// 			}
-//
-// 			// default user, cheap way to avoid 'null reference exception'
-// 			PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
-//
-// 			bool bViewAll = WebConnection.CanViewAllClaims (_context.PermissionLevel);
-//
-// 			JsonObject result = new JsonObject ();
-// 			result.Add ("claimsize",
-// 				new JsonNumber (GamePrefs.GetInt (EnumUtils.Parse<EnumGamePrefs> (nameof (EnumGamePrefs.LandClaimSize)))));
-//
-// 			JsonArray claimOwners = new JsonArray ();
-// 			result.Add ("claimowners", claimOwners);
-//
-// 			LandClaimList.OwnerFilter[] ownerFilters = null;
-// 			if (requestedUserId != null || !bViewAll) {
-// 				if (requestedUserId != null && !bViewAll) {
-// 					ownerFilters = new[] {
-// 						LandClaimList.UserIdFilter (userId),
-// 						LandClaimList.UserIdFilter (requestedUserId)
-// 					};
-// 				} else if (!bViewAll) {
-// 					ownerFilters = new[] {LandClaimList.UserIdFilter (userId)};
-// 				} else {
-// 					ownerFilters = new[] {LandClaimList.UserIdFilter (requestedUserId)};
-// 				}
-// 			}
-//
-// 			LandClaimList.PositionFilter[] posFilters = null;
-//
-// 			Dictionary<Player, List<Vector3i>> claims = LandClaimList.GetLandClaims (ownerFilters, posFilters);
-//
-// 			foreach ((Player player, List<Vector3i> claimPositions) in claims) {
-// 				JsonObject owner = new JsonObject ();
-// 				claimOwners.Add (owner);
-//
-// 				owner.Add ("steamid", new JsonString (player.PlatformId.CombinedString));
-// 				owner.Add ("claimactive", new JsonBoolean (player.LandProtectionActive));
-//
-// 				if (player.Name.Length > 0) {
-// 					owner.Add ("playername", new JsonString (player.Name));
-// 				} else {
-// 					owner.Add ("playername", new JsonNull ());
-// 				}
-//
-// 				JsonArray claimsJson = new JsonArray ();
-// 				owner.Add ("claims", claimsJson);
-//
-// 				foreach (Vector3i v in claimPositions) {
-// 					JsonObject claim = new JsonObject ();
-// 					claim.Add ("x", new JsonNumber (v.x));
-// 					claim.Add ("y", new JsonNumber (v.y));
-// 					claim.Add ("z", new JsonNumber (v.z));
-//
-// 					claimsJson.Add (claim);
-// 				}
-// 			}
-//
-// 			WebUtils.WriteJson (_context.Response, result);
-// 		}
-// 	}
-// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayerInventories.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayerInventories.cs	(revision 432)
+++ 	(revision )
@@ -1,25 +1,0 @@
-// using AllocsFixes.PersistentData;
-// using JetBrains.Annotations;
-//
-// namespace Webserver.WebAPI.APIs {
-// 	[UsedImplicitly]
-// 	public class GetPlayerInventories : AbsWebAPI {
-// 		public override void HandleRequest (RequestContext _context) {
-// 			GetPlayerInventory.GetInventoryArguments (_context.Request, out bool showIconColor, out bool showIconName);
-//
-// 			JsonArray allInventoriesResult = new JsonArray ();
-//
-// 			foreach ((PlatformUserIdentifierAbs userId, Player player) in PersistentContainer.Instance.Players.Dict) {
-// 				if (player == null) {
-// 					continue;
-// 				}
-//
-// 				if (player.IsOnline) {
-// 					allInventoriesResult.Add (GetPlayerInventory.DoPlayer (userId.CombinedString, player, showIconColor, showIconName));
-// 				}
-// 			}
-//
-// 			WebUtils.WriteJson (_context.Response, allInventoriesResult);
-// 		}
-// 	}
-// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayerInventory.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayerInventory.cs	(revision 432)
+++ 	(revision )
@@ -1,128 +1,0 @@
-// using System.Collections.Generic;
-// using System.Net;
-// using AllocsFixes.PersistentData;
-// using JetBrains.Annotations;
-// using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
-//
-// namespace Webserver.WebAPI.APIs {
-// 	[UsedImplicitly]
-// 	public class GetPlayerInventory : AbsWebAPI {
-// 		public override void HandleRequest (RequestContext _context) {
-// 			if (_context.Request.QueryString ["userid"] == null) {
-// 				WebUtils.WriteText (_context.Response, "No user id given", HttpStatusCode.BadRequest);
-// 				return;
-// 			}
-//
-// 			string userIdString = _context.Request.QueryString ["userid"];
-// 			if (!PlatformUserIdentifierAbs.TryFromCombinedString (userIdString, out PlatformUserIdentifierAbs userId)) {
-// 				WebUtils.WriteText (_context.Response, "Invalid user id given", HttpStatusCode.BadRequest);
-// 				return;
-// 			}
-//
-// 			Player p = PersistentContainer.Instance.Players [userId, false];
-// 			if (p == null) {
-// 				WebUtils.WriteText (_context.Response, "Unknown user id given", HttpStatusCode.NotFound);
-// 				return;
-// 			}
-//
-// 			GetInventoryArguments (_context.Request, out bool showIconColor, out bool showIconName);
-//
-// 			JsonObject result = DoPlayer (userIdString, p, showIconColor, showIconName);
-//
-// 			WebUtils.WriteJson (_context.Response, result);
-// 		}
-//
-// 		internal static void GetInventoryArguments (HttpListenerRequest _req, out bool _showIconColor, out bool _showIconName) {
-// 			if (_req.QueryString ["showiconcolor"] == null || !bool.TryParse (_req.QueryString ["showiconcolor"], out _showIconColor)) {
-// 				_showIconColor = true;
-// 			}
-// 			
-// 			if (_req.QueryString ["showiconname"] == null || !bool.TryParse (_req.QueryString ["showiconname"], out _showIconName)) {
-// 				_showIconName = true;
-// 			}
-// 		}
-//
-// 		internal static JsonObject DoPlayer (string _steamId, Player _player, bool _showIconColor, bool _showIconName) {
-// 			AllocsFixes.PersistentData.Inventory inv = _player.Inventory;
-//
-// 			JsonObject result = new JsonObject ();
-//
-// 			JsonArray bag = new JsonArray ();
-// 			JsonArray belt = new JsonArray ();
-// 			JsonObject equipment = new JsonObject ();
-// 			result.Add ("userid", new JsonString (_steamId));
-// 			result.Add ("entityid", new JsonNumber (_player.EntityID));
-// 			result.Add ("playername", new JsonString (_player.Name));
-// 			result.Add ("bag", bag);
-// 			result.Add ("belt", belt);
-// 			result.Add ("equipment", equipment);
-//
-// 			DoInventory (belt, inv.belt, _showIconColor, _showIconName);
-// 			DoInventory (bag, inv.bag, _showIconColor, _showIconName);
-//
-// 			AddEquipment (equipment, "head", inv.equipment, EquipmentSlots.Headgear, _showIconColor, _showIconName);
-// 			AddEquipment (equipment, "eyes", inv.equipment, EquipmentSlots.Eyewear, _showIconColor, _showIconName);
-// 			AddEquipment (equipment, "face", inv.equipment, EquipmentSlots.Face, _showIconColor, _showIconName);
-//
-// 			AddEquipment (equipment, "armor", inv.equipment, EquipmentSlots.ChestArmor, _showIconColor, _showIconName);
-// 			AddEquipment (equipment, "jacket", inv.equipment, EquipmentSlots.Jacket, _showIconColor, _showIconName);
-// 			AddEquipment (equipment, "shirt", inv.equipment, EquipmentSlots.Shirt, _showIconColor, _showIconName);
-//
-// 			AddEquipment (equipment, "legarmor", inv.equipment, EquipmentSlots.LegArmor, _showIconColor, _showIconName);
-// 			AddEquipment (equipment, "pants", inv.equipment, EquipmentSlots.Legs, _showIconColor, _showIconName);
-// 			AddEquipment (equipment, "boots", inv.equipment, EquipmentSlots.Feet, _showIconColor, _showIconName);
-//
-// 			AddEquipment (equipment, "gloves", inv.equipment, EquipmentSlots.Hands, _showIconColor, _showIconName);
-//
-// 			return result;
-// 		}
-//
-// 		private static void DoInventory (JsonArray _jsonRes, List<InvItem> _inv, bool _showIconColor, bool _showIconName) {
-// 			for (int i = 0; i < _inv.Count; i++) {
-// 				_jsonRes.Add (GetJsonForItem (_inv [i], _showIconColor, _showIconName));
-// 			}
-// 		}
-//
-// 		private static void AddEquipment (JsonObject _eq, string _slotname, InvItem[] _items, EquipmentSlots _slot, bool _showIconColor, bool _showIconName) {
-// 			int[] slotindices = XUiM_PlayerEquipment.GetSlotIndicesByEquipmentSlot (_slot);
-//
-// 			for (int i = 0; i < slotindices.Length; i++) {
-// 				if (_items? [slotindices [i]] == null) {
-// 					continue;
-// 				}
-//
-// 				InvItem item = _items [slotindices [i]];
-// 				_eq.Add (_slotname, GetJsonForItem (item, _showIconColor, _showIconName));
-// 				return;
-// 			}
-//
-// 			_eq.Add (_slotname, new JsonNull ());
-// 		}
-//
-// 		private static JsonNode GetJsonForItem (InvItem _item, bool _showIconColor, bool _showIconName) {
-// 			if (_item == null) {
-// 				return new JsonNull ();
-// 			}
-//
-// 			JsonObject jsonItem = new JsonObject ();
-// 			jsonItem.Add ("count", new JsonNumber (_item.count));
-// 			jsonItem.Add ("name", new JsonString (_item.itemName));
-// 			
-// 			if (_showIconName) {
-// 				jsonItem.Add ("icon", new JsonString (_item.icon));
-// 			}
-//
-// 			if (_showIconColor) {
-// 				jsonItem.Add ("iconcolor", new JsonString (_item.iconcolor));
-// 			}
-//
-// 			jsonItem.Add ("quality", new JsonNumber (_item.quality));
-// 			if (_item.quality >= 0) {
-// 				jsonItem.Add ("qualitycolor", new JsonString (QualityInfo.GetQualityColorHex (_item.quality)));
-// 			}
-//
-// 			return jsonItem;
-//
-// 		}
-// 	}
-// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayerList.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayerList.cs	(revision 432)
+++ 	(revision )
@@ -1,263 +1,0 @@
-// using System;
-// using System.Collections.Generic;
-// using System.Linq;
-// using System.Text.RegularExpressions;
-// using AllocsFixes.PersistentData;
-// using JetBrains.Annotations;
-//
-// namespace Webserver.WebAPI.APIs {
-// 	[UsedImplicitly]
-// 	public class GetPlayerList : AbsWebAPI {
-// 		private static readonly Regex numberFilterMatcher =
-// 			new Regex (@"^(>=|=>|>|<=|=<|<|==|=)?\s*([0-9]+(\.[0-9]*)?)$");
-//
-// 		private static readonly UnityEngine.Profiling.CustomSampler jsonSerializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Build");
-//
-// 		public override void HandleRequest (RequestContext _context) {
-// 			AdminTools admTools = GameManager.Instance.adminTools;
-// 			PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
-//
-// 			bool bViewAll = WebConnection.CanViewAllPlayers (_context.PermissionLevel);
-//
-// 			// TODO: Sort (and filter?) prior to converting to JSON ... hard as how to get the correct column's data? (i.e. column name matches JSON object field names, not source data)
-//
-// 			int rowsPerPage = 25;
-// 			if (_context.Request.QueryString ["rowsperpage"] != null) {
-// 				int.TryParse (_context.Request.QueryString ["rowsperpage"], out rowsPerPage);
-// 			}
-//
-// 			int page = 0;
-// 			if (_context.Request.QueryString ["page"] != null) {
-// 				int.TryParse (_context.Request.QueryString ["page"], out page);
-// 			}
-//
-// 			int firstEntry = page * rowsPerPage;
-//
-// 			Players playersList = PersistentContainer.Instance.Players;
-//
-// 			
-// 			List<JsonObject> playerList = new List<JsonObject> ();
-//
-// 			jsonSerializeSampler.Begin ();
-//
-// 			foreach (KeyValuePair<PlatformUserIdentifierAbs, Player> kvp in playersList.Dict) {
-// 				Player p = kvp.Value;
-//
-// 				if (bViewAll || p.PlatformId.Equals (userId)) {
-// 					JsonObject pos = new JsonObject ();
-// 					pos.Add ("x", new JsonNumber (p.LastPosition.x));
-// 					pos.Add ("y", new JsonNumber (p.LastPosition.y));
-// 					pos.Add ("z", new JsonNumber (p.LastPosition.z));
-//
-// 					JsonObject pJson = new JsonObject ();
-// 					pJson.Add ("steamid", new JsonString (kvp.Key.CombinedString));
-// 					pJson.Add ("entityid", new JsonNumber (p.EntityID));
-// 					pJson.Add ("ip", new JsonString (p.IP));
-// 					pJson.Add ("name", new JsonString (p.Name));
-// 					pJson.Add ("online", new JsonBoolean (p.IsOnline));
-// 					pJson.Add ("position", pos);
-//
-// 					pJson.Add ("totalplaytime", new JsonNumber (p.TotalPlayTime));
-// 					pJson.Add ("lastonline",
-// 						new JsonString (p.LastOnline.ToUniversalTime ().ToString ("yyyy-MM-ddTHH:mm:ssZ")));
-// 					pJson.Add ("ping", new JsonNumber (p.IsOnline ? p.ClientInfo.ping : -1));
-//
-// 					JsonBoolean banned = admTools != null ? new JsonBoolean (admTools.IsBanned (kvp.Key, out _, out _)) : new JsonBoolean (false);
-//
-// 					pJson.Add ("banned", banned);
-//
-// 					playerList.Add (pJson);
-// 				}
-// 			}
-//
-// 			jsonSerializeSampler.End ();
-//
-// 			IEnumerable<JsonObject> list = playerList;
-//
-// 			foreach (string key in _context.Request.QueryString.AllKeys) {
-// 				if (!string.IsNullOrEmpty (key) && key.StartsWith ("filter[")) {
-// 					string filterCol = key.Substring (key.IndexOf ('[') + 1);
-// 					filterCol = filterCol.Substring (0, filterCol.Length - 1);
-// 					string filterVal = _context.Request.QueryString.Get (key).Trim ();
-//
-// 					list = ExecuteFilter (list, filterCol, filterVal);
-// 				}
-// 			}
-//
-// 			int totalAfterFilter = list.Count ();
-//
-// 			foreach (string key in _context.Request.QueryString.AllKeys) {
-// 				if (!string.IsNullOrEmpty (key) && key.StartsWith ("sort[")) {
-// 					string sortCol = key.Substring (key.IndexOf ('[') + 1);
-// 					sortCol = sortCol.Substring (0, sortCol.Length - 1);
-// 					string sortVal = _context.Request.QueryString.Get (key);
-//
-// 					list = ExecuteSort (list, sortCol, sortVal == "0");
-// 				}
-// 			}
-//
-// 			list = list.Skip (firstEntry);
-// 			list = list.Take (rowsPerPage);
-//
-//
-// 			JsonArray playersJsResult = new JsonArray ();
-// 			foreach (JsonObject jsO in list) {
-// 				playersJsResult.Add (jsO);
-// 			}
-//
-// 			JsonObject result = new JsonObject ();
-// 			result.Add ("total", new JsonNumber (totalAfterFilter));
-// 			result.Add ("totalUnfiltered", new JsonNumber (playerList.Count));
-// 			result.Add ("firstResult", new JsonNumber (firstEntry));
-// 			result.Add ("players", playersJsResult);
-//
-// 			WebUtils.WriteJson (_context.Response, result);
-// 		}
-//
-// 		private IEnumerable<JsonObject> ExecuteFilter (IEnumerable<JsonObject> _list, string _filterCol,
-// 			string _filterVal) {
-// 			if (!_list.Any()) {
-// 				return _list;
-// 			}
-//
-// 			if (_list.First ().ContainsKey (_filterCol)) {
-// 				Type colType = _list.First () [_filterCol].GetType ();
-// 				if (colType == typeof (JsonNumber)) {
-// 					return ExecuteNumberFilter (_list, _filterCol, _filterVal);
-// 				}
-//
-// 				if (colType == typeof (JsonBoolean)) {
-// 					bool value = StringParsers.ParseBool (_filterVal);
-// 					return _list.Where (_line => ((JsonBoolean) _line [_filterCol]).GetBool () == value);
-// 				}
-//
-// 				if (colType == typeof (JsonString)) {
-// 					// regex-match whole ^string$, replace * by .*, ? by .?, + by .+
-// 					_filterVal = _filterVal.Replace ("*", ".*").Replace ("?", ".?").Replace ("+", ".+");
-// 					_filterVal = "^" + _filterVal + "$";
-//
-// 					//Log.Out ("GetPlayerList: Filter on String with Regex '" + _filterVal + "'");
-// 					Regex matcher = new Regex (_filterVal, RegexOptions.IgnoreCase);
-// 					return _list.Where (_line => matcher.IsMatch (((JsonString) _line [_filterCol]).GetString ()));
-// 				}
-// 			}
-//
-// 			return _list;
-// 		}
-//
-//
-// 		private IEnumerable<JsonObject> ExecuteNumberFilter (IEnumerable<JsonObject> _list, string _filterCol,
-// 			string _filterVal) {
-// 			// allow value (exact match), =, ==, >=, >, <=, <
-// 			Match filterMatch = numberFilterMatcher.Match (_filterVal);
-// 			if (filterMatch.Success) {
-// 				double value = StringParsers.ParseDouble (filterMatch.Groups [2].Value);
-// 				NumberMatchType matchType;
-// 				double epsilon = value / 100000;
-// 				switch (filterMatch.Groups [1].Value) {
-// 					case "":
-// 					case "=":
-// 					case "==":
-// 						matchType = NumberMatchType.Equal;
-// 						break;
-// 					case ">":
-// 						matchType = NumberMatchType.Greater;
-// 						break;
-// 					case ">=":
-// 					case "=>":
-// 						matchType = NumberMatchType.GreaterEqual;
-// 						break;
-// 					case "<":
-// 						matchType = NumberMatchType.Lesser;
-// 						break;
-// 					case "<=":
-// 					case "=<":
-// 						matchType = NumberMatchType.LesserEqual;
-// 						break;
-// 					default:
-// 						matchType = NumberMatchType.Equal;
-// 						break;
-// 				}
-//
-// 				return _list.Where (delegate (JsonObject _line) {
-// 					double objVal = ((JsonNumber) _line [_filterCol]).GetDouble ();
-// 					switch (matchType) {
-// 						case NumberMatchType.Greater:
-// 							return objVal > value;
-// 						case NumberMatchType.GreaterEqual:
-// 							return objVal >= value;
-// 						case NumberMatchType.Lesser:
-// 							return objVal < value;
-// 						case NumberMatchType.LesserEqual:
-// 							return objVal <= value;
-// 						case NumberMatchType.Equal:
-// 						default:
-// 							return NearlyEqual (objVal, value, epsilon);
-// 					}
-// 				});
-// 			}
-//
-// 			global::Log.Out ("[Web] GetPlayerList: ignoring invalid filter for number-column '{0}': '{1}'", _filterCol, _filterVal);
-// 			return _list;
-// 		}
-//
-//
-// 		private IEnumerable<JsonObject> ExecuteSort (IEnumerable<JsonObject> _list, string _sortCol, bool _ascending) {
-// 			if (_list.Count () == 0) {
-// 				return _list;
-// 			}
-//
-// 			if (_list.First ().ContainsKey (_sortCol)) {
-// 				Type colType = _list.First () [_sortCol].GetType ();
-// 				if (colType == typeof (JsonNumber)) {
-// 					if (_ascending) {
-// 						return _list.OrderBy (_line => ((JsonNumber) _line [_sortCol]).GetDouble ());
-// 					}
-//
-// 					return _list.OrderByDescending (_line => ((JsonNumber) _line [_sortCol]).GetDouble ());
-// 				}
-//
-// 				if (colType == typeof (JsonBoolean)) {
-// 					if (_ascending) {
-// 						return _list.OrderBy (_line => ((JsonBoolean) _line [_sortCol]).GetBool ());
-// 					}
-//
-// 					return _list.OrderByDescending (_line => ((JsonBoolean) _line [_sortCol]).GetBool ());
-// 				}
-//
-// 				if (_ascending) {
-// 					return _list.OrderBy (_line => _line [_sortCol].ToString ());
-// 				}
-//
-// 				return _list.OrderByDescending (_line => _line [_sortCol].ToString ());
-// 			}
-//
-// 			return _list;
-// 		}
-//
-//
-// 		private bool NearlyEqual (double _a, double _b, double _epsilon) {
-// 			double absA = Math.Abs (_a);
-// 			double absB = Math.Abs (_b);
-// 			double diff = Math.Abs (_a - _b);
-//
-// 			if (_a == _b) {
-// 				return true;
-// 			}
-//
-// 			if (_a == 0 || _b == 0 || diff < double.Epsilon) {
-// 				return diff < _epsilon;
-// 			}
-//
-// 			return diff / (absA + absB) < _epsilon;
-// 		}
-//
-// 		private enum NumberMatchType {
-// 			Equal,
-// 			Greater,
-// 			GreaterEqual,
-// 			Lesser,
-// 			LesserEqual
-// 		}
-// 	}
-// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayersLocation.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayersLocation.cs	(revision 432)
+++ 	(revision )
@@ -1,61 +1,0 @@
-// using AllocsFixes.PersistentData;
-// using JetBrains.Annotations;
-//
-// namespace Webserver.WebAPI.APIs {
-// 	[UsedImplicitly]
-// 	public class GetPlayersLocation : AbsWebAPI {
-// 		public override void HandleRequest (RequestContext _context) {
-// 			AdminTools admTools = GameManager.Instance.adminTools;
-// 			PlatformUserIdentifierAbs reqUserId = _context.Connection?.UserId;
-//
-// 			bool listOffline = false;
-// 			if (_context.Request.QueryString ["offline"] != null) {
-// 				bool.TryParse (_context.Request.QueryString ["offline"], out listOffline);
-// 			}
-//
-// 			bool bViewAll = WebConnection.CanViewAllPlayers (_context.PermissionLevel);
-//
-// 			JsonArray playersJsResult = new JsonArray ();
-//
-// 			Players playersList = PersistentContainer.Instance.Players;
-//
-// 			foreach ((PlatformUserIdentifierAbs userId, Player player) in playersList.Dict) {
-// 				if (admTools != null) {
-// 					if (admTools.IsBanned (userId, out _, out _)) {
-// 						continue;
-// 					}
-// 				}
-//
-// 				if (!listOffline && !player.IsOnline) {
-// 					continue;
-// 				}
-//
-// 				if (!bViewAll && !player.PlatformId.Equals (reqUserId)) {
-// 					continue;
-// 				}
-//
-// 				JsonObject pos = new JsonObject ();
-// 				pos.Add ("x", new JsonNumber (player.LastPosition.x));
-// 				pos.Add ("y", new JsonNumber (player.LastPosition.y));
-// 				pos.Add ("z", new JsonNumber (player.LastPosition.z));
-//
-// 				JsonObject pJson = new JsonObject ();
-// 				pJson.Add ("steamid", new JsonString (userId.CombinedString));
-//
-// 				//					pJson.Add("entityid", new JSONNumber (p.EntityID));
-// 				//                    pJson.Add("ip", new JSONString (p.IP));
-// 				pJson.Add ("name", new JsonString (player.Name));
-// 				pJson.Add ("online", new JsonBoolean (player.IsOnline));
-// 				pJson.Add ("position", pos);
-//
-// 				//					pJson.Add ("totalplaytime", new JSONNumber (p.TotalPlayTime));
-// 				//					pJson.Add ("lastonline", new JSONString (p.LastOnline.ToString ("s")));
-// 				//					pJson.Add ("ping", new JSONNumber (p.IsOnline ? p.ClientInfo.ping : -1));
-//
-// 				playersJsResult.Add (pJson);
-// 			}
-//
-// 			WebUtils.WriteJson (_context.Response, playersJsResult);
-// 		}
-// 	}
-// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayersOnline.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/GetPlayersOnline.cs	(revision 432)
+++ 	(revision )
@@ -1,46 +1,0 @@
-// using AllocsFixes.PersistentData;
-// using JetBrains.Annotations;
-//
-// namespace Webserver.WebAPI.APIs {
-// 	[UsedImplicitly]
-// 	public class GetPlayersOnline : AbsWebAPI {
-// 		public override void HandleRequest (RequestContext _context) {
-// 			JsonArray players = new JsonArray ();
-//
-// 			World w = GameManager.Instance.World;
-// 			foreach ((int entityId, EntityPlayer entityPlayer) in w.Players.dict) {
-// 				ClientInfo ci = ConnectionManager.Instance.Clients.ForEntityId (entityId);
-// 				Player player = PersistentContainer.Instance.Players [ci.InternalId, false];
-//
-// 				JsonObject pos = new JsonObject ();
-// 				pos.Add ("x", new JsonNumber ((int) entityPlayer.GetPosition ().x));
-// 				pos.Add ("y", new JsonNumber ((int) entityPlayer.GetPosition ().y));
-// 				pos.Add ("z", new JsonNumber ((int) entityPlayer.GetPosition ().z));
-//
-// 				JsonObject p = new JsonObject ();
-// 				p.Add ("steamid", new JsonString (ci.PlatformId.CombinedString));
-// 				p.Add ("entityid", new JsonNumber (ci.entityId));
-// 				p.Add ("ip", new JsonString (ci.ip));
-// 				p.Add ("name", new JsonString (entityPlayer.EntityName));
-// 				p.Add ("online", new JsonBoolean (true));
-// 				p.Add ("position", pos);
-//
-// 				p.Add ("level", new JsonNumber (player?.Level ?? -1));
-// 				p.Add ("health", new JsonNumber (entityPlayer.Health));
-// 				p.Add ("stamina", new JsonNumber (entityPlayer.Stamina));
-// 				p.Add ("zombiekills", new JsonNumber (entityPlayer.KilledZombies));
-// 				p.Add ("playerkills", new JsonNumber (entityPlayer.KilledPlayers));
-// 				p.Add ("playerdeaths", new JsonNumber (entityPlayer.Died));
-// 				p.Add ("score", new JsonNumber (entityPlayer.Score));
-//
-// 				p.Add ("totalplaytime", new JsonNumber (player?.TotalPlayTime ?? -1));
-// 				p.Add ("lastonline", new JsonString (player != null ? player.LastOnline.ToString ("s") : string.Empty));
-// 				p.Add ("ping", new JsonNumber (ci.ping));
-//
-// 				players.Add (p);
-// 			}
-//
-// 			WebUtils.WriteJson (_context.Response, players);
-// 		}
-// 	}
-// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/Hostile.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/Hostile.cs	(revision 432)
+++ 	(revision )
@@ -1,47 +1,0 @@
-﻿using System.Collections.Generic;
-using JetBrains.Annotations;
-using Utf8Json;
-using Webserver.LiveData;
-
-namespace Webserver.WebAPI.APIs {
-	[UsedImplicitly]
-	internal class Hostile : AbsRestApi {
-		private readonly List<EntityEnemy> entities = new List<EntityEnemy> ();
-
-		private static readonly byte[] jsonKeyId = JsonWriter.GetEncodedPropertyNameWithBeginObject ("id");
-		private static readonly byte[] jsonKeyName = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("name");
-		private static readonly byte[] jsonKeyPosition = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("position");
-
-		protected override void HandleRestGet (RequestContext _context) {
-			PrepareEnvelopedResult (out JsonWriter writer);
-			writer.WriteBeginArray ();
-			
-			lock (entities) {
-				Hostiles.Instance.Get (entities);
-				
-				for (int i = 0; i < entities.Count; i++) {
-					if (i > 0) {
-						writer.WriteValueSeparator ();
-					}
-					
-					EntityAlive entity = entities [i];
-					Vector3i position = new Vector3i (entity.GetPosition ());
-					
-					writer.WriteRaw (jsonKeyId);
-					writer.WriteInt32 (entity.entityId);
-					
-					writer.WriteRaw (jsonKeyName);
-					writer.WriteString (!string.IsNullOrEmpty (entity.EntityName) ? entity.EntityName : $"enemy class #{entity.entityClass}");
-					
-					writer.WriteRaw (jsonKeyPosition);
-					JsonCommons.WritePositionObject (ref writer, position);
-
-					writer.WriteEndObject ();
-				}
-			}
-			
-			writer.WriteEndArray ();
-			SendEnvelopedResult (_context, ref writer);
-		}
-	}
-}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/Item.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/Item.cs	(revision 432)
+++ 	(revision )
@@ -1,63 +1,0 @@
-﻿using JetBrains.Annotations;
-using Utf8Json;
-using Webserver.Permissions;
-
-namespace Webserver.WebAPI.APIs {
-	[UsedImplicitly]
-	internal class Item : AbsRestApi {
-		private static readonly byte[] jsonKeyName = JsonWriter.GetEncodedPropertyNameWithBeginObject ("name");
-		private static readonly byte[] jsonKeyLocalizedName = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("localizedName");
-		private static readonly byte[] jsonKeyIsBlock = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("isBlock");
-
-		private readonly byte[] allItemsSerialized;
-
-		public Item (Web _parent) {
-			JsonWriter writer = new JsonWriter ();
-			
-			writer.WriteBeginArray ();
-
-			int written = 0;
-			for (int i = 0; i < ItemClass.list.Length; i++) {
-				ItemClass item = ItemClass.list [i];
-				if (item == null) {
-					continue;
-				}
-				
-				if (written > 0) {
-					writer.WriteValueSeparator ();
-				}
-
-				written++;
-
-				string name = item.Name;
-				string localizedName = item.GetLocalizedItemName ();
-				bool isBlock = item.IsBlock ();
-				
-				writer.WriteRaw (jsonKeyName);
-				writer.WriteString (name);
-
-				writer.WriteRaw (jsonKeyLocalizedName);
-				writer.WriteString (localizedName);
-
-				writer.WriteRaw (jsonKeyIsBlock);
-				writer.WriteBoolean (isBlock);
-				
-				writer.WriteEndObject ();
-			}
-
-			writer.WriteEndArray ();
-
-			allItemsSerialized = writer.ToUtf8ByteArray ();
-		}
-
-		protected override void HandleRestGet (RequestContext _context) {
-			PrepareEnvelopedResult (out JsonWriter writer);
-			writer.WriteRaw (allItemsSerialized);
-			SendEnvelopedResult (_context, ref writer);
-		}
-
-		public override int DefaultPermissionLevel () {
-			return AdminWebModules.PermissionLevelGuest;
-		}
-	}
-}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/Mods.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/Mods.cs	(revision 432)
+++ 	(revision )
@@ -1,91 +1,0 @@
-using JetBrains.Annotations;
-using Utf8Json;
-using Webserver.Permissions;
-
-namespace Webserver.WebAPI.APIs {
-	[UsedImplicitly]
-	public class Mods : AbsRestApi {
-		private readonly byte[] loadedWebMods;
-
-		public Mods (Web _parent) {
-			JsonWriter writer = new JsonWriter ();
-			writer.WriteBeginArray ();
-
-			for (int i = 0; i < _parent.webMods.Count; i++) {
-				WebMod webMod = _parent.webMods [i];
-
-				if (i > 0) {
-					writer.WriteValueSeparator ();
-				}
-				
-				writer.WriteBeginObject ();
-
-				writeModJson (ref writer, webMod);
-
-				if (webMod.ReactBundle != null || webMod.CssPath != null) {
-					writer.WriteValueSeparator ();
-
-					writer.WritePropertyName ("web");
-					writer.WriteBeginObject ();
-					
-					string webModReactBundle = webMod.ReactBundle;
-					if (webModReactBundle != null) {
-						writer.WritePropertyName ("bundle");
-						writer.WriteString (webModReactBundle);
-					}
-
-					string webModCssFile = webMod.CssPath;
-					if (webModCssFile != null) {
-						if (webModReactBundle != null) {
-							writer.WriteValueSeparator ();
-						}
-
-						writer.WritePropertyName ("css");
-						writer.WriteString (webModCssFile);
-					}
-					
-					writer.WriteEndObject ();
-				}
-
-				writer.WriteEndObject ();
-			}
-
-			writer.WriteEndArray ();
-
-			loadedWebMods = writer.ToUtf8ByteArray ();
-		}
-
-		private void writeModJson (ref JsonWriter _writer, WebMod _webMod) {
-			_writer.WritePropertyName ("name");
-			_writer.WriteString (_webMod.ParentMod.Name);
-			
-			_writer.WriteValueSeparator ();
-			_writer.WritePropertyName ("displayName");
-			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.DisplayName);
-
-			_writer.WriteValueSeparator ();
-			_writer.WritePropertyName ("description");
-			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.Description);
-
-			_writer.WriteValueSeparator ();
-			_writer.WritePropertyName ("author");
-			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.Author);
-
-			_writer.WriteValueSeparator ();
-			_writer.WritePropertyName ("version");
-			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.VersionString);
-
-			_writer.WriteValueSeparator ();
-			_writer.WritePropertyName ("website");
-			JsonCommons.WriteStringOrNull (ref _writer, _webMod.ParentMod.Website);
-		}
-
-		protected override void HandleRestGet (RequestContext _context) {
-			PrepareEnvelopedResult (out JsonWriter writer);
-			writer.WriteRaw (loadedWebMods);
-			SendEnvelopedResult (_context, ref writer);
-		}
-
-		public override int DefaultPermissionLevel () => AdminWebModules.PermissionLevelGuest;
-	}
-}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/Permissions/RegisterUser.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/Permissions/RegisterUser.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/Permissions/RegisterUser.cs	(revision 433)
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using System.Text.RegularExpressions;
+using JetBrains.Annotations;
+using Utf8Json;
+using Webserver.Permissions;
+using Webserver.UrlHandlers;
+
+namespace Webserver.WebAPI.APIs {
+	[UsedImplicitly]
+	public class RegisterUser : AbsRestApi {
+		private static readonly byte[] jsonPlayerNameKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("playerName");
+		private static readonly byte[] jsonExpirationKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("expirationSeconds");
+		
+		// TODO: Rate-limiting
+
+		private static readonly Regex userValidationRegex = new Regex ("^\\w{4,16}$", RegexOptions.ECMAScript | RegexOptions.Compiled);
+		private static readonly Regex passValidationRegex = new Regex ("^\\w{4,16}$", RegexOptions.ECMAScript | RegexOptions.Compiled);
+
+		public RegisterUser (Web _parentWeb) : base (_parentWeb) {
+		}
+
+		protected override void HandleRestGet (RequestContext _context) {
+			string token = _context.RequestPath;
+
+			if (string.IsNullOrEmpty (token)) {
+				SendErrorResult (_context, HttpStatusCode.BadRequest, null, "NO_TOKEN");
+				return;
+			}
+
+			if (!UserRegistrationTokens.TryValidate (token, out UserRegistrationTokens.RegistrationData regData)) {
+				SendErrorResult (_context, HttpStatusCode.NotFound, null, "INVALID_OR_EXPIRED_TOKEN");
+				return;
+			}
+
+			PrepareEnvelopedResult (out JsonWriter writer);
+			
+			writer.WriteRaw (jsonPlayerNameKey);
+			writer.WriteString (regData.PlayerName);
+			
+			writer.WriteRaw (jsonExpirationKey);
+			writer.WriteDouble ((regData.ExpiryTime - DateTime.Now).TotalSeconds);
+			
+			writer.WriteEndObject ();
+
+			SendEnvelopedResult (_context, ref writer);
+		}
+
+		protected override void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
+			if (!TryGetJsonField (_jsonInput, "token", out string token)) {
+				SendErrorResult (_context, HttpStatusCode.BadRequest, _jsonInputData, "MISSING_TOKEN");
+				return;
+			}
+
+			if (!TryGetJsonField (_jsonInput, "username", out string username)) {
+				SendErrorResult (_context, HttpStatusCode.BadRequest, _jsonInputData, "MISSING_USERNAME");
+				return;
+			}
+
+			if (!TryGetJsonField (_jsonInput, "password", out string password)) {
+				SendErrorResult (_context, HttpStatusCode.BadRequest, _jsonInputData, "MISSING_PASSWORD");
+				return;
+			}
+
+			if (!UserRegistrationTokens.TryValidate (token, out UserRegistrationTokens.RegistrationData regData)) {
+				SendErrorResult (_context, HttpStatusCode.Unauthorized, null, "INVALID_OR_EXPIRED_TOKEN");
+				return;
+			}
+
+			if (!userValidationRegex.IsMatch (username)) {
+				SendErrorResult (_context, HttpStatusCode.Unauthorized, _jsonInputData, "INVALID_USERNAME");
+				return;
+			}
+			
+			if (!passValidationRegex.IsMatch (password)) {
+				SendErrorResult (_context, HttpStatusCode.Unauthorized, _jsonInputData, "INVALID_PASSWORD");
+				return;
+			}
+
+			if (AdminWebUsers.Instance.GetUsers ().TryGetValue (username, out AdminWebUsers.WebUser existingMapping)) {
+				// Username already exists
+
+				if (!PlatformUserIdentifierAbs.Equals (existingMapping.PlatformUser, regData.PlatformUserId) ||
+				    !PlatformUserIdentifierAbs.Equals (existingMapping.CrossPlatformUser, regData.CrossPlatformUserId)) {
+					// Username already in use by another player
+					SendErrorResult (_context, HttpStatusCode.Unauthorized, _jsonInputData, "DUPLICATE_USERNAME");
+					return;
+				}
+				
+				// Username used by the same player, allow overwriting his existing login
+			}
+			
+			// Log info
+			string crossplatformidString = regData.CrossPlatformUserId == null ? "" : $", crossplatform ID {regData.CrossPlatformUserId.CombinedString}";
+			global::Log.Out ($"[Web] User registered: Username '{username}' for platform ID {regData.PlatformUserId.CombinedString}{crossplatformidString}");
+
+			if (AdminWebUsers.Instance.HasUser (regData.PlatformUserId, regData.CrossPlatformUserId, out AdminWebUsers.WebUser existingUser)) {
+				// Remove existing username of player, only allowing one user per player
+
+				global::Log.Out ($"[Web] Re-registration, replacing existing username '{existingUser.Name}'");
+				AdminWebUsers.Instance.RemoveUser (existingUser.Name);
+			}
+
+			// Add new user
+			AdminWebUsers.Instance.AddUser (username, password, regData.PlatformUserId, regData.CrossPlatformUserId);
+			
+			// Login with new user and return response
+			string remoteEndpointString = _context.Request.RemoteEndPoint!.ToString ();
+			SessionHandler.HandleUserIdLogin (ParentWeb.ConnectionHandler, _context, remoteEndpointString, SessionHandler.userPassLoginName,
+				SessionHandler.userPassErrorPage, username, regData.PlatformUserId, regData.CrossPlatformUserId, false);
+
+			_context.Response.StatusCode = (int)HttpStatusCode.Created;
+			_context.Response.ContentType = WebUtils.MimePlain;
+			_context.Response.ContentEncoding = Encoding.UTF8;
+			_context.Response.ContentLength64 = 0;
+		}
+
+		public override int DefaultPermissionLevel () => AdminWebModules.PermissionLevelGuest;
+	}
+}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/Player.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/Player.cs	(revision 432)
+++ 	(revision )
@@ -1,260 +1,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net;
-using JetBrains.Annotations;
-using Utf8Json;
-using Webserver.Permissions;
-
-namespace Webserver.WebAPI.APIs {
-	[UsedImplicitly]
-	public class Player : AbsRestApi {
-		private static readonly byte[] jsonPlayersKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("players");
-
-		protected override void HandleRestGet (RequestContext _context) {
-			string id = _context.RequestPath;
-			int permissionLevel = _context.PermissionLevel;
-			
- 			bool bViewAll = PermissionUtils.CanViewAllPlayers (permissionLevel);
-            PlatformUserIdentifierAbs nativeUserId = _context.Connection?.UserId;
-
-			// string qs;
-			// if ((qs = _context.Request.QueryString ["count"]) == null || !int.TryParse (qs, out int count)) {
-			// 	count = 50;
-			// }
-
-			PrepareEnvelopedResult (out JsonWriter writer);
-			
-			writer.WriteRaw (jsonPlayersKey);
-			writer.WriteBeginArray ();
-
-			ClientInfo ci;
-			int written = 0;
-
-			if (string.IsNullOrEmpty (id)) {
-				for (int i = 0; i < ConnectionManager.Instance.Clients.List.Count; i++) {
-					ClientInfo clientInfo = ConnectionManager.Instance.Clients.List [i];
-
-					writePlayerJson (ref writer, ref written, clientInfo.PlatformId, bViewAll, nativeUserId);
-				}
-			} else if (int.TryParse (id, out int entityId) && (ci = ConnectionManager.Instance.Clients.ForEntityId (entityId)) != null) {
-				// TODO: Allow finding offline players, also for search other than by EntityId
-				writePlayerJson (ref writer, ref written, ci.PlatformId, bViewAll, nativeUserId);
-			} else {
-				writer.WriteEndArray ();
-				writer.WriteEndObject ();
-				SendEnvelopedResult (_context, ref writer, HttpStatusCode.NotFound);
-				return;
-			}
-
-			writer.WriteEndArray ();
-			writer.WriteEndObject ();
-
-			SendEnvelopedResult (_context, ref writer);
-		}
-
-
-#region JSON keys for player result
-
-		private static readonly byte[] jsonEntityIdKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("entityId");
-		private static readonly byte[] jsonNameKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("name");
-		private static readonly byte[] jsonPlatformIdKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("platformId");
-		private static readonly byte[] jsonCrossplatformIdKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("crossplatformId");
-		private static readonly byte[] jsonTotalPlayTimeKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("totalPlayTimeSeconds");
-		private static readonly byte[] jsonLastOnlineKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("lastOnline");
-		private static readonly byte[] jsonOnlineKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("online");
-		private static readonly byte[] jsonIpKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("ip");
-		private static readonly byte[] jsonPingKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("ping");
-		private static readonly byte[] jsonPositionKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("position");
-		private static readonly byte[] jsonLevelKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("level");
-		private static readonly byte[] jsonHealthKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("health");
-		private static readonly byte[] jsonStaminaKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("stamina");
-		private static readonly byte[] jsonScoreKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("score");
-		private static readonly byte[] jsonDeathsKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("deaths");
-		
-		private static readonly byte[] jsonKillsKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("kills");
-		private static readonly byte[] jsonKillsZombiesKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("zombies");
-		private static readonly byte[] jsonKillsPlayersKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("players");
-		
-		private static readonly byte[] jsonBannedKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("banned");
-		private static readonly byte[] jsonBanActiveKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("banActive");
-		private static readonly byte[] jsonBanReasonKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("reason");
-		private static readonly byte[] jsonBanUntilKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("until");
-
-#endregion
-
-		
-		private void writePlayerJson (ref JsonWriter _writer, ref int _written, PlatformUserIdentifierAbs _nativeUserId,
-			bool _allowViewAll, PlatformUserIdentifierAbs _requesterNativeUserId) {
-			
-			if (!_allowViewAll && (_requesterNativeUserId == null || !_requesterNativeUserId.Equals (_nativeUserId) )) {
-				return;
-			}
-			
-			ClientInfo ci = ConnectionManager.Instance.Clients.ForUserId (_nativeUserId);
-			if (ci == null) {
-				global::Log.Warning ($"[Web] Player.GET: ClientInfo null");
-				return;
-			}
-
-			int entityId = ci.entityId;
-			GameManager.Instance.World.Players.dict.TryGetValue (entityId, out EntityPlayer entity);
-
-			if (entity == null) {
-				global::Log.Warning ($"[Web] Player.GET: EntityPlayer null");
-				return;
-			}
-
-			if (_written > 0) {
-				_writer.WriteValueSeparator ();
-			}
-			_written++;
-
-			bool online = true; // TODO
-
-			_writer.WriteRaw (jsonEntityIdKey);
-			_writer.WriteInt32 (entityId);
-			
-			_writer.WriteRaw (jsonNameKey);
-			_writer.WriteString (ci.playerName);
-			
-			_writer.WriteRaw (jsonPlatformIdKey);
-			JsonCommons.WritePlatformUserIdentifier (ref _writer, _nativeUserId);
-			
-			_writer.WriteRaw (jsonCrossplatformIdKey);
-			JsonCommons.WritePlatformUserIdentifier (ref _writer, ci.CrossplatformId);
-			
-			_writer.WriteRaw (jsonTotalPlayTimeKey);
-			//_writer.WriteLong (player.TotalPlayTime); TODO
-			_writer.WriteNull ();
-			
-			_writer.WriteRaw (jsonLastOnlineKey);
-			//JsonCommons.WriteDateTime (ref _writer, player.LastOnline); TODO
-			_writer.WriteNull ();
-			
-			_writer.WriteRaw (jsonOnlineKey);
-			_writer.WriteBoolean (online);
-			
-			_writer.WriteRaw (jsonIpKey);
-			if (online) {
-				_writer.WriteString (ci.ip);
-				// TODO: Possibly show last used IP?
-			} else {
-				_writer.WriteNull ();
-			}
-			
-			_writer.WriteRaw (jsonPingKey);
-			if (online) {
-				_writer.WriteInt32 (ci.ping);
-			} else {
-				_writer.WriteNull ();
-			}
-			
-			_writer.WriteRaw (jsonPositionKey);
-			if (online) {
-				JsonCommons.WritePositionObject (ref _writer, entity.GetPosition ());
-				// TODO: Possibly show last position?
-			} else {
-				_writer.WriteNull ();
-			}
-
-			_writer.WriteRaw (jsonLevelKey);
-			_writer.WriteNull (); // TODO
-
-			_writer.WriteRaw (jsonHealthKey);
-			_writer.WriteInt32 (entity.Health);
-
-			_writer.WriteRaw (jsonStaminaKey);
-			_writer.WriteSingle (entity.Stamina);
-
-			_writer.WriteRaw (jsonScoreKey);
-			_writer.WriteInt32 (entity.Score);
-
-			_writer.WriteRaw (jsonDeathsKey);
-			_writer.WriteInt32 (entity.Died);
-
-			
-			_writer.WriteRaw (jsonKillsKey);
-			
-			_writer.WriteRaw (jsonKillsZombiesKey);
-			_writer.WriteInt32 (entity.KilledZombies);
-
-			_writer.WriteRaw (jsonKillsPlayersKey);
-			_writer.WriteInt32 (entity.KilledPlayers);
-			
-			_writer.WriteEndObject (); // End of jsonKillsKey
-			
-			
-			_writer.WriteRaw (jsonBannedKey);
-
-			bool banned = GameManager.Instance.adminTools.Blacklist.IsBanned (_nativeUserId, out DateTime bannedUntil, out string banReason);
-			if (!banned && ci.CrossplatformId != null) {
-				banned = GameManager.Instance.adminTools.Blacklist.IsBanned (ci.CrossplatformId, out bannedUntil, out banReason);
-			}
-
-			_writer.WriteRaw (jsonBanActiveKey);
-			_writer.WriteBoolean (banned);
-
-			_writer.WriteRaw (jsonBanReasonKey);
-			if (banned) {
-				_writer.WriteString (banReason);
-			} else {
-				_writer.WriteNull ();
-			}
-
-			_writer.WriteRaw (jsonBanUntilKey);
-			if (banned) {
-				JsonCommons.WriteDateTime (ref _writer, bannedUntil);
-			} else {
-				_writer.WriteNull ();
-			}
-
-			_writer.WriteEndObject (); // End of jsonBannedKeys
-			
-
-			_writer.WriteEndObject (); // End of jsonEntityIdKey
-		}
-
-		protected override void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
-			if (!TryGetJsonField (_jsonInput, "command", out string commandString)) {
-				SendErrorResult (_context, HttpStatusCode.BadRequest, _jsonInputData, "NO_COMMAND");
-				return;
-			}
-
-			WebCommandResult.ResultType responseType = WebCommandResult.ResultType.Full;
-
-			if (TryGetJsonField (_jsonInput, "format", out string formatString)) {
-				if (formatString.EqualsCaseInsensitive ("raw")) {
-					responseType = WebCommandResult.ResultType.Raw;
-				} else if (formatString.EqualsCaseInsensitive ("simple")) {
-					responseType = WebCommandResult.ResultType.ResultOnly;
-				}
-			}
-
-			int commandSepIndex = commandString.IndexOf (' ');
-			string commandPart = commandSepIndex > 0 ? commandString.Substring (0, commandSepIndex) : commandString;
-			string argumentsPart = commandSepIndex > 0
-				? commandString.Substring (commandPart.Length + 1)
-				: "";
-
-			IConsoleCommand command = SdtdConsole.Instance.GetCommand (commandPart, true);
-
-			if (command == null) {
-				SendErrorResult (_context, HttpStatusCode.NotFound, _jsonInputData, "UNKNOWN_COMMAND");
-				return;
-			}
-
-			int commandPermissionLevel = GameManager.Instance.adminTools.Commands.GetCommandPermissionLevel (command.GetCommands ());
-
-			if (_context.PermissionLevel > commandPermissionLevel) {
-				SendErrorResult (_context, HttpStatusCode.Forbidden, _jsonInputData, "NO_PERMISSION");
-				return;
-			}
-
-			_context.Response.SendChunked = true;
-			WebCommandResult wcr = new WebCommandResult (commandPart, argumentsPart, responseType, _context);
-			SdtdConsole.Instance.ExecuteAsync (commandString, wcr);
-		}
-
-		public override int DefaultPermissionLevel () => AdminWebModules.PermissionLevelGuest;
-	}
-}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/RegisterUser.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/RegisterUser.cs	(revision 432)
+++ 	(revision )
@@ -1,100 +1,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Text;
-using System.Text.RegularExpressions;
-using JetBrains.Annotations;
-using Utf8Json;
-using Webserver.Permissions;
-using Webserver.UrlHandlers;
-
-namespace Webserver.WebAPI.APIs {
-	[UsedImplicitly]
-	public class RegisterUser : AbsRestApi {
-		private static readonly byte[] jsonPlayerNameKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("playerName");
-		private static readonly byte[] jsonExpirationKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("expirationSeconds");
-		
-		// TODO: Rate-limiting
-
-		private static readonly Regex userValidationRegex = new Regex ("^\\w{4,16}$", RegexOptions.ECMAScript | RegexOptions.Compiled);
-		private static readonly Regex passValidationRegex = new Regex ("^\\w{4,16}$", RegexOptions.ECMAScript | RegexOptions.Compiled);
-
-		public RegisterUser (Web _parentWeb) : base (_parentWeb) {
-		}
-
-		protected override void HandleRestGet (RequestContext _context) {
-			string token = _context.RequestPath;
-
-			if (string.IsNullOrEmpty (token)) {
-				SendErrorResult (_context, HttpStatusCode.BadRequest, null, "NO_TOKEN");
-				return;
-			}
-
-			if (!UserRegistrationTokens.TryValidate (token, out UserRegistrationTokens.RegistrationData regData)) {
-				SendErrorResult (_context, HttpStatusCode.NotFound, null, "INVALID_OR_EXPIRED_TOKEN");
-				return;
-			}
-
-			PrepareEnvelopedResult (out JsonWriter writer);
-			
-			writer.WriteRaw (jsonPlayerNameKey);
-			writer.WriteString (regData.PlayerName);
-			
-			writer.WriteRaw (jsonExpirationKey);
-			writer.WriteDouble ((regData.ExpiryTime - DateTime.Now).TotalSeconds);
-			
-			writer.WriteEndObject ();
-
-			SendEnvelopedResult (_context, ref writer);
-		}
-
-		protected override void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
-			if (!TryGetJsonField (_jsonInput, "token", out string token)) {
-				SendErrorResult (_context, HttpStatusCode.BadRequest, _jsonInputData, "MISSING_TOKEN");
-				return;
-			}
-
-			if (!TryGetJsonField (_jsonInput, "username", out string username)) {
-				SendErrorResult (_context, HttpStatusCode.BadRequest, _jsonInputData, "MISSING_USERNAME");
-				return;
-			}
-
-			if (!TryGetJsonField (_jsonInput, "password", out string password)) {
-				SendErrorResult (_context, HttpStatusCode.BadRequest, _jsonInputData, "MISSING_PASSWORD");
-				return;
-			}
-
-			if (!UserRegistrationTokens.TryValidate (token, out UserRegistrationTokens.RegistrationData regData)) {
-				SendErrorResult (_context, HttpStatusCode.Unauthorized, null, "INVALID_OR_EXPIRED_TOKEN");
-				return;
-			}
-
-			if (!userValidationRegex.IsMatch (username)) {
-				SendErrorResult (_context, HttpStatusCode.Unauthorized, _jsonInputData, "INVALID_USERNAME");
-				return;
-			}
-			
-			if (!passValidationRegex.IsMatch (password)) {
-				SendErrorResult (_context, HttpStatusCode.Unauthorized, _jsonInputData, "INVALID_PASSWORD");
-				return;
-			}
-			
-			// TODO: Check if username is already used by someone else!
-			// TODO: Remove existing username if player already had one!
-
-			AdminWebUsers.Instance.AddUser (username, password, regData.PlatformUserId, regData.CrossPlatformUserId);
-			
-			string remoteEndpointString = _context.Request.RemoteEndPoint!.ToString ();
-			SessionHandler.HandleUserIdLogin (ParentWeb.ConnectionHandler, _context, remoteEndpointString, SessionHandler.userPassLoginName,
-				SessionHandler.userPassErrorPage, username, regData.PlatformUserId, regData.CrossPlatformUserId, false);
-
-			_context.Response.StatusCode = (int)HttpStatusCode.Created;
-			_context.Response.ContentType = WebUtils.MimePlain;
-			_context.Response.ContentEncoding = Encoding.UTF8;
-			_context.Response.ContentLength64 = 0;
-			// _context.Response.OutputStream.Write (jsonData.Array!, 0, jsonData.Count);
-		}
-
-		public override int DefaultPermissionLevel () => AdminWebModules.PermissionLevelGuest;
-	}
-}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/Animal.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/Animal.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/Animal.cs	(revision 433)
@@ -0,0 +1,47 @@
+﻿using System.Collections.Generic;
+using JetBrains.Annotations;
+using Utf8Json;
+using Webserver.LiveData;
+
+namespace Webserver.WebAPI.APIs {
+	[UsedImplicitly]
+	internal class Animal : AbsRestApi {
+		private readonly List<EntityAnimal> entities = new List<EntityAnimal> ();
+
+		private static readonly byte[] jsonKeyId = JsonWriter.GetEncodedPropertyNameWithBeginObject ("id");
+		private static readonly byte[] jsonKeyName = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("name");
+		private static readonly byte[] jsonKeyPosition = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("position");
+
+		protected override void HandleRestGet (RequestContext _context) {
+			PrepareEnvelopedResult (out JsonWriter writer);
+			writer.WriteBeginArray ();
+			
+			lock (entities) {
+				Animals.Instance.Get (entities);
+				
+				for (int i = 0; i < entities.Count; i++) {
+					if (i > 0) {
+						writer.WriteValueSeparator ();
+					}
+					
+					EntityAlive entity = entities [i];
+					Vector3i position = new Vector3i (entity.GetPosition ());
+					
+					writer.WriteRaw (jsonKeyId);
+					writer.WriteInt32 (entity.entityId);
+					
+					writer.WriteRaw (jsonKeyName);
+					writer.WriteString (!string.IsNullOrEmpty (entity.EntityName) ? entity.EntityName : $"animal class #{entity.entityClass}");
+					
+					writer.WriteRaw (jsonKeyPosition);
+					JsonCommons.WritePositionObject (ref writer, position);
+
+					writer.WriteEndObject ();
+				}
+			}
+			
+			writer.WriteEndArray ();
+			SendEnvelopedResult (_context, ref writer);
+		}
+	}
+}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetLandClaims.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetLandClaims.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetLandClaims.cs	(revision 433)
@@ -0,0 +1,78 @@
+// using System.Collections.Generic;
+// using System.Net;
+// using AllocsFixes;
+// using AllocsFixes.PersistentData;
+// using JetBrains.Annotations;
+//
+// namespace Webserver.WebAPI.APIs {
+// 	[UsedImplicitly]
+// 	public class GetLandClaims : AbsWebAPI {
+// 		public override void HandleRequest (RequestContext _context) {
+// 			PlatformUserIdentifierAbs requestedUserId = null;
+// 			if (_context.Request.QueryString ["userid"] != null) {
+// 				if (!PlatformUserIdentifierAbs.TryFromCombinedString (_context.Request.QueryString ["userid"], out requestedUserId)) {
+// 					WebUtils.WriteText (_context.Response, "Invalid user id given", HttpStatusCode.BadRequest);
+// 					return;
+// 				}
+// 			}
+//
+// 			// default user, cheap way to avoid 'null reference exception'
+// 			PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
+//
+// 			bool bViewAll = WebConnection.CanViewAllClaims (_context.PermissionLevel);
+//
+// 			JsonObject result = new JsonObject ();
+// 			result.Add ("claimsize",
+// 				new JsonNumber (GamePrefs.GetInt (EnumUtils.Parse<EnumGamePrefs> (nameof (EnumGamePrefs.LandClaimSize)))));
+//
+// 			JsonArray claimOwners = new JsonArray ();
+// 			result.Add ("claimowners", claimOwners);
+//
+// 			LandClaimList.OwnerFilter[] ownerFilters = null;
+// 			if (requestedUserId != null || !bViewAll) {
+// 				if (requestedUserId != null && !bViewAll) {
+// 					ownerFilters = new[] {
+// 						LandClaimList.UserIdFilter (userId),
+// 						LandClaimList.UserIdFilter (requestedUserId)
+// 					};
+// 				} else if (!bViewAll) {
+// 					ownerFilters = new[] {LandClaimList.UserIdFilter (userId)};
+// 				} else {
+// 					ownerFilters = new[] {LandClaimList.UserIdFilter (requestedUserId)};
+// 				}
+// 			}
+//
+// 			LandClaimList.PositionFilter[] posFilters = null;
+//
+// 			Dictionary<Player, List<Vector3i>> claims = LandClaimList.GetLandClaims (ownerFilters, posFilters);
+//
+// 			foreach ((Player player, List<Vector3i> claimPositions) in claims) {
+// 				JsonObject owner = new JsonObject ();
+// 				claimOwners.Add (owner);
+//
+// 				owner.Add ("steamid", new JsonString (player.PlatformId.CombinedString));
+// 				owner.Add ("claimactive", new JsonBoolean (player.LandProtectionActive));
+//
+// 				if (player.Name.Length > 0) {
+// 					owner.Add ("playername", new JsonString (player.Name));
+// 				} else {
+// 					owner.Add ("playername", new JsonNull ());
+// 				}
+//
+// 				JsonArray claimsJson = new JsonArray ();
+// 				owner.Add ("claims", claimsJson);
+//
+// 				foreach (Vector3i v in claimPositions) {
+// 					JsonObject claim = new JsonObject ();
+// 					claim.Add ("x", new JsonNumber (v.x));
+// 					claim.Add ("y", new JsonNumber (v.y));
+// 					claim.Add ("z", new JsonNumber (v.z));
+//
+// 					claimsJson.Add (claim);
+// 				}
+// 			}
+//
+// 			WebUtils.WriteJson (_context.Response, result);
+// 		}
+// 	}
+// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayerInventories.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayerInventories.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayerInventories.cs	(revision 433)
@@ -0,0 +1,25 @@
+// using AllocsFixes.PersistentData;
+// using JetBrains.Annotations;
+//
+// namespace Webserver.WebAPI.APIs {
+// 	[UsedImplicitly]
+// 	public class GetPlayerInventories : AbsWebAPI {
+// 		public override void HandleRequest (RequestContext _context) {
+// 			GetPlayerInventory.GetInventoryArguments (_context.Request, out bool showIconColor, out bool showIconName);
+//
+// 			JsonArray allInventoriesResult = new JsonArray ();
+//
+// 			foreach ((PlatformUserIdentifierAbs userId, Player player) in PersistentContainer.Instance.Players.Dict) {
+// 				if (player == null) {
+// 					continue;
+// 				}
+//
+// 				if (player.IsOnline) {
+// 					allInventoriesResult.Add (GetPlayerInventory.DoPlayer (userId.CombinedString, player, showIconColor, showIconName));
+// 				}
+// 			}
+//
+// 			WebUtils.WriteJson (_context.Response, allInventoriesResult);
+// 		}
+// 	}
+// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayerInventory.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayerInventory.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayerInventory.cs	(revision 433)
@@ -0,0 +1,128 @@
+// using System.Collections.Generic;
+// using System.Net;
+// using AllocsFixes.PersistentData;
+// using JetBrains.Annotations;
+// using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
+//
+// namespace Webserver.WebAPI.APIs {
+// 	[UsedImplicitly]
+// 	public class GetPlayerInventory : AbsWebAPI {
+// 		public override void HandleRequest (RequestContext _context) {
+// 			if (_context.Request.QueryString ["userid"] == null) {
+// 				WebUtils.WriteText (_context.Response, "No user id given", HttpStatusCode.BadRequest);
+// 				return;
+// 			}
+//
+// 			string userIdString = _context.Request.QueryString ["userid"];
+// 			if (!PlatformUserIdentifierAbs.TryFromCombinedString (userIdString, out PlatformUserIdentifierAbs userId)) {
+// 				WebUtils.WriteText (_context.Response, "Invalid user id given", HttpStatusCode.BadRequest);
+// 				return;
+// 			}
+//
+// 			Player p = PersistentContainer.Instance.Players [userId, false];
+// 			if (p == null) {
+// 				WebUtils.WriteText (_context.Response, "Unknown user id given", HttpStatusCode.NotFound);
+// 				return;
+// 			}
+//
+// 			GetInventoryArguments (_context.Request, out bool showIconColor, out bool showIconName);
+//
+// 			JsonObject result = DoPlayer (userIdString, p, showIconColor, showIconName);
+//
+// 			WebUtils.WriteJson (_context.Response, result);
+// 		}
+//
+// 		internal static void GetInventoryArguments (HttpListenerRequest _req, out bool _showIconColor, out bool _showIconName) {
+// 			if (_req.QueryString ["showiconcolor"] == null || !bool.TryParse (_req.QueryString ["showiconcolor"], out _showIconColor)) {
+// 				_showIconColor = true;
+// 			}
+// 			
+// 			if (_req.QueryString ["showiconname"] == null || !bool.TryParse (_req.QueryString ["showiconname"], out _showIconName)) {
+// 				_showIconName = true;
+// 			}
+// 		}
+//
+// 		internal static JsonObject DoPlayer (string _steamId, Player _player, bool _showIconColor, bool _showIconName) {
+// 			AllocsFixes.PersistentData.Inventory inv = _player.Inventory;
+//
+// 			JsonObject result = new JsonObject ();
+//
+// 			JsonArray bag = new JsonArray ();
+// 			JsonArray belt = new JsonArray ();
+// 			JsonObject equipment = new JsonObject ();
+// 			result.Add ("userid", new JsonString (_steamId));
+// 			result.Add ("entityid", new JsonNumber (_player.EntityID));
+// 			result.Add ("playername", new JsonString (_player.Name));
+// 			result.Add ("bag", bag);
+// 			result.Add ("belt", belt);
+// 			result.Add ("equipment", equipment);
+//
+// 			DoInventory (belt, inv.belt, _showIconColor, _showIconName);
+// 			DoInventory (bag, inv.bag, _showIconColor, _showIconName);
+//
+// 			AddEquipment (equipment, "head", inv.equipment, EquipmentSlots.Headgear, _showIconColor, _showIconName);
+// 			AddEquipment (equipment, "eyes", inv.equipment, EquipmentSlots.Eyewear, _showIconColor, _showIconName);
+// 			AddEquipment (equipment, "face", inv.equipment, EquipmentSlots.Face, _showIconColor, _showIconName);
+//
+// 			AddEquipment (equipment, "armor", inv.equipment, EquipmentSlots.ChestArmor, _showIconColor, _showIconName);
+// 			AddEquipment (equipment, "jacket", inv.equipment, EquipmentSlots.Jacket, _showIconColor, _showIconName);
+// 			AddEquipment (equipment, "shirt", inv.equipment, EquipmentSlots.Shirt, _showIconColor, _showIconName);
+//
+// 			AddEquipment (equipment, "legarmor", inv.equipment, EquipmentSlots.LegArmor, _showIconColor, _showIconName);
+// 			AddEquipment (equipment, "pants", inv.equipment, EquipmentSlots.Legs, _showIconColor, _showIconName);
+// 			AddEquipment (equipment, "boots", inv.equipment, EquipmentSlots.Feet, _showIconColor, _showIconName);
+//
+// 			AddEquipment (equipment, "gloves", inv.equipment, EquipmentSlots.Hands, _showIconColor, _showIconName);
+//
+// 			return result;
+// 		}
+//
+// 		private static void DoInventory (JsonArray _jsonRes, List<InvItem> _inv, bool _showIconColor, bool _showIconName) {
+// 			for (int i = 0; i < _inv.Count; i++) {
+// 				_jsonRes.Add (GetJsonForItem (_inv [i], _showIconColor, _showIconName));
+// 			}
+// 		}
+//
+// 		private static void AddEquipment (JsonObject _eq, string _slotname, InvItem[] _items, EquipmentSlots _slot, bool _showIconColor, bool _showIconName) {
+// 			int[] slotindices = XUiM_PlayerEquipment.GetSlotIndicesByEquipmentSlot (_slot);
+//
+// 			for (int i = 0; i < slotindices.Length; i++) {
+// 				if (_items? [slotindices [i]] == null) {
+// 					continue;
+// 				}
+//
+// 				InvItem item = _items [slotindices [i]];
+// 				_eq.Add (_slotname, GetJsonForItem (item, _showIconColor, _showIconName));
+// 				return;
+// 			}
+//
+// 			_eq.Add (_slotname, new JsonNull ());
+// 		}
+//
+// 		private static JsonNode GetJsonForItem (InvItem _item, bool _showIconColor, bool _showIconName) {
+// 			if (_item == null) {
+// 				return new JsonNull ();
+// 			}
+//
+// 			JsonObject jsonItem = new JsonObject ();
+// 			jsonItem.Add ("count", new JsonNumber (_item.count));
+// 			jsonItem.Add ("name", new JsonString (_item.itemName));
+// 			
+// 			if (_showIconName) {
+// 				jsonItem.Add ("icon", new JsonString (_item.icon));
+// 			}
+//
+// 			if (_showIconColor) {
+// 				jsonItem.Add ("iconcolor", new JsonString (_item.iconcolor));
+// 			}
+//
+// 			jsonItem.Add ("quality", new JsonNumber (_item.quality));
+// 			if (_item.quality >= 0) {
+// 				jsonItem.Add ("qualitycolor", new JsonString (QualityInfo.GetQualityColorHex (_item.quality)));
+// 			}
+//
+// 			return jsonItem;
+//
+// 		}
+// 	}
+// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayerList.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayerList.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayerList.cs	(revision 433)
@@ -0,0 +1,263 @@
+// using System;
+// using System.Collections.Generic;
+// using System.Linq;
+// using System.Text.RegularExpressions;
+// using AllocsFixes.PersistentData;
+// using JetBrains.Annotations;
+//
+// namespace Webserver.WebAPI.APIs {
+// 	[UsedImplicitly]
+// 	public class GetPlayerList : AbsWebAPI {
+// 		private static readonly Regex numberFilterMatcher =
+// 			new Regex (@"^(>=|=>|>|<=|=<|<|==|=)?\s*([0-9]+(\.[0-9]*)?)$");
+//
+// 		private static readonly UnityEngine.Profiling.CustomSampler jsonSerializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Build");
+//
+// 		public override void HandleRequest (RequestContext _context) {
+// 			AdminTools admTools = GameManager.Instance.adminTools;
+// 			PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
+//
+// 			bool bViewAll = WebConnection.CanViewAllPlayers (_context.PermissionLevel);
+//
+// 			// TODO: Sort (and filter?) prior to converting to JSON ... hard as how to get the correct column's data? (i.e. column name matches JSON object field names, not source data)
+//
+// 			int rowsPerPage = 25;
+// 			if (_context.Request.QueryString ["rowsperpage"] != null) {
+// 				int.TryParse (_context.Request.QueryString ["rowsperpage"], out rowsPerPage);
+// 			}
+//
+// 			int page = 0;
+// 			if (_context.Request.QueryString ["page"] != null) {
+// 				int.TryParse (_context.Request.QueryString ["page"], out page);
+// 			}
+//
+// 			int firstEntry = page * rowsPerPage;
+//
+// 			Players playersList = PersistentContainer.Instance.Players;
+//
+// 			
+// 			List<JsonObject> playerList = new List<JsonObject> ();
+//
+// 			jsonSerializeSampler.Begin ();
+//
+// 			foreach (KeyValuePair<PlatformUserIdentifierAbs, Player> kvp in playersList.Dict) {
+// 				Player p = kvp.Value;
+//
+// 				if (bViewAll || p.PlatformId.Equals (userId)) {
+// 					JsonObject pos = new JsonObject ();
+// 					pos.Add ("x", new JsonNumber (p.LastPosition.x));
+// 					pos.Add ("y", new JsonNumber (p.LastPosition.y));
+// 					pos.Add ("z", new JsonNumber (p.LastPosition.z));
+//
+// 					JsonObject pJson = new JsonObject ();
+// 					pJson.Add ("steamid", new JsonString (kvp.Key.CombinedString));
+// 					pJson.Add ("entityid", new JsonNumber (p.EntityID));
+// 					pJson.Add ("ip", new JsonString (p.IP));
+// 					pJson.Add ("name", new JsonString (p.Name));
+// 					pJson.Add ("online", new JsonBoolean (p.IsOnline));
+// 					pJson.Add ("position", pos);
+//
+// 					pJson.Add ("totalplaytime", new JsonNumber (p.TotalPlayTime));
+// 					pJson.Add ("lastonline",
+// 						new JsonString (p.LastOnline.ToUniversalTime ().ToString ("yyyy-MM-ddTHH:mm:ssZ")));
+// 					pJson.Add ("ping", new JsonNumber (p.IsOnline ? p.ClientInfo.ping : -1));
+//
+// 					JsonBoolean banned = admTools != null ? new JsonBoolean (admTools.IsBanned (kvp.Key, out _, out _)) : new JsonBoolean (false);
+//
+// 					pJson.Add ("banned", banned);
+//
+// 					playerList.Add (pJson);
+// 				}
+// 			}
+//
+// 			jsonSerializeSampler.End ();
+//
+// 			IEnumerable<JsonObject> list = playerList;
+//
+// 			foreach (string key in _context.Request.QueryString.AllKeys) {
+// 				if (!string.IsNullOrEmpty (key) && key.StartsWith ("filter[")) {
+// 					string filterCol = key.Substring (key.IndexOf ('[') + 1);
+// 					filterCol = filterCol.Substring (0, filterCol.Length - 1);
+// 					string filterVal = _context.Request.QueryString.Get (key).Trim ();
+//
+// 					list = ExecuteFilter (list, filterCol, filterVal);
+// 				}
+// 			}
+//
+// 			int totalAfterFilter = list.Count ();
+//
+// 			foreach (string key in _context.Request.QueryString.AllKeys) {
+// 				if (!string.IsNullOrEmpty (key) && key.StartsWith ("sort[")) {
+// 					string sortCol = key.Substring (key.IndexOf ('[') + 1);
+// 					sortCol = sortCol.Substring (0, sortCol.Length - 1);
+// 					string sortVal = _context.Request.QueryString.Get (key);
+//
+// 					list = ExecuteSort (list, sortCol, sortVal == "0");
+// 				}
+// 			}
+//
+// 			list = list.Skip (firstEntry);
+// 			list = list.Take (rowsPerPage);
+//
+//
+// 			JsonArray playersJsResult = new JsonArray ();
+// 			foreach (JsonObject jsO in list) {
+// 				playersJsResult.Add (jsO);
+// 			}
+//
+// 			JsonObject result = new JsonObject ();
+// 			result.Add ("total", new JsonNumber (totalAfterFilter));
+// 			result.Add ("totalUnfiltered", new JsonNumber (playerList.Count));
+// 			result.Add ("firstResult", new JsonNumber (firstEntry));
+// 			result.Add ("players", playersJsResult);
+//
+// 			WebUtils.WriteJson (_context.Response, result);
+// 		}
+//
+// 		private IEnumerable<JsonObject> ExecuteFilter (IEnumerable<JsonObject> _list, string _filterCol,
+// 			string _filterVal) {
+// 			if (!_list.Any()) {
+// 				return _list;
+// 			}
+//
+// 			if (_list.First ().ContainsKey (_filterCol)) {
+// 				Type colType = _list.First () [_filterCol].GetType ();
+// 				if (colType == typeof (JsonNumber)) {
+// 					return ExecuteNumberFilter (_list, _filterCol, _filterVal);
+// 				}
+//
+// 				if (colType == typeof (JsonBoolean)) {
+// 					bool value = StringParsers.ParseBool (_filterVal);
+// 					return _list.Where (_line => ((JsonBoolean) _line [_filterCol]).GetBool () == value);
+// 				}
+//
+// 				if (colType == typeof (JsonString)) {
+// 					// regex-match whole ^string$, replace * by .*, ? by .?, + by .+
+// 					_filterVal = _filterVal.Replace ("*", ".*").Replace ("?", ".?").Replace ("+", ".+");
+// 					_filterVal = "^" + _filterVal + "$";
+//
+// 					//Log.Out ("GetPlayerList: Filter on String with Regex '" + _filterVal + "'");
+// 					Regex matcher = new Regex (_filterVal, RegexOptions.IgnoreCase);
+// 					return _list.Where (_line => matcher.IsMatch (((JsonString) _line [_filterCol]).GetString ()));
+// 				}
+// 			}
+//
+// 			return _list;
+// 		}
+//
+//
+// 		private IEnumerable<JsonObject> ExecuteNumberFilter (IEnumerable<JsonObject> _list, string _filterCol,
+// 			string _filterVal) {
+// 			// allow value (exact match), =, ==, >=, >, <=, <
+// 			Match filterMatch = numberFilterMatcher.Match (_filterVal);
+// 			if (filterMatch.Success) {
+// 				double value = StringParsers.ParseDouble (filterMatch.Groups [2].Value);
+// 				NumberMatchType matchType;
+// 				double epsilon = value / 100000;
+// 				switch (filterMatch.Groups [1].Value) {
+// 					case "":
+// 					case "=":
+// 					case "==":
+// 						matchType = NumberMatchType.Equal;
+// 						break;
+// 					case ">":
+// 						matchType = NumberMatchType.Greater;
+// 						break;
+// 					case ">=":
+// 					case "=>":
+// 						matchType = NumberMatchType.GreaterEqual;
+// 						break;
+// 					case "<":
+// 						matchType = NumberMatchType.Lesser;
+// 						break;
+// 					case "<=":
+// 					case "=<":
+// 						matchType = NumberMatchType.LesserEqual;
+// 						break;
+// 					default:
+// 						matchType = NumberMatchType.Equal;
+// 						break;
+// 				}
+//
+// 				return _list.Where (delegate (JsonObject _line) {
+// 					double objVal = ((JsonNumber) _line [_filterCol]).GetDouble ();
+// 					switch (matchType) {
+// 						case NumberMatchType.Greater:
+// 							return objVal > value;
+// 						case NumberMatchType.GreaterEqual:
+// 							return objVal >= value;
+// 						case NumberMatchType.Lesser:
+// 							return objVal < value;
+// 						case NumberMatchType.LesserEqual:
+// 							return objVal <= value;
+// 						case NumberMatchType.Equal:
+// 						default:
+// 							return NearlyEqual (objVal, value, epsilon);
+// 					}
+// 				});
+// 			}
+//
+// 			global::Log.Out ("[Web] GetPlayerList: ignoring invalid filter for number-column '{0}': '{1}'", _filterCol, _filterVal);
+// 			return _list;
+// 		}
+//
+//
+// 		private IEnumerable<JsonObject> ExecuteSort (IEnumerable<JsonObject> _list, string _sortCol, bool _ascending) {
+// 			if (_list.Count () == 0) {
+// 				return _list;
+// 			}
+//
+// 			if (_list.First ().ContainsKey (_sortCol)) {
+// 				Type colType = _list.First () [_sortCol].GetType ();
+// 				if (colType == typeof (JsonNumber)) {
+// 					if (_ascending) {
+// 						return _list.OrderBy (_line => ((JsonNumber) _line [_sortCol]).GetDouble ());
+// 					}
+//
+// 					return _list.OrderByDescending (_line => ((JsonNumber) _line [_sortCol]).GetDouble ());
+// 				}
+//
+// 				if (colType == typeof (JsonBoolean)) {
+// 					if (_ascending) {
+// 						return _list.OrderBy (_line => ((JsonBoolean) _line [_sortCol]).GetBool ());
+// 					}
+//
+// 					return _list.OrderByDescending (_line => ((JsonBoolean) _line [_sortCol]).GetBool ());
+// 				}
+//
+// 				if (_ascending) {
+// 					return _list.OrderBy (_line => _line [_sortCol].ToString ());
+// 				}
+//
+// 				return _list.OrderByDescending (_line => _line [_sortCol].ToString ());
+// 			}
+//
+// 			return _list;
+// 		}
+//
+//
+// 		private bool NearlyEqual (double _a, double _b, double _epsilon) {
+// 			double absA = Math.Abs (_a);
+// 			double absB = Math.Abs (_b);
+// 			double diff = Math.Abs (_a - _b);
+//
+// 			if (_a == _b) {
+// 				return true;
+// 			}
+//
+// 			if (_a == 0 || _b == 0 || diff < double.Epsilon) {
+// 				return diff < _epsilon;
+// 			}
+//
+// 			return diff / (absA + absB) < _epsilon;
+// 		}
+//
+// 		private enum NumberMatchType {
+// 			Equal,
+// 			Greater,
+// 			GreaterEqual,
+// 			Lesser,
+// 			LesserEqual
+// 		}
+// 	}
+// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayersLocation.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayersLocation.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayersLocation.cs	(revision 433)
@@ -0,0 +1,61 @@
+// using AllocsFixes.PersistentData;
+// using JetBrains.Annotations;
+//
+// namespace Webserver.WebAPI.APIs {
+// 	[UsedImplicitly]
+// 	public class GetPlayersLocation : AbsWebAPI {
+// 		public override void HandleRequest (RequestContext _context) {
+// 			AdminTools admTools = GameManager.Instance.adminTools;
+// 			PlatformUserIdentifierAbs reqUserId = _context.Connection?.UserId;
+//
+// 			bool listOffline = false;
+// 			if (_context.Request.QueryString ["offline"] != null) {
+// 				bool.TryParse (_context.Request.QueryString ["offline"], out listOffline);
+// 			}
+//
+// 			bool bViewAll = WebConnection.CanViewAllPlayers (_context.PermissionLevel);
+//
+// 			JsonArray playersJsResult = new JsonArray ();
+//
+// 			Players playersList = PersistentContainer.Instance.Players;
+//
+// 			foreach ((PlatformUserIdentifierAbs userId, Player player) in playersList.Dict) {
+// 				if (admTools != null) {
+// 					if (admTools.IsBanned (userId, out _, out _)) {
+// 						continue;
+// 					}
+// 				}
+//
+// 				if (!listOffline && !player.IsOnline) {
+// 					continue;
+// 				}
+//
+// 				if (!bViewAll && !player.PlatformId.Equals (reqUserId)) {
+// 					continue;
+// 				}
+//
+// 				JsonObject pos = new JsonObject ();
+// 				pos.Add ("x", new JsonNumber (player.LastPosition.x));
+// 				pos.Add ("y", new JsonNumber (player.LastPosition.y));
+// 				pos.Add ("z", new JsonNumber (player.LastPosition.z));
+//
+// 				JsonObject pJson = new JsonObject ();
+// 				pJson.Add ("steamid", new JsonString (userId.CombinedString));
+//
+// 				//					pJson.Add("entityid", new JSONNumber (p.EntityID));
+// 				//                    pJson.Add("ip", new JSONString (p.IP));
+// 				pJson.Add ("name", new JsonString (player.Name));
+// 				pJson.Add ("online", new JsonBoolean (player.IsOnline));
+// 				pJson.Add ("position", pos);
+//
+// 				//					pJson.Add ("totalplaytime", new JSONNumber (p.TotalPlayTime));
+// 				//					pJson.Add ("lastonline", new JSONString (p.LastOnline.ToString ("s")));
+// 				//					pJson.Add ("ping", new JSONNumber (p.IsOnline ? p.ClientInfo.ping : -1));
+//
+// 				playersJsResult.Add (pJson);
+// 			}
+//
+// 			WebUtils.WriteJson (_context.Response, playersJsResult);
+// 		}
+// 	}
+// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayersOnline.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayersOnline.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/GetPlayersOnline.cs	(revision 433)
@@ -0,0 +1,46 @@
+// using AllocsFixes.PersistentData;
+// using JetBrains.Annotations;
+//
+// namespace Webserver.WebAPI.APIs {
+// 	[UsedImplicitly]
+// 	public class GetPlayersOnline : AbsWebAPI {
+// 		public override void HandleRequest (RequestContext _context) {
+// 			JsonArray players = new JsonArray ();
+//
+// 			World w = GameManager.Instance.World;
+// 			foreach ((int entityId, EntityPlayer entityPlayer) in w.Players.dict) {
+// 				ClientInfo ci = ConnectionManager.Instance.Clients.ForEntityId (entityId);
+// 				Player player = PersistentContainer.Instance.Players [ci.InternalId, false];
+//
+// 				JsonObject pos = new JsonObject ();
+// 				pos.Add ("x", new JsonNumber ((int) entityPlayer.GetPosition ().x));
+// 				pos.Add ("y", new JsonNumber ((int) entityPlayer.GetPosition ().y));
+// 				pos.Add ("z", new JsonNumber ((int) entityPlayer.GetPosition ().z));
+//
+// 				JsonObject p = new JsonObject ();
+// 				p.Add ("steamid", new JsonString (ci.PlatformId.CombinedString));
+// 				p.Add ("entityid", new JsonNumber (ci.entityId));
+// 				p.Add ("ip", new JsonString (ci.ip));
+// 				p.Add ("name", new JsonString (entityPlayer.EntityName));
+// 				p.Add ("online", new JsonBoolean (true));
+// 				p.Add ("position", pos);
+//
+// 				p.Add ("level", new JsonNumber (player?.Level ?? -1));
+// 				p.Add ("health", new JsonNumber (entityPlayer.Health));
+// 				p.Add ("stamina", new JsonNumber (entityPlayer.Stamina));
+// 				p.Add ("zombiekills", new JsonNumber (entityPlayer.KilledZombies));
+// 				p.Add ("playerkills", new JsonNumber (entityPlayer.KilledPlayers));
+// 				p.Add ("playerdeaths", new JsonNumber (entityPlayer.Died));
+// 				p.Add ("score", new JsonNumber (entityPlayer.Score));
+//
+// 				p.Add ("totalplaytime", new JsonNumber (player?.TotalPlayTime ?? -1));
+// 				p.Add ("lastonline", new JsonString (player != null ? player.LastOnline.ToString ("s") : string.Empty));
+// 				p.Add ("ping", new JsonNumber (ci.ping));
+//
+// 				players.Add (p);
+// 			}
+//
+// 			WebUtils.WriteJson (_context.Response, players);
+// 		}
+// 	}
+// }
Index: binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/Hostile.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/Hostile.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/Hostile.cs	(revision 433)
@@ -0,0 +1,47 @@
+﻿using System.Collections.Generic;
+using JetBrains.Annotations;
+using Utf8Json;
+using Webserver.LiveData;
+
+namespace Webserver.WebAPI.APIs {
+	[UsedImplicitly]
+	internal class Hostile : AbsRestApi {
+		private readonly List<EntityEnemy> entities = new List<EntityEnemy> ();
+
+		private static readonly byte[] jsonKeyId = JsonWriter.GetEncodedPropertyNameWithBeginObject ("id");
+		private static readonly byte[] jsonKeyName = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("name");
+		private static readonly byte[] jsonKeyPosition = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("position");
+
+		protected override void HandleRestGet (RequestContext _context) {
+			PrepareEnvelopedResult (out JsonWriter writer);
+			writer.WriteBeginArray ();
+			
+			lock (entities) {
+				Hostiles.Instance.Get (entities);
+				
+				for (int i = 0; i < entities.Count; i++) {
+					if (i > 0) {
+						writer.WriteValueSeparator ();
+					}
+					
+					EntityAlive entity = entities [i];
+					Vector3i position = new Vector3i (entity.GetPosition ());
+					
+					writer.WriteRaw (jsonKeyId);
+					writer.WriteInt32 (entity.entityId);
+					
+					writer.WriteRaw (jsonKeyName);
+					writer.WriteString (!string.IsNullOrEmpty (entity.EntityName) ? entity.EntityName : $"enemy class #{entity.entityClass}");
+					
+					writer.WriteRaw (jsonKeyPosition);
+					JsonCommons.WritePositionObject (ref writer, position);
+
+					writer.WriteEndObject ();
+				}
+			}
+			
+			writer.WriteEndArray ();
+			SendEnvelopedResult (_context, ref writer);
+		}
+	}
+}
Index: binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/Player.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/Player.cs	(revision 433)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/WorldState/Player.cs	(revision 433)
@@ -0,0 +1,260 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using JetBrains.Annotations;
+using Utf8Json;
+using Webserver.Permissions;
+
+namespace Webserver.WebAPI.APIs {
+	[UsedImplicitly]
+	public class Player : AbsRestApi {
+		private static readonly byte[] jsonPlayersKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("players");
+
+		protected override void HandleRestGet (RequestContext _context) {
+			string id = _context.RequestPath;
+			int permissionLevel = _context.PermissionLevel;
+			
+ 			bool bViewAll = PermissionUtils.CanViewAllPlayers (permissionLevel);
+            PlatformUserIdentifierAbs nativeUserId = _context.Connection?.UserId;
+
+			// string qs;
+			// if ((qs = _context.Request.QueryString ["count"]) == null || !int.TryParse (qs, out int count)) {
+			// 	count = 50;
+			// }
+
+			PrepareEnvelopedResult (out JsonWriter writer);
+			
+			writer.WriteRaw (jsonPlayersKey);
+			writer.WriteBeginArray ();
+
+			ClientInfo ci;
+			int written = 0;
+
+			if (string.IsNullOrEmpty (id)) {
+				for (int i = 0; i < ConnectionManager.Instance.Clients.List.Count; i++) {
+					ClientInfo clientInfo = ConnectionManager.Instance.Clients.List [i];
+
+					writePlayerJson (ref writer, ref written, clientInfo.PlatformId, bViewAll, nativeUserId);
+				}
+			} else if (int.TryParse (id, out int entityId) && (ci = ConnectionManager.Instance.Clients.ForEntityId (entityId)) != null) {
+				// TODO: Allow finding offline players, also for search other than by EntityId
+				writePlayerJson (ref writer, ref written, ci.PlatformId, bViewAll, nativeUserId);
+			} else {
+				writer.WriteEndArray ();
+				writer.WriteEndObject ();
+				SendEnvelopedResult (_context, ref writer, HttpStatusCode.NotFound);
+				return;
+			}
+
+			writer.WriteEndArray ();
+			writer.WriteEndObject ();
+
+			SendEnvelopedResult (_context, ref writer);
+		}
+
+
+#region JSON keys for player result
+
+		private static readonly byte[] jsonEntityIdKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("entityId");
+		private static readonly byte[] jsonNameKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("name");
+		private static readonly byte[] jsonPlatformIdKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("platformId");
+		private static readonly byte[] jsonCrossplatformIdKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("crossplatformId");
+		private static readonly byte[] jsonTotalPlayTimeKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("totalPlayTimeSeconds");
+		private static readonly byte[] jsonLastOnlineKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("lastOnline");
+		private static readonly byte[] jsonOnlineKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("online");
+		private static readonly byte[] jsonIpKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("ip");
+		private static readonly byte[] jsonPingKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("ping");
+		private static readonly byte[] jsonPositionKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("position");
+		private static readonly byte[] jsonLevelKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("level");
+		private static readonly byte[] jsonHealthKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("health");
+		private static readonly byte[] jsonStaminaKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("stamina");
+		private static readonly byte[] jsonScoreKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("score");
+		private static readonly byte[] jsonDeathsKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("deaths");
+		
+		private static readonly byte[] jsonKillsKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("kills");
+		private static readonly byte[] jsonKillsZombiesKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("zombies");
+		private static readonly byte[] jsonKillsPlayersKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("players");
+		
+		private static readonly byte[] jsonBannedKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("banned");
+		private static readonly byte[] jsonBanActiveKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("banActive");
+		private static readonly byte[] jsonBanReasonKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("reason");
+		private static readonly byte[] jsonBanUntilKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("until");
+
+#endregion
+
+		
+		private void writePlayerJson (ref JsonWriter _writer, ref int _written, PlatformUserIdentifierAbs _nativeUserId,
+			bool _allowViewAll, PlatformUserIdentifierAbs _requesterNativeUserId) {
+			
+			if (!_allowViewAll && (_requesterNativeUserId == null || !_requesterNativeUserId.Equals (_nativeUserId) )) {
+				return;
+			}
+			
+			ClientInfo ci = ConnectionManager.Instance.Clients.ForUserId (_nativeUserId);
+			if (ci == null) {
+				global::Log.Warning ($"[Web] Player.GET: ClientInfo null");
+				return;
+			}
+
+			int entityId = ci.entityId;
+			GameManager.Instance.World.Players.dict.TryGetValue (entityId, out EntityPlayer entity);
+
+			if (entity == null) {
+				global::Log.Warning ($"[Web] Player.GET: EntityPlayer null");
+				return;
+			}
+
+			if (_written > 0) {
+				_writer.WriteValueSeparator ();
+			}
+			_written++;
+
+			bool online = true; // TODO
+
+			_writer.WriteRaw (jsonEntityIdKey);
+			_writer.WriteInt32 (entityId);
+			
+			_writer.WriteRaw (jsonNameKey);
+			_writer.WriteString (ci.playerName);
+			
+			_writer.WriteRaw (jsonPlatformIdKey);
+			JsonCommons.WritePlatformUserIdentifier (ref _writer, _nativeUserId);
+			
+			_writer.WriteRaw (jsonCrossplatformIdKey);
+			JsonCommons.WritePlatformUserIdentifier (ref _writer, ci.CrossplatformId);
+			
+			_writer.WriteRaw (jsonTotalPlayTimeKey);
+			//_writer.WriteLong (player.TotalPlayTime); TODO
+			_writer.WriteNull ();
+			
+			_writer.WriteRaw (jsonLastOnlineKey);
+			//JsonCommons.WriteDateTime (ref _writer, player.LastOnline); TODO
+			_writer.WriteNull ();
+			
+			_writer.WriteRaw (jsonOnlineKey);
+			_writer.WriteBoolean (online);
+			
+			_writer.WriteRaw (jsonIpKey);
+			if (online) {
+				_writer.WriteString (ci.ip);
+				// TODO: Possibly show last used IP?
+			} else {
+				_writer.WriteNull ();
+			}
+			
+			_writer.WriteRaw (jsonPingKey);
+			if (online) {
+				_writer.WriteInt32 (ci.ping);
+			} else {
+				_writer.WriteNull ();
+			}
+			
+			_writer.WriteRaw (jsonPositionKey);
+			if (online) {
+				JsonCommons.WritePositionObject (ref _writer, entity.GetPosition ());
+				// TODO: Possibly show last position?
+			} else {
+				_writer.WriteNull ();
+			}
+
+			_writer.WriteRaw (jsonLevelKey);
+			_writer.WriteNull (); // TODO
+
+			_writer.WriteRaw (jsonHealthKey);
+			_writer.WriteInt32 (entity.Health);
+
+			_writer.WriteRaw (jsonStaminaKey);
+			_writer.WriteSingle (entity.Stamina);
+
+			_writer.WriteRaw (jsonScoreKey);
+			_writer.WriteInt32 (entity.Score);
+
+			_writer.WriteRaw (jsonDeathsKey);
+			_writer.WriteInt32 (entity.Died);
+
+			
+			_writer.WriteRaw (jsonKillsKey);
+			
+			_writer.WriteRaw (jsonKillsZombiesKey);
+			_writer.WriteInt32 (entity.KilledZombies);
+
+			_writer.WriteRaw (jsonKillsPlayersKey);
+			_writer.WriteInt32 (entity.KilledPlayers);
+			
+			_writer.WriteEndObject (); // End of jsonKillsKey
+			
+			
+			_writer.WriteRaw (jsonBannedKey);
+
+			bool banned = GameManager.Instance.adminTools.Blacklist.IsBanned (_nativeUserId, out DateTime bannedUntil, out string banReason);
+			if (!banned && ci.CrossplatformId != null) {
+				banned = GameManager.Instance.adminTools.Blacklist.IsBanned (ci.CrossplatformId, out bannedUntil, out banReason);
+			}
+
+			_writer.WriteRaw (jsonBanActiveKey);
+			_writer.WriteBoolean (banned);
+
+			_writer.WriteRaw (jsonBanReasonKey);
+			if (banned) {
+				_writer.WriteString (banReason);
+			} else {
+				_writer.WriteNull ();
+			}
+
+			_writer.WriteRaw (jsonBanUntilKey);
+			if (banned) {
+				JsonCommons.WriteDateTime (ref _writer, bannedUntil);
+			} else {
+				_writer.WriteNull ();
+			}
+
+			_writer.WriteEndObject (); // End of jsonBannedKeys
+			
+
+			_writer.WriteEndObject (); // End of jsonEntityIdKey
+		}
+
+		protected override void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
+			if (!TryGetJsonField (_jsonInput, "command", out string commandString)) {
+				SendErrorResult (_context, HttpStatusCode.BadRequest, _jsonInputData, "NO_COMMAND");
+				return;
+			}
+
+			WebCommandResult.ResultType responseType = WebCommandResult.ResultType.Full;
+
+			if (TryGetJsonField (_jsonInput, "format", out string formatString)) {
+				if (formatString.EqualsCaseInsensitive ("raw")) {
+					responseType = WebCommandResult.ResultType.Raw;
+				} else if (formatString.EqualsCaseInsensitive ("simple")) {
+					responseType = WebCommandResult.ResultType.ResultOnly;
+				}
+			}
+
+			int commandSepIndex = commandString.IndexOf (' ');
+			string commandPart = commandSepIndex > 0 ? commandString.Substring (0, commandSepIndex) : commandString;
+			string argumentsPart = commandSepIndex > 0
+				? commandString.Substring (commandPart.Length + 1)
+				: "";
+
+			IConsoleCommand command = SdtdConsole.Instance.GetCommand (commandPart, true);
+
+			if (command == null) {
+				SendErrorResult (_context, HttpStatusCode.NotFound, _jsonInputData, "UNKNOWN_COMMAND");
+				return;
+			}
+
+			int commandPermissionLevel = GameManager.Instance.adminTools.Commands.GetCommandPermissionLevel (command.GetCommands ());
+
+			if (_context.PermissionLevel > commandPermissionLevel) {
+				SendErrorResult (_context, HttpStatusCode.Forbidden, _jsonInputData, "NO_PERMISSION");
+				return;
+			}
+
+			_context.Response.SendChunked = true;
+			WebCommandResult wcr = new WebCommandResult (commandPart, argumentsPart, responseType, _context);
+			SdtdConsole.Instance.ExecuteAsync (commandString, wcr);
+		}
+
+		public override int DefaultPermissionLevel () => AdminWebModules.PermissionLevelGuest;
+	}
+}
