Index: binary-improvements/MapRendering/API.cs
===================================================================
--- binary-improvements/MapRendering/API.cs	(revision 249)
+++ binary-improvements/MapRendering/API.cs	(revision 250)
@@ -7,4 +7,5 @@
 		public override void GameAwake () {
 			new AllocsFixes.NetConnections.Servers.Web.Web ();
+			AllocsFixes.NetConnections.Servers.Web.LogBuffer.Instance.GetType ();
 		}
 
Index: binary-improvements/MapRendering/ModInfo.xml
===================================================================
--- binary-improvements/MapRendering/ModInfo.xml	(revision 249)
+++ binary-improvements/MapRendering/ModInfo.xml	(revision 250)
@@ -5,5 +5,5 @@
 		<Description value="Render the game map to image map tiles as it is uncovered" />
 		<Author value="Christian 'Alloc' Illy" />
-		<Version value="8" />
+		<Version value="9" />
 		<Website value="http://7dtd.illy.bz" />
 	</ModInfo>
Index: binary-improvements/MapRendering/Web/API/GetLog.cs
===================================================================
--- binary-improvements/MapRendering/Web/API/GetLog.cs	(revision 250)
+++ binary-improvements/MapRendering/Web/API/GetLog.cs	(revision 250)
@@ -0,0 +1,45 @@
+using AllocsFixes.JSON;
+using AllocsFixes.PersistentData;
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace AllocsFixes.NetConnections.Servers.Web.API
+{
+	public class GetLog : WebAPI {
+		public override void HandleRequest (HttpListenerRequest req, HttpListenerResponse resp, WebConnection user, int permissionLevel) {
+			int firstLine, lastLine;
+
+			if (req.QueryString ["firstLine"] == null || !int.TryParse (req.QueryString ["firstLine"], out firstLine)) {
+				firstLine = LogBuffer.Instance.OldestLine;
+			}
+
+			if (req.QueryString ["lastLine"] == null || !int.TryParse (req.QueryString ["lastLine"], out lastLine)) {
+				lastLine = -1;
+			}
+
+			JSONObject result = new JSONObject ();
+
+			List<LogBuffer.LogEntry> logEntries = LogBuffer.Instance.GetRange (ref firstLine, ref lastLine);
+
+			JSONArray entries = new JSONArray ();
+			foreach (LogBuffer.LogEntry logEntry in logEntries) {
+				JSONObject entry = new JSONObject ();
+				entry.Add ("date", new JSONString (logEntry.date));
+				entry.Add ("time", new JSONString (logEntry.time));
+				entry.Add ("uptime", new JSONString (logEntry.uptime));
+				entry.Add ("msg", new JSONString (logEntry.message));
+				entry.Add ("trace", new JSONString (logEntry.trace));
+				entry.Add ("type", new JSONString (logEntry.type.ToString ()));
+				entries.Add (entry);
+			}
+
+			result.Add ("firstLine", new JSONNumber (firstLine));
+			result.Add ("lastLine", new JSONNumber (lastLine));
+			result.Add ("entries", entries);
+
+			WriteJSON (resp, result);
+		}
+	}
+}
+
Index: binary-improvements/MapRendering/Web/API/GetPlayerInventory.cs
===================================================================
--- binary-improvements/MapRendering/Web/API/GetPlayerInventory.cs	(revision 249)
+++ binary-improvements/MapRendering/Web/API/GetPlayerInventory.cs	(revision 250)
@@ -35,26 +35,6 @@
 			result.Add ("equipment", equipment);
 
-			for (int i = 0; i < inv.belt.Count; i++) {
-				JSONObject item = new JSONObject ();
-				if (inv.belt [i] != null) {
-					item.Add ("count", new JSONNumber (inv.belt [i].count));
-					item.Add ("name", new JSONString (inv.belt [i].itemName));
-				} else {
-					item.Add ("count", new JSONNumber (0));
-					item.Add ("name", new JSONString (string.Empty));
-				}
-				belt.Add (item);
-			}
-			for (int i = 0; i < inv.bag.Count; i++) {
-				JSONObject item = new JSONObject ();
-				if (inv.bag [i] != null) {
-					item.Add ("count", new JSONNumber (inv.bag [i].count));
-					item.Add ("name", new JSONString (inv.bag [i].itemName));
-				} else {
-					item.Add ("count", new JSONNumber (0));
-					item.Add ("name", new JSONString (string.Empty));
-				}
-				bag.Add (item);
-			}
+			DoInventory (belt, inv.belt);
+			DoInventory (bag, inv.bag);
 
 			AddEquipment (equipment, "head", inv.equipment, XMLData.Item.EnumEquipmentSlot.Head, NGuiInvGridEquipment.EnumClothingLayer.Middle);
