diff --git a/Nibriboard/NibriboardServer.cs b/Nibriboard/NibriboardServer.cs
index 3853212..56cef25 100644
--- a/Nibriboard/NibriboardServer.cs
+++ b/Nibriboard/NibriboardServer.cs
@@ -122,7 +122,9 @@ namespace Nibriboard
 							await destination.WriteLineAsync("    save     Save the ripplespace to disk");
 							break;
 						case "save":
+							await destination.WriteAsync("Saving ripple space - ");
 							await planeManager.Save();
+							await destination.WriteLineAsync("done.");
 							break;
 					}
 				}
diff --git a/Nibriboard/RippleSpace/Chunk.cs b/Nibriboard/RippleSpace/Chunk.cs
index a5bdae8..261d9e9 100644
--- a/Nibriboard/RippleSpace/Chunk.cs
+++ b/Nibriboard/RippleSpace/Chunk.cs
@@ -75,7 +75,7 @@ namespace Nibriboard.RippleSpace
 		/// The location of this chunk, in chunk-space, on the plane.
 		/// </summary>
 		[JsonProperty]
-		public readonly ChunkReference Location;
+		public ChunkReference Location { get; private set; }
 
 		/// <summary>
 		/// Fired when this chunk is updated.
@@ -223,6 +223,15 @@ namespace Nibriboard.RippleSpace
 		{
 			Chunk loadedChunk = JsonConvert.DeserializeObject<Chunk>(await chunkSource.ReadToEndAsync());
 			loadedChunk.plane = plane;
+			loadedChunk.Location.Plane = plane;
+			foreach(DrawnLine line in loadedChunk.lines)
+			{
+				foreach(LocationReference point in line.Points)
+				{
+					point.Plane = plane;
+				}
+			}
+
 			loadedChunk.OnChunkUpdate += plane.HandleChunkUpdate;
 
 			return loadedChunk;
diff --git a/Nibriboard/RippleSpace/ChunkReference.cs b/Nibriboard/RippleSpace/ChunkReference.cs
index 9d08699..0d8080a 100644
--- a/Nibriboard/RippleSpace/ChunkReference.cs
+++ b/Nibriboard/RippleSpace/ChunkReference.cs
@@ -18,6 +18,14 @@ namespace Nibriboard.RippleSpace
 		public ChunkReference(Plane inPlane, int inX, int inY) : base(inPlane, inX, inY)
 		{
 			
+		}
+		/// <summary>
+		/// Creates a new blank <see cref="Nibriboard.RippleSpace.ChunkReference" />.
+		/// Don't use this yourself! This is only for Newtonsoft.Json to use when deserialising references.
+		/// </summary>
+		public ChunkReference() : base()
+		{
+
 		}
 
 		/// <summary>
diff --git a/Nibriboard/RippleSpace/DrawnLine.cs b/Nibriboard/RippleSpace/DrawnLine.cs
index bbd14a0..2692621 100644
--- a/Nibriboard/RippleSpace/DrawnLine.cs
+++ b/Nibriboard/RippleSpace/DrawnLine.cs
@@ -1,38 +1,48 @@
 ﻿using System;
 using System.Collections.Generic;
 using System.Linq;
+
+using Newtonsoft.Json;
+
 namespace Nibriboard.RippleSpace
 {
 	/// <summary>
 	/// Represents a line drawn across the plane.
 	/// </summary>
+	[Serializable]
+	[JsonObject(MemberSerialization.OptIn)]
 	public class DrawnLine
 	{
 		/// <summary>
 		/// The id of line that this <see cref="NibriboardServer.RippleSpace.DrawnLine" /> is part of.
 		/// Note that this id may not be unique - several lines that were all
 		/// drawn at once may also have the same id. This is such that a single
-        /// line that was split across multiple chunks can still be referenced and
-        /// joined together.
+		/// line that was split across multiple chunks can still be referenced and
+		/// joined together.
 		/// </summary>
+		[JsonProperty]
 		public readonly string LineId;
 
 		/// <summary>
 		/// The width of the line.
 		/// </summary>
+		[JsonProperty]
 		public int Width;
 		/// <summary>
 		/// The colour of the line.
 		/// </summary>
+		[JsonProperty]
 		public string Colour;
 		/// <summary>
 		/// The points that represent the line.
 		/// </summary>
+		[JsonProperty]
 		public List<LocationReference> Points = new List<LocationReference>();
 
 		/// <summary>
 		/// Whether this line spans multiple chunks.
 		/// </summary>
+		[JsonProperty]
 		public bool SpansMultipleChunks {
 			get {
 				// TODO: Make this more intelligent such that connecting lines
diff --git a/Nibriboard/RippleSpace/LocationReference.cs b/Nibriboard/RippleSpace/LocationReference.cs
index f233d9a..baf054e 100644
--- a/Nibriboard/RippleSpace/LocationReference.cs
+++ b/Nibriboard/RippleSpace/LocationReference.cs
@@ -24,6 +24,14 @@ namespace Nibriboard.RippleSpace
 		public LocationReference(Plane inPlane, int inX, int inY) : base(inPlane, inX, inY)
 		{
 			
+		}
+		/// <summary>
+		/// Creates a new blank <see cref="Nibriboard.RippleSpace.LocationReference" />.
+		/// Don't use this yourself! This is only for Newtonsoft.Json to use when deserialising references.
+		/// </summary>
+		public LocationReference() : base()
+		{
+
 		}
 
 		public override bool Equals(object obj)
diff --git a/Nibriboard/RippleSpace/Plane.cs b/Nibriboard/RippleSpace/Plane.cs
index 820e16b..e410f8e 100644
--- a/Nibriboard/RippleSpace/Plane.cs
+++ b/Nibriboard/RippleSpace/Plane.cs
@@ -67,6 +67,12 @@ namespace Nibriboard.RippleSpace
 		/// </summary>
 		protected Dictionary<ChunkReference, Chunk> loadedChunkspace = new Dictionary<ChunkReference, Chunk>();
 
+		public PlaneInfo Info {
+			get {
+				return PlaneInfo.FromPlane(this);
+			}
+		}
+
 		/// <summary>
 		/// The number of chunks that this plane currently has laoded into active memory.
 		/// </summary>
@@ -261,12 +267,19 @@ namespace Nibriboard.RippleSpace
 			}
 			await Task.WhenAll(chunkSavers);
 
-			// Pack the chunks into an nplane file
+			// Save the plane information
+			StreamWriter planeInfoWriter = new StreamWriter(CalcPaths.UnpackedPlaneIndex(StorageDirectory));
+			await planeInfoWriter.WriteLineAsync(JsonConvert.SerializeObject(Info));
+			planeInfoWriter.Close();
+
+			// Pack the chunks & plane information into an nplane file
 			WriterOptions packingOptions = new WriterOptions(CompressionType.Deflate);
 
 			IEnumerable<string> chunkFiles = Directory.GetFiles(StorageDirectory.TrimEnd("/".ToCharArray()));
 			using(IWriter packer = WriterFactory.Open(destination, ArchiveType.Zip, packingOptions))
 			{
+				packer.Write("plane-index.json", CalcPaths.UnpackedPlaneIndex(StorageDirectory));
+
 				foreach(string nextChunkFile in chunkFiles)
 				{
 					packer.Write($"{Name}/{Path.GetFileName(nextChunkFile)}", nextChunkFile);
@@ -313,6 +326,7 @@ namespace Nibriboard.RippleSpace
 			using(Stream sourceStream = File.OpenRead(sourceFilename))
 			using(IReader unpacker = ReaderFactory.Open(sourceStream))
 			{
+				Directory.CreateDirectory(targetUnpackingPath);
 				unpacker.WriteAllToDirectory(targetUnpackingPath);
 			}
 
diff --git a/Nibriboard/RippleSpace/PlaneInfo.cs b/Nibriboard/RippleSpace/PlaneInfo.cs
index ee17d60..c40a459 100644
--- a/Nibriboard/RippleSpace/PlaneInfo.cs
+++ b/Nibriboard/RippleSpace/PlaneInfo.cs
@@ -1,7 +1,11 @@
 ﻿using System;
 
+using Newtonsoft.Json;
+
 namespace Nibriboard.RippleSpace
 {
+	[Serializable]
+	[JsonObject(MemberSerialization.OptOut)]
 	public class PlaneInfo
 	{
 		public string Name { get; set; }
@@ -18,6 +22,12 @@ namespace Nibriboard.RippleSpace
 			Name = inName;
 			ChunkSize = inChunkSize;
 		}
-	}
 
+		public static PlaneInfo FromPlane(Plane plane)
+		{
+			PlaneInfo result = new PlaneInfo(plane.Name, plane.ChunkSize);
+
+			return result;
+		}
+	}
 }
diff --git a/Nibriboard/RippleSpace/Reference.cs b/Nibriboard/RippleSpace/Reference.cs
index 624571e..dd7ff4e 100644
--- a/Nibriboard/RippleSpace/Reference.cs
+++ b/Nibriboard/RippleSpace/Reference.cs
@@ -10,7 +10,7 @@ namespace Nibriboard.RippleSpace
 	[JsonObject(MemberSerialization.OptIn)]
 	public abstract class Reference : ICloneable
 	{
-		public readonly Plane Plane;
+		public Plane Plane { get; set; }
 
         /// <summary>
         /// The name of the plane that this reference is located on.
@@ -40,6 +40,14 @@ namespace Nibriboard.RippleSpace
 			Plane = inPlane;
 			X = inX; Y = inY;
 		}
+		/// <summary>
+		/// Creates a new blank <see cref="Nibriboard.RippleSpace.Reference" />.
+		/// Don't use this yourself! This is only for Newtonsoft.Json to use when deserialising references.
+		/// </summary>
+		public Reference()
+		{
+
+		}
 
 		public override string ToString()
 		{
diff --git a/Nibriboard/RippleSpace/RippleSpaceManager.cs b/Nibriboard/RippleSpace/RippleSpaceManager.cs
index 23a26f7..dcab278 100644
--- a/Nibriboard/RippleSpace/RippleSpaceManager.cs
+++ b/Nibriboard/RippleSpace/RippleSpaceManager.cs
@@ -126,8 +126,12 @@ namespace Nibriboard.RippleSpace
 
 			// Save the planes to disk
 			List<Task> planeSavers = new List<Task>();
+			StreamWriter indexWriter = new StreamWriter(UnpackedDirectory + "index.list");
 			foreach(Plane item in Planes)
 			{
+				// Add the plane to the index
+				await indexWriter.WriteLineAsync(item.Name);
+
 				// Figure out where the plane should save itself to and create the appropriate directories
 				string planeSavePath = CalcPaths.UnpackedPlaneFile(UnpackedDirectory, item.Name);
 				Directory.CreateDirectory(Path.GetDirectoryName(planeSavePath));
@@ -135,14 +139,17 @@ namespace Nibriboard.RippleSpace
 				// Ask the plane to save to the directory
 				planeSavers.Add(item.Save(File.OpenWrite(planeSavePath)));
 			}
+			indexWriter.Close();
 			await Task.WhenAll(planeSavers);
 
+
 			// Pack the planes into the ripplespace archive
 			Stream destination = File.OpenWrite(SourceFilename);
 			string[] planeFiles = Directory.GetFiles(UnpackedDirectory, "*.nplane.zip", SearchOption.TopDirectoryOnly);
 
 			using(IWriter rippleSpacePacker = WriterFactory.Open(destination, ArchiveType.Zip, new WriterOptions(CompressionType.Deflate)))
 			{
+				rippleSpacePacker.Write("index.list", UnpackedDirectory + "index.list");
 				foreach(string planeFilename in planeFiles)
 				{
 					rippleSpacePacker.Write(Path.GetFileName(planeFilename), planeFilename);
