Index: AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 604)
@@ -1,4 +1,4 @@
 appname=AE Installer 2
-appversion=0.55
+appversion=0.62
 
 invalidPath.title=Wrong directory
Index: AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java	(revision 604)
@@ -66,5 +66,5 @@
 
 	private boolean printNamesNotInMap = false;
-
+	
 	/**
 	 * Get the singleton instance
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotConfig.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotConfig.java	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotConfig.java	(revision 604)
@@ -61,5 +61,5 @@
 		return "Package";
 	}
-	
+
 	/**
 	 * @return Taxonomy term name for modtype Tool
@@ -68,3 +68,11 @@
 		return "Tool";
 	}
+
+	/**
+	 * @return First package number that's not a mandatory tool/mod. Everything
+	 *         below is considered mandatory
+	 */
+	public static int getMandatoryLimit() {
+		return 8000;
+	}
 }
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/Mod.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/Mod.java	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/Mod.java	(revision 604)
@@ -13,4 +13,5 @@
 import net.oni2.aeinstaller.backend.Settings;
 import net.oni2.aeinstaller.backend.Settings.Platform;
+import net.oni2.aeinstaller.backend.depot.DepotConfig;
 import net.oni2.aeinstaller.backend.depot.DepotManager;
 import net.oni2.aeinstaller.backend.depot.model.NodeMod;
@@ -21,6 +22,4 @@
  */
 public class Mod implements Comparable<Mod> {
-	// TODO: Dependencies/Conflicts
-
 	private String name = "";
 	private int packageNumber = 0;
@@ -76,5 +75,5 @@
 	public void updateLocalData() {
 		File config = new File(getLocalPath(), "Mod_Info.cfg");
-		File timestamp = new File(getLocalPath(), "aei.cfg");
+		File aeicfg = new File(getLocalPath(), "aei.cfg");
 		if (config.exists()) {
 			try {
@@ -112,4 +111,28 @@
 						if (node == null)
 							description = sVal.replaceAll("\\\\n", "<br>");
+					} else if (sName.equalsIgnoreCase("Depends")) {
+						String[] depsS = sVal.split(",");
+						for (String s : depsS) {
+							try {
+								int dep = Integer.parseInt(s);
+								dependencies.add(dep);
+							} catch (NumberFormatException e) {
+								System.err
+										.format("Mod %05d does contain a non-number dependency: '%s'\n",
+												packageNumber, s);
+							}
+						}
+					} else if (sName.equalsIgnoreCase("Conflicts")) {
+						String[] confS = sVal.split(",");
+						for (String s : confS) {
+							try {
+								int conf = Integer.parseInt(s);
+								conflicts.add(conf);
+							} catch (NumberFormatException e) {
+								System.err
+										.format("Mod %05d does contain a non-number dependency: '%s'\n",
+												packageNumber, s);
+							}
+						}
 					}
 				}
@@ -123,11 +146,22 @@
 					+ getLocalPath().getPath());
 		}
