Index: /binary-improvements/7dtd-server-fixes/src/PersistentData/Players.cs
===================================================================
--- /binary-improvements/7dtd-server-fixes/src/PersistentData/Players.cs	(revision 331)
+++ /binary-improvements/7dtd-server-fixes/src/PersistentData/Players.cs	(revision 332)
@@ -6,5 +6,5 @@
 	[Serializable]
 	public class Players {
-		private readonly Dictionary<string, Player> players = new Dictionary<string, Player> (StringComparer.OrdinalIgnoreCase);
+		public readonly Dictionary<string, Player> Dict = new Dictionary<string, Player> (StringComparer.OrdinalIgnoreCase);
 
 		public Player this [string steamId, bool create] {
@@ -14,6 +14,6 @@
 				}
 
-				if (players.ContainsKey (steamId)) {
-					return players [steamId];
+				if (Dict.ContainsKey (steamId)) {
+					return Dict [steamId];
 				}
 
@@ -24,15 +24,11 @@
 				Log.Out ("Created new player entry for ID: " + steamId);
 				Player p = new Player (steamId);
-				players.Add (steamId, p);
+				Dict.Add (steamId, p);
 				return p;
 			}
 		}
 
-		public List<string> SteamIDs {
-			get { return new List<string> (players.Keys); }
-		}
-
 		public int Count {
-			get { return players.Count; }
+			get { return Dict.Count; }
 		}
 