@@ -76,10 +56,31 @@
 		}
 
+		private void DoInventory (JSONArray _jsonRes, List<InvItem> _inv) {
+			for (int i = 0; i < _inv.Count; i++) {
+				_jsonRes.Add (GetJsonForItem (_inv [i]));
+			}
+		}
+
 		private void AddEquipment (JSONObject _eq, string _slotname, InvItem[] _items, XMLData.Item.EnumEquipmentSlot _slot, NGuiInvGridEquipment.EnumClothingLayer _layer) {
 			int index = (int)_slot + (int)_layer * (int)XMLData.Item.EnumEquipmentSlot.Count;
-			if (_items != null && _items [index] != null) {
-				_eq.Add (_slotname, new JSONString (_items [index].itemName));
+			if (_items != null) {
+				_eq.Add (_slotname, GetJsonForItem (_items [index]));
 			} else {
-				_eq.Add (_slotname, new JSONBoolean (false));
+				_eq.Add (_slotname, new JSONNull ());
+			}
+		}
+
+		private JSONNode GetJsonForItem (InvItem _item) {
+			if (_item != null) {
+				JSONObject jsonItem = new JSONObject ();
+				jsonItem.Add ("count", new JSONNumber (_item.count));
+				jsonItem.Add ("name", new JSONString (_item.itemName));
+				jsonItem.Add ("quality", new JSONNumber (_item.quality));
+				if (_item.quality >= 0) {
+					jsonItem.Add ("qualitycolor", new JSONString (QualityInfo.GetQualityColorHex (_item.quality)));
+				}
+				return jsonItem;
+			} else {
+				return new JSONNull ();
 			}
 		}
Index: binary-improvements/MapRendering/Web/API/GetWebUIUpdates.cs
===================================================================
--- binary-improvements/MapRendering/Web/API/GetWebUIUpdates.cs	(revision 250)
+++ binary-improvements/MapRendering/Web/API/GetWebUIUpdates.cs	(revision 250)
@@ -0,0 +1,32 @@
+using AllocsFixes.JSON;
+using AllocsFixes.PersistentData;
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace AllocsFixes.NetConnections.Servers.Web.API
+{
+	public class GetWebUIUpdates : WebAPI {
+		public override void HandleRequest (HttpListenerRequest req, HttpListenerResponse resp, WebConnection user, int permissionLevel) {
+			int latestLine;
+			if (req.QueryString ["latestLine"] == null || !int.TryParse (req.QueryString ["latestLine"], out latestLine)) {
+				latestLine = 0;
+			}
+
+			JSONObject result = new JSONObject ();
+
+			JSONObject time = new JSONObject ();
+			time.Add ("days", new JSONNumber (GameUtils.WorldTimeToDays (GameManager.Instance.World.worldTime)));
+			time.Add ("hours", new JSONNumber (GameUtils.WorldTimeToHours (GameManager.Instance.World.worldTime)));
+			time.Add ("minutes", new JSONNumber (GameUtils.WorldTimeToMinutes (GameManager.Instance.World.worldTime)));
+			result.Add ("gametime", time);
+
+			result.Add ("players", new JSONNumber (GameManager.Instance.World.Players.Count));
+
+			result.Add ("newlogs", new JSONNumber (LogBuffer.Instance.LatestLine - latestLine));
+
+			WriteJSON (resp, result);
+		}
+	}
+}
+
Index: binary-improvements/MapRendering/Web/ConnectionHandler.cs
===================================================================
--- binary-improvements/MapRendering/Web/ConnectionHandler.cs	(revision 249)
+++ binary-improvements/MapRendering/Web/ConnectionHandler.cs	(revision 250)
@@ -50,10 +50,4 @@
 		}
 
