Index: TFP-WebServer/WebServer/src/Commands/EnableOpenIDDebug.cs
===================================================================
--- TFP-WebServer/WebServer/src/Commands/EnableOpenIDDebug.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/Commands/EnableOpenIDDebug.cs	(revision 487)
@@ -5,9 +5,9 @@
 	[UsedImplicitly]
 	public class EnableOpenIDDebug : ConsoleCmdAbstract {
-		protected override string getDescription () {
+		public override string getDescription () {
 			return "enable/disable OpenID debugging";
 		}
 
-		protected override string[] getCommands () {
+		public override string[] getCommands () {
 			return new[] {"openiddebug"};
 		}
Index: TFP-WebServer/WebServer/src/Commands/WebPermissionsCmd.cs
===================================================================
--- TFP-WebServer/WebServer/src/Commands/WebPermissionsCmd.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/Commands/WebPermissionsCmd.cs	(revision 487)
@@ -6,13 +6,13 @@
 	[UsedImplicitly]
 	public class WebPermissionsCmd : ConsoleCmdAbstract {
-		protected override string[] getCommands () {
+		public override string[] getCommands () {
 			return new[] {"webpermission"};
 		}
 
-		protected override string getDescription () {
+		public override string getDescription () {
 			return "Manage web permission levels";
 		}
 
-		protected override string getHelp () {
+		public override string getHelp () {
 			return @"
 				|Set/get permission levels required to access a given web functionality. Default
@@ -84,5 +84,5 @@
 			if (permissionLevelString.EqualsCaseInsensitive (AdminWebModules.MethodLevelInheritKeyword)) {
 				if (isGlobal) {
-					SdtdConsole.Instance.Output ($"Permission level can not use the 'inherit' keyword with the 'global' method keyword.");
+					SdtdConsole.Instance.Output ("Permission level can not use the 'inherit' keyword with the 'global' method keyword.");
 					return;
 				}
@@ -97,9 +97,8 @@
 			}
 
-			module.IsDefault = false;
 			if (isGlobal) {
-				module.LevelGlobal = level;
+				module = module.SetLevelGlobal (level);
 			} else {
-				module.LevelPerMethod [(int)method] = level;
+				module = module.SetLevelForMethod (method, level);
 			}
 			
Index: TFP-WebServer/WebServer/src/Commands/WebTokens.cs
===================================================================
--- TFP-WebServer/WebServer/src/Commands/WebTokens.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/Commands/WebTokens.cs	(revision 487)
@@ -9,13 +9,13 @@
 		private static readonly Regex validNameTokenMatcher = new Regex (@"^\w+$");
 
-		protected override string[] getCommands () {
+		public override string[] getCommands () {
 			return new[] {"webtokens"};
 		}
 
-		protected override string getDescription () {
+		public override string getDescription () {
 			return "Manage web tokens";
 		}
 
-		protected override string getHelp () {
+		public override string getHelp () {
 			return "Set/get webtoken permission levels. A level of 0 is maximum permission.\n" +
 			       "Usage:\n" +
Index: TFP-WebServer/WebServer/src/FileCache/InvalidateCachesCmd.cs
===================================================================
--- TFP-WebServer/WebServer/src/FileCache/InvalidateCachesCmd.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/FileCache/InvalidateCachesCmd.cs	(revision 487)
@@ -5,13 +5,13 @@
 	[UsedImplicitly]
 	public class InvalidateCachesCmd : ConsoleCmdAbstract {
-		protected override string[] getCommands () {
+		public override string[] getCommands () {
 			return new[] {"invalidatecaches"};
 		}
 
-		protected override string getDescription () {
+		public override string getDescription () {
 			return "Invalidate contents of web file caches";
 		}
 
-		protected override string getHelp () {
+		public override string getHelp () {
 			return "TODO";
 		}
Index: TFP-WebServer/WebServer/src/FileCache/SimpleCache.cs
===================================================================
--- TFP-WebServer/WebServer/src/FileCache/SimpleCache.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/FileCache/SimpleCache.cs	(revision 487)
@@ -11,6 +11,6 @@
 			try {
 				lock (fileCache) {
-					if (fileCache.ContainsKey (_filename)) {
-						return fileCache [_filename];
+					if (fileCache.TryGetValue(_filename, out byte[] content)) {
+						return content;
 					}
 
@@ -19,7 +19,8 @@
 					}
 
-					fileCache.Add (_filename, File.ReadAllBytes (_filename));
+					byte[] newContent = File.ReadAllBytes (_filename);
+					fileCache.Add (_filename, newContent);
 
-					return fileCache [_filename];
+					return newContent;
 				}
 			} catch (Exception e) {
Index: TFP-WebServer/WebServer/src/Permissions/AdminApiTokens.cs
===================================================================
--- TFP-WebServer/WebServer/src/Permissions/AdminApiTokens.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/Permissions/AdminApiTokens.cs	(revision 487)
@@ -19,5 +19,5 @@
 		}
 
-		protected override void ParseElement (XmlElement _childElement) {
+		public override void ParseElement (XmlElement _childElement) {
 			if (ApiToken.TryParse (_childElement, out ApiToken apiToken)) {
 				tokens [apiToken.Name] = apiToken;
Index: TFP-WebServer/WebServer/src/Permissions/AdminWebModules.cs
===================================================================
--- TFP-WebServer/WebServer/src/Permissions/AdminWebModules.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/Permissions/AdminWebModules.cs	(revision 487)
@@ -1,4 +1,7 @@
+using System;
 using System.Collections.Generic;
 using System.Xml;
+using JetBrains.Annotations;
+using UnityEngine;
 
 namespace Webserver.Permissions {
@@ -16,11 +19,20 @@
 
 		public override void Clear () {
+			allModulesList.Clear ();
 			modules.Clear ();
 		}
 
-		protected override void ParseElement (XmlElement _childElement) {
-			if (WebModule.TryParse (_childElement, out WebModule webModule)) {
-				modules [webModule.Name] = webModule;
-			}
+		public override void ParseElement (XmlElement _childElement) {
+			allModulesList.Clear ();
+
+			if (!WebModule.TryParse (_childElement, out WebModule webModule)) {
+				return;
+			}
+
+			if (knownModules.TryGetValue (webModule.Name, out WebModule knownModule)) {
+				webModule = webModule.FixPermissionLevelsFromKnownModule (knownModule);
+			}
+			
+			modules [webModule.Name] = webModule;
 		}
 
@@ -43,5 +55,5 @@
 			lock (Parent) {
 				allModulesList.Clear ();
-				
+			
 				modules [_module.Name] = _module;
 				Parent.Save ();
@@ -80,11 +92,11 @@
 #endregion
 		
-		public struct WebModule {
-			public string Name;
-			public int LevelGlobal;
-			public int[] LevelPerMethod;
-			public bool IsDefault;
-
-			public WebModule (string _name, int _level, bool _isDefault = false) {
+		public readonly struct WebModule {
+			public readonly string Name;
+			public readonly int LevelGlobal;
+			public readonly int[] LevelPerMethod;
+			public readonly bool IsDefault;
+
+			public WebModule (string _name, int _level, bool _isDefault) {
 				LevelPerMethod = null;
 				
@@ -94,6 +106,6 @@
 			}
 
-			public WebModule (string _name, int _levelGlobal, int[] _levelPerMethod, bool _isDefault = false) {
-				if (_levelPerMethod == null || _levelPerMethod.Length != (int)ERequestMethod.Count) {
+			public WebModule (string _name, int _levelGlobal, int[] _levelPerMethod, bool _isDefault) {
+				if (_levelPerMethod != null && _levelPerMethod.Length != (int)ERequestMethod.Count) {
 					LevelPerMethod = createDefaultPerMethodArray ();
 
@@ -205,9 +217,31 @@
 				}
 
-				_result = perMethodLevels != null ? new WebModule (name, permissionLevel, perMethodLevels) : new WebModule (name, permissionLevel);
+				_result = new WebModule (name, permissionLevel, perMethodLevels, false);
 
 				return true;
 			}
 
+			[MustUseReturnValue]
+			public WebModule SetLevelGlobal (int _level) {
+				int[] perMethodClone = LevelPerMethod == null ? null : new int[LevelPerMethod.Length];
+				if (perMethodClone != null) {
+					Array.Copy (LevelPerMethod, perMethodClone, perMethodClone.Length);
+				}
+
+				return new WebModule (Name, _level, perMethodClone, false);
+			}
+
+			[MustUseReturnValue]
+			public WebModule SetLevelForMethod (ERequestMethod _method, int _level) {
+				int[] perMethodClone = createDefaultPerMethodArray ();
+				if (LevelPerMethod != null) {
+					Array.Copy (LevelPerMethod, perMethodClone, perMethodClone.Length);
+				}
+
+				perMethodClone[(int)_method] = _level;
+				
+				return new WebModule (Name, LevelGlobal, perMethodClone, false);
+			}
+			
 			private static int[] createDefaultPerMethodArray () {
 				int[] result = new int[(int)ERequestMethod.Count];
@@ -219,4 +253,23 @@
 				return result;
 			}
+
+			[MustUseReturnValue]
+			public WebModule FixPermissionLevelsFromKnownModule (WebModule _knownModule) {
+				if (_knownModule.LevelPerMethod == null) {
+					if (LevelPerMethod != null) {
+						return new WebModule (Name, LevelGlobal, false);
+					}
+					return this;
+				}
+				
+				WebModule result = this;
+				for (int i = 0; i < _knownModule.LevelPerMethod.Length; i++) {
+					if (result.LevelPerMethod == null || result.LevelPerMethod[i] == MethodLevelNotSupported) {
+						result = result.SetLevelForMethod ((ERequestMethod)i, _knownModule.LevelPerMethod[i]);
+					}
+				}
+
+				return result;
+			}
 		}
 
@@ -253,13 +306,20 @@
 
 		public void AddKnownModule (WebModule _module) {
+			if (!_module.IsDefault) {
+				Log.Warning ($"Call to AddKnownModule with IsDefault==false! From:\n{StackTraceUtility.ExtractStackTrace()}");
+			}
+
 			if (string.IsNullOrEmpty (_module.Name)) {
 				return;
 			}
 
-			_module.IsDefault = true;
-			
 			lock (Parent) {
 				allModulesList.Clear ();
 				knownModules [_module.Name] = _module;
+				
+				if (modules.TryGetValue (_module.Name, out WebModule overrideModule)) {
+					overrideModule = overrideModule.FixPermissionLevelsFromKnownModule (_module);
+					modules[_module.Name] = overrideModule;
+				}
 			}
 		}
Index: TFP-WebServer/WebServer/src/Permissions/AdminWebUsers.cs
===================================================================
--- TFP-WebServer/WebServer/src/Permissions/AdminWebUsers.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/Permissions/AdminWebUsers.cs	(revision 487)
@@ -21,5 +21,5 @@
 		}
 
-		protected override void ParseElement (XmlElement _childElement) {
+		public override void ParseElement (XmlElement _childElement) {
 			if (WebUser.TryParse (_childElement, out WebUser webUser)) {
 				users [webUser.Name] = webUser;
Index: TFP-WebServer/WebServer/src/SSE/AbsEvent.cs
===================================================================
--- TFP-WebServer/WebServer/src/SSE/AbsEvent.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/SSE/AbsEvent.cs	(revision 487)
@@ -1,15 +1,10 @@
 using System;
 using System.Collections.Generic;
-using System.IO;
-using System.Net.Sockets;
 using System.Text;
 using Webserver.UrlHandlers;
-using HttpListenerResponse = SpaceWizards.HttpListener.HttpListenerResponse;
 
 namespace Webserver.SSE {
 	public abstract class AbsEvent {
 		private const int encodingBufferSize = 1024 * 1024;
-		private const int keepAliveIntervalSeconds = 10;
-		private static readonly byte[] keepAliveData = Encoding.UTF8.GetBytes (": KeepAlive\n\n");
 
 		private readonly SseHandler parent;
@@ -18,7 +13,6 @@
 		private readonly byte[] encodingBuffer;
 		private readonly StringBuilder stringBuilder = new StringBuilder ();
-		private DateTime lastMessageSent;
 
-		private readonly List<HttpListenerResponse> openStreams = new List<HttpListenerResponse> ();
+		private readonly List<SseClient> openClients = new List<SseClient> ();
 
 		private readonly BlockingQueue<(string _eventName, string _data)> sendQueue =
@@ -37,9 +31,9 @@
 		}
 
-		public void AddListener (HttpListenerResponse _resp) {
+		public void AddListener (SseClient _client) {
 			totalOpened++;
 			currentlyOpen++;
 
-			openStreams.Add (_resp);
+			openClients.Add (_client);
 		}
 
@@ -50,6 +44,4 @@
 
 		public void ProcessSendQueue () {
-			bool dataSent = false;
-			
 			while (sendQueue.HasData ()) {
 				(string eventName, string data) = sendQueue.Dequeue ();
@@ -81,55 +73,20 @@
 				}
 
-				dataSent = true;
-
 				sendBufToListeners (buf, bytesToSend);
-			}
-
-			DateTime now = DateTime.Now;
-			if (dataSent) {
-				lastMessageSent = now;
-			} else if ((now - lastMessageSent).TotalSeconds >= keepAliveIntervalSeconds) {
-				sendBufToListeners (keepAliveData, keepAliveData.Length);
-				lastMessageSent = now;
 			}
 		}
 
 		private void sendBufToListeners (byte[] _bytes, int _bytesToSend) {
-			for (int i = openStreams.Count - 1; i >= 0; i--) {
-				HttpListenerResponse resp = openStreams [i];
-				try {
-					if (resp.OutputStream.CanWrite) {
-						resp.OutputStream.Write (_bytes, 0, _bytesToSend);
-						resp.OutputStream.Flush ();
-					} else {
-						currentlyOpen--;
-						totalClosed++;
+			for (int i = openClients.Count - 1; i >= 0; i--) {
+				ESseClientWriteResult writeResult = openClients [i].Write (_bytes, _bytesToSend);
+				if (writeResult == ESseClientWriteResult.Ok) {
+					continue;
+				}
 
-						logError ("Can not write to endpoint, closing", true);
-						openStreams.RemoveAt (i);
-						resp.Close ();
-					}
-				} catch (IOException e) {
-					currentlyOpen--;
-					totalClosed++;
+				currentlyOpen--;
+				totalClosed++;
 
-					openStreams.RemoveAt (i);
-
-					if (e.InnerException is SocketException se) {
-						if (se.SocketErrorCode != SocketError.ConnectionAborted && se.SocketErrorCode != SocketError.Shutdown) {
-							logError ($"SocketError ({se.SocketErrorCode.ToStringCached ()}) while trying to write", true);
-						}
-					} else {
-						logError ("IOException while trying to write:", true);
-						Log.Exception (e);
-					}
-				} catch (Exception e) {
-					currentlyOpen--;
-					totalClosed++;
-
-					openStreams.RemoveAt (i);
-					logError ("Exception while trying to write:", true);
-					Log.Exception (e);
-					resp.Close ();
+				if (writeResult == ESseClientWriteResult.Error) {
+					logError ("Can not write to endpoint, closing", true);
 				}
 			}
@@ -143,4 +100,8 @@
 
 		public virtual int DefaultPermissionLevel () => 0;
+
+		public void ClientClosed (SseClient _client) {
+			openClients.Remove (_client);
+		}
 	}
 }
Index: TFP-WebServer/WebServer/src/SSE/SseClient.cs
===================================================================
--- TFP-WebServer/WebServer/src/SSE/SseClient.cs	(revision 487)
+++ TFP-WebServer/WebServer/src/SSE/SseClient.cs	(revision 487)
@@ -0,0 +1,85 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Text;
+using SpaceWizards.HttpListener;
+using Webserver.UrlHandlers;
+
+namespace Webserver.SSE {
+    public enum ESseClientWriteResult {
+        Ok,
+        Closed,
+        Error,
+    }
+    
+    public class SseClient {
+        private const int keepAliveIntervalSeconds = 10;
+        private static readonly byte[] keepAliveData = Encoding.UTF8.GetBytes (": KeepAlive\n\n");
+
+        private readonly SseHandler parent;
+        private readonly HttpListenerResponse response;
+        private DateTime lastMessageSent = DateTime.Now;
+
+        public SseClient (SseHandler _parent, HttpListenerResponse _response) {
+            parent = _parent;
+            response = _response;
+            
+            // Keep the request open
+            _response.SendChunked = true;
+
+            _response.AddHeader ("Content-Type", "text/event-stream");
+            _response.OutputStream.Flush ();
+        }
+
+        public ESseClientWriteResult Write (byte[] _bytes, int _bytesToSend) {
+            HttpListenerResponse resp = response;
+            try {
+                if (!resp.OutputStream.CanWrite) {
+                    parent.ClientClosed (this);
+                    resp.Close ();
+                    
+                    return ESseClientWriteResult.Closed;
+                }
+
+                resp.OutputStream.Write (_bytes, 0, _bytesToSend);
+                resp.OutputStream.Flush ();
+                lastMessageSent = DateTime.Now;
+                return ESseClientWriteResult.Ok;
+            } catch (IOException e) {
+                parent.ClientClosed (this);
+
+                if (e.InnerException is SocketException se) {
+                    if (se.SocketErrorCode == SocketError.ConnectionAborted || se.SocketErrorCode == SocketError.Shutdown) {
+                        return ESseClientWriteResult.Closed;
+                    }
+
+                    Log.Error ($"[Web] [SSE] SocketError ({se.SocketErrorCode.ToStringCached ()}) while trying to write", true);
+                    return ESseClientWriteResult.Error;
+                }
+
+                Log.Error ("[Web] [SSE] IOException while trying to write:", true);
+                Log.Exception (e);
+                return ESseClientWriteResult.Error;
+            } catch (Exception e) {
+                parent.ClientClosed (this);
+                resp.Close ();
+
+                Log.Error ("[Web] [SSE] Exception while trying to write:", true);
+                Log.Exception (e);
+
+                return ESseClientWriteResult.Error;
+            }
+        }
+
+        public void HandleKeepAlive () {
+            DateTime now = DateTime.Now;
+            if (!((now - lastMessageSent).TotalSeconds >= keepAliveIntervalSeconds)) {
+                return;
+            }
+
+            Write (keepAliveData, keepAliveData.Length);
+            lastMessageSent = now;
+        }
+        
+    }
+}
Index: TFP-WebServer/WebServer/src/UrlHandlers/ApiHandler.cs
===================================================================
--- TFP-WebServer/WebServer/src/UrlHandlers/ApiHandler.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/UrlHandlers/ApiHandler.cs	(revision 487)
@@ -30,6 +30,6 @@
 
 		private void apiFoundCallback (Type _type) {
-			ConstructorInfo ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, (Binder)null,
-				apiWithParentCtorTypes, (ParameterModifier[])null);
+			ConstructorInfo ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
+				apiWithParentCtorTypes, null);
 			if (ctor != null) {
 				AbsWebAPI apiInstance = (AbsWebAPI)ctor.Invoke (apiWithParentCtorArgs);
@@ -38,6 +38,6 @@
 			}
 
-			ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, (Binder)null,
-				apiEmptyCtorTypes, (ParameterModifier[])null);
+			ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
+				apiEmptyCtorTypes, null);
 			if (ctor != null) {
 				AbsWebAPI apiInstance = (AbsWebAPI)ctor.Invoke (apiEmptyCtorArgs);
Index: TFP-WebServer/WebServer/src/UrlHandlers/SessionHandler.cs
===================================================================
--- TFP-WebServer/WebServer/src/UrlHandlers/SessionHandler.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/UrlHandlers/SessionHandler.cs	(revision 487)
@@ -20,5 +20,4 @@
 		private const string userPassLoginUrl = "login";
 		public const string userPassLoginName = "User/pass";
-		private const string userPassErrorPage = "UserPassLoginFailed";
 
 		public SessionHandler () : base (null) {
Index: TFP-WebServer/WebServer/src/UrlHandlers/SseHandler.cs
===================================================================
--- TFP-WebServer/WebServer/src/UrlHandlers/SseHandler.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/UrlHandlers/SseHandler.cs	(revision 487)
@@ -20,4 +20,6 @@
 		private static readonly Type[] ctorTypes = { typeof (SseHandler) };
 		private static readonly object[] ctorParams = new object[1];
+
+		private readonly List<SseClient> clients = new List<SseClient>();
 
 		public SseHandler (string _moduleName = null) : base (_moduleName) {
@@ -57,13 +59,57 @@
 
 		public override void HandleRequest (RequestContext _context) {
-			string eventName = _context.RequestPath.Remove (0, urlBasePath.Length);
-
-			if (!events.TryGetValue (eventName, out AbsEvent eventInstance)) {
-				Log.Warning ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): No handler found for event \"{eventName}\"");
-				_context.Response.StatusCode = (int)HttpStatusCode.NotFound;
+			string eventNames = _context.QueryParameters ["events"];
+			if (string.IsNullOrEmpty (eventNames)) {
+				Log.Warning ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): No 'events' query parameter given");
+				_context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
 				return;
 			}
 
-			if (!IsAuthorizedForEvent (eventName, _context.PermissionLevel)) {
+			SseClient client;
+			try {
+				client = new SseClient(this, _context.Response);
+			} catch (Exception e) {
+				Log.Error ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): Could not create client:");
+				Log.Exception (e);
+				_context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+				return;
+			}
+
+			int eventsFound = 0;
+			int eventsAuthorized = 0;
+			int eventsRegistered = 0;
+			foreach (string eventName in eventNames.Split (',', StringSplitOptions.RemoveEmptyEntries)) {
+				if (!events.TryGetValue (eventName, out AbsEvent eventInstance)) {
+					Log.Warning ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): No handler found for event \"{eventName}\"");
+					continue;
+				}
+
+				eventsFound++;
+				
+				if (!IsAuthorizedForEvent (eventName, _context.PermissionLevel)) {
+					continue;
+				}
+
+				eventsAuthorized++;
+
+				try
+				{
+					eventInstance.AddListener (client);
+				} catch (Exception e) {
+					Log.Error ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): Handler {eventInstance.Name} threw an exception:");
+					Log.Exception (e);
+					_context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+				}
+
+				eventsRegistered++;
+			}
+
+			if (eventsFound == 0) {
+				_context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
+				_context.Response.Close ();
+				return;
+			}
+
+			if (eventsAuthorized == 0) {
 				_context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
 				if (_context.Connection != null) {
@@ -71,20 +117,9 @@
 				}
 
+				_context.Response.Close ();
 				return;
 			}
 
-			try {
-				eventInstance.AddListener (_context.Response);
-
-				// Keep the request open
-				_context.Response.SendChunked = true;
-
-				_context.Response.AddHeader ("Content-Type", "text/event-stream");
-				_context.Response.OutputStream.Flush ();
-			} catch (Exception e) {
-				Log.Error ($"[Web] [SSE] In {nameof (SseHandler)}.HandleRequest(): Handler {eventInstance.Name} threw an exception:");
-				Log.Exception (e);
-				_context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
-			}
+			clients.Add (client);
 		}
 
@@ -105,4 +140,8 @@
 					}
 				}
+
+				for (int index = clients.Count - 1; index >= 0; index--) {
+					clients[index].HandleKeepAlive ();
+				}
 			}
 		}
@@ -111,4 +150,17 @@
 			evSendRequest.Set ();
 		}
+
+		public void ClientClosed (SseClient _client) {
+			foreach ((string eventName, AbsEvent eventHandler) in events) {
+				try {
+					eventHandler.ClientClosed (_client);
+				} catch (Exception e) {
+					Log.Error($"[Web] [SSE] '{eventName}': Error closing client");
+					Log.Exception(e);
+				}
+			}
+
+			clients.Remove (_client);
+		}
 	}
 }
Index: TFP-WebServer/WebServer/src/Web.cs
===================================================================
--- TFP-WebServer/WebServer/src/Web.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/Web.cs	(revision 487)
@@ -172,5 +172,7 @@
 		}
 
+#if ENABLE_PROFILER
 		private readonly UnityEngine.Profiling.CustomSampler getContextSampler = UnityEngine.Profiling.CustomSampler.Create ("GetCtx");
+#endif
 		private readonly UnityEngine.Profiling.CustomSampler authSampler = UnityEngine.Profiling.CustomSampler.Create ("Auth");
 		private readonly UnityEngine.Profiling.CustomSampler cookieSampler = UnityEngine.Profiling.CustomSampler.Create ("ConCookie");
@@ -191,5 +193,5 @@
 #else
 			HttpListenerContext ctx = listenerInstance.EndGetContext (_result);
-			listenerInstance.BeginGetContext (HandleRequest, listenerInstance);
+			listenerInstance.BeginGetContext (handleRequestDelegate, listenerInstance);
 #endif
 			try {
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/Permissions/CommandPermissions.cs
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/Permissions/CommandPermissions.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/Permissions/CommandPermissions.cs	(revision 487)
@@ -10,7 +10,9 @@
 		private const string propertyCommand = "command";
 		private const string propertyPermissionLevel = "permissionLevel";
+		private const string propertyIsDefault = "default";
 
 		private static readonly byte[] jsonKeyCommand = JsonWriter.GetEncodedPropertyNameWithBeginObject (propertyCommand);
 		private static readonly byte[] jsonKeyPermissionLevel = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator (propertyPermissionLevel);
+		private static readonly byte[] jsonKeyIsDefault = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator (propertyIsDefault);
 
 		private static AdminCommands CommandsInstance => GameManager.Instance.adminTools.Commands;
@@ -26,5 +28,6 @@
 			
 				bool first = true;
-				foreach ((_, AdminCommands.CommandPermission commandPermission) in CommandsInstance.GetCommands ()) {
+				
+				foreach (IConsoleCommand command in SdtdConsole.Instance.GetCommands ()) {
 					if (!first) {
 						writer.WriteValueSeparator ();
@@ -33,7 +36,14 @@
 					first = false;
 
-					writeCommandJson (ref writer, commandPermission);
+					AdminCommands.CommandPermission commandPermission = CommandsInstance.GetAdminToolsCommandPermission (command.GetCommands());
+					bool isDefault = commandPermission.PermissionLevel == command.DefaultPermissionLevel;
+					if (commandPermission.Command == "") {
+						commandPermission =
+							new AdminCommands.CommandPermission (command.GetCommands ()[0], commandPermission.PermissionLevel);
+					}
+
+					writeCommandJson (ref writer, commandPermission, isDefault);
 				}
-
+				
 				writer.WriteEndArray ();
 				
@@ -46,9 +56,11 @@
 		}
 
-		private void writeCommandJson (ref JsonWriter _writer, AdminCommands.CommandPermission _commandPermission) {
+		private void writeCommandJson(ref JsonWriter _writer, AdminCommands.CommandPermission _commandPermission, bool _isDefault) {
 			_writer.WriteRaw (jsonKeyCommand);
 			_writer.WriteString (_commandPermission.Command);
 			_writer.WriteRaw (jsonKeyPermissionLevel);
 			_writer.WriteInt32 (_commandPermission.PermissionLevel);
+			_writer.WriteRaw (jsonKeyIsDefault);
+			_writer.WriteBoolean (_isDefault);
 			_writer.WriteEndObject ();
 		}
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/Permissions/CommandPermissions.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/Permissions/CommandPermissions.openapi.yaml	(revision 486)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/Permissions/CommandPermissions.openapi.yaml	(revision 487)
@@ -21,7 +21,11 @@
           type: integer
           description: Permission level of the command
+        default:
+          type: boolean
+          description: Whether the permission level is the default value for the command
       required:
         - command
         - permissionLevel
+        - default
 
     CommandPermissionList:
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/Permissions/WebModules.cs
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/Permissions/WebModules.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/Permissions/WebModules.cs	(revision 487)
@@ -124,5 +124,5 @@
 				}
 
-				module.LevelGlobal = permissionLevelGlobal;
+				module = module.SetLevelGlobal (permissionLevelGlobal);
 			}
 
@@ -165,9 +165,8 @@
 					}
 
-					module.LevelPerMethod [(int)method] = permissionLevel;
+					module = module.SetLevelForMethod (method, permissionLevel);
 				}
 			}
 
-			module.IsDefault = false;
 			ModulesInstance.AddModule (module);
 
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Player.cs
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Player.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Player.cs	(revision 487)
@@ -92,5 +92,5 @@
 			ClientInfo ci = ConnectionManager.Instance.Clients.ForUserId (_nativeUserId);
 			if (ci == null) {
-				Log.Warning ($"[Web] Player.GET: ClientInfo null");
+				Log.Warning ("[Web] Player.GET: ClientInfo null");
 				return;
 			}
@@ -100,5 +100,5 @@
 
 			if (entity == null) {
-				Log.Warning ($"[Web] Player.GET: EntityPlayer null");
+				Log.Warning ("[Web] Player.GET: EntityPlayer null");
 				return;
 			}
Index: TFP-WebServer/WebServer/src/WebAPI/AbsRestApi.cs
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/AbsRestApi.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/WebAPI/AbsRestApi.cs	(revision 487)
@@ -103,5 +103,10 @@
 				}
 			} catch (Exception e) {
-				SendEmptyResponse (_context, HttpStatusCode.InternalServerError, jsonInputData, "ERROR_PROCESSING", e);
+				try {
+					SendEmptyResponse (_context, HttpStatusCode.InternalServerError, jsonInputData, "ERROR_PROCESSING", e);
+				} catch (Exception e2) {
+					Log.Error ($"[Web] In {nameof(AbsRestApi)}.HandleRequest(): Handler {Name} threw an exception while trying to send a previous exception to the client:");
+					Log.Exception (e2);
+				}
 			}
 		}
Index: TFP-WebServer/WebServer/src/WebAPI/OpenApiHelpers.cs
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/OpenApiHelpers.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/WebAPI/OpenApiHelpers.cs	(revision 487)
@@ -105,5 +105,5 @@
 		}
 
-		private static readonly Regex pathMatcher = new Regex ("^\\s{1,2}(/\\S+):.*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
+		private static readonly Regex pathMatcher = new Regex (@"^\s{1,2}(/\S+):.*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
 		private Dictionary<string, string> findExportedPaths (string _spec, string _replaceBasePath = null) {
 			Dictionary<string, string> result = new Dictionary<string, string> ();
@@ -161,5 +161,5 @@
 						break;
 					case '\\':
-						_targetSb.Append ("\\\\");
+						_targetSb.Append (@"\\");
 						break;
 					case '\b':
Index: TFP-WebServer/WebServer/src/WebCommandResult.cs
===================================================================
--- TFP-WebServer/WebServer/src/WebCommandResult.cs	(revision 486)
+++ TFP-WebServer/WebServer/src/WebCommandResult.cs	(revision 487)
@@ -17,4 +17,5 @@
 		private readonly string command;
 		private readonly string parameters;
+		private readonly string sourceName;
 
 		private readonly RequestContext context;
@@ -26,4 +27,5 @@
 			parameters = _parameters;
 			responseType = _responseType;
+			sourceName = _context.Connection?.Username ?? "Unauth-PermLevel-" + _context.PermissionLevel;
 		}
 
@@ -93,5 +95,5 @@
 
 		public string GetDescription () {
-			return $"WebCommandResult_for_{command}";
+			return $"WebCommandResult_for_{command}_by_{sourceName}";
 		}
 	}
