VOID_Tools: New UnpackedTime class that now powered the old time span convert function and a new date format function.
VOID_Tools: New UnpackedTime class that now powered the old time span convert function and a new date format function.

--- a/IVOID_Module.cs
+++ b/IVOID_Module.cs
@@ -51,6 +51,7 @@
 	{
 		void Update();
 		void FixedUpdate();
+		void OnDestroy();
 	}
 
 	public interface IVOID_EditorModule : IVOID_Module {}

--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -39,7 +39,7 @@
 // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
 // The form "{Major}.{Minor}.*" will automatically update the build and revision,
 // and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion("0.14.3.*")]
+[assembly: AssemblyVersion("0.15.*")]
 // The following attributes are used to specify the signing key for the assembly,
 // if desired. See the Mono documentation for more information about signing.
 //[assembly: AssemblyDelaySign(false)]

--- a/VOID_Core.cs
+++ b/VOID_Core.cs
@@ -120,9 +120,8 @@
 		protected int _windowID = 0;
 
 		protected bool GUIStylesLoaded = false;
-		protected Dictionary<string, GUIStyle> _LabelStyles = new Dictionary<string, GUIStyle>();
-
-		protected CelestialBody _Kerbin;
+
+		protected CelestialBody _homeBody;
 
 		[AVOID_SaveValue("togglePower")]
 		public VOID_SaveValue<bool> togglePower = true;
@@ -230,14 +229,6 @@
 			}
 		}
 
-		public Dictionary<string, GUIStyle> LabelStyles
-		{
-			get
-			{
-				return this._LabelStyles;
-			}
-		}
-
 		public List<CelestialBody> allBodies
 		{
 			get
@@ -252,19 +243,19 @@
 			private set;
 		}
 