-		public void SendLog (string text, string trace, UnityEngine.LogType type) {
-			foreach (WebConnection wc in connections.Values) {
-				wc.SendLog (text, trace, type);
-			}
-		}
-
 	}
 }
Index: binary-improvements/MapRendering/Web/Handlers/ApiHandler.cs
===================================================================
--- binary-improvements/MapRendering/Web/Handlers/ApiHandler.cs	(revision 249)
+++ binary-improvements/MapRendering/Web/Handlers/ApiHandler.cs	(revision 250)
@@ -4,4 +4,5 @@
 using System.IO;
 using System.Net;
+using System.Reflection;
 using System.Threading;
 
@@ -14,9 +15,14 @@
 		public ApiHandler (string staticPart, string moduleName = null) : base(moduleName) {
 			this.staticPart = staticPart;
-			addApi ("getlandclaims", new GetLandClaims ());
-			addApi ("getplayersonline", new GetPlayersOnline ());
-			addApi ("getplayerslocation", new GetPlayersLocation ());
-			addApi ("getplayerinventory", new GetPlayerInventory ());
-			addApi ("getstats", new GetStats ());
+
+			foreach (Type t in Assembly.GetExecutingAssembly ().GetTypes ()) {
+				if (!t.IsAbstract && t.IsSubclassOf (typeof(WebAPI))) {
+					ConstructorInfo ctor = t.GetConstructor (new Type[0]);
+					if (ctor != null) {
+						WebAPI apiInstance = (WebAPI)ctor.Invoke (new object[0]);
+						addApi (t.Name.ToLower (), apiInstance);
+					}
+				}
+			}
 		}
 