@@ -58,5 +54,5 @@
 			int entityId;
 			if (int.TryParse (_nameOrId, out entityId)) {
-				foreach (KeyValuePair<string, Player> kvp in players) {
+				foreach (KeyValuePair<string, Player> kvp in Dict) {
 					if (kvp.Value.IsOnline && kvp.Value.EntityID == entityId) {
 						return kvp.Key;
@@ -65,5 +61,5 @@
 			}
 
-			foreach (KeyValuePair<string, Player> kvp in players) {
+			foreach (KeyValuePair<string, Player> kvp in Dict) {
 				string name = kvp.Value.Name;
 				if (_ignoreColorCodes) {
Index: /binary-improvements/AllocsCommands/Commands/ListKnownPlayers.cs
===================================================================
--- /binary-improvements/AllocsCommands/Commands/ListKnownPlayers.cs	(revision 331)
+++ /binary-improvements/AllocsCommands/Commands/ListKnownPlayers.cs	(revision 332)
@@ -62,15 +62,15 @@
 				} else {
 					int num = 0;
-					foreach (string sid in PersistentContainer.Instance.Players.SteamIDs) {
-						Player p = PersistentContainer.Instance.Players [sid, false];
+					foreach (KeyValuePair<string, Player> kvp in PersistentContainer.Instance.Players.Dict) {
+						Player p = kvp.Value;
 
 						if (
 							(!onlineOnly || p.IsOnline)
-							&& (!notBannedOnly || !admTools.IsBanned (sid))
+							&& (!notBannedOnly || !admTools.IsBanned (kvp.Key))
 							&& (nameFilter.Length == 0 || p.Name.ContainsCaseInsensitive (nameFilter))
 						) {
 							SdtdConsole.Instance.Output (string.Format (
 								"{0}. {1}, id={2}, steamid={3}, online={4}, ip={5}, playtime={6} m, seen={7}",
-								++num, p.Name, p.EntityID, sid, p.IsOnline, p.IP,
+								++num, p.Name, p.EntityID, kvp.Key, p.IsOnline, p.IP,
 								p.TotalPlayTime / 60,
 								p.LastOnline.ToString ("yyyy-MM-dd HH:mm"))
Index: /binary-improvements/MapRendering/MapRendering/MapRenderBlockBuffer.cs
===================================================================
--- /binary-improvements/MapRendering/MapRendering/MapRenderBlockBuffer.cs	(revision 331)
+++ /binary-improvements/MapRendering/MapRendering/MapRenderBlockBuffer.cs	(revision 332)
@@ -17,5 +17,4 @@
 		private Vector2i currentBlockMapPos = new Vector2i (Int32.MinValue, Int32.MinValue);
 		private string currentBlockMapFolder = string.Empty;
-		private string currentBlockMapFilename = string.Empty;
 
 		public MapRenderBlockBuffer (int level, MapTileCache cache) {
@@ -46,5 +45,4 @@
 		public void ResetBlock () {
 			currentBlockMapFolder = string.Empty;
-			currentBlockMapFilename = string.Empty;
 			currentBlockMapPos = new Vector2i (Int32.MinValue, Int32.MinValue);
 		}
@@ -84,5 +82,4 @@
 					currentBlockMapFolder = folder;
 					currentBlockMapPos = block;
-					currentBlockMapFilename = fileName;
 
 					Profiler.EndSample ();
Index: /binary-improvements/MapRendering/Web/API/GetLandClaims.cs
===================================================================
--- /binary-improvements/MapRendering/Web/API/GetLandClaims.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/API/GetLandClaims.cs	(revision 332)
@@ -21,5 +21,5 @@
 
 			// default user, cheap way to avoid 'null reference exception'
-			user = user ?? new WebConnection ("", "", 0L);
+			user = user ?? new WebConnection ("", IPAddress.None, 0L);
 
 			bool bViewAll = WebConnection.CanViewAllClaims (permissionLevel);
Index: /binary-improvements/MapRendering/Web/API/GetPlayerInventories.cs
===================================================================
--- /binary-improvements/MapRendering/Web/API/GetPlayerInventories.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/API/GetPlayerInventories.cs	(revision 332)
@@ -1,2 +1,3 @@
+using System.Collections.Generic;
 using System.Net;
 using AllocsFixes.JSON;
@@ -9,6 +10,6 @@
 			JSONArray AllInventoriesResult = new JSONArray ();
 
-			foreach (string sid in PersistentContainer.Instance.Players.SteamIDs) {
-				Player p = PersistentContainer.Instance.Players [sid, false];
+			foreach (KeyValuePair<string, Player> kvp in PersistentContainer.Instance.Players.Dict) {
+				Player p = kvp.Value;
 
 				if (p == null) {
@@ -23,5 +24,5 @@
 					JSONArray belt = new JSONArray ();
 					JSONObject equipment = new JSONObject ();
-					result.Add ("steamid", new JSONString (sid));
+					result.Add ("steamid", new JSONString (kvp.Key));
 					result.Add ("entityid", new JSONNumber (p.EntityID));
 					result.Add ("playername", new JSONString (p.Name));
Index: /binary-improvements/MapRendering/Web/API/GetPlayerList.cs
===================================================================
--- /binary-improvements/MapRendering/Web/API/GetPlayerList.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/API/GetPlayerList.cs	(revision 332)
@@ -6,4 +6,5 @@
 using AllocsFixes.JSON;
 using AllocsFixes.PersistentData;
+using UnityEngine.Profiling;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
@@ -12,8 +13,12 @@
 			new Regex (@"^(>=|=>|>|<=|=<|<|==|=)?\s*([0-9]+(\.[0-9]*)?)$");
 
+#if ENABLE_PROFILER
+		private static readonly CustomSampler jsonSerializeSampler = CustomSampler.Create ("JSON_Build");
+#endif
+
 		public override void HandleRequest (HttpListenerRequest req, HttpListenerResponse resp, WebConnection user,
 			int permissionLevel) {
 			AdminTools admTools = GameManager.Instance.adminTools;
-			user = user ?? new WebConnection ("", "", 0L);
+			user = user ?? new WebConnection ("", IPAddress.None, 0L);
 
 			bool bViewAll = WebConnection.CanViewAllPlayers (permissionLevel);
@@ -35,11 +40,16 @@
 			Players playersList = PersistentContainer.Instance.Players;
 
+			
 			List<JSONObject> playerList = new List<JSONObject> ();
 
-			foreach (string sid in playersList.SteamIDs) {
-				Player p = playersList [sid, false];
+#if ENABLE_PROFILER
+			jsonSerializeSampler.Begin ();
+#endif
+
+			foreach (KeyValuePair<string, Player> kvp in playersList.Dict) {
+				Player p = kvp.Value;
 
 				ulong player_steam_ID;
-				if (!ulong.TryParse (sid, out player_steam_ID)) {
+				if (!ulong.TryParse (kvp.Key, out player_steam_ID)) {
 					player_steam_ID = 0L;
 				}
@@ -52,5 +62,5 @@
 
 					JSONObject pJson = new JSONObject ();
-					pJson.Add ("steamid", new JSONString (sid));
+					pJson.Add ("steamid", new JSONString (kvp.Key));
 					pJson.Add ("entityid", new JSONNumber (p.EntityID));
 					pJson.Add ("ip", new JSONString (p.IP));
@@ -66,5 +76,5 @@
 					JSONBoolean banned;
 					if (admTools != null) {
-						banned = new JSONBoolean (admTools.IsBanned (sid));
+						banned = new JSONBoolean (admTools.IsBanned (kvp.Key));
 					} else {
 						banned = new JSONBoolean (false);
@@ -76,4 +86,8 @@
 				}
 			}
+
+#if ENABLE_PROFILER
+			jsonSerializeSampler.End ();
+#endif
 
 			IEnumerable<JSONObject> list = playerList;
Index: /binary-improvements/MapRendering/Web/API/GetPlayersLocation.cs
===================================================================
--- /binary-improvements/MapRendering/Web/API/GetPlayersLocation.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/API/GetPlayersLocation.cs	(revision 332)
@@ -1,2 +1,3 @@
+using System.Collections.Generic;
 using System.Net;
 using AllocsFixes.JSON;
@@ -8,5 +9,5 @@
 			int permissionLevel) {
 			AdminTools admTools = GameManager.Instance.adminTools;
-			user = user ?? new WebConnection ("", "", 0L);
+			user = user ?? new WebConnection ("", IPAddress.None, 0L);
 
 			bool listOffline = false;
@@ -21,16 +22,16 @@
 			Players playersList = PersistentContainer.Instance.Players;
 
-			foreach (string sid in playersList.SteamIDs) {
+			foreach (KeyValuePair<string, Player> kvp in playersList.Dict) {
 				if (admTools != null) {
-					if (admTools.IsBanned (sid)) {
+					if (admTools.IsBanned (kvp.Key)) {
 						continue;
 					}
 				}
 
-				Player p = playersList [sid, false];
+				Player p = kvp.Value;
 
 				if (listOffline || p.IsOnline) {
 					ulong player_steam_ID;
-					if (!ulong.TryParse (sid, out player_steam_ID)) {
+					if (!ulong.TryParse (kvp.Key, out player_steam_ID)) {
 						player_steam_ID = 0L;
 					}
@@ -43,5 +44,5 @@
 
 						JSONObject pJson = new JSONObject ();
-						pJson.Add ("steamid", new JSONString (sid));
+						pJson.Add ("steamid", new JSONString (kvp.Key));
 
 						//					pJson.Add("entityid", new JSONNumber (p.EntityID));
Index: /binary-improvements/MapRendering/Web/API/WebAPI.cs
===================================================================
--- /binary-improvements/MapRendering/Web/API/WebAPI.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/API/WebAPI.cs	(revision 332)
@@ -2,4 +2,5 @@
 using System.Text;
 using AllocsFixes.JSON;
+using UnityEngine.Profiling;
 
 namespace AllocsFixes.NetConnections.Servers.Web.API {
@@ -11,7 +12,19 @@
 		}
 
+#if ENABLE_PROFILER
+		private static readonly CustomSampler jsonSerializeSampler = CustomSampler.Create ("JSON_Serialize");
+		private static readonly CustomSampler netWriteSampler = CustomSampler.Create ("JSON_Write");
+#endif
+
 		public static void WriteJSON (HttpListenerResponse resp, JSONNode root) {
+#if ENABLE_PROFILER
+			jsonSerializeSampler.Begin ();
+#endif
 			StringBuilder sb = new StringBuilder ();
 			root.ToString (sb);
+#if ENABLE_PROFILER
+			jsonSerializeSampler.End ();
+			netWriteSampler.Begin ();
+#endif
 			byte[] buf = Encoding.UTF8.GetBytes (sb.ToString ());
 			resp.ContentLength64 = buf.Length;
@@ -19,4 +32,7 @@
 			resp.ContentEncoding = Encoding.UTF8;
 			resp.OutputStream.Write (buf, 0, buf.Length);
+#if ENABLE_PROFILER
+			netWriteSampler.End ();
+#endif
 		}
 
Index: /binary-improvements/MapRendering/Web/ConnectionHandler.cs
===================================================================
--- /binary-improvements/MapRendering/Web/ConnectionHandler.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/ConnectionHandler.cs	(revision 332)
@@ -1,4 +1,5 @@
 using System;
 using System.Collections.Generic;
+using System.Net;
 
 namespace AllocsFixes.NetConnections.Servers.Web {
@@ -6,5 +7,5 @@
 		private readonly Dictionary<string, WebConnection> connections = new Dictionary<string, WebConnection> ();
 
-		public WebConnection IsLoggedIn (string _sessionId, string _endpoint) {
+		public WebConnection IsLoggedIn (string _sessionId, IPAddress _ip) {
 			if (!connections.ContainsKey (_sessionId)) {
 				return null;
@@ -18,6 +19,7 @@
 //			}
 
-			if (con.Endpoint != _endpoint) {
-				connections.Remove (_sessionId);
+			if (!Equals (con.Endpoint, _ip)) {
+				// Fixed: Allow different clients from same NAT network
+//				connections.Remove (_sessionId);
 				return null;
 			}
@@ -32,7 +34,7 @@
 		}
 
-		public WebConnection LogIn (ulong _steamId, string _endpoint) {
+		public WebConnection LogIn (ulong _steamId, IPAddress _ip) {
 			string sessionId = Guid.NewGuid ().ToString ();
-			WebConnection con = new WebConnection (sessionId, _endpoint, _steamId);
+			WebConnection con = new WebConnection (sessionId, _ip, _steamId);
 			connections.Add (sessionId, con);
 			return con;
@@ -40,6 +42,6 @@
 
 		public void SendLine (string line) {
-			foreach (WebConnection wc in connections.Values) {
-				wc.SendLine (line);
+			foreach (KeyValuePair<string, WebConnection> kvp in connections) {
+				kvp.Value.SendLine (line);
 			}
 		}
Index: /binary-improvements/MapRendering/Web/Handlers/ApiHandler.cs
===================================================================
--- /binary-improvements/MapRendering/Web/Handlers/ApiHandler.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/Handlers/ApiHandler.cs	(revision 332)
@@ -4,4 +4,5 @@
 using System.Reflection;
 using AllocsFixes.NetConnections.Servers.Web.API;
+using UnityEngine.Profiling;
 
 namespace AllocsFixes.NetConnections.Servers.Web.Handlers {
@@ -40,4 +41,8 @@
 		}
 
+#if ENABLE_PROFILER
+		private static readonly CustomSampler apiHandlerSampler = CustomSampler.Create ("API_Handler");
+#endif
+
 		public override void HandleRequest (HttpListenerRequest req, HttpListenerResponse resp, WebConnection user,
 			int permissionLevel) {
@@ -55,5 +60,11 @@
 			if (apis.TryGetValue (apiName, out api)) {
 				try {
+#if ENABLE_PROFILER
+					apiHandlerSampler.Begin ();
+#endif
 					api.HandleRequest (req, resp, user, permissionLevel);
+#if ENABLE_PROFILER
+					apiHandlerSampler.End ();
+#endif
 					return;
 				} catch (Exception e) {
Index: /binary-improvements/MapRendering/Web/Handlers/StaticHandler.cs
===================================================================
--- /binary-improvements/MapRendering/Web/Handlers/StaticHandler.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/Handlers/StaticHandler.cs	(revision 332)
@@ -13,5 +13,5 @@
 			string moduleName = null) : base (moduleName) {
 			this.staticPart = staticPart;
-			datapath = filePath;
+			datapath = filePath + (filePath [filePath.Length - 1] == '/' ? "" : "/");
 			this.cache = cache;
 			this.logMissingFiles = logMissingFiles;
@@ -22,5 +22,5 @@
 			string fn = req.Url.AbsolutePath.Remove (0, staticPart.Length);
 
-			byte[] content = cache.GetFileContent (datapath + "/" + fn);
+			byte[] content = cache.GetFileContent (datapath + fn);
 
 			if (content != null) {
@@ -31,6 +31,5 @@
 				resp.StatusCode = (int) HttpStatusCode.NotFound;
 				if (logMissingFiles) {
-					Log.Out ("Web:Static:FileNotFound: \"" + req.Url.AbsolutePath + "\" @ \"" + datapath + "/" +
-					         req.Url.AbsolutePath.Remove (0, staticPart.Length) + "\"");
+					Log.Out ("Web:Static:FileNotFound: \"" + req.Url.AbsolutePath + "\" @ \"" + datapath + fn + "\"");
 				}
 			}
Index: /binary-improvements/MapRendering/Web/Handlers/UserStatusHandler.cs
===================================================================
--- /binary-improvements/MapRendering/Web/Handlers/UserStatusHandler.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/Handlers/UserStatusHandler.cs	(revision 332)
@@ -19,6 +19,5 @@
 				JSONObject permObj = new JSONObject ();
 				permObj.Add ("module", new JSONString (perm.module));
-				permObj.Add ("allowed",
-					new JSONBoolean (WebPermissions.Instance.ModuleAllowedWithLevel (perm.module, permissionLevel)));
+				permObj.Add ("allowed", new JSONBoolean (perm.permissionLevel >= permissionLevel));
 				perms.Add (permObj);
 			}
Index: /binary-improvements/MapRendering/Web/MimeType.cs
===================================================================
--- /binary-improvements/MapRendering/Web/MimeType.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/MimeType.cs	(revision 332)
@@ -5,5 +5,5 @@
 	public class MimeType {
 		private static readonly IDictionary<string, string> _mappings =
-			new Dictionary<string, string> (StringComparer.InvariantCultureIgnoreCase) {
+			new CaseInsensitiveStringDictionary<string> {
 				{".323", "text/h323"},
 				{".3g2", "video/3gpp2"},
Index: /binary-improvements/MapRendering/Web/Web.cs
===================================================================
--- /binary-improvements/MapRendering/Web/Web.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/Web.cs	(revision 332)
@@ -10,4 +10,5 @@
 using AllocsFixes.NetConnections.Servers.Web.Handlers;
 using UnityEngine;
+using UnityEngine.Profiling;
 
 namespace AllocsFixes.NetConnections.Servers.Web {
@@ -19,5 +20,5 @@
 		private readonly HttpListener _listener = new HttpListener ();
 		private readonly string dataFolder;
-		private readonly Dictionary<string, PathHandler> handlers = new Dictionary<string, PathHandler> ();
+		private readonly Dictionary<string, PathHandler> handlers = new CaseInsensitiveStringDictionary<PathHandler> ();
 		private readonly bool useStaticCache;
 
@@ -147,4 +148,11 @@
 			return false;
 		}
+		
+		private readonly Version HttpProtocolVersion = new Version(1, 1);
+		
+#if ENABLE_PROFILER
+		private readonly CustomSampler authSampler = CustomSampler.Create ("Auth");
+		private readonly CustomSampler handlerSampler = CustomSampler.Create ("Handler");
+#endif
 
 		private void HandleRequest (IAsyncResult result) {
@@ -157,6 +165,12 @@
 
 //				MicroStopwatch msw = new MicroStopwatch ();
+#if ENABLE_PROFILER
+			Profiler.BeginThreadProfiling ("AllocsMods", "WebRequest");
+			HttpListenerContext ctx = _listener.EndGetContext (result);
+			try {
+#else
 			HttpListenerContext ctx = _listener.EndGetContext (result);
 			_listener.BeginGetContext (HandleRequest, _listener);
+#endif
 			try {
 				HttpListenerRequest request = ctx.Request;
@@ -164,8 +178,14 @@
 				response.SendChunked = false;
 
-				response.ProtocolVersion = new Version ("1.1");
+				response.ProtocolVersion = HttpProtocolVersion;
 
 				WebConnection conn;
+#if ENABLE_PROFILER
+				authSampler.Begin ();
+#endif
 				int permissionLevel = DoAuthentication (request, out conn);
+#if ENABLE_PROFILER
+				authSampler.End ();
+#endif
 
 
@@ -200,5 +220,11 @@
 								}
 							} else {
+#if ENABLE_PROFILER
+								handlerSampler.Begin ();
+#endif
 								kvp.Value.HandleRequest (request, response, conn, permissionLevel);
+#if ENABLE_PROFILER
+								handlerSampler.End ();
+#endif
 							}
 
@@ -230,4 +256,10 @@
 				Interlocked.Decrement (ref currentHandlers);
 			}
+#if ENABLE_PROFILER
+			} finally {
+				_listener.BeginGetContext (HandleRequest, _listener);
+				Profiler.EndThreadProfiling ();
+			}
+#endif
 		}
 
@@ -241,5 +273,5 @@
 
 			if (!string.IsNullOrEmpty (sessionId)) {
-				WebConnection con = connectionHandler.IsLoggedIn (sessionId, _req.RemoteEndPoint.Address.ToString ());
+				WebConnection con = connectionHandler.IsLoggedIn (sessionId, _req.RemoteEndPoint.Address);
 				if (con != null) {
 					_con = con;
@@ -259,9 +291,9 @@
 			}
 
-			if (_req.Url.AbsolutePath.StartsWith ("/session/verify")) {
+			if (_req.Url.AbsolutePath.StartsWith ("/session/verify", StringComparison.OrdinalIgnoreCase)) {
 				try {
 					ulong id = OpenID.Validate (_req);
 					if (id > 0) {
-						WebConnection con = connectionHandler.LogIn (id, _req.RemoteEndPoint.Address.ToString ());
+						WebConnection con = connectionHandler.LogIn (id, _req.RemoteEndPoint.Address);
 						_con = con;
 						int level = GameManager.Instance.adminTools.GetAdminToolsClientInfo (id.ToString ())
Index: /binary-improvements/MapRendering/Web/WebCommandResult.cs
===================================================================
--- /binary-improvements/MapRendering/Web/WebCommandResult.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/WebCommandResult.cs	(revision 332)
@@ -39,5 +39,5 @@
 
 		public void SendLines (List<string> _output) {
-			MicroStopwatch msw = new MicroStopwatch ();
+//			MicroStopwatch msw = new MicroStopwatch ();
 
 			StringBuilder sb = new StringBuilder ();
@@ -81,9 +81,9 @@
 				}
 
-				msw.Stop ();
-				if (GamePrefs.GetInt (EnumGamePrefs.HideCommandExecutionLog) < 1) {
-					totalHandlingTime += msw.ElapsedMicroseconds;
-					Log.Out ("WebCommandResult.SendLines(): Took {0} µs", msw.ElapsedMicroseconds);
-				}
+//				msw.Stop ();
+//				if (GamePrefs.GetInt (EnumGamePrefs.HideCommandExecutionLog) < 1) {
+//					totalHandlingTime += msw.ElapsedMicroseconds;
+//					Log.Out ("WebCommandResult.SendLines(): Took {0} µs", msw.ElapsedMicroseconds);
+//				}
 
 				Interlocked.Decrement (ref currentHandlers);
Index: /binary-improvements/MapRendering/Web/WebConnection.cs
===================================================================
--- /binary-improvements/MapRendering/Web/WebConnection.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/WebConnection.cs	(revision 332)
@@ -1,4 +1,5 @@
 using System;
 using System.Collections.Generic;
+using System.Net;
 using UnityEngine;
 
@@ -10,5 +11,5 @@
 		private readonly string conDescription;
 
-		public WebConnection (string _sessionId, string _endpoint, ulong _steamId) {
+		public WebConnection (string _sessionId, IPAddress _endpoint, ulong _steamId) {
 			SessionID = _sessionId;
 			Endpoint = _endpoint;
@@ -21,5 +22,5 @@
 		public string SessionID { get; private set; }
 
-		public string Endpoint { get; private set; }
+		public IPAddress Endpoint { get; private set; }
 
 		public ulong SteamID { get; private set; }
@@ -30,29 +31,9 @@
 
 		public static bool CanViewAllPlayers (int _permissionLevel) {
-			const int defaultPermissionLevel = 0;
-
-			bool val = _permissionLevel <= defaultPermissionLevel;
-
-			foreach (WebPermissions.WebModulePermission wap in WebPermissions.Instance.GetModules ()) {
-				if (wap.module.EqualsCaseInsensitive ("webapi.viewallplayers")) {
-					val = _permissionLevel <= wap.permissionLevel;
-				}
-			}
-
-			return val;
+			return WebPermissions.Instance.ModuleAllowedWithLevel ("webapi.viewallplayers", _permissionLevel);
 		}
 
 		public static bool CanViewAllClaims (int _permissionLevel) {
-			const int defaultPermissionLevel = 0;
-
-			bool val = _permissionLevel <= defaultPermissionLevel;
-
-			foreach (WebPermissions.WebModulePermission wap in WebPermissions.Instance.GetModules ()) {
-				if (wap.module.EqualsCaseInsensitive ("webapi.viewallclaims")) {
-					val = _permissionLevel <= wap.permissionLevel;
-				}
-			}
-
-			return val;
+			return WebPermissions.Instance.ModuleAllowedWithLevel ("webapi.viewallclaims", _permissionLevel);
 		}
 
Index: /binary-improvements/MapRendering/Web/WebPermissions.cs
===================================================================
--- /binary-improvements/MapRendering/Web/WebPermissions.cs	(revision 331)
+++ /binary-improvements/MapRendering/Web/WebPermissions.cs	(revision 332)
@@ -1,5 +1,7 @@
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.IO;
 using System.Xml;
+using UniLinq;
 
 namespace AllocsFixes.NetConnections.Servers.Web {
@@ -10,5 +12,5 @@
 
 		private readonly Dictionary<string, WebModulePermission> knownModules =
-			new Dictionary<string, WebModulePermission> ();
+			new CaseInsensitiveStringDictionary<WebModulePermission> ();
 
 		private readonly Dictionary<string, AdminToken> admintokens = new CaseInsensitiveStringDictionary<AdminToken> ();
@@ -18,4 +20,6 @@
 
 		public WebPermissions () {
+			allModulesList = new List<WebModulePermission> ();
+			allModulesListRO = new ReadOnlyCollection<WebModulePermission> (allModulesList);
 			Directory.CreateDirectory (GetFilePath ());
 			InitFileWatcher ();
@@ -54,4 +58,8 @@
 			}
 
+			if (knownModules.TryGetValue (_module, out result)) {
+				return result;
+			}
+
 			return defaultModulePermission;
 		}
@@ -93,4 +101,5 @@
 			WebModulePermission p = new WebModulePermission (_module, _permissionLevel);
 			lock (this) {
+				allModulesList.Clear ();
 				modules [_module] = p;
 				if (_save) {
@@ -104,13 +113,10 @@
 				return;
 			}
-
-			lock (this) {
-				if (!IsKnownModule (_module)) {
-					knownModules.Add (_module, new WebModulePermission (_module, _defaultPermission));
-				}
-
-				if (_defaultPermission > 0 && !modules.ContainsKey (_module)) {
-					AddModulePermission (_module, _defaultPermission);
-				}
+			
+			WebModulePermission p = new WebModulePermission (_module, _defaultPermission);
+
+			lock (this) {
+				allModulesList.Clear ();
+				knownModules [_module] = p;
 			}
 		}
@@ -129,4 +135,5 @@
 		public void RemoveModulePermission (string _module, bool _save = true) {
 			lock (this) {
+				allModulesList.Clear ();
 				modules.Remove (_module);
 				if (_save) {
@@ -136,15 +143,19 @@
 		}
 
-		public List<WebModulePermission> GetModules () {
-			List<WebModulePermission> result = new List<WebModulePermission> ();
-			foreach (string module in knownModules.Keys) {
-				if (modules.ContainsKey (module)) {
-					result.Add (modules [module]);
-				} else {
-					result.Add (knownModules [module]);
-				}
-			}
-
-			return result;
+		private readonly List<WebModulePermission> allModulesList;
+		private readonly ReadOnlyCollection<WebModulePermission> allModulesListRO; 
+
+		public IList<WebModulePermission> GetModules () {
+			if (allModulesList.Count == 0) {
+				foreach (KeyValuePair<string, WebModulePermission> kvp in knownModules) {
+					if (modules.ContainsKey (kvp.Key)) {
+						allModulesList.Add (modules [kvp.Key]);
+					} else {
+						allModulesList.Add (kvp.Value);
+					}
+				}
+			}
+
+			return allModulesListRO;
 		}
 
@@ -302,7 +313,7 @@
 				sw.WriteLine (
 					"		<!-- <token name=\"adminuser1\" token=\"supersecrettoken\" permission_level=\"0\" /> -->");
-				foreach (AdminToken at in admintokens.Values) {
-					sw.WriteLine ("		<token name=\"{0}\" token=\"{1}\" permission_level=\"{2}\" />", at.name, at.token,
-						at.permissionLevel);
+				foreach (KeyValuePair<string, AdminToken> kvp in admintokens) {
+					sw.WriteLine ("		<token name=\"{0}\" token=\"{1}\" permission_level=\"{2}\" />", kvp.Value.name,
+						kvp.Value.token, kvp.Value.permissionLevel);
 				}
 
@@ -310,7 +321,7 @@
 				sw.WriteLine ();
 				sw.WriteLine ("	<permissions>");
-				foreach (WebModulePermission wap in modules.Values) {
-					sw.WriteLine ("		<permission module=\"{0}\" permission_level=\"{1}\" />", wap.module,
-						wap.permissionLevel);
+				foreach (KeyValuePair<string, WebModulePermission> kvp in modules) {
+					sw.WriteLine ("		<permission module=\"{0}\" permission_level=\"{1}\" />", kvp.Value.module,
+						kvp.Value.permissionLevel);
 				}
 
