diff --git a/Larkator.sln b/Larkator.sln
index 0687951..f33b899 100644
--- a/Larkator.sln
+++ b/Larkator.sln
@@ -20,6 +20,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SavegameToolkit", "ArkSaveg
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SavegameToolkitAdditions", "ArkSavegameToolkit\SavegameToolkitAdditions\SavegameToolkitAdditions.csproj", "{CA24E3C7-3BEE-4774-977E-E9253352CFB2}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapCalibrator", "MapCalibrator\MapCalibrator.csproj", "{AA6DF52F-AECA-465B-B45B-14D18DB7F411}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -112,6 +114,22 @@ Global
 		{CA24E3C7-3BEE-4774-977E-E9253352CFB2}.Release|x64.Build.0 = Release|Any CPU
 		{CA24E3C7-3BEE-4774-977E-E9253352CFB2}.Release|x86.ActiveCfg = Release|Any CPU
 		{CA24E3C7-3BEE-4774-977E-E9253352CFB2}.Release|x86.Build.0 = Release|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Debug|ARM.ActiveCfg = Debug|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Debug|ARM.Build.0 = Debug|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Debug|x64.Build.0 = Debug|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Debug|x86.Build.0 = Debug|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Release|Any CPU.Build.0 = Release|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Release|ARM.ActiveCfg = Release|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Release|ARM.Build.0 = Release|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Release|x64.ActiveCfg = Release|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Release|x64.Build.0 = Release|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Release|x86.ActiveCfg = Release|Any CPU
