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);
+}