@@ -38,13 +44,14 @@
 			} else {
 				foreach (KeyValuePair<string, WebAPI> kvp in apis) {
-					try {
-						if (apiName.StartsWith (kvp.Key)) {
+					if (apiName.StartsWith (kvp.Key)) {
+						try {
 							kvp.Value.HandleRequest (req, resp, user, permissionLevel);
 							return;
+						} catch (Exception e) {
+							Log.Error ("Error in ApiHandler.HandleRequest(): Handler {0} threw an exception:", kvp.Key);
+							Log.Exception (e);
+							resp.StatusCode = (int)HttpStatusCode.InternalServerError;
+							return;
 						}
-					} catch (Exception e) {
-						Log.Out ("Error in ApiHandler.HandleRequest(): Handler threw an exception: " + e);
-						resp.StatusCode = (int)HttpStatusCode.InternalServerError;
-						return;
 					}
 				}
Index: binary-improvements/MapRendering/Web/LogBuffer.cs
===================================================================
--- binary-improvements/MapRendering/Web/LogBuffer.cs	(revision 250)
+++ binary-improvements/MapRendering/Web/LogBuffer.cs	(revision 250)
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+using UnityEngine;
+
+namespace AllocsFixes.NetConnections.Servers.Web
+{
+	public class LogBuffer {
+		private const int MAX_ENTRIES = 50000;
+		private static LogBuffer instance;
+
+		public static LogBuffer Instance {
+			get {
+				if (instance == null) {
+					instance = new LogBuffer ();
+				}
+				return instance;
+			}
+		}
+
+		private static Regex logMessageMatcher = new Regex (@"^([0-9]{4}-[0-9]{2}-[0-9]{2})T([0-9]{2}:[0-9]{2}:[0-9]{2}) ([0-9]+[,.][0-9]+) [A-Z]+ (.*)$");
+		private List<LogEntry> logEntries = new List<LogEntry> ();
+		private int listOffset = 0;
+
+		public int OldestLine {
+			get {
+				lock (logEntries) {
+					return listOffset;
+				}
+			}
+		}
+
+		public int LatestLine {
+			get {
+				lock (logEntries) {
+					return listOffset + logEntries.Count - 1;
+				}
+			}
+		}
+
+		public int StoredLines {
+			get {
+				lock (logEntries) {
+					return logEntries.Count;
+				}
+			}
+		}
+
+		public LogEntry this [int index] {
+			get {
+				lock (logEntries) {
+					if (index >= listOffset && index < listOffset + logEntries.Count) {
+						return logEntries [index];
+					}
+				}
+				return null;
+			}
+		}
+
+		private LogBuffer () {
+			Logger.Main.LogCallbacks += LogCallback;
+		}
+
+		private void LogCallback (string _msg, string _trace, LogType _type) {
+			LogEntry le = new LogEntry ();
+
+			Match match = logMessageMatcher.Match (_msg);
+			if (match.Success) {
+				le.date = match.Groups [1].Value;
+				le.time = match.Groups [2].Value;
+				le.uptime = match.Groups [3].Value;
+				le.message = match.Groups [4].Value;
+			} else {
+				DateTime dt = DateTime.Now;
+				le.date = string.Format ("{0:0000}-{1:00}-{2:00}", dt.Year, dt.Month, dt.Day);
+				le.time = string.Format ("{0:00}:{1:00}:{2:00}", dt.Hour, dt.Minute, dt.Second);
+				le.uptime = "";
+				le.message = _msg;
+			}
+
+			le.trace = _trace;
+			le.type = _type;
+
+			lock (logEntries) {
+				logEntries.Add (le);
+				if (logEntries.Count > MAX_ENTRIES) {
+					listOffset += logEntries.Count - MAX_ENTRIES;
+					logEntries.RemoveRange (0, logEntries.Count - MAX_ENTRIES);
+				}
+			}
+		}
+
+		public List<LogEntry> GetRange (ref int _start, ref int _end) {
+			lock (logEntries) {
+				if (_end < 0) {
+					_end = listOffset + logEntries.Count;
+				}
+
+				if (_start < listOffset) {
+					_start = listOffset;
+				}
+
+				if (_start >= listOffset + logEntries.Count) {
+					_end = _start;
+					return new List<LogEntry> ();
+				}
+
+				if (_end < _start) {
+					Log.Error ("GetRange: invalid end {0} (listOffset: {1}, count: {2})", _end, listOffset, logEntries.Count);
+					return null;
+				}
+
+				if (_end >= listOffset + logEntries.Count) {
+					_end = listOffset + logEntries.Count - 1;
+				}
+
+				return logEntries.GetRange (_start - listOffset, _end - _start + 1);
+			}
+		}
+
+
+		public class LogEntry {
+			public string date;
+			public string time;
+			public string uptime;
+			public string message;
+			public string trace;
+			public LogType type;
+		}
+	}
+}
+
Index: binary-improvements/MapRendering/Web/Web.cs
===================================================================
--- binary-improvements/MapRendering/Web/Web.cs	(revision 249)
+++ binary-improvements/MapRendering/Web/Web.cs	(revision 250)
@@ -250,5 +250,5 @@
 
 		public void SendLog (string text, string trace, UnityEngine.LogType type) {
-			connectionHandler.SendLog (text, trace, type);
+			// Do nothing, handled by LogBuffer internally
 		}
 
Index: binary-improvements/MapRendering/Web/WebConnection.cs
===================================================================
--- binary-improvements/MapRendering/Web/WebConnection.cs	(revision 249)
+++ binary-improvements/MapRendering/Web/WebConnection.cs	(revision 250)
@@ -12,7 +12,5 @@
 		private DateTime login;
 		private DateTime lastAction;
-
 		private List<string> outputLines = new List<string> ();
-		private List<LogLine> logLines = new List<LogLine> ();
 
 		public string SessionID {
@@ -57,16 +55,7 @@
 
 		public override void SendLog (string _msg, string _trace, LogType _type) {
-			LogLine ll = new LogLine ();
-			ll.message = _msg;
-			ll.trace = _trace;
-			ll.type = _type;
-			logLines.Add (ll);
+			// Do nothing, handled by LogBuffer
 		}
 
-		private struct LogLine {
-			public string message;
-			public string trace;
-			public LogType type;
-		}
 	}
 }
Index: binary-improvements/MapRendering/WebAndMapRendering.csproj
===================================================================
--- binary-improvements/MapRendering/WebAndMapRendering.csproj	(revision 249)
+++ binary-improvements/MapRendering/WebAndMapRendering.csproj	(revision 250)
@@ -75,4 +75,7 @@
     <Compile Include="Commands\WebTokens.cs" />
     <Compile Include="Commands\WebPermissionsCmd.cs" />
+    <Compile Include="Web\LogBuffer.cs" />
+    <Compile Include="Web\API\GetLog.cs" />
+    <Compile Include="Web\API\GetWebUIUpdates.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