+		{AA6DF52F-AECA-465B-B45B-14D18DB7F411}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/LarkatorGUI/ArkReader.cs b/LarkatorGUI/ArkReader.cs
index 4e4d3b1..bfd4826 100644
--- a/LarkatorGUI/ArkReader.cs
+++ b/LarkatorGUI/ArkReader.cs
@@ -13,7 +13,7 @@ namespace LarkatorGUI
 {
     public class ArkReader
     {
-        public int MapSize { get; set; } = 8000;
+        public MapCalibration MapCalibration { get; set; }
 
         public Dictionary<string, List<Dino>> WildDinos { get; } = new Dictionary<string, List<Dino>>();
         public Dictionary<string, List<Dino>> TamedDinos { get; } = new Dictionary<string, List<Dino>>();
@@ -22,10 +22,18 @@ public class ArkReader
         public List<string> WildSpecies { get; } = new List<string>();
         public int NumberOfTamedSpecies { get => TamedSpecies.Count; }
         public int NumberOfWildSpecies { get => WildSpecies.Count; }
-        
-        private ArkData arkData;
+
+        public void SetArkData(ArkData data)
+        {
+            arkData = data;
+
+            // Create some easy to use mappings for better performance
+            classMap = arkData.Creatures.ToDictionary(c => c.Class, c => c.Name);
+        }
 
         private static readonly string[] RAFT_CLASSES = { "Raft_BP_C", "MotorRaft_BP_C", "Barge_BP_C" };
+        private ArkData arkData;
+        private Dictionary<string, string> classMap;
 
         private static Task<(GameObjectContainer gameObjects, float gameTime)> ReadSavegameFile(string fileName)
         {
@@ -64,6 +72,9 @@ public class ArkReader
 
         public async Task PerformConversion(string saveFile)
         {
+            if (MapCalibration == null)
+                throw new ArgumentNullException(nameof(MapCalibration), "Callibration required");
+
             // Clear previously loaded data
             TamedSpecies.Clear();
             WildSpecies.Clear();
@@ -78,33 +89,41 @@ public async Task PerformConversion(string saveFile)
             var creatureObjects = gameObjectContainer
                 .Where(o => o.IsCreature() && !o.IsUnclaimedBaby() && !RAFT_CLASSES.Contains(o.ClassString))
                 .ToList();
-            
-            var tameObjects = creatureObjects.Where(o => !o.IsWild()).GroupBy(o => o.ClassString);
-            TamedSpecies.AddRange(tameObjects.Select(o => o.Key));
+
+            var tameObjects = creatureObjects.Where(o => !o.IsWild()).GroupBy(o => SpeciesName(o.ClassString));
+            TamedSpecies.AddRange(tameObjects.Select(o => o.Key).Distinct());
             foreach (var group in tameObjects)
                 TamedDinos.Add(group.Key, group.Select(o => ConvertCreature(o)).ToList());
 
-            var wildObjects = creatureObjects.Where(o => o.IsWild()).GroupBy(o => o.ClassString);
-            WildSpecies.AddRange(wildObjects.Select(o => o.Key));
+            var wildObjects = creatureObjects.Where(o => o.IsWild()).GroupBy(o => SpeciesName(o.ClassString));
+            WildSpecies.AddRange(wildObjects.Select(o => o.Key).Distinct());
             foreach (var group in wildObjects)
                 WildDinos.Add(group.Key, group.Select(o => ConvertCreature(o)).ToList());
 
-            AllSpecies.AddRange(creatureObjects.Select(o => o.ClassString).Distinct());
+            AllSpecies.AddRange(creatureObjects.Select(o => SpeciesName(o.ClassString)).Distinct());
+        }
+
+        private string SpeciesName(string className)
+        {
+            if (classMap.TryGetValue(className, out var output))
+                return output;
+
+            return className;
         }
 
         private Dino ConvertCreature(GameObject obj)
         {
             var dino = new Dino
             {
-                Type = obj.ClassString,
+                Type = SpeciesName(obj.ClassString),
                 Female = obj.IsFemale(),
                 Id = (ulong)obj.GetDinoId(),
                 BaseLevel = obj.GetBaseLevel(),
-                Name = obj.GetPropertyValue("TamedName", defaultValue:""),
+                Name = obj.GetPropertyValue("TamedName", defaultValue: ""),
                 Location = ConvertCoordsToLatLong(obj.Location),
                 WildLevels = new StatPoints(),
             };
-            
+
             var status = obj.CharacterStatusComponent();
             if (status != null)
             {
@@ -127,22 +146,11 @@ private Position ConvertCoordsToLatLong(LocationData location)
                 Y = location.Y,
                 Z = location.Z,
 
-                Lat = 50 + location.Y / 8000,
-                Lon = 50 + location.X / 8000,
+                Lat = MapCalibration.LatOffset + location.Y / MapCalibration.LatDivisor,
+                Lon = MapCalibration.LonOffset + location.X / MapCalibration.LonDivisor,
             };
         }
 
-        private bool IsConversionRequired()
-        {
-            return true;
-
-            //var classFile = Path.Combine(outputDir, Properties.Resources.ClassesJson);
-            //if (!File.Exists(classFile)) return true;
-            //var arkTimestamp = File.GetLastWriteTimeUtc(Properties.Settings.Default.SaveFile);
-            //var convertTimestamp = File.GetLastWriteTimeUtc(classFile);
-            //return (arkTimestamp >= convertTimestamp);
-        }
-
         private string GenerateNameVariant(string name, string cls)
         {
             var clsParts = cls.Split('_');
diff --git a/LarkatorGUI/CalibrationWindow.xaml b/LarkatorGUI/CalibrationWindow.xaml
index 89d1ecd..616cd51 100644
--- a/LarkatorGUI/CalibrationWindow.xaml
+++ b/LarkatorGUI/CalibrationWindow.xaml
@@ -35,6 +35,18 @@
                     <TextBlock Text="Y1" Margin="8,2"/>
                     <TextBox Text="{Binding Bounds.Y2, Mode=TwoWay}" Width="64"/>
                 </StackPanel>
+                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="4">
+                    <TextBlock Text="Lat +" Margin="8,2"/>
+                    <TextBox Text="{Binding LatOffset, Mode=TwoWay}" Width="64"/>
+                    <TextBlock Text="/" Margin="8,2"/>
+                    <TextBox Text="{Binding LatDivisor, Mode=TwoWay}" Width="64"/>
+                </StackPanel>
+                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="4">
+                    <TextBlock Text="Lon +" Margin="8,2"/>
+                    <TextBox Text="{Binding LonOffset, Mode=TwoWay}" Width="64"/>
+                    <TextBlock Text="/" Margin="8,2"/>
+                    <TextBox Text="{Binding LonDivisor, Mode=TwoWay}" Width="64"/>
+                </StackPanel>
                 <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="4">
                     <TextBlock Text="ARK filename" Margin="8,2"/>
                     <TextBox Text="{Binding Filename, Mode=TwoWay}" Width="100" TextChanged="Filename_TextChanged"/>
@@ -43,17 +55,13 @@
                 </StackPanel>
                 <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="4">
                     <TextBlock Text="+X" Margin="8,2"/>
-                    <TextBox Text="{Binding OffsetX}" Width="32"/>
+                    <TextBox Text="{Binding PixelOffsetX}" Width="32"/>
                     <TextBlock Text="+Y" Margin="8,2"/>
-                    <TextBox Text="{Binding OffsetY}" Width="32"/>
+                    <TextBox Text="{Binding PixelOffsetY}" Width="32"/>
                     <TextBlock Text="*X" Margin="8,2"/>
-                    <TextBox Text="{Binding ScaleX}" Width="64"/>
+                    <TextBox Text="{Binding PixelScaleX}" Width="64"/>
                     <TextBlock Text="*Y" Margin="8,2"/>
-                    <TextBox Text="{Binding ScaleY}" Width="64"/>
-                </StackPanel>
-                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="4">
-                    <TextBlock Text="Units" Margin="8,2"/>
-                    <TextBox Text="{Binding Units, Mode=TwoWay}" Width="60" TextChanged="UpdateOutput_TextChanged"/>
+                    <TextBox Text="{Binding PixelScaleY}" Width="64"/>
                 </StackPanel>
             </StackPanel>
             <TextBox Grid.Row="1" Text="{Binding Output}" Margin="8" MinWidth="150" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" IsReadOnly="True"/>
diff --git a/LarkatorGUI/CalibrationWindow.xaml.cs b/LarkatorGUI/CalibrationWindow.xaml.cs
index 19b22f3..27a8de4 100644
--- a/LarkatorGUI/CalibrationWindow.xaml.cs
+++ b/LarkatorGUI/CalibrationWindow.xaml.cs
@@ -92,6 +92,12 @@ public ExampleCalibration()
 
     public class Calibration : DependencyObject
     {
+        public Calibration()
+        {
+            LatDivisor = LonDivisor = 8000;
+            LatOffset = LonOffset = 50;
+        }
+
         public Bounds Bounds
         {
             get { return (Bounds)GetValue(BoundsProperty); }
@@ -110,34 +116,52 @@ public string Image
             set { SetValue(ImageProperty, value); }
         }
 
-        public int Units
+        public double PixelOffsetX
+        {
+            get { return (double)GetValue(PixelOffsetXProperty); }
+            set { SetValue(PixelOffsetXProperty, value); }
+        }
+
+        public double PixelOffsetY
+        {
+            get { return (double)GetValue(PixelOffsetYProperty); }
+            set { SetValue(PixelOffsetYProperty, value); }
+        }
+
+        public double PixelScaleX
         {
-            get { return (int)GetValue(UnitsProperty); }
-            set { SetValue(UnitsProperty, value); }
+            get { return (double)GetValue(PixelScaleXProperty); }
+            set { SetValue(PixelScaleXProperty, value); }
         }
 
-        public double OffsetX
+        public double PixelScaleY
         {
-            get { return (double)GetValue(OffsetXProperty); }
-            set { SetValue(OffsetXProperty, value); }
+            get { return (double)GetValue(PixelScaleYProperty); }
+            set { SetValue(PixelScaleYProperty, value); }
         }
 
-        public double OffsetY
+        public double LatOffset
         {
-            get { return (double)GetValue(OffsetYProperty); }
-            set { SetValue(OffsetYProperty, value); }
+            get { return (double)GetValue(LatOffsetProperty); }
+            set { SetValue(LatOffsetProperty, value); }
         }
 
-        public double ScaleX
+        public double LonOffset
         {
-            get { return (double)GetValue(ScaleXProperty); }
-            set { SetValue(ScaleXProperty, value); }
+            get { return (double)GetValue(LonOffsetProperty); }
+            set { SetValue(LonOffsetProperty, value); }
         }
 
-        public double ScaleY
+        public double LatDivisor
         {
-            get { return (double)GetValue(ScaleYProperty); }
-            set { SetValue(ScaleYProperty, value); }
+            get { return (double)GetValue(LatDivisorProperty); }
+            set { SetValue(LatDivisorProperty, value); }
+        }
+
+        public double LonDivisor
+        {
+            get { return (double)GetValue(LonDivisorProperty); }
+            set { SetValue(LonDivisorProperty, value); }
         }
 
         public string Output
@@ -149,24 +173,33 @@ public string Output
         public static readonly DependencyProperty OutputProperty =
             DependencyProperty.Register("Output", typeof(string), typeof(Calibration), new PropertyMetadata(""));
 
-        public static readonly DependencyProperty ScaleYProperty =
-            DependencyProperty.Register("ScaleY", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
+        public static readonly DependencyProperty LonDivisorProperty =
+            DependencyProperty.Register("LonDivisor", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
 
-        public static readonly DependencyProperty ScaleXProperty =
-            DependencyProperty.Register("ScaleX", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
+        public static readonly DependencyProperty LatDivisorProperty =
+            DependencyProperty.Register("LatDivisor", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
 
-        public static readonly DependencyProperty OffsetYProperty =
-            DependencyProperty.Register("OffsetY", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
+        public static readonly DependencyProperty LonOffsetProperty =
+            DependencyProperty.Register("LonOffset", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
 
-        public static readonly DependencyProperty OffsetXProperty =
-            DependencyProperty.Register("OffsetX", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
+        public static readonly DependencyProperty LatOffsetProperty =
+            DependencyProperty.Register("LatOffset", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
 
-        public static readonly DependencyProperty ImageProperty =
-            DependencyProperty.Register("Image", typeof(string), typeof(Calibration), new PropertyMetadata(""));
+        public static readonly DependencyProperty PixelScaleYProperty =
+            DependencyProperty.Register("PixelScaleY", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
 
-        public static readonly DependencyProperty UnitsProperty =
-            DependencyProperty.Register("Units", typeof(int), typeof(Calibration), new PropertyMetadata(0));
+        public static readonly DependencyProperty PixelScaleXProperty =
+            DependencyProperty.Register("PixelScaleX", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
 
+        public static readonly DependencyProperty PixelOffsetYProperty =
+            DependencyProperty.Register("PixelOffsetY", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
+
+        public static readonly DependencyProperty PixelOffsetXProperty =
+            DependencyProperty.Register("PixelOffsetX", typeof(double), typeof(Calibration), new PropertyMetadata(0.0));
+
+        public static readonly DependencyProperty ImageProperty =
+            DependencyProperty.Register("Image", typeof(string), typeof(Calibration), new PropertyMetadata(""));
+        
         public static readonly DependencyProperty FilenameProperty =
             DependencyProperty.Register("Filename", typeof(string), typeof(Calibration), new PropertyMetadata(""));
 
@@ -182,11 +215,11 @@ public void Recalculate()
             var maxX = Math.Max(Bounds.X1, Bounds.X2);
             var maxY = Math.Max(Bounds.Y1, Bounds.Y2);
 
-            ScaleX = (maxX - minX) / 80.0;
-            ScaleY = (maxY - minY) / 80.0;
+            PixelScaleX = (maxX - minX) / 80.0;
+            PixelScaleY = (maxY - minY) / 80.0;
 
-            OffsetX = minX - ScaleX * 10;
-            OffsetY = minY - ScaleY * 10;
+            PixelOffsetX = minX - PixelScaleX * 10;
+            PixelOffsetY = minY - PixelScaleY * 10;
 
             Output = RecreateOutput();
         }
@@ -195,11 +228,14 @@ private string RecreateOutput()
         {
             return $"{{\n" +
                 $"  \"Filename\": \"{Filename}\",\n" +
-                $"  \"Units\": {Units},\n" +
-                $"  \"OffsetX\": {OffsetX},\n" +
-                $"  \"OffsetY\": {OffsetY},\n" +
-                $"  \"ScaleX\": {ScaleX},\n" +
-                $"  \"ScaleY\": {ScaleY}\n" +
+                $"  \"LatOffset\": {LatOffset},\n" +
+                $"  \"LatDivisor\": {LatDivisor},\n" +
+                $"  \"LonOffset\": {LonOffset},\n" +
+                $"  \"LonDivisor\": {LonDivisor},\n" +
+                $"  \"PixelOffsetX\": {PixelOffsetX},\n" +
+                $"  \"PixelOffsetY\": {PixelOffsetY},\n" +
+                $"  \"PixelScaleX\": {PixelScaleX},\n" +
+                $"  \"PixelScaleY\": {PixelScaleY}\n" +
                 $"}},";
         }
     }
diff --git a/LarkatorGUI/DirectoryEntryBox.xaml b/LarkatorGUI/DirectoryEntryBox.xaml
deleted file mode 100644
index 919e438..0000000
--- a/LarkatorGUI/DirectoryEntryBox.xaml
+++ /dev/null
@@ -1,17 +0,0 @@
-<UserControl x:Class="LarkatorGUI.DirectoryEntryBox"
-             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-             xmlns:local="clr-namespace:LarkatorGUI"
-             mc:Ignorable="d" d:DesignWidth="240"
-             Margin="0" Padding="0">
-    <Grid>
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="*"/>
-            <ColumnDefinition Width="Auto"/>
-        </Grid.ColumnDefinitions>
-        <TextBox Grid.Column="0" MaxLines="1" Text="{Binding Path=Value, Mode=TwoWay}" ToolTip="{Binding Tooltip}" HorizontalContentAlignment="Right"/>
-        <Button Grid.Column="1" Padding="8,2" Margin="2,0" Click="Browse_Click" ToolTip="Browse...">...</Button>
-    </Grid>
-</UserControl>
diff --git a/LarkatorGUI/DirectoryEntryBox.xaml.cs b/LarkatorGUI/DirectoryEntryBox.xaml.cs
deleted file mode 100644
index 1ea935a..0000000
--- a/LarkatorGUI/DirectoryEntryBox.xaml.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using Avalon.Windows.Dialogs;
-using Microsoft.Win32;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-
-namespace LarkatorGUI
-{
-    /// <summary>
-    /// Interaction logic for FileEntryBox.xaml
-    /// </summary>
-    public partial class DirectoryEntryBox : UserControl
-    {
-        public string Value
-        {
-            get { return (string)GetValue(ValueProperty); }
-            set { SetValue(ValueProperty, value); }
-        }
-
-        public string Title
-        {
-            get { return (string)GetValue(TitleProperty); }
-            set { SetValue(TitleProperty, value); }
-        }
-
-        public string Tooltip
-        {
-            get { return (string)GetValue(TooltipProperty); }
-            set { SetValue(TooltipProperty, value); }
-        }
-
-        public static readonly DependencyProperty TooltipProperty =
-            DependencyProperty.Register("Tooltip", typeof(string), typeof(DirectoryEntryBox), new PropertyMetadata("Enter path to the directory"));
-
-        public static readonly DependencyProperty TitleProperty =
-            DependencyProperty.Register("Title", typeof(string), typeof(DirectoryEntryBox), new PropertyMetadata("Select directory"));
-
-        public static readonly DependencyProperty ValueProperty =
-            DependencyProperty.Register("Value", typeof(string), typeof(DirectoryEntryBox), new PropertyMetadata(""));
-
-        public DirectoryEntryBox()
-        {
-            InitializeComponent();
-
-            DataContext = this;
-        }
-
-        private void Browse_Click(object sender, RoutedEventArgs e)
-        {
-            var dialog = new FolderBrowserDialog()
-            {
-                RootType = RootType.Path,
-                ValidateResult = true,
-                Title = Title,
-                SelectedPath = Value,
-            };
-
-            var result = dialog.ShowDialog();
-            if (result == true)
-            {
-                Value = dialog.SelectedPath;
-            }
-        }
-    }
-}
diff --git a/LarkatorGUI/DummyMainWindow.cs b/LarkatorGUI/DummyMainWindow.cs
index 659ce1c..4b11d61 100644
--- a/LarkatorGUI/DummyMainWindow.cs
+++ b/LarkatorGUI/DummyMainWindow.cs
@@ -35,10 +35,10 @@ public class DummyMainWindow
         private MapCalibration calibration = new MapCalibration
         {
             Filename = "TheIsland",
-            OffsetX = 13.75,
-            OffsetY = 23.75,
-            ScaleX = 9.8875,
-            ScaleY = 9.625
+            PixelOffsetX = 13.75,
+            PixelOffsetY = 23.75,
+            PixelScaleX = 9.8875,
+            PixelScaleY = 9.625
         };
 
         public DummyMainWindow()
diff --git a/LarkatorGUI/LarkatorGUI.csproj b/LarkatorGUI/LarkatorGUI.csproj
index 38e3c7d..3d353ea 100644
--- a/LarkatorGUI/LarkatorGUI.csproj
+++ b/LarkatorGUI/LarkatorGUI.csproj
@@ -117,9 +117,6 @@
     <Compile Include="DinoViewModel.cs" />
     <Compile Include="DummyMainWindow.cs" />
     <Compile Include="ExternalToolsException.cs" />
-    <Compile Include="DirectoryEntryBox.xaml.cs">
-      <DependentUpon>DirectoryEntryBox.xaml</DependentUpon>
-    </Compile>
     <Compile Include="FileEntryBox.xaml.cs">
       <DependentUpon>FileEntryBox.xaml</DependentUpon>
     </Compile>
@@ -148,10 +145,6 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
-    <Page Include="DirectoryEntryBox.xaml">
-      <Generator>MSBuild:Compile</Generator>
-      <SubType>Designer</SubType>
-    </Page>
     <Page Include="FileEntryBox.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
diff --git a/LarkatorGUI/MainWindow.xaml.cs b/LarkatorGUI/MainWindow.xaml.cs
index 7e4bb12..7bc52f6 100644
--- a/LarkatorGUI/MainWindow.xaml.cs
+++ b/LarkatorGUI/MainWindow.xaml.cs
@@ -2,13 +2,17 @@
 using GongSolutions.Wpf.DragDrop;
 using Larkator.Common;
 using Newtonsoft.Json;
+using SavegameToolkitAdditions;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Deployment.Application;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
+using System.Net;
+using System.Net.Http;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
@@ -167,7 +171,6 @@ public MainWindow()
             }, DispatcherPriority.Loaded);
 
             LoadSavedSearches();
-            EnsureOutputDirectory();
             SetupFileWatcher();
 
             var cmdThrowExceptionAndExit = new RoutedCommand();
@@ -222,6 +225,8 @@ private void DiscoverCalibration()
             var imgFilename = $"pack://application:,,,/imgs/map_{MapCalibration.Filename}.jpg";
             MapImage = (new ImageSourceConverter()).ConvertFromString(imgFilename) as ImageSource;
             if (image != null) image.Source = MapImage;
+
+            arkReader.MapCalibration = MapCalibration;
         }
 
         private void ValidateWindowPositionAndSize()
@@ -241,19 +246,6 @@ private void ValidateWindowPositionAndSize()
             }
         }
 
-        private void EnsureOutputDirectory()
-        {
-            if (String.IsNullOrWhiteSpace(Properties.Settings.Default.OutputDir))
-            {
-                Properties.Settings.Default.OutputDir = Path.Combine(Path.GetTempPath(), Properties.Resources.ProgramName);
-                if (!Directory.Exists(Properties.Settings.Default.OutputDir))
-                {
-                    Directory.CreateDirectory(Properties.Settings.Default.OutputDir);
-                    Properties.Settings.Default.Save();
-                }
-            }
-        }
-
         private void NotifyArkChanged()
         {
             // Cause a fresh conversion of the new ark
@@ -287,10 +279,91 @@ private async void ReloadTimer_Tick(object sender, EventArgs e)
 
         private async void Window_Loaded(object sender, RoutedEventArgs e)
         {
-            //await UpdateArkToolsData(); // Maybe fetch data file?
+            await UpdateArkToolsData();
             await ReReadArk();
         }
 
+        private async Task UpdateArkToolsData()
+        {
+            StatusText = "Fetching latest species data...";
+
+            try
+            {
+                var targetFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Properties.Resources.ProgramName);
+                var targetFile = Path.Combine(targetFolder, "ark-data.json");
+                if (!Directory.Exists(targetFolder))
+                    Directory.CreateDirectory(targetFolder);
+
+                var fetchOkay = await FetchArkData(targetFile);
+                var loadOkay = await LoadArkData(targetFile);
+
+                if (!loadOkay) throw new ApplicationException("No species data available");
+                if (fetchOkay)
+                    StatusText = "Species data loaded";
+                else
+                    StatusText = "Using old species data - offline?";
+            }
+            catch (Exception)
+            {
+                MessageBox.Show("Unable to fetch species data - Larkator cannot function");
+                Environment.Exit(3);
+            }
+        }
+
+        private async Task<bool> FetchArkData(string targetFile)
+        {
+            try
+            {
+                using (var client = new HttpClient())
+                {
+                    if (File.Exists(targetFile))
+                        client.DefaultRequestHeaders.IfModifiedSince = new FileInfo(targetFile).LastWriteTimeUtc;
+
+                    using (var response = await client.GetAsync(Properties.Resources.ArkDataURL))
+                    {
+                        Debug.WriteLine("Response status = ", response.StatusCode);
+
+                        // Throw exception on failure
+                        response.EnsureSuccessStatusCode();
+
+                        Debug.WriteLine("Response was successful");
+
+                        // Don't do anything if the file hasn't changed
+                        if (response.StatusCode == HttpStatusCode.NotModified)
+                            return true;
+
+                        // Write the data to file
+                        using (var fileWriter = File.OpenWrite(targetFile))
+                        {
+                            await response.Content.CopyToAsync(fileWriter);
+                        }
+                    }
+                }
+
+                return true;
+            }
+            catch (Exception)
+            {
+                return false;
+            }
+        }
+
+        private async Task<bool> LoadArkData(string targetFile)
+        {
+            return await Task.Run<bool>(() =>
+            {
+                try
+                {
+                    arkReader.SetArkData(ArkDataReader.ReadFromFile(targetFile));
+                    return true;
+                }
+                catch (Exception)
+                {
+                    return false;
+                }
+            });
+        }
+
         private void Searches_SelectionChanged(object sender, SelectionChangedEventArgs e)
         {
             UpdateCurrentSearch();
@@ -729,7 +802,6 @@ private void ShowWildSearches()
         private void OnSettingsChanged()
         {
             DiscoverCalibration();
-            EnsureOutputDirectory();
             CheckIfArkChanged();
             UpdateCurrentSearch();
 
diff --git a/LarkatorGUI/MapPositionConverter.cs b/LarkatorGUI/MapPositionConverter.cs
index de95384..5bc9168 100644
--- a/LarkatorGUI/MapPositionConverter.cs
+++ b/LarkatorGUI/MapPositionConverter.cs
@@ -47,8 +47,8 @@ public static void OnChanged(DependencyObject d, DependencyPropertyChangedEventA
                 var pos = GetPosition(d);
                 if (cal == null || pos == null) return;
 
-                tx.X = pos.Lon * cal.ScaleX + cal.OffsetX;
-                tx.Y = pos.Lat * cal.ScaleY + cal.OffsetY;
+                tx.X = pos.Lon * cal.PixelScaleX + cal.PixelOffsetX;
+                tx.Y = pos.Lat * cal.PixelScaleY + cal.PixelOffsetY;
             }
             else
             {
@@ -60,11 +60,17 @@ public static void OnChanged(DependencyObject d, DependencyPropertyChangedEventA
     public class MapCalibration
     {
         public string Filename { get; set; }
+        
+        public double PixelOffsetX { get; set; }
+        public double PixelOffsetY { get; set; }
+
+        public double PixelScaleX { get; set; }
+        public double PixelScaleY { get; set; }
 
-        public double OffsetX { get; set; }
-        public double OffsetY { get; set; }
+        public double LatOffset { get; set; }
+        public double LonOffset { get; set; }
 
-        public double ScaleX { get; set; }
-        public double ScaleY { get; set; }
+        public double LatDivisor { get; set; }
+        public double LonDivisor { get; set; }
     }
 }
diff --git a/LarkatorGUI/Properties/Resources.Designer.cs b/LarkatorGUI/Properties/Resources.Designer.cs
index dffe194..ce83a1e 100644
--- a/LarkatorGUI/Properties/Resources.Designer.cs
+++ b/LarkatorGUI/Properties/Resources.Designer.cs
@@ -60,18 +60,29 @@ internal Resources() {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to https://ark-data.seen-von-ragan.de/data/loc/ark_data.json.
+        /// </summary>
+        internal static string ArkDataURL {
+            get {
+                return ResourceManager.GetString("ArkDataURL", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to [
         ///    
         ///    {
         ///        &quot;Filename&quot;: &quot;TheIsland&quot;,
-        ///        &quot;OffsetX&quot;: &quot;17.25&quot;,
-        ///        &quot;OffsetY&quot;: &quot;23.75&quot;,
-        ///        &quot;ScaleX&quot;: &quot;9.575&quot;,
-        ///        &quot;ScaleY&quot;: &quot;9.625&quot;
+        ///        &quot;Units&quot;: 8000,
+        ///        &quot;OffsetX&quot;: 17.25,
+        ///        &quot;OffsetY&quot;: 23.75,
+        ///        &quot;ScaleX&quot;: 9.575,
+        ///        &quot;ScaleY&quot;: 9.625
         ///    },
         ///    {
         ///        &quot;Filename&quot;: &quot;TheCenter&quot;,
+        ///        &quot;Units&quot;: 8000,
         ///        &quot;OffsetX&quot;: 14.0,
         ///        &quot;OffsetY&quot;: 23.75,
         ///        &quot;ScaleX&quot;: 9.9,
@@ -79,14 +90,12 @@ internal Resources() {
         ///    },
         ///    {
         ///        &quot;Filename&quot;: &quot;Aberration&quot;,
+        ///        &quot;Units&quot;: 8000,
         ///        &quot;OffsetX&quot;: 15.125,
         ///        &quot;OffsetY&quot;: 19.0,
         ///        &quot;ScaleX&quot;: 9.8875,
         ///        &quot;ScaleY&quot;: 9.7
-        ///    },
-        ///    {
-        ///        &quot;Filename&quot;: &quot;Ragnarok&quot;,
-        ///        &quot;OffsetX&quot;: 15 [rest of string was truncated]&quot;;.
+        ///    } [rest of string was truncated]&quot;;.
         /// </summary>
         internal static string calibrationsJson {
             get {
diff --git a/LarkatorGUI/Properties/Resources.resx b/LarkatorGUI/Properties/Resources.resx
index e851a40..67c645f 100644
--- a/LarkatorGUI/Properties/Resources.resx
+++ b/LarkatorGUI/Properties/Resources.resx
@@ -124,4 +124,7 @@
   <data name="ProgramName" xml:space="preserve">
     <value>Larkator</value>
   </data>
+  <data name="ArkDataURL" xml:space="preserve">
+    <value>https://ark-data.seen-von-ragan.de/data/loc/ark_data.json</value>
+  </data>
 </root>
\ No newline at end of file
diff --git a/LarkatorGUI/SettingsWindow.xaml b/LarkatorGUI/SettingsWindow.xaml
index e18a7be..407a282 100644
--- a/LarkatorGUI/SettingsWindow.xaml
+++ b/LarkatorGUI/SettingsWindow.xaml
@@ -10,71 +10,83 @@
         Title="Settings" Background="{DynamicResource WindowBackgroundBrush}">
     <Window.Resources>
         <local:SettingsWindowModel x:Key="Model"/>
+        <Style TargetType="TextBlock">
+            <Setter Property="Foreground" Value="AntiqueWhite"/>
+        </Style>
     </Window.Resources>
     <StackPanel>
-        <TabControl>
-            <TabItem Selector.IsSelected="True" Header="Paths &amp; Files">
-                <Grid>
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition Width="Auto"/>
-                        <ColumnDefinition Width="*"/>
-                    </Grid.ColumnDefinitions>
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                    </Grid.RowDefinitions>
-                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,4,0">ARK Tools (ark-tools.exe)</TextBlock>
-                    <local:FileEntryBox Grid.Column="1" Grid.Row="0" Value="{Binding Source={StaticResource Model}, Path=Settings.ArkTools, Mode=TwoWay}"
+        <Grid Margin="2,4,0,16">
+            <Grid.Resources>
+                <Style TargetType="Border">
+                    <Setter Property="Margin" Value="3"/>
+                </Style>
+            </Grid.Resources>
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="*"/>
+            </Grid.ColumnDefinitions>
+            <Grid.RowDefinitions>
+                <RowDefinition Height="1*"/>
+                <RowDefinition Height="1*"/>
+                <RowDefinition Height="1*"/>
+                <RowDefinition Height="1*"/>
+                <RowDefinition Height="1*"/>
+                <RowDefinition Height="1*"/>
+            </Grid.RowDefinitions>
+
+            <Border Grid.Row="0" Grid.Column="0">
+                <TextBlock VerticalAlignment="Center" Margin="0,0,4,0">ARK Tools (ark-tools.exe)</TextBlock>
+            </Border>
+            <Border Grid.Column="1" Grid.Row="0">
+                <local:FileEntryBox Value="{Binding Source={StaticResource Model}, Path=Settings.ArkTools, Mode=TwoWay}"
                                         Filter="ARK Tools Executable|ark-tools.exe" DefaultExt="ark-tools.exe" Title="Locate ark-tools.exe"/>
-                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,4,0">Save file (.ark)</TextBlock>
-                    <local:FileEntryBox Grid.Column="1" Grid.Row="1" Value="{Binding Source={StaticResource Model}, Path=Settings.SaveFile, Mode=TwoWay}"
+            </Border>
+
+            <Border Grid.Row="1" Grid.Column="0">
+                <TextBlock VerticalAlignment="Center" Margin="0,0,4,0">Save file (.ark)</TextBlock>
+            </Border>
+            <Border Grid.Column="1" Grid.Row="1">
+                <local:FileEntryBox Value="{Binding Source={StaticResource Model}, Path=Settings.SaveFile, Mode=TwoWay}"
                                         Filter="ARK Save File|*.ark" DefaultExt=".ark" Title="Locate saved ARK"/>
-                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,4,0">Output directory</TextBlock>
-                    <DockPanel Grid.Column="1" Grid.Row="2">
-                        <Button DockPanel.Dock="Right" Content="Reset" Click="ResetTmp_Click"/>
-                        <local:DirectoryEntryBox Value="{Binding Source={StaticResource Model}, Path=Settings.OutputDir, Mode=TwoWay}"
-                                                 Title="Choose a directory for temporary output files"/>
-                    </DockPanel>
-                </Grid>
-            </TabItem>
-            <TabItem Header="Misc">
-                <Grid>
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition Width="Auto"/>
-                        <ColumnDefinition Width="*"/>
-                    </Grid.ColumnDefinitions>
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                        <RowDefinition Height="1*"/>
-                    </Grid.RowDefinitions>
-                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,4,0">Max level</TextBlock>
-                    <local:NumericEntryControl Grid.Row="0" Grid.Column="1" ToolTip="The maximum level of normal spawns in the map (only affects filtering)"
+            </Border>
+
+            <Border Grid.Row="2" Grid.Column="0">
+                <TextBlock VerticalAlignment="Center" Margin="0,0,4,0">Max level</TextBlock>
+            </Border>
+            <Border Grid.Row="2" Grid.Column="1">
+                <local:NumericEntryControl ToolTip="The maximum level of normal spawns in the map (only affects filtering)"
                                                Value="{Binding Source={StaticResource Model}, Path=Settings.MaxLevel, Mode=TwoWay}" MaxWidth="90" HorizontalAlignment="Right"
-                                               MaxValue="1000" MinValue="1" Increment="1" LargeIncrement="5"/>
-                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,4,0">Level step</TextBlock>
-                    <local:NumericEntryControl Grid.Row="1" Grid.Column="1" ToolTip="The step between levels, typically 4 for 120 or 5 for 150 (only affects filter adjustments)"
+                                               MaxValue="1000" MinValue="1" Increment="1" LargeIncrement="5" Margin="0,0,2,0"/>
+            </Border>
+
+            <Border Grid.Row="3" Grid.Column="0">
+                <TextBlock VerticalAlignment="Center" Margin="0,0,4,0">Level step</TextBlock>
+            </Border>
+            <Border Grid.Row="3" Grid.Column="1">
+                <local:NumericEntryControl ToolTip="The step between levels, typically 4 for 120 or 5 for 150 (only affects filter adjustments)"
                                                Value="{Binding Source={StaticResource Model}, Path=Settings.LevelStep, Mode=TwoWay}" MaxWidth="90" HorizontalAlignment="Right"
-                                               MaxValue="100" MinValue="1" Increment="1" LargeIncrement="1"/>
-                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,4,0">List font size</TextBlock>
-                    <local:NumericEntryControl Grid.Row="2" Grid.Column="1" ToolTip="Font size of the search filters and results  (requires a restart)"
+                                               MaxValue="100" MinValue="1" Increment="1" LargeIncrement="1" Margin="0,0,2,0"/>
+            </Border>
+
+            <Border Grid.Row="4" Grid.Column="0">
+                <TextBlock VerticalAlignment="Center" Margin="0,0,4,0">List font size</TextBlock>
+            </Border>
+            <Border Grid.Row="4" Grid.Column="1">
+                <local:NumericEntryControl ToolTip="Font size of the search filters and results  (requires a restart)"
                                                Value="{Binding Source={StaticResource Model}, Path=Settings.ListFontSize, Mode=TwoWay}" MaxWidth="90" HorizontalAlignment="Right"
-                                               MaxValue="17" MinValue="11" Increment="1" LargeIncrement="1"/>
-                    <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="0,0,4,0">Conversion delay (ms)</TextBlock>
-                    <local:NumericEntryControl Grid.Row="3" Grid.Column="1" ToolTip="The delay between detecting a change to the saved ark and running the conversion process (in milliseconds)"
+                                               MaxValue="17" MinValue="11" Increment="1" LargeIncrement="1" Margin="0,0,2,0"/>
+            </Border>
+
+            <Border Grid.Row="5" Grid.Column="0">
+                <TextBlock VerticalAlignment="Center" Margin="0,0,4,0">Conversion delay (ms)</TextBlock>
+            </Border>
+            <Border Grid.Row="5" Grid.Column="1">
+                <local:NumericEntryControl ToolTip="The delay between detecting a change to the saved ark and running the conversion process (in milliseconds)"
                                                Value="{Binding Source={StaticResource Model}, Path=Settings.ConvertDelay, Mode=TwoWay}" MaxWidth="110" HorizontalAlignment="Right"
-                                               MaxValue="10000" MinValue="200" Increment="100" LargeIncrement="500"/>
-                </Grid>
-            </TabItem>
-        </TabControl>
-        <DockPanel Margin="2,6,2,2" LastChildFill="True">
+                                               MaxValue="10000" MinValue="200" Increment="100" LargeIncrement="500" Margin="0,0,2,0"/>
+            </Border>
+        </Grid>
+        <DockPanel Margin="4,6,6,4" LastChildFill="True">
             <TextBlock Text="Restore all defaults" DockPanel.Dock="Left" VerticalAlignment="Bottom" TextDecorations="Underline" MouseDown="Restore_MouseDown" FontSize="10"/>
             <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                 <Button Padding="8,2" IsDefault="True" Click="Apply_Click" Content="Apply" Margin="0,0,4,0"/>
diff --git a/LarkatorGUI/calibrations.json b/LarkatorGUI/calibrations.json
index 20ceede..bdb6bd5 100644
--- a/LarkatorGUI/calibrations.json
+++ b/LarkatorGUI/calibrations.json
@@ -1,51 +1,68 @@
 [
-    
     {
         "Filename": "TheIsland",
-        "Units": 8000,
-        "OffsetX": 17.25,
-        "OffsetY": 23.75,
-        "ScaleX": 9.575,
-        "ScaleY": 9.625
+        "LatOffset": 50,
+        "LatDivisor": 8000,
+        "LonOffset": 50,
+        "LonDivisor": 8000,
+        "PixelOffsetX": 18.375,
+        "PixelOffsetY": 23.75,
+        "PixelScaleX": 9.55,
+        "PixelScaleY": 9.625
     },
     {
         "Filename": "TheCenter",
-        "Units": 8000,
-        "OffsetX": 14.0,
-        "OffsetY": 23.75,
-        "ScaleX": 9.9,
-        "ScaleY": 9.625
+        "LatOffset": 30.30309,
+        "LatDivisor": 9584.15646605143,
+        "LonOffset": 55,
+        "LonDivisor": 9600.55903111401,
+        "PixelOffsetX": 18.875,
+        "PixelOffsetY": 50.75,
+        "PixelScaleX": 9.1,
+        "PixelScaleY": 9.1
     },
     {
         "Filename": "Aberration",
-        "Units": 8000,
-        "OffsetX": 15.125,
-        "OffsetY": 19.0,
-        "ScaleX": 9.8875,
-        "ScaleY": 9.7
+        "LatOffset": 50,
+        "LatDivisor": 8000,
+        "LonOffset": 50,
+        "LonDivisor": 8000,
+        "PixelOffsetX": 15.125,
+        "PixelOffsetY": 19.0,
+        "PixelScaleX": 9.8875,
+        "PixelScaleY": 9.7
     },
     {
         "Filename": "Ragnarok",
-        "Units": 13100,
-        "OffsetX": 15.125,
-        "OffsetY": 18.875,
-        "ScaleX": 9.8875,
-        "ScaleY": 9.7125
+        "LatOffset": 50,
+        "LatDivisor": 13100,
+        "LonOffset": 50,
+        "LonDivisor": 13100,
+        "PixelOffsetX": 15.25,
+        "PixelOffsetY": 19,
+        "PixelScaleX": 9.875,
+        "PixelScaleY": 9.7
     },
     {
         "Filename": "ScorchedEarth",
-        "Units": 8000,
-        "OffsetX": 13.875,
-        "OffsetY": 21.125,
-        "ScaleX": 9.9125,
-        "ScaleY": 9.6875
+        "LatOffset": 50,
+        "LatDivisor": 8000,
+        "LonOffset": 50,
+        "LonDivisor": 8000,
+        "PixelOffsetX": 13.875,
+        "PixelOffsetY": 21.125,
+        "PixelScaleX": 9.9125,
+        "PixelScaleY": 9.6875
     },
     {
         "Filename": "Extinction",
-        "Units": 8000,
-        "OffsetX": 92.375,
-        "OffsetY": 86.125,
-        "ScaleX": 8.4625,
-        "ScaleY": 8.2875
+        "LatOffset": 50,
+        "LatDivisor": 8000,
+        "LonOffset": 50,
+        "LonDivisor": 8000,
+        "PixelOffsetX": 92.375,
+        "PixelOffsetY": 86.125,
+        "PixelScaleX": 8.4625,
+        "PixelScaleY": 8.2875
     }
 ]
\ No newline at end of file
diff --git a/MapCalibrator/App.config b/MapCalibrator/App.config
new file mode 100644
index 0000000..016d28f
--- /dev/null
+++ b/MapCalibrator/App.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
+    </startup>
+</configuration>
\ No newline at end of file
diff --git a/MapCalibrator/MapCalibrator.csproj b/MapCalibrator/MapCalibrator.csproj
new file mode 100644
index 0000000..b1356fc
--- /dev/null
+++ b/MapCalibrator/MapCalibrator.csproj
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{AA6DF52F-AECA-465B-B45B-14D18DB7F411}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>MapCalibrator</RootNamespace>
+    <AssemblyName>MapCalibrator</AssemblyName>
+    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <LangVersion>latest</LangVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\ArkSavegameToolkit\SavegameToolkitAdditions\SavegameToolkitAdditions.csproj">
+      <Project>{ca24e3c7-3bee-4774-977e-e9253352cfb2}</Project>
+      <Name>SavegameToolkitAdditions</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\ArkSavegameToolkit\SavegameToolkit\SavegameToolkit.csproj">
+      <Project>{1a7b8e8c-7a2c-44ab-8077-59a5f1b2a7b5}</Project>
+      <Name>SavegameToolkit</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="MathNet.Numerics">
+      <Version>4.7.0</Version>
+    </PackageReference>
+    <PackageReference Include="Newtonsoft.Json">
+      <Version>11.0.2</Version>
+    </PackageReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/MapCalibrator/Program.cs b/MapCalibrator/Program.cs
new file mode 100644
index 0000000..293e1d3
--- /dev/null
+++ b/MapCalibrator/Program.cs
@@ -0,0 +1,118 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+using SavegameToolkit;
+using SavegameToolkitAdditions;
+
+using MathNet.Numerics;
+
+
+namespace MapCalibrator
+{
+    /// <summary>
+    /// Coordinate conversion discovery.
+    /// </summary>
+    /// <remarks>
+    /// Reads the given savegame and finds all storage containers with a name "Calibration: NN.N, NN.N",
+    /// where NN.N, NN.N is Lat and Lon from your GPS.
+    /// Calculates the best coordinate conversion values that matches all of the found containers.
+    /// </remarks>
+    class Program
+    {
+        static async Task Main(string[] args)
+        {
+            var savegameFile = args.FirstOrDefault();
+            if (savegameFile == null)
+            {
+                Console.Error.WriteLine("Usage: MapCalibrator.exe <savegame>");
+                Environment.Exit(1);
+            }
+
+            var arkDataFile = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Larkator"), "ark-data.json");
+            var arkData = ArkDataReader.ReadFromFile(arkDataFile);
+
+            (GameObjectContainer gameObjects, float gameTime) = await ReadSavegameFile(savegameFile);
+
+            // Find any objects that have a relevant BoxName
+            var items = gameObjects
+                .Where(o => o.Parent == null && o.GetPropertyValue<string>("BoxName", defaultValue: "").StartsWith("Calibration:"))
+                .ToList();
+
+            // Extract XYZ location and calibration lat/lon
+            var inputs = items.Select(o => (o.Location, LatLon: LatLongFromName(o.GetPropertyValue<string>("BoxName")))).ToArray();
+
+            // Perform linear regression on the values for best fit, separately for X and Y
+            double[] xValues = inputs.Select(i => (double)i.Location.X).ToArray();
+            double[] yValues = inputs.Select(i => (double)i.Location.Y).ToArray();
+            double[] lonValues = inputs.Select(i => i.LatLon.Lon).ToArray();
+            double[] latValues = inputs.Select(i => i.LatLon.Lat).ToArray();
+            var (xOffset, xMult) = Fit.Line(xValues, lonValues);
+            var (yOffset, yMult) = Fit.Line(yValues, latValues);
+            var xCorr = GoodnessOfFit.RSquared(xValues.Select(x => xOffset + xMult * x), lonValues);
+            var yCorr = GoodnessOfFit.RSquared(yValues.Select(y => yOffset + yMult * y), latValues);
+
+            Console.WriteLine($"X: {xOffset} + X/{1 / xMult}  (corr {xCorr})");
+            Console.WriteLine($"Y: {yOffset} + X/{1 / yMult}  (corr {yCorr})");
+
+            Console.ReadLine();
+        }
+
+        private static LatLon LatLongFromName(string name)
+        {
+            var coordsString = name.Split(':').Select(s => s.Trim()).Skip(1).FirstOrDefault();
+            var parts = coordsString.Split(',').Select(s => s.Trim());
+            var values = parts.Select(s => double.Parse(s)).ToArray();
+            var result = new LatLon(values[0], values[1]);
+            return result;
+        }
+
+        private static Task<(GameObjectContainer gameObjects, float gameTime)> ReadSavegameFile(string fileName)
+        {
+            return Task.Run(() =>
+            {
+                if (new FileInfo(fileName).Length > int.MaxValue)
+                    throw new Exception("Input file is too large.");
+
+                var arkSavegame = new ArkSavegame();
+
+                using (var stream = new MemoryStream(File.ReadAllBytes(fileName)))
+                using (var archive = new ArkArchive(stream))
+                {
+                    arkSavegame.ReadBinary(archive, ReadingOptions.Create()
+                            .WithDataFiles(false)
+                            .WithEmbeddedData(false)
+                            .WithDataFilesObjectMap(false)
+                            .WithObjectFilter(o => !o.IsItem && (o.Parent != null || o.Components.Any()))
+                            .WithBuildComponentTree(true));
+                }
+
+                if (!arkSavegame.HibernationEntries.Any())
+                    return (arkSavegame, arkSavegame.GameTime);
+
+                var combinedObjects = arkSavegame.Objects;
+
+                foreach (var entry in arkSavegame.HibernationEntries)
+                {
+                    var collector = new ObjectCollector(entry, 1);
+                    combinedObjects.AddRange(collector.Remap(combinedObjects.Count));
+                }
+
+                return (new GameObjectContainer(combinedObjects), arkSavegame.GameTime);
+            });
+        }
+
+        public struct LatLon
+        {
+            public LatLon(double lat, double lon) : this()
+            {
+                Lat = lat;
+                Lon = lon;
+            }
+
+            public double Lat { get; set; }
+            public double Lon { get; set; }
+        }
+    }
+}
diff --git a/MapCalibrator/Properties/AssemblyInfo.cs b/MapCalibrator/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..7dd94e9
--- /dev/null
+++ b/MapCalibrator/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MapCalibrator")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MapCalibrator")]
+[assembly: AssemblyCopyright("Copyright ©  2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("aa6df52f-aeca-465b-b45b-14d18db7f411")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]