Index: AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 623)
+++ AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 624)
@@ -1,4 +1,4 @@
 appname=AE Installer 2
-appversion=0.83
+appversion=0.84
 
 invalidPath.title=Wrong directory
Index: AE/installer2/src/net/oni2/aeinstaller/AEInstaller2.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/AEInstaller2.java	(revision 623)
+++ AE/installer2/src/net/oni2/aeinstaller/AEInstaller2.java	(revision 624)
@@ -121,5 +121,5 @@
 		JFrame.setDefaultLookAndFeelDecorated(true);
 
-		// TODO
+		// TODO: Remove debug output
 		System.out.println("JarPath:   " + Paths.getInstallerPath());
 		System.out.println("PrefsPath: " + Paths.getPrefsPath());
@@ -134,4 +134,5 @@
 		System.out.println("Globalized:" + Installer.isEditionInitialized());
 
+		// TODO: Check available space
 		System.out
 				.println("Free space on temp: "
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java	(revision 623)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java	(revision 624)
@@ -286,5 +286,5 @@
 	 */
 	public HashMap<Mod, HashSet<Mod>> checkDependencies(TreeSet<Mod> mods) {
-		// TODO: Verify functionality
+		// TODO: Verify functionality (checkDependencies)
 		HashMap<Mod, HashSet<Mod>> res = new HashMap<Mod, HashSet<Mod>>();
 
@@ -311,5 +311,5 @@
 	 */
 	public HashMap<Mod, HashSet<Mod>> checkIncompabitilites(TreeSet<Mod> mods) {
-		// TODO: Verify functionality
+		// TODO: Verify functionality (checkIncompatibilities)
 		HashMap<Mod, HashSet<Mod>> res = new HashMap<Mod, HashSet<Mod>>();
 
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownload.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownload.java	(revision 623)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownload.java	(revision 624)
@@ -72,6 +72,5 @@
 		zipFile = new File(Paths.getDownloadPath(),
 				mod.getPackageNumberString() + ".zip");
-		targetFolder = new File(Paths.getModsPath(),
-				mod.getPackageNumberString());
+		targetFolder = mod.getLocalPath();
 		try {
 			downloader = new FileDownloader(mod.getFile().getUri_full(),
Index: AE/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java	(revision 623)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java	(revision 624)
@@ -3,5 +3,7 @@
 import java.io.File;
 import java.io.FileFilter;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.FilenameFilter;
 import java.io.IOException;
@@ -10,4 +12,5 @@
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Scanner;
@@ -24,4 +27,7 @@
 
 import org.apache.commons.io.FileUtils;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
 
 /**
@@ -58,8 +64,49 @@
 
 	/**
+	 * @return Currently installed tools
+	 */
+	@SuppressWarnings("unchecked")
+	public static TreeSet<Integer> getInstalledTools() {
+		File installCfg = new File(Paths.getInstallerPath(),
+				"installed_tools.xml");
+		TreeSet<Integer> res = new TreeSet<Integer>();
+		try {
+			if (installCfg.exists()) {
+				FileInputStream fis = new FileInputStream(installCfg);
+				XStream xs = new XStream(new StaxDriver());
+				Object obj = xs.fromXML(fis);
+				if (obj instanceof TreeSet<?>)
+					res = (TreeSet<Integer>) obj;
+				fis.close();
+			}
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	private static void writeInstalledTools(TreeSet<Integer> tools) {
+		File installCfg = new File(Paths.getInstallerPath(),
+				"installed_tools.xml");
+		try {
+			FileOutputStream fos = new FileOutputStream(installCfg);
+			XStream xs = new XStream(new StaxDriver());
+			xs.toXML(tools, fos);
+			fos.close();
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
 	 * @param tools
 	 *            Tools to install
 	 */
 	public static void installTools(TreeSet<Mod> tools) {
+		TreeSet<Integer> installed = getInstalledTools();
 		for (Mod m : tools) {
 			File plain = new File(m.getLocalPath(), "plain");
@@ -80,5 +127,7 @@
 				}
 			}
-		}
+			installed.add(m.getPackageNumber());
+		}
+		writeInstalledTools(installed);
 	}
 
@@ -88,5 +137,31 @@
 	 */
 	public static void uninstallTools(TreeSet<Mod> tools) {
-		// TODO: implement this!
+		TreeSet<Integer> installed = getInstalledTools();
+		for (Mod m : tools) {
+			if (installed.contains(m.getPackageNumber())) {
+				File plain = new File(m.getLocalPath(), "plain");
+				if (plain.exists()) {
+					if (m.hasSeparatePlatformDirs()) {
+						File plainCommon = new File(plain, "common");
+						File plainMac = new File(plain, "mac_only");
+						File plainWin = new File(plain, "win_only");
+						if (plainCommon.exists())
+							removeToolsFiles(plainCommon,
+									Paths.getEditionBasePath());
+						if (Settings.getPlatform() == Platform.MACOS
+								&& plainMac.exists())
+							removeToolsFiles(plainMac,
+									Paths.getEditionBasePath());
+						else if (plainWin.exists())
+							removeToolsFiles(plainWin,
+									Paths.getEditionBasePath());
+					} else {
+						removeToolsFiles(plain, Paths.getEditionBasePath());
+					}
+				}
+			}
+			installed.remove(m.getPackageNumber());
+		}
+		writeInstalledTools(installed);
 	}
 
@@ -106,4 +181,18 @@
 	}
 
+	private static void removeToolsFiles(File srcFolder, File target) {
+		for (File f : srcFolder.listFiles()) {
+			if (f.isDirectory())
+				removeToolsFiles(f, new File(target, f.getName()));
+			else {
+				File targetFile = new File(target, f.getName());
+				if (targetFile.exists())
+					targetFile.delete();
+			}
+		}
+		if (target.list().length == 0)
+			target.delete();
+	}
+
 	/**
 	 * Install the given set of mods
@@ -125,8 +214,13 @@
 		ModManager.getInstance().saveModSelection(installCfg, mods);
 
+		HashSet<Integer> unlockLevels = new HashSet<Integer>();
+
 		Vector<File> foldersOni = new Vector<File>();
 		foldersOni.add(Paths.getVanillaOnisPath());
 
 		for (Mod m : mods) {
+			for (int lev : m.getUnlockLevels())
+				unlockLevels.add(lev);
+
 			File oni = new File(m.getLocalPath(), "oni");
 			if (oni.exists()) {
@@ -149,4 +243,17 @@
 		combineBinaryFiles(foldersOni, listener);
 		combineBSLFolders(mods, listener);
+
+		if (unlockLevels.size() > 0) {
+			File dat = new File(Paths.getEditionBasePath(), "persist.dat");
+			if (dat.exists()) {
+				PersistDat save = new PersistDat(dat);
+				HashSet<Integer> currentlyUnlocked = save.getUnlockedLevels();
+				currentlyUnlocked.addAll(unlockLevels);
+				save.setUnlockedLevels(currentlyUnlocked);
+				save.close();
+			} else {
+				// TODO: what if persist.dat does not exist?
+			}
+		}
 	}
 
Index: AE/installer2/src/net/oni2/aeinstaller/backend/oni/OniSplit.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/oni/OniSplit.java	(revision 623)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/oni/OniSplit.java	(revision 624)
@@ -8,5 +8,5 @@
 
 import net.oni2.aeinstaller.backend.Paths;
-import net.oni2.aeinstaller.backend.QuickAppExecution;
+import net.oni2.aeinstaller.backend.AppExecution;
 import net.oni2.aeinstaller.backend.Settings;
 import net.oni2.aeinstaller.backend.Settings.Architecture;
@@ -55,5 +55,5 @@
 				Vector<String> res = null;
 				try {
-					res = QuickAppExecution.execute(cmd);
+					res = AppExecution.executeAndWait(cmd);
 				} catch (IOException e) {
 					e.printStackTrace();
@@ -97,5 +97,5 @@
 		Vector<String> res = null;
 		try {
-			res = QuickAppExecution.execute(cmdLine);
+			res = AppExecution.executeAndWait(cmdLine);
 		} catch (IOException e) {
 			e.printStackTrace();
@@ -122,5 +122,5 @@
 		Vector<String> res = null;
 		try {
-			res = QuickAppExecution.execute(cmdLine);
+			res = AppExecution.executeAndWait(cmdLine);
 		} catch (IOException e) {
 			e.printStackTrace();
@@ -151,5 +151,5 @@
 		Vector<String> res = null;
 		try {
-			res = QuickAppExecution.execute(cmdLine);
+			res = AppExecution.executeAndWait(cmdLine);
 		} catch (IOException e) {
 			e.printStackTrace();
@@ -182,5 +182,5 @@
 		Vector<String> res = null;
 		try {
-			res = QuickAppExecution.execute(cmdLine);
+			res = AppExecution.executeAndWait(cmdLine);
 		} catch (IOException e) {
 			e.printStackTrace();
Index: AE/installer2/src/net/oni2/aeinstaller/backend/oni/PersistDat.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/oni/PersistDat.java	(revision 624)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/oni/PersistDat.java	(revision 624)
@@ -0,0 +1,90 @@
+package net.oni2.aeinstaller.backend.oni;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.HashSet;
+
+/**
+ * @author Christian Illy
+ */
+public class PersistDat {
+	private RandomAccessFile dat = null;
+
+	/**
+	 * Open the given persist.dat
+	 * 
+	 * @param dat
+	 *            Path to persist.dat
+	 */
+	public PersistDat(File dat) {
+		try {
+			this.dat = new RandomAccessFile(dat, "rw");
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * @return Currently unlocked levels in persist.dat
+	 */
+	public HashSet<Integer> getUnlockedLevels() {
+		HashSet<Integer> result = new HashSet<Integer>();
+		if (dat != null) {
+			try {
+				dat.seek(8);
+				for (int i = 0; i < 32; i++) {
+					int val = dat.read();
+					for (int j = 0; j < 8; j++) {
+						if ((val & (1 << j)) > 0) {
+							result.add(i * 8 + j);
+						}
+					}
+				}
+			} catch (IOException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * @param unlock
+	 *            Levels to be unlocked in persist.dat
+	 */
+	public void setUnlockedLevels(HashSet<Integer> unlock) {
+		if (dat != null) {
+			if (unlock != null) {
+				try {
+					dat.seek(8);
+					for (int i = 0; i < 32; i++) {
+						int val = 0;
+						for (int j = 0; j < 8; j++) {
+							if (unlock.contains(i * 8 + j))
+								val |= 1 << j;
+						}
+						dat.write(val);
+					}
+				} catch (IOException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Close file
+	 */
+	public void close() {
+		if (dat != null) {
+			try {
+				dat.close();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 623)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 624)
@@ -322,10 +322,24 @@
 	}
 
-	@DoInBackground(progressMessage = "doUpdate.title", cancelable = false, indeterminateProgress = false)
-	private void doUpdate(final BackgroundEvent evt) {
+	@SuppressWarnings("unused")
+	private void doUpdate() {
 		if (execUpdates != null) {
-			// TODO
-			System.out.println("Update: " + execUpdates.toString());
-			// TODO: install new tools if previously installed
+			Downloader dl = new Downloader(execUpdates);
+			try {
+				dl.setVisible(true);
+				if (dl.isFinished()) {
+					TreeSet<Integer> installed = Installer.getInstalledTools();
+					TreeSet<Mod> tools = new TreeSet<Mod>();
+					for (Mod m : execUpdates)
+						if (m.isTool()
+								&& installed.contains(m.getPackageNumber()))
+							tools.add(m);
+					if (tools.size() > 0) {
+						Installer.installTools(tools);
+					}
+				}
+			} finally {
+				dl.dispose();
+			}
 		}
 		execUpdates = null;
@@ -414,5 +428,5 @@
 	@SuppressWarnings("unused")
 	private void tools() {
-		// TODO method stub
+		// TODO: Open tools manager
 		JOptionPane.showMessageDialog(this, "tools", "todo",
 				JOptionPane.INFORMATION_MESSAGE);
@@ -435,5 +449,6 @@
 					ico = new ImageIcon(m.getIconFile().getPath());
 				} else {
-					URL icon = AEInstaller2.class.getResource("images/transparent.png");
+					URL icon = AEInstaller2.class
+							.getResource("images/transparent.png");
 					ico = new ImageIcon(icon);
 				}