-		if (timestamp.exists()) {
+		if (aeicfg.exists()) {
 			try {
-				FileInputStream fstream = new FileInputStream(timestamp);
+				FileInputStream fstream = new FileInputStream(aeicfg);
 				InputStreamReader isr = new InputStreamReader(fstream);
 				BufferedReader br = new BufferedReader(isr);
-				String ts = br.readLine();
-				localTimestamp = Long.parseLong(ts);
+				String strLine;
+				while ((strLine = br.readLine()) != null) {
+					if (strLine.indexOf("->") < 1)
+						continue;
+					if (strLine.indexOf("//") >= 0)
+						strLine = strLine.substring(0, strLine.indexOf("//"));
+					String[] split = strLine.split("->", 2);
+					String sName = split[0].trim();
+					String sVal = split[1].trim();
+					if (sName.equalsIgnoreCase("Timestamp")) {
+						localTimestamp = Long.parseLong(sVal);
+					}
+				}
 				isr.close();
 			} catch (FileNotFoundException e) {
@@ -214,4 +248,11 @@
 
 	/**
+	 * @return the package number as 5 digit string
+	 */
+	public String getPackageNumberString() {
+		return String.format("%05d", packageNumber);
+	}
+
+	/**
 	 * @return Types of mod
 	 */
@@ -266,5 +307,5 @@
 	 */
 	public boolean isMandatoryMod() {
-		return packageNumber < 10000;
+		return packageNumber < DepotConfig.getMandatoryLimit();
 	}
 
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java	(revision 604)
@@ -3,4 +3,8 @@
 import java.io.File;
 import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.Collection;
 import java.util.HashMap;
@@ -9,8 +13,12 @@
 import java.util.Vector;
 
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+
 import net.oni2.aeinstaller.backend.Paths;
 import net.oni2.aeinstaller.backend.depot.DepotManager;
 import net.oni2.aeinstaller.backend.depot.model.NodeMod;
 import net.oni2.aeinstaller.backend.depot.model.TaxonomyTerm;
+import net.oni2.aeinstaller.backend.oni.Installer;
 
 /**
@@ -18,7 +26,4 @@
  */
 public class ModManager {
-	// Download mods
-	// Update mods
-
 	private static ModManager instance = new ModManager();
 
@@ -26,4 +31,52 @@
 	private HashMap<Integer, Mod> mods = new HashMap<Integer, Mod>();
 	private HashMap<Integer, Mod> tools = new HashMap<Integer, Mod>();
+
+	private Vector<Integer> currentlyInstalled = new Vector<Integer>();
+
+	/**
+	 * @param f
+	 *            Mod selection file
+	 * @return Mod selection
+	 */
+	@SuppressWarnings("unchecked")
+	public Vector<Integer> loadModSelection(File f) {
+		Vector<Integer> res = new Vector<Integer>();
+		try {
+			FileInputStream fis = new FileInputStream(f);
+			XStream xs = new XStream(new StaxDriver());
+			Object obj = xs.fromXML(fis);
+			if (obj instanceof Vector<?>)
+				res = (Vector<Integer>) obj;
+			fis.close();
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	/**
+	 * @param f
+	 *            Mod selection file
+	 * @param mods
+	 *            Selected mods
+	 */
+	public void saveModSelection(File f, TreeSet<Mod> mods) {
+		try {
+			Vector<Integer> installed = new Vector<Integer>();
+			for (Mod m : mods) {
+				installed.add(m.getPackageNumber());
+			}
+			FileOutputStream fos = new FileOutputStream(f);
+			XStream xs = new XStream(new StaxDriver());
+			xs.toXML(installed, fos);
+			fos.close();
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
 
 	/**
@@ -69,8 +122,8 @@
 			mods.put(m.getPackageNumber(), m);
 		}
-	}
-
-	public void refreshLocalMods() {
-		// TODO: evtl nur private e.g. als listener für downloads?
+
+		currentlyInstalled = Installer.getInstalledMods();
+		if (currentlyInstalled == null)
+			currentlyInstalled = new Vector<Integer>();
 	}
 
@@ -108,6 +161,6 @@
 	 * @return Mods which are always installed
 	 */
-	public Collection<Mod> getMandatoryMods() {
-		Vector<Mod> res = new Vector<Mod>();
+	public TreeSet<Mod> getMandatoryMods() {
+		TreeSet<Mod> res = new TreeSet<Mod>();
 		for (Mod m : mods.values()) {
 			if (m.isMandatoryMod())
@@ -116,5 +169,5 @@
 		return res;
 	}
-	
+
 	/**
 	 * @return Collection of tools
@@ -123,10 +176,10 @@
 		return tools.values();
 	}
-	
+
 	/**
 	 * @return Tools which are always installed
 	 */
-	public Collection<Mod> getMandatoryTools() {
-		Vector<Mod> res = new Vector<Mod>();
+	public TreeSet<Mod> getMandatoryTools() {
+		TreeSet<Mod> res = new TreeSet<Mod>();
 		for (Mod m : tools.values()) {
 			if (m.isMandatoryMod())
@@ -201,3 +254,11 @@
 	}
 
+	/**
+	 * @param m
+	 *            Mod to check
+	 * @return Is mod installed?
+	 */
+	public boolean isModInstalled(Mod m) {
+		return currentlyInstalled.contains(m.getPackageNumber());
+	}
 }
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownload.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownload.java	(revision 604)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownload.java	(revision 604)
@@ -0,0 +1,187 @@
+package net.oni2.aeinstaller.backend.mods.download;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.mods.Mod;
+import net.oni2.aeinstaller.backend.network.FileDownloadListener;
+import net.oni2.aeinstaller.backend.network.FileDownloader;
+import net.oni2.aeinstaller.backend.network.FileDownloader.EState;
+import net.oni2.aeinstaller.backend.unpack.UnpackListener;
+import net.oni2.aeinstaller.backend.unpack.Unpacker;
+
+/**
+ * @author Christian Illy
+ */
+public class ModDownload implements FileDownloadListener, UnpackListener {
+	/**
+	 * @author Christian Illy
+	 */
+	public enum ModDownloadState {
+		/**
+		 * Downloader initialized but not started
+		 */
+		INIT,
+		/**
+		 * Download running
+		 */
+		RUNNING,
+		/**
+		 * Aborted because of an error
+		 */
+		ERROR,
+		/**
+		 * Download interrupted
+		 */
+		INTERRUPTED,
+		/**
+		 * Download finished successfully
+		 */
+		DOWNLOADED,
+		/**
+		 * Package unzipped successfully
+		 */
+		UNPACKED
+	};
+
+	private Mod mod;
+	private FileDownloader downloader;
+	private Unpacker unpacker;
+	private File zipFile;
+	private File targetFolder;
+	private ModDownloadListener listener;
+	private int size;
+
+	private ModDownloadState state = ModDownloadState.INIT;
+
+	/**
+	 * Create a mod download
+	 * 
+	 * @param mod
+	 *            Mod to download
+	 * @param listener
+	 *            Listener for progress
+	 */
+	public ModDownload(Mod mod, ModDownloadListener listener) {
+		this.mod = mod;
+		this.listener = listener;
+
+		zipFile = new File(Paths.getDownloadPath(),
+				mod.getPackageNumberString() + ".zip");
+		targetFolder = new File(Paths.getModsPath(),
+				mod.getPackageNumberString());
+		try {
+			downloader = new FileDownloader(mod.getFile().getUri_full(),
+					zipFile.getPath());
+			downloader.addListener(this);
+			unpacker = new Unpacker(zipFile, targetFolder, this);
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * @return Size of this download
+	 */
+	public int getSize() {
+		return mod.getZipSize();
+	}
+
+	/**
+	 * Start this download
+	 */
+	public void start() {
+		downloader.start();
+	}
+
+	/**
+	 * Abort this download
+	 */
+	public void abort() {
+		switch (state) {
+			case UNPACKED:
+			case INIT:
+			case ERROR:
+			case INTERRUPTED:
+				break;
+			case RUNNING:
+				downloader.stop();
+				break;
+			case DOWNLOADED:
+				unpacker.stop();
+				break;
+		}
+		state = ModDownloadState.INTERRUPTED;
+	}
+
+	/**
+	 * @return the mod object handled by this download
+	 */
+	public Mod getMod() {
+		return mod;
+	}
+
+	private void writeTimestamp() {
+		File logFile = new File(targetFolder, "aei.cfg");
+		PrintWriter log = null;
+		try {
+			log = new PrintWriter(logFile);
+			log.println("Timestamp -> " + mod.getFile().getTimestamp());
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		}
+		if (log != null)
+			log.close();
+	}
+
+	@Override
+	public void statusUpdate(FileDownloader source, EState state, int done,
+			int total) {
+		switch (state) {
+			case INIT:
+				break;
+			case RUNNING:
+				listener.modDownloadStatusUpdate(this, this.state, done, total);
+				break;
+			case PAUSED:
+				break;
+			case INTERRUPTED:
+				break;
+			case ERROR:
+				this.state = ModDownloadState.ERROR;
+				listener.modDownloadStatusUpdate(this, this.state, done, total);
+				break;
+			case FINISHED:
+				this.state = ModDownloadState.DOWNLOADED;
+				listener.modDownloadStatusUpdate(this, this.state, done, total);
+				this.size = done;
+				unpacker.start();
+				break;
+		}
+	}
+
+	@Override
+	public void statusUpdate(Unpacker source,
+			net.oni2.aeinstaller.backend.unpack.Unpacker.EState state) {
+		switch (state) {
+			case INIT:
+				break;
+			case RUNNING:
+				break;
+			case INTERRUPTED:
+				this.state = ModDownloadState.INTERRUPTED;
+				listener.modDownloadStatusUpdate(this, this.state, size, size);
+				break;
+			case FINISHED:
+				this.state = ModDownloadState.UNPACKED;
+				writeTimestamp();
+				zipFile.delete();
+				listener.modDownloadStatusUpdate(this, this.state, size, size);
+				break;
+		}
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownloadListener.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownloadListener.java	(revision 604)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownloadListener.java	(revision 604)
@@ -0,0 +1,23 @@
+package net.oni2.aeinstaller.backend.mods.download;
+
+import net.oni2.aeinstaller.backend.mods.download.ModDownload.ModDownloadState;
+
+/**
+ * @author Christian Illy
+ */
+public interface ModDownloadListener {
+	/**
+	 * Called for progress changes within the mod download/unpack process
+	 * 
+	 * @param source
+	 *            Source of event
+	 * @param state
+	 *            Current state
+	 * @param done
+	 *            Bytes downloaded
+	 * @param total
+	 *            Bytes total
+	 */
+	public void modDownloadStatusUpdate(ModDownload source,
+			ModDownloadState state, int done, int total);
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownloader.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownloader.java	(revision 604)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownloader.java	(revision 604)
@@ -0,0 +1,135 @@
+package net.oni2.aeinstaller.backend.mods.download;
+
+import java.util.Date;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import net.oni2.aeinstaller.backend.mods.Mod;
+import net.oni2.aeinstaller.backend.mods.download.ModDownload.ModDownloadState;
+
+/**
+ * @author Christian Illy
+ */
+public class ModDownloader implements ModDownloadListener {
+	/**
+	 * @author Christian Illy
+	 */
+	public enum State {
+		/**
+		 * Downloads running
+		 */
+		RUNNING,
+		/**
+		 * Aborted because of an error
+		 */
+		ERROR,
+		/**
+		 * Downloads interrupted
+		 */
+		INTERRUPTED,
+		/**
+		 * Everything completed
+		 */
+		FINISHED
+	};
+
+	private int currentDownload = -1;
+	private int unpacked = 0;
+	private Vector<ModDownload> downloads = new Vector<ModDownload>();
+	private int totalSize = 0;
+	private int downloadedComplete = 0;
+	private int downloadedCurrent = 0;
+	private long startMS;
+	private State state = State.RUNNING;
+	private ModDownloaderListener listener;
+
+	/**
+	 * Create a mods-download-process
+	 * 
+	 * @param mods
+	 *            Mods to download
+	 * @param listener
+	 *            Listener for status updates
+	 */
+	public ModDownloader(TreeSet<Mod> mods, ModDownloaderListener listener) {
+		this.listener = listener;
+		for (Mod m : mods) {
+			downloads.add(new ModDownload(m, this));
+			totalSize += m.getZipSize();
+		}
+		startMS = new Date().getTime();
+		startNextDownload();
+	}
+
+	private void startNextDownload() {
+		if (currentDownload >= 0)
+			downloadedComplete += downloads.get(currentDownload).getSize();
+		currentDownload++;
+		downloadedCurrent = 0;
+		if (currentDownload < downloads.size()) {
+			downloads.get(currentDownload).start();
+		} else {
+			notifyListener();
+		}
+	}
+
+	/**
+	 * @return Average download speed for up to now in B/s
+	 */
+	public int getDownloadSpeed() {
+		long duration = new Date().getTime() - startMS;
+		int down = downloadedComplete + downloadedCurrent;
+		return (int) (down * 1000 / duration);
+	}
+
+	/**
+	 * @return Remaining time for all downloads by avg-speed in seconds
+	 */
+	public int getTimeRemaining() {
+		int remainingSize = totalSize
+				- (downloadedComplete + downloadedCurrent);
+		return remainingSize / getDownloadSpeed();
+	}
+
+	private void notifyListener() {
+		listener.updateStatus(this, state, unpacked, downloads.size(),
+				downloadedComplete + downloadedCurrent, totalSize);
+	}
+
+	/**
+	 * @return Is this process finished
+	 */
+	public boolean isFinished() {
+		return state == State.FINISHED;
+	}
+
+	@Override
+	public void modDownloadStatusUpdate(ModDownload source,
+			ModDownloadState state, int done, int total) {
+		switch (state) {
+			case RUNNING:
+				downloadedCurrent = done;
+				notifyListener();
+				break;
+			case ERROR:
+				this.state = State.ERROR;
+				break;
+			case DOWNLOADED:
+				if (source == downloads.get(currentDownload))
+					startNextDownload();
+				break;
+			case UNPACKED:
+				source.getMod().updateLocalData();
+				unpacked++;
+				if (unpacked >= downloads.size())
+					this.state = State.FINISHED;
+				notifyListener();
+				break;
+			case INIT:
+				break;
+			case INTERRUPTED:
+				break;
+		}
+	}
+
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownloaderListener.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownloaderListener.java	(revision 604)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownloaderListener.java	(revision 604)
@@ -0,0 +1,27 @@
+package net.oni2.aeinstaller.backend.mods.download;
+
+import net.oni2.aeinstaller.backend.mods.download.ModDownloader.State;
+
+/**
+ * @author Christian Illy
+ */
+public interface ModDownloaderListener {
+	/**
+	 * Callback for progress updates on mod downloads
+	 * 
+	 * @param source
+	 *            Event source
+	 * @param state
+	 *            Current state
+	 * @param filesDown
+	 *            Downloaded(+unpacked) files
+	 * @param filesTotal
+	 *            Files in total to handle
+	 * @param bytesDown
+	 *            Bytes downloaded
+	 * @param bytesTotal
+	 *            Bytes in total to handle
+	 */
+	public void updateStatus(ModDownloader source, State state, int filesDown,
+			int filesTotal, int bytesDown, int bytesTotal);
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java	(revision 604)
@@ -94,4 +94,5 @@
 			t.start();
 			state = EState.RUNNING;
+			updateStatus(downloaded, size);
 		}
 	}
@@ -107,4 +108,5 @@
 			else
 				state = EState.RUNNING;
+			updateStatus(downloaded, size);
 		}
 	}
@@ -114,15 +116,19 @@
 	 */
 	public synchronized void stop() {
-		state = EState.INTERRUPTED;
-		try {
-			t.join();
-		} catch (InterruptedException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-		updateStatus(0, 1);
-		t = null;
-		if (target.exists())
-			target.delete();
+		if (state != EState.FINISHED) {
+			state = EState.INTERRUPTED;
+			if (t != null) {
+				try {
+					t.join();
+				} catch (InterruptedException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+				t = null;
+			}
+			updateStatus(0, 1);
+			if (target.exists())
+				target.delete();
+		}
 	}
 
@@ -147,86 +153,89 @@
 			e1.printStackTrace();
 			state = EState.ERROR;
+			updateStatus(downloaded, fileLength);
 			return;
 		}
 
-		while (downloaded < fileLength) {
-			switch (state) {
-				case ERROR:
-					updateStatus(downloaded, fileLength);
-					return;
-				case PAUSED:
-					try {
-						Thread.sleep(100);
-					} catch (InterruptedException e) {
-						e.printStackTrace();
-					}
-					break;
-				case INTERRUPTED:
-					return;
-				case RUNNING:
-					BufferedInputStream input = null;
-					try {
-						URLConnection connection = url.openConnection();
-						if (downloaded == 0) {
-							connection.connect();
-							strLastModified = connection
-									.getHeaderField("Last-Modified");
-							strEtag = connection.getHeaderField("ETag");
-							fileLength = connection.getContentLength();
-						} else {
-							connection.setRequestProperty("Range", "bytes="
-									+ downloaded + "-");
-							if (strEtag != null)
-								connection.setRequestProperty("If-Range",
-										strEtag);
-							else
-								connection.setRequestProperty("If-Range",
-										strLastModified);
-							connection.connect();
+		try {
+			while (downloaded < fileLength) {
+				switch (state) {
+					case ERROR:
+						updateStatus(downloaded, fileLength);
+						return;
+					case PAUSED:
+						try {
+							Thread.sleep(100);
+						} catch (InterruptedException e) {
+							e.printStackTrace();
 						}
-
-						// Setup streams and buffers.
-						input = new BufferedInputStream(
-								connection.getInputStream(), 8192);
-						if (downloaded > 0)
-							outFile.seek(downloaded);
-						byte data[] = new byte[1024];
-
-						// Download file.
-						int dataRead = 0;
-						int i = 0;
-						while (((dataRead = input.read(data, 0, 1024)) != -1)
-								&& (state == EState.RUNNING)) {
-							outFile.write(data, 0, dataRead);
-							downloaded += dataRead;
-							if (downloaded >= fileLength)
-								break;
-
-							i++;
-							if ((i % 10) == 0)
-								updateStatus(downloaded, fileLength);
+						break;
+					case INTERRUPTED:
+						return;
+					case RUNNING:
+						BufferedInputStream input = null;
+						try {
+							URLConnection connection = url.openConnection();
+							if (downloaded == 0) {
+								connection.connect();
+								strLastModified = connection
+										.getHeaderField("Last-Modified");
+								strEtag = connection.getHeaderField("ETag");
+								fileLength = connection.getContentLength();
+							} else {
+								connection.setRequestProperty("Range", "bytes="
+										+ downloaded + "-");
+								if (strEtag != null)
+									connection.setRequestProperty("If-Range",
+											strEtag);
+								else
+									connection.setRequestProperty("If-Range",
+											strLastModified);
+								connection.connect();
+							}
+
+							// Setup streams and buffers.
+							input = new BufferedInputStream(
+									connection.getInputStream(), 8192);
+							if (downloaded > 0)
+								outFile.seek(downloaded);
+							byte data[] = new byte[1024];
+
+							// Download file.
+							int dataRead = 0;
+							int i = 0;
+							while (((dataRead = input.read(data, 0, 1024)) != -1)
+									&& (state == EState.RUNNING)) {
+								outFile.write(data, 0, dataRead);
+								downloaded += dataRead;
+								if (downloaded >= fileLength)
+									break;
+
+								i++;
+								if ((i % 10) == 0)
+									updateStatus(downloaded, fileLength);
+							}
+							input.close();
+						} catch (IOException e) {
+							// TODO Auto-generated catch block
+							e.printStackTrace();
+							try {
+								if (input != null)
+									input.close();
+							} catch (IOException e2) {
+								e2.printStackTrace();
+							}
 						}
-						input.close();
-					} catch (IOException e) {
-						// TODO Auto-generated catch block
-						e.printStackTrace();
-						try {
-							if (input != null)
-								input.close();
-						} catch (IOException e2) {
-							e2.printStackTrace();
-						}
-					}
-					break;
-				default:
-					break;
+						break;
+					default:
+						break;
+				}
 			}
-		}
-
-		try {
-			// Close streams.
-			outFile.close();
-		} catch (IOException e) {
-			e.printStackTrace();
+		} finally {
+			try {
+				// Close streams.
+				outFile.close();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
 		}
 
Index: AE/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java	(revision 604)
@@ -19,4 +19,5 @@
 import net.oni2.aeinstaller.backend.Settings.Platform;
 import net.oni2.aeinstaller.backend.mods.Mod;
+import net.oni2.aeinstaller.backend.mods.ModManager;
 
 import org.apache.commons.io.FileUtils;
@@ -39,4 +40,61 @@
 	}
 
+	/**
+	 * @return list of currently installed mods
+	 */
+	public static Vector<Integer> getInstalledMods() {
+		File installCfg = new File(Paths.getEditionGDF(), "installed_mods.xml");
+		return ModManager.getInstance().loadModSelection(installCfg);
+	}
+
+	/**
+	 * @param tools
+	 *            Tools to install
+	 */
+	public static void installTools(TreeSet<Mod> tools) {
+		for (Mod m : tools) {
+			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())
+						copyToolsFiles(plainCommon);
+					if (Settings.getPlatform() == Platform.MACOS
+							&& plainMac.exists())
+						copyToolsFiles(plainMac);
+					else if (plainWin.exists())
+						copyToolsFiles(plainWin);
+				} else {
+					copyToolsFiles(plain);
+				}
+			}
+		}
+	}
+
+	/**
+	 * @param tools
+	 *            Tools to uninstall
+	 */
+	public static void uninstallTools(TreeSet<Mod> tools) {
+		// TODO: implement this!
+	}
+
+	private static void copyToolsFiles(File srcFolder) {
+		for (File f : srcFolder.listFiles()) {
+			try {
+				if (f.isDirectory())
+					FileUtils.copyDirectoryToDirectory(f,
+							Paths.getEditionBasePath());
+				else
+					FileUtils
+							.copyFileToDirectory(f, Paths.getEditionBasePath());
+			} catch (IOException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+		}
+	}
 	/**
 	 * Install the given set of mods
@@ -49,4 +107,13 @@
 	public static void install(TreeSet<Mod> mods,
 			InstallProgressListener listener) {
+		try {
+			createEmptyPath(Paths.getEditionGDF());
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+
+		File installCfg = new File(Paths.getEditionGDF(), "installed_mods.xml");
+		ModManager.getInstance().saveModSelection(installCfg, mods);
+
 		Vector<File> folders = new Vector<File>();
 		folders.add(Paths.getVanillaOnisPath());
@@ -79,77 +146,67 @@
 	private static void combineBinaryFiles(List<File> srcFoldersFiles,
 			InstallProgressListener listener) {
+		HashMap<String, Vector<File>> levels = new HashMap<String, Vector<File>>();
+
+		for (File path : srcFoldersFiles) {
+			for (File levelF : path.listFiles()) {
+				String fn = levelF.getName().toLowerCase();
+				String levelN = null;
+				if (levelF.isDirectory()) {
+					levelN = fn;
+				} else if (fn.endsWith(".dat")) {
+					levelN = fn.substring(0, fn.lastIndexOf('.'));
+				}
+				if (levelN != null) {
+					if (!levels.containsKey(levelN))
+						levels.put(levelN, new Vector<File>());
+					levels.get(levelN).add(levelF);
+				}
+			}
+		}
+
+		int totalSteps = 0;
+		int stepsDone = 0;
+
+		for (@SuppressWarnings("unused")
+		String s : levels.keySet())
+			totalSteps++;
+
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+		File logFile = new File(Paths.getInstallerPath(), "Installation.log");
+		PrintWriter log = null;
 		try {
-			HashMap<String, Vector<File>> levels = new HashMap<String, Vector<File>>();
-
-			for (File path : srcFoldersFiles) {
-				for (File levelF : path.listFiles()) {
-					String fn = levelF.getName().toLowerCase();
-					String levelN = null;
-					if (levelF.isDirectory()) {
-						levelN = fn;
-					} else if (fn.endsWith(".dat")) {
-						levelN = fn.substring(0, fn.lastIndexOf('.'));
-					}
-					if (levelN != null) {
-						if (!levels.containsKey(levelN))
-							levels.put(levelN, new Vector<File>());
-						levels.get(levelN).add(levelF);
-					}
-				}
-			}
-
-			int totalSteps = 0;
-			int stepsDone = 0;
-
-			for (@SuppressWarnings("unused")
-			String s : levels.keySet())
-				totalSteps++;
-
-			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
-			File logFile = new File(Paths.getInstallerPath(),
-					"Installation.log");
-			PrintWriter log = null;
-			try {
-				log = new PrintWriter(logFile);
-			} catch (FileNotFoundException e) {
-				e.printStackTrace();
-			}
-
-			Date start = new Date();
-			log.println("Installation of mods started at " + sdf.format(start));
-
-			log.println("Cleaning directories");
+			log = new PrintWriter(logFile);
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		}
+
+		Date start = new Date();
+		log.println("Installation of mods started at " + sdf.format(start));
+
+		log.println("Importing levels");
+		for (String l : levels.keySet()) {
+			log.println("\tLevel " + l);
 			listener.installProgressUpdate(stepsDone, totalSteps,
-					"Cleaning up directories");
-
-			createEmptyPath(Paths.getEditionGDF());
-
-			log.println("Importing levels");
-			for (String l : levels.keySet()) {
-				log.println("\tLevel " + l);
-				for (File f : levels.get(l)) {
-					log.println("\t\t\t" + f.getPath());
-				}
-
-				Vector<String> res = OniSplit.packLevel(levels.get(l),
-						new File(Paths.getEditionGDF(), l + ".dat"));
-				if (res != null && res.size() > 0) {
-					for (String s : res)
-						log.println("\t\t" + s);
-				}
-
-				log.println();
-			}
-
-			Date end = new Date();
-			log.println("Initialization ended at " + sdf.format(end));
-			log.println("Process took "
-					+ ((end.getTime() - start.getTime()) / 1000) + " seconds");
-			log.close();
-		} catch (IOException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
+					"Installing level " + l);
+			for (File f : levels.get(l)) {
+				log.println("\t\t\t" + f.getPath());
+			}
+
+			Vector<String> res = OniSplit.packLevel(levels.get(l), new File(
+					Paths.getEditionGDF(), l + ".dat"));
+			if (res != null && res.size() > 0) {
+				for (String s : res)
+					log.println("\t\t" + s);
+			}
+
+			log.println();
+		}
+
+		Date end = new Date();
+		log.println("Initialization ended at " + sdf.format(end));
+		log.println("Process took "
+				+ ((end.getTime() - start.getTime()) / 1000) + " seconds");
+		log.close();
 	}
 
@@ -273,5 +330,5 @@
 
 			FileUtils.deleteDirectory(init);
-			
+
 			Date end = new Date();
 			log.println("Initialization ended at " + sdf.format(end));
@@ -387,9 +444,12 @@
 
 	/**
-	 * Verify that the Edition is within a subfolder to vanilla Oni (..../Oni/Edition/AEInstaller)
+	 * Verify that the Edition is within a subfolder to vanilla Oni
+	 * (..../Oni/Edition/AEInstaller)
+	 * 
 	 * @return true if GDF can be found in the parent's parent-path
 	 */
 	public static boolean verifyRunningDirectory() {
-		return Paths.getVanillaGDF().exists() && Paths.getVanillaGDF().isDirectory();
+		return Paths.getVanillaGDF().exists()
+				&& Paths.getVanillaGDF().isDirectory();
 	}
 }
Index: AE/installer2/src/net/oni2/aeinstaller/backend/unpack/UnpackListener.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/unpack/UnpackListener.java	(revision 604)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/unpack/UnpackListener.java	(revision 604)
@@ -0,0 +1,18 @@
+package net.oni2.aeinstaller.backend.unpack;
+
+import net.oni2.aeinstaller.backend.unpack.Unpacker.EState;
+
+/**
+ * @author Christian Illy
+ */
+public interface UnpackListener {
+	/**
+	 * Called for progress changes within the unpacker
+	 * 
+	 * @param source
+	 *            Source of event
+	 * @param state
+	 *            Current state
+	 */
+	public void statusUpdate(Unpacker source, EState state);
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/unpack/Unpacker.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/unpack/Unpacker.java	(revision 604)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/unpack/Unpacker.java	(revision 604)
@@ -0,0 +1,171 @@
+package net.oni2.aeinstaller.backend.unpack;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.io.FileUtils;
+
+/**
+ * @author Christian Illy
+ */
+public class Unpacker implements Runnable {
+	/**
+	 * @author Christian Illy
+	 */
+	public enum EState {
+		/**
+		 * Unpacker initialized but not started
+		 */
+		INIT,
+		/**
+		 * Unpack running
+		 */
+		RUNNING,
+		/**
+		 * Unpack interrupted
+		 */
+		INTERRUPTED,
+		/**
+		 * Unpack finished successfully
+		 */
+		FINISHED,
+	};
+
+	private UnpackListener listener;
+
+	private File zip;
+	private File target;
+
+	private Thread t = null;
+
+	private EState state = EState.INIT;
+
+	/**
+	 * Initialize a new AE package unpacker
+	 * 
+	 * @param zipFile
+	 *            AE zip package
+	 * @param targetFolder
+	 *            Target folder
+	 * @param listener
+	 *            Listener for progress updates
+	 */
+	public Unpacker(File zipFile, File targetFolder, UnpackListener listener) {
+		this.listener = listener;
+		zip = zipFile;
+		target = targetFolder;
+	}
+
+	/**
+	 * Start the unpack process
+	 */
+	public synchronized void start() {
+		if (t == null) {
+			t = new Thread(this);
+			t.start();
+			state = EState.RUNNING;
+			updateStatus();
+		}
+	}
+
+	/**
+	 * Stop (abort) the process
+	 */
+	public synchronized void stop() {
+		if (state != EState.FINISHED) {
+			state = EState.INTERRUPTED;
+			if (t != null) {
+				try {
+					t.join();
+				} catch (InterruptedException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+				t = null;
+			}
+			updateStatus();
+			if (state != EState.FINISHED) {
+				if (target.exists()) {
+					try {
+						FileUtils.deleteDirectory(target);
+					} catch (IOException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					}
+				}
+			}
+		}
+	}
+
+	private synchronized void updateStatus() {
+		listener.statusUpdate(this, state);
+	}
+
+	@Override
+	public void run() {
+		try {
+			switch (state) {
+				case INTERRUPTED:
+					return;
+				case RUNNING:
+					try {
+						int pathStart = 0;
+
+						ZipFile zf = new ZipFile(zip);
+						target.mkdirs();
+						for (Enumeration<? extends ZipEntry> e = zf.entries(); e
+								.hasMoreElements();) {
+							ZipEntry ze = e.nextElement();
+							if (ze.getName().toLowerCase()
+									.endsWith("mod_info.cfg")) {
+								pathStart = ze.getName().toLowerCase()
+										.indexOf("mod_info.cfg");
+							}
+						}
+
+						for (Enumeration<? extends ZipEntry> e = zf.entries(); e
+								.hasMoreElements();) {
+							if (state == EState.INTERRUPTED)
+								return;
+							ZipEntry ze = e.nextElement();
+							if (!ze.isDirectory()) {
+								File targetFile = new File(target, ze.getName()
+										.substring(pathStart));
+								targetFile.getParentFile().mkdirs();
+
+								InputStream in = zf.getInputStream(ze);
+
+								int read = 0;
+								byte[] data = new byte[1024];
+								FileOutputStream fileOut = new FileOutputStream(
+										targetFile);
+								while ((read = in.read(data, 0, 1024)) != -1) {
+									fileOut.write(data, 0, read);
+								}
+								fileOut.close();
+							}
+						}
+					} catch (ZipException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					} catch (IOException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					}
+					break;
+				default:
+					break;
+			}
+		} finally {
+		}
+
+		state = EState.FINISHED;
+		updateStatus();
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 604)
@@ -1,4 +1,5 @@
 package net.oni2.aeinstaller.gui;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
@@ -10,4 +11,5 @@
 import javax.swing.JComboBox;
 import javax.swing.JComponent;
+import javax.swing.JFileChooser;
 import javax.swing.JFrame;
 import javax.swing.JLabel;
@@ -22,6 +24,8 @@
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
+import javax.swing.filechooser.FileFilter;
 import javax.swing.table.TableRowSorter;
 
+import net.oni2.aeinstaller.backend.Paths;
 import net.oni2.aeinstaller.backend.Settings;
 import net.oni2.aeinstaller.backend.Settings.Platform;
@@ -32,4 +36,7 @@
 import net.oni2.aeinstaller.backend.mods.ModManager;
 import net.oni2.aeinstaller.backend.mods.Type;
+import net.oni2.aeinstaller.backend.mods.download.ModDownloader;
+import net.oni2.aeinstaller.backend.mods.download.ModDownloader.State;
+import net.oni2.aeinstaller.backend.mods.download.ModDownloaderListener;
 import net.oni2.aeinstaller.backend.oni.InstallProgressListener;
 import net.oni2.aeinstaller.backend.oni.Installer;
@@ -222,16 +229,49 @@
 	}
 
+	private JFileChooser getConfigOpenSaveDialog(boolean save) {
+		JFileChooser fc = new JFileChooser();
+		fc.setCurrentDirectory(Paths.getEditionBasePath());
+		if (save)
+			fc.setDialogType(JFileChooser.SAVE_DIALOG);
+		else
+			fc.setDialogType(JFileChooser.OPEN_DIALOG);
+		fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+		fc.setFileFilter(new FileFilter() {
+			@Override
+			public String getDescription() {
+				return "XML files";
+			}
+
+			@Override
+			public boolean accept(File arg0) {
+				return (arg0.isDirectory())
+						|| (arg0.getName().toLowerCase().endsWith(".xml"));
+			}
+		});
+		fc.setMultiSelectionEnabled(false);
+		return fc;
+	}
+
 	@SuppressWarnings("unused")
 	private void loadConfig() {
-		// TODO method stub
-		JOptionPane.showMessageDialog(this, "loadConfig", "todo",
-				JOptionPane.INFORMATION_MESSAGE);
+		JFileChooser fc = getConfigOpenSaveDialog(false);
+		int res = fc.showOpenDialog(this);
+		if (res == JFileChooser.APPROVE_OPTION) {
+			if (fc.getSelectedFile().exists())
+				model.reloadSelection(fc.getSelectedFile());
+		}
 	}
 
 	@SuppressWarnings("unused")
 	private void saveConfig() {
-		// TODO method stub
-		JOptionPane.showMessageDialog(this, "saveConfig", "todo",
-				JOptionPane.INFORMATION_MESSAGE);
+		JFileChooser fc = getConfigOpenSaveDialog(true);
+		int res = fc.showSaveDialog(this);
+		if (res == JFileChooser.APPROVE_OPTION) {
+			File f = fc.getSelectedFile();
+			if (!f.getName().endsWith(".xml"))
+				f = new File(f.getParentFile(), f.getName() + ".xml");
+			ModManager.getInstance().saveModSelection(f,
+					model.getSelectedMods());
+		}
 	}
 
@@ -257,22 +297,42 @@
 	@SuppressWarnings("unused")
 	private void revertSelection() {
-		// TODO method stub
-		JOptionPane.showMessageDialog(this, "revertSelection", "todo",
-				JOptionPane.INFORMATION_MESSAGE);
+		model.revertSelection();
 	}
 
 	@DoInBackground(progressMessage = "mandatoryFiles.title", cancelable = false, indeterminateProgress = false)
 	private void checkMandatoryFiles(final BackgroundEvent evt) {
-		//TODO
-		System.out.println("Mandatory Tools:");
+		TreeSet<Mod> mand = new TreeSet<Mod>();
 		for (Mod m : ModManager.getInstance().getMandatoryTools()) {
-			System.out.format("  %05d %15s - Local: %b - Update: %b", m.getPackageNumber(), m.getName(), m.isLocalAvailable(), m.isNewerAvailable());
-		}
-		System.out.println();
-		
-		System.out.println("Mandatory Mods:");
+			if (m.isNewerAvailable()) {
+				mand.add(m);
+			}
+		}
 		for (Mod m : ModManager.getInstance().getMandatoryMods()) {
-			System.out.format("  %05d %15s - Local: %b - Update: %b", m.getPackageNumber(), m.getName(), m.isLocalAvailable(), m.isNewerAvailable());
-		}
+			if (m.isNewerAvailable()) {
+				mand.add(m);
+			}
+		}
+		if (mand.size() > 0) {
+			ModDownloader m = new ModDownloader(mand,
+					new ModDownloaderListener() {
+						@Override
+						public void updateStatus(ModDownloader source,
+								State state, int filesDown, int filesTotal,
+								int bytesDown, int bytesTotal) {
+							evt.setProgressEnd(filesTotal);
+							evt.setProgressValue(filesDown);
+						}
+					});
+			while (!m.isFinished()) {
+				try {
+					Thread.sleep(50);
+				} catch (InterruptedException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+		evt.setProgressMessage(bundle.getString("mandatoryToolsInstall.title"));
+		Installer.installTools(ModManager.getInstance().getMandatoryTools());
 	}
 
@@ -287,6 +347,6 @@
 		System.out.println("Install mods:");
 		for (Mod m : mods) {
-			System.out
-					.println("  " + m.getPackageNumber() + ": " + m.getName());
+			System.out.println("  " + m.getPackageNumberString() + ": "
+					+ m.getName());
 		}
 
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.properties	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.properties	(revision 604)
@@ -47,2 +47,3 @@
 installing.title=Installing mods
 mandatoryFiles.title=Checking for mandatory files
+mandatoryToolsInstall.title=Installing mandatory tools
Index: AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.java	(revision 603)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.java	(revision 604)
@@ -1,4 +1,5 @@
 package net.oni2.aeinstaller.gui.modtable;
 
+import java.io.File;
 import java.util.HashSet;
 import java.util.ResourceBundle;
@@ -44,5 +45,5 @@
 				return mod.getName();
 			case 1:
-				return mod.getPackageNumber();
+				return mod.getPackageNumberString();
 			case 2:
 				String type = "";
@@ -94,5 +95,5 @@
 				return String.class;
 			case 1:
-				return Integer.class;
+				return String.class;
 			case 2:
 				return String.class;
@@ -149,9 +150,33 @@
 		items.clear();
 		items.addAll(ModManager.getInstance().getMods());
+		revertSelection();
+	}
+
+	/**
+	 * Revert the selection to the mods that are currently installed
+	 */
+	public void revertSelection() {
 		install.clear();
-		// TODO check installed
 		for (int i = 0; i < items.size(); i++) {
-			install.add(i, false);
-		}
+			install.add(i, ModManager.getInstance()
+					.isModInstalled(items.get(i)));
+		}
+		fireTableDataChanged();
+	}
+
+	/**
+	 * Reload the selection after a config was loaded
+	 * 
+	 * @param config
+	 *            Config to load
+	 */
+	public void reloadSelection(File config) {
+		Vector<Integer> selected = ModManager.getInstance().loadModSelection(
+				config);
+		install.clear();
+		for (int i = 0; i < items.size(); i++) {
+			install.add(i, selected.contains(items.get(i).getPackageNumber()));
+		}
+		fireTableDataChanged();
 	}
 