-		public CelestialBody Kerbin
-		{
-			get
-			{
-				if (this._Kerbin == null)
-				{
-					if (FlightGlobals.Bodies != null)
+		public CelestialBody HomeBody
+		{
+			get
+			{
+				if (this._homeBody == null)
+				{
+					if (Planetarium.fetch != null)
 					{
-						this._Kerbin = FlightGlobals.Bodies.First(b => b.name == "Kerbin");
+						this._homeBody = Planetarium.fetch.Home;
 					}
 				}
 
-				return this._Kerbin;
+				return this._homeBody;
 			}
 		}
 
@@ -614,10 +605,23 @@
 				}
 			}
 
-			foreach (IVOID_BehaviorModule module in
-			         this._modules.OfType<IVOID_BehaviorModule>().Where(m => !m.GetType().IsAbstract))
-			{
-				module.FixedUpdate();
+			foreach (IVOID_Module module in this.Modules)
+			{
+				if (module is IVOID_BehaviorModule)
+				{
+					((IVOID_BehaviorModule)module).FixedUpdate();
+				}
+			}
+		}
+
+		public void OnDestroy()
+		{
+			foreach (IVOID_Module module in this.Modules)
+			{
+				if (module is IVOID_BehaviorModule)
+				{
+					((IVOID_BehaviorModule)module).OnDestroy();
+				}
 			}
 		}
 

--- a/VOID_Data.cs
+++ b/VOID_Data.cs
@@ -37,6 +37,15 @@
 {
 	public static class VOID_Data
 	{
+		private static Dictionary<int, IVOID_DataValue> dataValues = new Dictionary<int, IVOID_DataValue>();
+		public static Dictionary<int, IVOID_DataValue> DataValues
+		{
+			get
+			{
+				return dataValues;
+			}
+		}
+
 		#region Constants
 
 		private static double kerbinGee;
@@ -47,7 +56,7 @@
 			{
 				if (kerbinGee == default(double))
 				{
-					kerbinGee = core.Kerbin.gravParameter / (core.Kerbin.Radius * core.Kerbin.Radius);
+					kerbinGee = core.HomeBody.gravParameter / (core.HomeBody.Radius * core.HomeBody.Radius);
 				}
 
 				return kerbinGee;
@@ -726,6 +735,20 @@
 
 		#region Kinematics
 
+		public static readonly VOID_DoubleValue geeForce =
+			new VOID_DoubleValue(
+				"G-force",
+				new Func<double>(() => core.vessel.geeForce),
+				"gees"
+			);
+
+		public static readonly VOID_DoubleValue horzVelocity =
+			new VOID_DoubleValue(
+				"Horizontal speed",
+				new Func<double>(() => core.vessel.horizontalSrfSpeed),
+				"m/s"
+			);
+
 		public static readonly VOID_DoubleValue surfVelocity =
 			new VOID_DoubleValue(
 				"Surface velocity",
@@ -740,43 +763,28 @@
 				"m/s"
 			);
 
-		public static readonly VOID_DoubleValue horzVelocity =
-			new VOID_DoubleValue(
-				"Horizontal speed",
-				new Func<double>(() => core.vessel.horizontalSrfSpeed),
-				"m/s"
-			);
-
-		public static readonly VOID_DoubleValue geeForce =
-			new VOID_DoubleValue(
-				"G-force",
-				new Func<double>(() => core.vessel.geeForce),
-				"gees"
-			);
-
-
-		public static readonly VOID_DoubleValue vesselAngularVelocity =
-			new VOID_DoubleValue(
-				"Angular Velocity",
-				delegate()
-				{
-					if (core.vessel != null)
-					{
-						return core.vessel.angularVelocity.magnitude;
-					}
-					else
-					{
-						return double.NaN;
-					}
-				},
-				"rad/s"
-			);
-
 		public static readonly VOID_DoubleValue vesselAccel =
 			new VOID_DoubleValue(
 				"Acceleration",
 				() => geeForce * KerbinGee,
 				"m/s²"
+			);
+
+		public static readonly VOID_DoubleValue vesselAngularVelocity =
+			new VOID_DoubleValue(
+				"Angular Velocity",
+				delegate()
+				{
+					if (core.vessel != null)
+					{
+						return core.vessel.angularVelocity.magnitude;
+					}
+					else
+					{
+						return double.NaN;
+					}
+				},
+				"rad/s"
 			);
 
 		#endregion
@@ -798,108 +806,6 @@
 			}
 		}
 
-		public static readonly VOID_DoubleValue currManeuverDeltaV =
-			new VOID_DoubleValue(
-				"Current Maneuver Delta-V",
-				delegate()
-				{
-					if (upcomingManeuverNodes > 0)
-					{
-						return core.vessel.patchedConicSolver.maneuverNodes[0].DeltaV.magnitude;
-					}
-					else
-					{
-						return double.NaN;
-					}
-				},
-				"m/s"
-			);
-
-		public static readonly VOID_DoubleValue currManeuverDVRemaining =
-			new VOID_DoubleValue(
-				"Remaining Maneuver Delta-V",
-				delegate()
-				{
-					if (upcomingManeuverNodes > 0)
-					{
-						return core.vessel.patchedConicSolver.maneuverNodes[0].GetBurnVector(core.vessel.orbit).magnitude;
-					}
-					else
-					{
-						return double.NaN;
-					}
-				},
-				"m/s"
-			);
-
-		public static readonly VOID_DoubleValue nextManeuverDeltaV =
-			new VOID_DoubleValue(
-				"Current Maneuver Delta-V",
-				delegate()
-				{
-					if (upcomingManeuverNodes > 1)
-					{
-						return core.vessel.patchedConicSolver.maneuverNodes[1].DeltaV.magnitude;
-					}
-					else
-					{
-						return double.NaN;
-					}
-				},
-				"m/s"
-			);
-
-		public static readonly VOID_DoubleValue currentNodeBurnDuration =
-			new VOID_DoubleValue(
-				"Total Burn Time",
-				delegate()
-				{
-					if (core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
-					{
-						return double.NaN;
-					}
-
-					double stageThrust = stageNominalThrust;
-
-					return burnTime(currManeuverDeltaV.Value, totalMass, stageMassFlow, stageThrust);
-				},
-				"s"
-			);
-
-		public static readonly VOID_DoubleValue currentNodeBurnRemaining =
-			new VOID_DoubleValue(
-				"Burn Time Remaining",
-				delegate()
-				{
-					if (core.LastStage == null || currManeuverDVRemaining == double.NaN)
-					{
-						return double.NaN;
-					}
-
-					double stageThrust = stageNominalThrust;
-
-					return burnTime(currManeuverDVRemaining, totalMass, stageMassFlow, stageThrust);
-				},
-				"s"
-			);
-
-		public static readonly VOID_DoubleValue currentNodeHalfBurnDuration =
-			new VOID_DoubleValue(
-				"Half Burn Time",
-				delegate()
-				{
-					if (core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
-					{
-						return double.NaN;
-					}
-
-					double stageThrust = stageNominalThrust;
-
-					return burnTime(currManeuverDeltaV.Value / 2d, totalMass, stageMassFlow, stageThrust);
-				},
-				"s"
-			);
-
 		public static readonly VOID_StrValue burnTimeDoneAtNode =
 			new VOID_StrValue(
 				"Full burn time to be half done at node",
@@ -982,6 +888,108 @@
 
 					return string.Format(format, VOID_Tools.ConvertInterval(interval));
 				}
+			);
+
+		public static readonly VOID_DoubleValue currManeuverDeltaV =
+			new VOID_DoubleValue(
+				"Current Maneuver Delta-V",
+				delegate()
+				{
+					if (upcomingManeuverNodes > 0)
+					{
+						return core.vessel.patchedConicSolver.maneuverNodes[0].DeltaV.magnitude;
+					}
+					else
+					{
+						return double.NaN;
+					}
+				},
+				"m/s"
+			);
+
+		public static readonly VOID_DoubleValue currManeuverDVRemaining =
+			new VOID_DoubleValue(
+				"Remaining Maneuver Delta-V",
+				delegate()
+				{
+					if (upcomingManeuverNodes > 0)
+					{
+						return core.vessel.patchedConicSolver.maneuverNodes[0].GetBurnVector(core.vessel.orbit).magnitude;
+					}
+					else
+					{
+						return double.NaN;
+					}
+				},
+				"m/s"
+			);
+
+		public static readonly VOID_DoubleValue currentNodeBurnDuration =
+			new VOID_DoubleValue(
+				"Total Burn Time",
+				delegate()
+				{
+					if (core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					double stageThrust = stageNominalThrust;
+
+					return burnTime(currManeuverDeltaV.Value, totalMass, stageMassFlow, stageThrust);
+				},
+				"s"
+			);
+
+		public static readonly VOID_DoubleValue currentNodeBurnRemaining =
+			new VOID_DoubleValue(
+				"Burn Time Remaining",
+				delegate()
+				{
+					if (core.LastStage == null || currManeuverDVRemaining == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					double stageThrust = stageNominalThrust;
+
+					return burnTime(currManeuverDVRemaining, totalMass, stageMassFlow, stageThrust);
+				},
+				"s"
+			);
+
+		public static readonly VOID_DoubleValue currentNodeHalfBurnDuration =
+			new VOID_DoubleValue(
+				"Half Burn Time",
+				delegate()
+				{
+					if (core.LastStage == null || currManeuverDeltaV.Value == double.NaN)
+					{
+						return double.NaN;
+					}
+
+					double stageThrust = stageNominalThrust;
+
+					return burnTime(currManeuverDeltaV.Value / 2d, totalMass, stageMassFlow, stageThrust);
+				},
+				"s"
+			);
+
+		public static readonly VOID_DoubleValue nextManeuverDeltaV =
+			new VOID_DoubleValue(
+				"Current Maneuver Delta-V",
+				delegate()
+				{
+					if (upcomingManeuverNodes > 1)
+					{
+						return core.vessel.patchedConicSolver.maneuverNodes[1].DeltaV.magnitude;
+					}
+					else
+					{
+						return double.NaN;
+					}
+				},
+				"m/s"
 			);
 
 		#endregion

--- a/VOID_DataLogger.cs
+++ b/VOID_DataLogger.cs
@@ -29,6 +29,7 @@
 using KSP;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Text;
 using ToadicusTools;
 using UnityEngine;
@@ -42,7 +43,7 @@
 		 * */
 		protected bool stopwatch1_running;
 
-		protected bool csv_logging;
+		protected bool _loggingActive;
 		protected bool first_write;
 
 		protected double stopwatch1;
@@ -51,15 +52,100 @@
 
 		protected float csv_log_interval;
 
-		protected double csvWriteTimer;
 		protected double csvCollectTimer;
+
+		protected System.Text.UTF8Encoding utf8Encoding;
+		protected FileStream _outputFile;
+
+		protected uint outstandingWrites;
 
 		protected List<string> csvList = new List<string>();
 
 		/*
 		 * Properties
 		 * */
-
+		// TODO: Add configurable or incremental file names.
+		protected bool loggingActive
+		{
+			get
+			{
+				return this._loggingActive;
+			}
+			set
+			{
+				if (value != this._loggingActive)
+				{
+					if (value)
+					{
+
+					}
+					else
+					{
+						if (this._outputFile != null)
+						{
+							Tools.DebugLogger logger = Tools.DebugLogger.New(this);
+
+							logger.Append("CSV logging disabled, ");
+
+							logger.Append("disposing file.");
+							logger.Print();
+							this.outputFile.Dispose();
+							this._outputFile = null;
+						}
+					}
+
+					this._loggingActive = value;
+				}
+			}
+		}
+		protected string fileName
+		{
+			get
+			{
+				return KSP.IO.IOUtils.GetFilePathFor(
+					typeof(VOID_Core),
+					string.Format(
+						"{0}_{1}",
+						this.vessel.vesselName,
+						"data.csv"
+					),
+					null
+				);
+			}
+		}
+
+		protected FileStream outputFile
+		{
+			get
+			{
+				if (this._outputFile == null)
+				{
+					Tools.DebugLogger logger = Tools.DebugLogger.New(this);
+					logger.AppendFormat("Initializing output file '{0}' with mode ", this.fileName);
+
+					if (File.Exists(this.fileName))
+					{
+						logger.Append("append");
+						this._outputFile = new FileStream(this.fileName, FileMode.Append, FileAccess.Write, FileShare.Write, 512, true);
+					}
+					else
+					{
+						logger.Append("create");
+						this._outputFile = new FileStream(this.fileName, FileMode.Create, FileAccess.Write, FileShare.Write, 512, true);
+
+						byte[] bom = utf8Encoding.GetPreamble();
+
+						logger.Append(" and writing preamble");
+						outputFile.Write(bom, 0, bom.Length);
+					}
+
+					logger.Append('.');
+					logger.Print();
+				}
+
+				return this._outputFile;
+			}
+		}
 
 		/*
 		 * Methods
@@ -70,14 +156,14 @@
 
 			this.stopwatch1_running = false;
 
-			this.csv_logging = false;
+			this.loggingActive = false;
 			this.first_write = true;
 
 			this.stopwatch1 = 0;
 			this.csv_log_interval_str = "0.5";
 
-			this.csvWriteTimer = 0;
 			this.csvCollectTimer = 0;
+			this.outstandingWrites = 0;
 
 			this.WindowPos.x = Screen.width - 520;
 			this.WindowPos.y = 85;
@@ -118,18 +204,19 @@
 
 			GUIStyle label_style = txt_white;
 			string log_label = "Inactive";
-			if (csv_logging && vessel.situation.ToString() == "PRELAUNCH")
+			if (loggingActive && vessel.situation.ToString() == "PRELAUNCH")
 			{
 				log_label = "Awaiting launch";
 				label_style = txt_yellow;
 			}
-			if (csv_logging && vessel.situation.ToString() != "PRELAUNCH")
+			if (loggingActive && vessel.situation.ToString() != "PRELAUNCH")
 			{
 				log_label = "Active";
 				label_style = txt_green;
 			}
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
-			csv_logging = GUILayout.Toggle(csv_logging, "Data logging: ", GUILayout.ExpandWidth(false));
+			this.loggingActive = GUILayout.Toggle(loggingActive, "Data logging: ", GUILayout.ExpandWidth(false));
+
 			GUILayout.Label(log_label, label_style, GUILayout.ExpandWidth(true));
 			GUILayout.EndHorizontal();
 
@@ -153,11 +240,10 @@
 		{
 			// CSV Logging
 			// from ISA MapSat
-			if (csv_logging)
+			if (loggingActive)
 			{
 				//data logging is on
 				//increment timers
-				csvWriteTimer += Time.deltaTime;
 				csvCollectTimer += Time.deltaTime;
 
 				if (csvCollectTimer >= csv_log_interval && vessel.situation != Vessel.Situations.PRELAUNCH)
@@ -167,22 +253,20 @@
 					line_to_csvList();  //write to the csv
 				}
 
-				if (csvList.Count != 0 && csvWriteTimer >= 15f)
+				if (csvList.Count > 0)
 				{
 					// csvList is not empty and interval between writings to file has elapsed
 					//write it
-					string[] csvData;
-					csvData = (string[])csvList.ToArray();
-					Innsewerants_writeData(csvData);
-					csvList.Clear();
-					csvWriteTimer = 0f;
+
+					// Tools.PostDebugMessage("")
+
+					this.AsyncWriteData();
 				}
 			}
 			else
 			{
 				//data logging is off
 				//reset any timers and clear anything from csvList
-				csvWriteTimer = 0f;
 				csvCollectTimer = 0f;
 				if (csvList.Count > 0) csvList.Clear();
 			}
@@ -195,14 +279,72 @@
 
 		public void FixedUpdate() {}
 
-		private void Innsewerants_writeData(string[] csvArray)
-		{
-			var efile = KSP.IO.File.AppendText<VOID_Core>(vessel.vesselName + "_data.csv", null);
-			foreach (string line in csvArray)
-			{
-				efile.Write(line);
-			}
-			efile.Close();
+		public void OnDestroy()
+		{
+			Tools.DebugLogger logger = Tools.DebugLogger.New(this);
+
+			logger.Append("Destroying...");
+
+			if (this.csvList.Count > 0)
+			{
+				logger.Append(" Writing final data...");
+				this.AsyncWriteData();
+			}
+
+			while (this.outstandingWrites > 0)
+			{
+				System.Threading.Thread.Sleep(10);
+			}
+
+			if (this._outputFile != null)
+			{
+				logger.Append(" Closing File...");
+				this.outputFile.Close();
+			}
+
+			logger.Append(" Done.");
+			logger.Print();
+		}
+
+		~VOID_DataLogger()
+		{
+			this.OnDestroy();
+		}
+
+		protected void AsyncWriteCallback(IAsyncResult result)
+		{
+			Tools.PostDebugMessage(this, "Got async callback, IsCompleted = {0}", result.IsCompleted);
+
+			this.outputFile.EndWrite(result);
+			this.outstandingWrites--;
+		}
+
+		private void AsyncWriteData()
+		{
+			if (this.utf8Encoding == null)
+			{
+				this.utf8Encoding = new System.Text.UTF8Encoding(true, true);
+			}
+
+			List<byte> bytes = new List<byte>();
+
+			foreach (string line in this.csvList)
+			{
+				byte[] lineBytes = utf8Encoding.GetBytes(line);
+				bytes.AddRange(lineBytes);
+			}
+
+			WriteState state = new WriteState();
+
+			state.bytes = bytes.ToArray();
+			state.stream = this.outputFile;
+
+			this.outstandingWrites++;
+			var writeCallback = new AsyncCallback(this.AsyncWriteCallback);
+
+			this.outputFile.BeginWrite(state.bytes, 0, state.bytes.Length, writeCallback, state);
+
+			this.csvList.Clear();
 		}
 
 		private void line_to_csvList()
@@ -212,93 +354,99 @@
 
 			StringBuilder line = new StringBuilder();
 
-			if (first_write && !KSP.IO.File.Exists<VOID_Core>(vessel.vesselName + "_data.csv", null))
+			if (first_write)
 			{
 				first_write = false;
 				line.Append(
-					"Mission Elapsed Time (s);" +
-					"Altitude ASL (m);" +
-					"Altitude above terrain (m);" +
-					"Surface Latitude (°);" +
-					"Surface Longitude (°);" +
-					"Orbital Velocity (m/s);" +
-					"Surface Velocity (m/s);" +
-					"Vertical Speed (m/s);" +
-					"Horizontal Speed (m/s);" +
-					"Gee Force (gees);" +
-					"Temperature (°C);" +
-					"Gravity (m/s²);" +
-					"Atmosphere Density (g/m³);" +
-					"Downrange Distance  (m);" +
+					"\"Mission Elapsed Time (s)\t\"," +
+					"\"Altitude ASL (m)\"," +
+					"\"Altitude above terrain (m)\"," +
+					"\"Surface Latitude (°)\"," +
+					"\"Surface Longitude (°)\"," +
+					"\"Orbital Velocity (m/s)\"," +
+					"\"Surface Velocity (m/s)\"," +
+					"\"Vertical Speed (m/s)\"," +
+					"\"Horizontal Speed (m/s)\"," +
+					"\"Gee Force (gees)\"," +
+					"\"Temperature (°C)\"," +
+					"\"Gravity (m/s²)\"," +
+					"\"Atmosphere Density (g/m³)\"," +
+					"\"Downrange Distance  (m)\"," +
 					"\n"
 				);
 			}
 
 			//Mission time
 			line.Append(vessel.missionTime.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
+
 
 			//Altitude ASL
-			line.Append(vessel.orbit.altitude.ToString("F3"));
-			line.Append(';');
+			line.Append(VOID_Data.orbitAltitude.Value.ToString("F3"));
+			line.Append(',');
 
 			//Altitude (true)
-			double alt_true = vessel.orbit.altitude - vessel.terrainAltitude;
-			if (vessel.terrainAltitude < 0) alt_true = vessel.orbit.altitude;
-			line.Append(alt_true.ToString("F3"));
-			line.Append(';');
+			line.Append(VOID_Data.trueAltitude.Value.ToString("F3"));
+			line.Append(',');
 
 			// Surface Latitude
+			line.Append('"');
 			line.Append(VOID_Data.surfLatitude.Value);
-			line.Append(';');
+			line.Append('"');
+			line.Append(',');
 
 			// Surface Longitude
+			line.Append('"');
 			line.Append(VOID_Data.surfLongitude.Value);
-			line.Append(';');
+			line.Append('"');
+			line.Append(',');
 
 			//Orbital velocity
-			line.Append(vessel.orbit.vel.magnitude.ToString("F3"));
-			line.Append(';');
+			line.Append(VOID_Data.orbitVelocity.Value.ToString("F3"));
+			line.Append(',');
 
 			//surface velocity
-			line.Append(vessel.srf_velocity.magnitude.ToString("F3"));
-			line.Append(';');
+			line.Append(VOID_Data.surfVelocity.Value.ToString("F3"));
+			line.Append(',');
 
 			//vertical speed
-			line.Append(vessel.verticalSpeed.ToString("F3"));
-			line.Append(';');
+			line.Append(VOID_Data.vertVelocity.Value.ToString("F3"));
+			line.Append(',');
 
 			//horizontal speed
-			line.Append(vessel.horizontalSrfSpeed.ToString("F3"));
-			line.Append(';');
+			line.Append(VOID_Data.horzVelocity.Value.ToString("F3"));
+			line.Append(',');
 
 			//gee force
-			line.Append(vessel.geeForce.ToString("F3"));
-			line.Append(';');
+			line.Append(VOID_Data.geeForce.Value.ToString("F3"));
+			line.Append(',');
 
 			//temperature
-			line.Append(vessel.flightIntegrator.getExternalTemperature().ToString("F2"));
-			line.Append(';');
+			line.Append(VOID_Data.temperature.Value.ToString("F2"));
+			line.Append(',');
 
 			//gravity
-			double r_vessel = vessel.mainBody.Radius + vessel.mainBody.GetAltitude(vessel.findWorldCenterOfMass());
-			double g_vessel = (VOID_Core.Constant_G * vessel.mainBody.Mass) / (r_vessel * r_vessel);
-			line.Append(g_vessel.ToString("F3"));
-			line.Append(';');
+			line.Append(VOID_Data.gravityAccel.Value.ToString("F3"));
+			line.Append(',');
 
 			//atm density
-			line.Append((vessel.atmDensity * 1000).ToString("F3"));
-			line.Append(';');
+			line.Append(Tools.MuMech_ToSI(VOID_Data.atmDensity.Value * 1000d, 3));
+			line.Append(',');
 
 			// Downrange Distance
 			line.Append((VOID_Data.downrangeDistance.Value.ToString("G3")));
-			line.Append(';');
 
 			line.Append('\n');
 
 			csvList.Add(line.ToString());
 
 			csvCollectTimer = 0f;
+		}
+
+		private class WriteState
+		{
+			public byte[] bytes;
+			public FileStream stream;
 		}
 	}
 }

--- a/VOID_DataValue.cs
+++ b/VOID_DataValue.cs
@@ -34,6 +34,10 @@
 {
 	public interface IVOID_DataValue
 	{
+		string Label { get; }
+		string Units { get; }
+		object Value { get; }
+
 		void Refresh();
 		string ValueUnitString();
 		void DoGUIHorizontal();
@@ -64,6 +68,14 @@
 		 * */
 		public string Label { get; protected set; }
 		public string Units { get; protected set; }
+
+		object IVOID_DataValue.Value
+		{
+			get
+			{
+				return (object)this.Value;
+			}
+		}
 
 		public T Value
 		{
@@ -90,6 +102,8 @@
 			this.Units = Units;
 			this.ValueFunc = ValueFunc;
 			this.lastUpdate = 0;
+
+			VOID_Data.DataValues[this.GetHashCode()] = this;
 		}
 
 		public void Refresh()
@@ -115,6 +129,21 @@
 			GUILayout.FlexibleSpace ();
 			GUILayout.Label (this.ValueUnitString(), GUILayout.ExpandWidth (false));
 			GUILayout.EndHorizontal ();
+		}
+
+		public override int GetHashCode()
+		{
+			int hash;
+			unchecked
+			{
+				hash = 79999;
+
+				hash = hash * 104399 + this.Label.GetHashCode();
+				hash = hash * 104399 + this.ValueFunc.GetHashCode();
+				hash = hash * 104399 + this.Units.GetHashCode();
+			}
+
+			return hash;
 		}
 
 		public override string ToString()

--- a/VOID_HUD.cs
+++ b/VOID_HUD.cs
@@ -118,7 +118,7 @@
 
 			leftHUD = new StringBuilder();
 
-			this.core.LabelStyles["hud"].alignment = TextAnchor.UpperRight;
+			VOID_Styles.labelHud.alignment = TextAnchor.UpperRight;
 
 			if (this.core.powerAvailable)
 			{
@@ -145,11 +145,11 @@
 			}
 			else
 			{
-				this.core.LabelStyles["hud"].normal.textColor = Color.red;
+				VOID_Styles.labelHud.normal.textColor = Color.red;
 				leftHUD.Append(string.Intern("-- POWER LOST --"));
 			}
 
-			GUILayout.Label(leftHUD.ToString(), this.core.LabelStyles["hud"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(leftHUD.ToString(), VOID_Styles.labelHud, GUILayout.ExpandWidth(true));
 
 			if (!this.positionsLocked)
 			{
@@ -165,7 +165,7 @@
 
 			rightHUD = new StringBuilder();
 
-			this.core.LabelStyles["hud"].alignment = TextAnchor.UpperLeft;
+			VOID_Styles.labelHud.alignment = TextAnchor.UpperLeft;
 
 			if (this.core.powerAvailable)
 			{
@@ -191,7 +191,7 @@
 				);
 
 				if (
-					this.core.vessel.mainBody == this.core.Kerbin &&
+					this.core.vessel.mainBody == this.core.HomeBody &&
 					(
 						this.core.vessel.situation == Vessel.Situations.FLYING ||
 						this.core.vessel.situation == Vessel.Situations.SUB_ORBITAL ||
@@ -205,12 +205,12 @@
 			}
 			else
 			{
-				this.core.LabelStyles["hud"].normal.textColor = Color.red;
+				VOID_Styles.labelHud.normal.textColor = Color.red;
 				rightHUD.Append(string.Intern("-- POWER LOST --"));
 			}
 
 
-			GUILayout.Label(rightHUD.ToString(), this.core.LabelStyles["hud"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(rightHUD.ToString(), VOID_Styles.labelHud, GUILayout.ExpandWidth(true));
 
 			if (!this.positionsLocked)
 			{
@@ -222,12 +222,7 @@
 
 		public override void DrawGUI()
 		{
-			if (!this.core.LabelStyles.ContainsKey("hud"))
-			{
-				this.core.LabelStyles["hud"] = new GUIStyle(GUI.skin.label);
-			}
-
-			this.core.LabelStyles["hud"].normal.textColor = textColors [ColorIndex];
+			VOID_Styles.labelHud.normal.textColor = textColors [ColorIndex];
 
 			GUI.skin = this.core.Skin;
 

--- a/VOID_HUDAdvanced.cs
+++ b/VOID_HUDAdvanced.cs
@@ -105,7 +105,7 @@
 
 			leftHUD = new StringBuilder();
 
-			this.core.LabelStyles["hud"].alignment = TextAnchor.UpperRight;
+			VOID_Styles.labelHud.alignment = TextAnchor.UpperRight;
 
 			if (this.core.powerAvailable)
 			{
@@ -144,11 +144,11 @@
 			}
 			else
 			{
-				this.core.LabelStyles["hud"].normal.textColor = Color.red;
+				VOID_Styles.labelHud.normal.textColor = Color.red;
 				leftHUD.Append(string.Intern("-- POWER LOST --"));
 			}
 
-			GUILayout.Label(leftHUD.ToString(), this.core.LabelStyles["hud"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(leftHUD.ToString(), VOID_Styles.labelHud, GUILayout.ExpandWidth(true));
 
 			if (!this.positionsLocked)
 			{
@@ -164,7 +164,7 @@
 
 			rightHUD = new StringBuilder();
 
-			this.core.LabelStyles["hud"].alignment = TextAnchor.UpperLeft;
+			VOID_Styles.labelHud.alignment = TextAnchor.UpperLeft;
 
 			if (this.core.powerAvailable)
 			{
@@ -203,11 +203,11 @@
 			}
 			else
 			{
-				this.core.LabelStyles["hud"].normal.textColor = Color.red;
+				VOID_Styles.labelHud.normal.textColor = Color.red;
 				rightHUD.Append(string.Intern("-- POWER LOST --"));
 			}
 
-			GUILayout.Label(rightHUD.ToString(), this.core.LabelStyles["hud"], GUILayout.ExpandWidth(true));
+			GUILayout.Label(rightHUD.ToString(), VOID_Styles.labelHud, GUILayout.ExpandWidth(true));
 
 			if (!this.positionsLocked)
 			{

--- a/VOID_StageInfo.cs
+++ b/VOID_StageInfo.cs
@@ -20,6 +20,7 @@
 		private Table.Column<int> stageNumberCol;
 		private Table.Column<double> stageDeltaVCol;
 		private Table.Column<double> stageTotalDVCol;
+		private Table.Column<double> stageInvertDVCol;
 		private Table.Column<double> stageMassCol;
 		private Table.Column<double> stageTotalMassCol;
 		private Table.Column<double> stageThrustCol;
@@ -51,27 +52,31 @@
 			this.stageNumberCol = new Table.Column<int>("Stage", 40f);
 			this.stageTable.Add(this.stageNumberCol);
 
-			this.stageDeltaVCol = new Table.Column<double>("DeltaV [m/s]", 80f);
+			this.stageDeltaVCol = new Table.Column<double>("DeltaV [m/s]", 60f);
 			this.stageDeltaVCol.Format = "S2";
 			this.stageTable.Add(this.stageDeltaVCol);
 
-			this.stageTotalDVCol = new Table.Column<double>("Total ΔV [m/s]", 80f);
+			this.stageTotalDVCol = new Table.Column<double>("Total ΔV [m/s]", 60f);
 			this.stageTotalDVCol.Format = "S2";
 			this.stageTable.Add(this.stageTotalDVCol);
 
-			this.stageMassCol = new Table.Column<double>("Mass [Mg]", 80f);
+			this.stageInvertDVCol = new Table.Column<double>("Invert ΔV [m/s]", 60f);
+			this.stageInvertDVCol.Format = "S2";
+			this.stageTable.Add(this.stageInvertDVCol);
+
+			this.stageMassCol = new Table.Column<double>("Mass [Mg]", 60f);
 			this.stageMassCol.Format = "#.#";
 			this.stageTable.Add(this.stageMassCol);
 
-			this.stageTotalMassCol = new Table.Column<double>("Total Mass [Mg]", 80f);
+			this.stageTotalMassCol = new Table.Column<double>("Total [Mg]", 60f);
 			this.stageTotalMassCol.Format = "#.#";
 			this.stageTable.Add(this.stageTotalMassCol);
 
-			this.stageThrustCol = new Table.Column<double>("Thrust [N]", 80f);
+			this.stageThrustCol = new Table.Column<double>("Thrust [N]", 60f);
 			this.stageThrustCol.Format = "S2";
 			this.stageTable.Add(this.stageThrustCol);
 
-			this.stageTWRCol = new Table.Column<double>("T/W Ratio", 80f);
+			this.stageTWRCol = new Table.Column<double>("T/W Ratio", 60f);
 			this.stageTWRCol.Format = "#.#";
 			this.stageTable.Add(this.stageTWRCol);
 		}
@@ -127,12 +132,13 @@
 
 				this.stageDeltaVCol.Add(stage.deltaV);
 				this.stageTotalDVCol.Add(stage.totalDeltaV);
+				this.stageInvertDVCol.Add(stage.inverseTotalDeltaV);
 
 				this.stageMassCol.Add(stage.mass);
 				this.stageTotalMassCol.Add(stage.totalMass);      
 
 				this.stageThrustCol.Add(stage.thrust * 1000f);
-				this.stageTWRCol.Add(stage.thrustToWeight / (this.selectedBody ?? core.Kerbin).GeeASL);
+				this.stageTWRCol.Add(stage.thrustToWeight / (this.selectedBody ?? core.HomeBody).GeeASL);
 			}
 
 			this.stageTable.Render();
@@ -146,7 +152,7 @@
 					this.bodyIdx--;
 				}
 
-				this.showBodyList = GUILayout.Toggle(this.showBodyList, (this.selectedBody ?? core.Kerbin).bodyName, GUI.skin.button);
+				this.showBodyList = GUILayout.Toggle(this.showBodyList, (this.selectedBody ?? core.HomeBody).bodyName, GUI.skin.button);
 				Rect bodyButtonPos = GUILayoutUtility.GetLastRect();
 
 				if (Event.current.type == EventType.Repaint)

--- a/VOID_Styles.cs
+++ b/VOID_Styles.cs
@@ -28,14 +28,6 @@
 
 using System;
 using UnityEngine;
-
-#if COMMENT
-this.LabelStyles["link"] = new GUIStyle(GUI.skin.label);
-this.LabelStyles["center"] = new GUIStyle(GUI.skin.label);
-this.LabelStyles["center_bold"] = new GUIStyle(GUI.skin.label);
-this.LabelStyles["right"] = new GUIStyle(GUI.skin.label);
-labelRed = new GUIStyle(GUI.skin.label);
-#endif
 
 namespace VOID
 {
@@ -71,6 +63,12 @@
 			private set;
 		}
 
+		public static GUIStyle labelHud
+		{
+			get;
+			private set;
+		}
+
 		public static GUIStyle labelRight
 		{
 			get;
@@ -100,6 +98,8 @@
 			labelCenterBold.alignment = TextAnchor.UpperCenter;
 			labelCenterBold.fontStyle = FontStyle.Bold;
 
+			labelHud = new GUIStyle(labelDefault);
+
 			labelRight = new GUIStyle(GUI.skin.label);
 			labelRight.normal.textColor = Color.white;
 			labelRight.alignment = TextAnchor.UpperRight;

--- a/VOID_Tools.cs
+++ b/VOID_Tools.cs
@@ -366,8 +366,8 @@
 		}
 
 		/// <summary>
-		/// Converts the interval given in seconds to a human-friendly
-		/// time period in [years], [days], hours, minutes, and seconds.
+		/// Formats the interval given in seconds as a human-friendly
+		/// time period in [[[[years, ]days, ]hours, ]minutes, and ]seconds.
 		/// 
 		/// Uses sidereal days, since "6 hours per day" is the Kerbal standard.
 		/// </summary>
@@ -375,69 +375,162 @@
 		/// <param name="seconds"></param>
 		public static string ConvertInterval(double seconds)
 		{
-			double SecondsPerMinute = 60d;
-			double SecondsPerHour = 3600d;
-			double SecondsPerDay;
-			double SecondsPerYear;
-
-			if (GameSettings.KERBIN_TIME)
-			{
-				SecondsPerDay = 21600d;
-				SecondsPerYear = 9203545d;
-			}
-			else
-			{
-				SecondsPerDay = 86164.1d;
-				SecondsPerYear = 31558149d;
-			}
-
-			int years;
-			int days;
-			int hours;
-			int minutes;
-
-			years = (int)(seconds / SecondsPerYear);
-
-			seconds %= SecondsPerYear;
-
-			days = (int)(seconds / SecondsPerDay);
-
-			seconds %= SecondsPerDay;
-
-			hours = (int)(seconds / SecondsPerHour);
-
-			seconds %= SecondsPerHour;
-
-			minutes = (int)(seconds / SecondsPerMinute);
-
-			seconds %= SecondsPerMinute;
-
-			string format_1 = string.Intern("{0:D1}y {1:D1}d {2:D2}h {3:D2}m {4:00.0}s");
-			string format_2 = string.Intern("{0:D1}d {1:D2}h {2:D2}m {3:00.0}s");
-			string format_3 = string.Intern("{0:D2}h {1:D2}m {2:00.0}s");
-			string format_4 = string.Intern("{0:D2}m {1:00.0}s");
-			string format_5 = string.Intern("{0:00.0}s");
-
-			if (years > 0)
-			{
-				return string.Format(format_1, years, days, hours, minutes, seconds);
-			}
-			else if (days > 0)
-			{
-				return string.Format(format_2, days, hours, minutes, seconds);
-			}
-			else if (hours > 0)
-			{
-				return string.Format(format_3, hours, minutes, seconds);
-			}
-			else if (minutes > 0)
-			{
-				return string.Format(format_4, minutes, seconds);
-			}
-			else
-			{
-				return string.Format(format_5, seconds);
-			}
+			return UnpackedTime.FromSeconds(seconds).FormatAsSpan();
+		}
+
+		/// <summary>
+		/// Formats the date given in seconds since epoch as a human-friendly
+		/// date in the format YY, DD, HH:MM:SS
+		/// </summary>
+		/// <returns>The date.</returns>
+		/// <param name="seconds">Seconds.</param>
+		public static string FormatDate(double seconds)
+		{
+			return UnpackedTime.FromSeconds(seconds).FormatAsDate();
+		}
+
+		public class UnpackedTime
+		{
+			public const double SecondsPerMinute = 60d;
+			public const double SecondsPerHour = 3600d;
+
+			public static double SecondsPerDay
+			{
+				get
+				{
+					if (GameSettings.KERBIN_TIME)
+					{
+						return 21600d;
+					}
+					else
+					{
+						return 86164.1d;
+					}
+				}
+			}
+
+			public static double SecondsPerYear
+			{
+				get
+				{
+					if (GameSettings.KERBIN_TIME)
+					{
+						return 9203545d;
+					}
+					else
+					{
+						return 31558149d;
+					}
+				}
+			}
+
+			public static UnpackedTime FromSeconds(double seconds)
+			{
+				UnpackedTime time = new UnpackedTime();
+
+				time.years = (int)(seconds / SecondsPerYear);
+
+				seconds %= SecondsPerYear;
+
+				time.days = (int)(seconds / SecondsPerDay);
+
+				seconds %= SecondsPerDay;
+
+				time.hours = (int)(seconds / SecondsPerHour);
+
+				seconds %= SecondsPerHour;
+
+				time.minutes = (int)(seconds / SecondsPerMinute);
+
+				seconds %= SecondsPerMinute;
+
+				time.seconds = seconds;
+
+				return time;
+			}
+
+			public static explicit operator UnpackedTime(double seconds)
+			{
+				return FromSeconds(seconds);
+			}
+
+			public static implicit operator double(UnpackedTime time)
+			{
+				return time.ToSeconds();
+			}
+
+			public static UnpackedTime operator+ (UnpackedTime lhs, UnpackedTime rhs)
+			{
+				return FromSeconds(lhs.ToSeconds() + rhs.ToSeconds());
+			}
+
+			public static UnpackedTime operator- (UnpackedTime lhs, UnpackedTime rhs)
+			{
+				return FromSeconds(lhs.ToSeconds() - rhs.ToSeconds());
+			}
+
+			public int years;
+			public int days;
+			public int hours;
+			public int minutes;
+			public double seconds;
+
+			public double ToSeconds()
+			{
+				return (double)years * SecondsPerYear +
+					(double)days * SecondsPerDay +
+					(double)hours * SecondsPerHour +
+					(double)minutes * SecondsPerMinute +
+					seconds;
+			}
+
+			public string FormatAsSpan()
+			{
+				string format_1 = "{0:D1}y {1:D1}d {2:D2}h {3:D2}m {4:00.0}s";
+				string format_2 = "{0:D1}d {1:D2}h {2:D2}m {3:00.0}s";
+				string format_3 = "{0:D2}h {1:D2}m {2:00.0}s";
+				string format_4 = "{0:D2}m {1:00.0}s";
+				string format_5 = "{0:00.0}s";
+
+				if (this.years > 0)
+				{
+					return string.Format(format_1, this.years, this.days, this.hours, this.minutes, this.seconds);
+				}
+				else if (this.days > 0)
+				{
+					return string.Format(format_2, this.days, this.hours, this.minutes, this.seconds);
+				}
+				else if (this.hours > 0)
+				{
+					return string.Format(format_3, this.hours, this.minutes, this.seconds);
+				}
+				else if (this.minutes > 0)
+				{
+					return string.Format(format_4, this.minutes, this.seconds);
+				}
+				else
+				{
+					return string.Format(format_5, this.seconds);
+				}
+			}
+
+			public string FormatAsDate()
+			{
+				string format = "Y{0:D1}, D{1:D1} {2:D2}:{3:D2}:{4:00.0}s";
+
+				return string.Format(format, years, days, hours, minutes, seconds);
+			}
+
+			public UnpackedTime(int years, int days, int hours, int minutes, double seconds)
+			{
+				this.years = years;
+				this.days = days;
+				this.hours = hours;
+				this.minutes = minutes;
+				this.seconds = seconds;
+			}
+
+			public UnpackedTime() : this(0, 0, 0, 0, 0d) {}
 		}
 
 		public static string UppercaseFirst(string s)