Index: binary-improvements2/WebServer/ModInfo.xml
===================================================================
--- binary-improvements2/WebServer/ModInfo.xml	(revision 423)
+++ binary-improvements2/WebServer/ModInfo.xml	(revision 425)
@@ -5,5 +5,5 @@
 	<Description value="Integrated Webserver for the Web Dashboard and server APIs" />
 	<Author value="The Fun Pimps LLC" />
-	<Version value="21.0.250.0" />
+	<Version value="21.0.258.0" />
 	<Website value="" />
 </xml>
Index: binary-improvements2/WebServer/WebServer.csproj
===================================================================
--- binary-improvements2/WebServer/WebServer.csproj	(revision 423)
+++ binary-improvements2/WebServer/WebServer.csproj	(revision 425)
@@ -112,4 +112,5 @@
     <Compile Include="src\Permissions\AdminApiTokens.cs" />
     <Compile Include="src\Permissions\AdminWebUsers.cs" />
+    <Compile Include="src\Permissions\PermissionUtils.cs" />
     <Compile Include="src\Permissions\RegisterModules.cs" />
     <Compile Include="src\UserRegistrationTokens.cs" />
@@ -128,4 +129,5 @@
     <Compile Include="src\WebAPI\APIs\Hostile.cs" />
     <Compile Include="src\WebAPI\APIs\Log.cs" />
+    <Compile Include="src\WebAPI\APIs\Player.cs" />
     <Compile Include="src\WebAPI\APIs\RegisterUser.cs" />
     <Compile Include="src\WebAPI\APIs\ServerInfo.cs" />
Index: binary-improvements2/WebServer/src/Permissions/PermissionUtils.cs
===================================================================
--- binary-improvements2/WebServer/src/Permissions/PermissionUtils.cs	(revision 425)
+++ binary-improvements2/WebServer/src/Permissions/PermissionUtils.cs	(revision 425)
@@ -0,0 +1,11 @@
+namespace Webserver.Permissions {
+	public static class PermissionUtils {
+		public static bool CanViewAllPlayers (int _permissionLevel) {
+			return AdminWebModules.Instance.ModuleAllowedWithLevel ("webapi.viewallplayers", _permissionLevel);
+		}
+
+		public static bool CanViewAllClaims (int _permissionLevel) {
+			return AdminWebModules.Instance.ModuleAllowedWithLevel ("webapi.viewallclaims", _permissionLevel);
+		}
+	}
+}
Index: binary-improvements2/WebServer/src/SSE/EventLog.cs
===================================================================
--- binary-improvements2/WebServer/src/SSE/EventLog.cs	(revision 423)
+++ binary-improvements2/WebServer/src/SSE/EventLog.cs	(revision 425)
@@ -4,4 +4,5 @@
 using Utf8Json;
 using Webserver.UrlHandlers;
+using Webserver.WebAPI;
 
 namespace Webserver.SSE {
@@ -19,5 +20,4 @@
 
 		private void LogCallback (string _formattedMsg, string _plainMsg, string _trace, LogType _type, DateTime _timestamp, long _uptime) {
-			string isotime = _timestamp.ToString ("o");
 			string uptime = _uptime.ToString ();
 
@@ -34,5 +34,5 @@
 			
 			writer.WriteRaw (jsonIsotimeKey);
-			writer.WriteString (isotime);
+			JsonCommons.WriteDateTime (ref writer, _timestamp);
 			
 			writer.WriteRaw (jsonUptimeKey);
Index: binary-improvements2/WebServer/src/WebAPI/APIs/Player.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/APIs/Player.cs	(revision 425)
+++ binary-improvements2/WebServer/src/WebAPI/APIs/Player.cs	(revision 425)
@@ -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 () => 2000;
+	}
+}
Index: binary-improvements2/WebServer/src/WebAPI/JsonCommons.cs
===================================================================
--- binary-improvements2/WebServer/src/WebAPI/JsonCommons.cs	(revision 423)
+++ binary-improvements2/WebServer/src/WebAPI/JsonCommons.cs	(revision 425)
@@ -1,2 +1,4 @@
+using System;
+using UnityEngine;
 using Utf8Json;
 
@@ -19,5 +21,43 @@
 			_writer.WriteEndObject ();
 		}
-		
+
+		public static void WritePositionObject (ref JsonWriter _writer, Vector3 _position) {
+			_writer.WriteRaw (jsonKeyPositionX);
+			_writer.WriteSingle (_position.x);
+			
+			_writer.WriteRaw (jsonKeyPositionY);
+			_writer.WriteSingle (_position.y);
+			
+			_writer.WriteRaw (jsonKeyPositionZ);
+			_writer.WriteSingle (_position.z);
+			
+			_writer.WriteEndObject ();
+		}
+
+		private static readonly byte[] jsonKeyCombinedString = JsonWriter.GetEncodedPropertyNameWithBeginObject ("combinedString");
+		private static readonly byte[] jsonKeyPlatformId = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("platformId");
+		private static readonly byte[] jsonKeyUserId = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("userId");
+
+		public static void WritePlatformUserIdentifier (ref JsonWriter _writer, PlatformUserIdentifierAbs _userIdentifier) {
+			if (_userIdentifier == null) {
+				_writer.WriteNull ();
+				return;
+			}
+			
+			_writer.WriteRaw (jsonKeyCombinedString);
+			_writer.WriteString (_userIdentifier.CombinedString);
+			
+			_writer.WriteRaw (jsonKeyPlatformId);
+			_writer.WriteString (_userIdentifier.PlatformIdentifierString);
+			
+			_writer.WriteRaw (jsonKeyUserId);
+			_writer.WriteString (_userIdentifier.ReadablePlatformUserIdentifier);
+			
+			_writer.WriteEndObject ();
+		}
+
+		public static void WriteDateTime (ref JsonWriter _writer, DateTime _dateTime) {
+			_writer.WriteString (_dateTime.ToString ("o"));
+		}
 	}
 }
Index: binary-improvements2/WebServer/src/WebConnection.cs
===================================================================
--- binary-improvements2/WebServer/src/WebConnection.cs	(revision 423)
+++ binary-improvements2/WebServer/src/WebConnection.cs	(revision 425)
@@ -33,12 +33,4 @@
 		}
 
-		public static bool CanViewAllPlayers (int _permissionLevel) {
-			return AdminWebModules.Instance.ModuleAllowedWithLevel ("webapi.viewallplayers", _permissionLevel);
-		}
-
-		public static bool CanViewAllClaims (int _permissionLevel) {
-			return AdminWebModules.Instance.ModuleAllowedWithLevel ("webapi.viewallclaims", _permissionLevel);
-		}
-
 		public void UpdateUsage () {
 			lastAction = DateTime.Now;
Index: binary-improvements2/WebServer/src/WebUtils.cs
===================================================================
--- binary-improvements2/WebServer/src/WebUtils.cs	(revision 423)
+++ binary-improvements2/WebServer/src/WebUtils.cs	(revision 425)
@@ -5,4 +5,5 @@
 using System.Text;
 using Utf8Json;
+using Webserver.WebAPI;
 using HttpListenerRequest = SpaceWizards.HttpListener.HttpListenerRequest;
 using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
@@ -80,5 +81,5 @@
 
 			_writer.WriteRaw (jsonRawMetaServertimeKey);
-			_writer.WriteString (DateTime.Now.ToString ("o"));
+			JsonCommons.WriteDateTime (ref _writer, DateTime.Now);
 
 			if (!string.IsNullOrEmpty (_errorCode)) {
