Index: AE/installer2/src/net/oni2/aeinstaller/backend/QuickAppExecution.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/QuickAppExecution.java	(revision 600)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/QuickAppExecution.java	(revision 600)
@@ -0,0 +1,43 @@
+package net.oni2.aeinstaller.backend;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * @author Christian Illy
+ */
+public class QuickAppExecution {
+
+	/**
+	 * Execute a short running application
+	 * 
+	 * @param cmdLine
+	 *            List of command and arguments
+	 * @return List of output lines
+	 * @throws IOException
+	 *             Exc
+	 */
+	public static Vector<String> execute(List<String> cmdLine)
+			throws IOException {
+		ProcessBuilder pb = new ProcessBuilder(cmdLine);
+		pb.redirectErrorStream(true);
+		Process proc = pb.start();
+
+		InputStream is = proc.getInputStream();
+		InputStreamReader isr = new InputStreamReader(is);
+		BufferedReader br = new BufferedReader(isr);
+
+		String line;
+		Vector<String> lines = new Vector<String>();
+
+		while ((line = br.readLine()) != null) {
+			lines.add(line);
+		}
+		return lines;
+	}
+
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java	(revision 599)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java	(revision 600)
@@ -10,5 +10,4 @@
 import java.util.Vector;
 
-import net.oni2.aeinstaller.backend.app_launcher.QuickAppExecution;
 
 import com.thoughtworks.xstream.XStream;
@@ -112,5 +111,4 @@
 					res = QuickAppExecution.execute(cmd);
 				} catch (IOException e) {
-					// TODO Auto-generated catch block
 					e.printStackTrace();
 				}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java	(revision 599)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java	(revision 600)
@@ -18,4 +18,5 @@
 import net.oni2.aeinstaller.backend.depot.model.TaxonomyTerm;
 import net.oni2.aeinstaller.backend.depot.model.TaxonomyVocabulary;
+import net.oni2.aeinstaller.backend.mods.ECompatiblePlatform;
 import net.oni2.aeinstaller.backend.network.DrupalJSONQuery;
 
@@ -287,20 +288,46 @@
 
 	/**
-	 * @return Mod-Nodes
-	 */
-	public Vector<NodeMod> getModPackageNodes() {
-		Vector<NodeMod> result = new Vector<NodeMod>();
-		TaxonomyTerm tt = getTaxonomyTerm(DepotConfig.getTaxonomyName_InstallType_Package());
-		if (tt == null)
-			return result;
-
-		int packageterm_id = tt.getTid();
-
+	 * Get a node by node id
+	 * 
+	 * @param id
+	 *            Node id
+	 * @return Node
+	 */
+	public Node getNodeById(int id) {
+		return nodes.get(id);
+	}
+
+	/**
+	 * Get a Mod-Node by a given package number
+	 * 
+	 * @param packageNumber
+	 *            Package number to find
+	 * @return The Mod-Node or null
+	 */
+	public NodeMod getNodeByPackageNumber(int packageNumber) {
 		Vector<Node> files = getNodesByType(DepotConfig.getNodeType_Mod());
 		for (Node n : files) {
 			if (n instanceof NodeMod) {
 				NodeMod nm = (NodeMod) n;
-				if (nm.getTaxonomyTerms().get(vocabId_instmethod)
-						.contains(packageterm_id))
+				if (nm.getPackageNumber() == packageNumber)
+					return nm;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * @return Mod-Nodes
+	 */
+	public Vector<NodeMod> getModPackageNodes() {
+		Vector<NodeMod> result = new Vector<NodeMod>();
+		String instMethName = DepotConfig.getTaxonomyName_InstallType_Package();
+
+		Vector<Node> files = getNodesByType(DepotConfig.getNodeType_Mod());
+		for (Node n : files) {
+			if (n instanceof NodeMod) {
+				NodeMod nm = (NodeMod) n;
+				if (nm.getInstallMethod().getName()
+						.equalsIgnoreCase(instMethName))
 					result.add(nm);
 			}
@@ -317,16 +344,15 @@
 	 */
 	public boolean isModValidOnPlatform(NodeMod node, Settings.Platform platform) {
-		int termId = node.getTaxonomyTerms().get(vocabId_platform).iterator()
-				.next();
-		String validPlatform = getTaxonomyTerm(termId).getName();
-		if (validPlatform.equalsIgnoreCase(DepotConfig.getTaxonomyName_Platform_Both()))
-			return true;
-
-		if ((platform == Platform.WIN) || (platform == Platform.LINUX))
-			return validPlatform.equalsIgnoreCase(DepotConfig.getTaxonomyName_Platform_Win());
-		else if (platform == Platform.MACOS)
-			return validPlatform.equalsIgnoreCase(DepotConfig.getTaxonomyName_Platform_Mac());
-		else
-			return false;
+		ECompatiblePlatform plat = node.getPlatform();
+		switch (plat) {
+			case BOTH:
+				return true;
+			case WIN:
+				return (platform == Platform.WIN)
+						|| (platform == Platform.LINUX);
+			case MACOS:
+				return (platform == Platform.MACOS);
+		}
+		return false;
 	}
 
@@ -344,17 +370,44 @@
 	 */
 	public boolean isModOfType(NodeMod node, HashSet<Integer> type, boolean or) {
-		boolean matching = true;
-		if (or)
-			matching = false;
+		boolean matching = !or;
+		HashSet<TaxonomyTerm> terms = node.getTypes();
 
 		for (int t : type) {
 			if (or)
-				matching |= node.getTaxonomyTerms().get(vocabId_type)
-						.contains(t);
+				matching |= terms.contains(t);
 			else
-				matching &= node.getTaxonomyTerms().get(vocabId_type)
-						.contains(t);
+				matching &= terms.contains(t);
 		}
 		return matching;
+	}
+
+	/**
+	 * @return VocabId of Platform vocabulary
+	 */
+	public int getVocabIdPlatform() {
+		return vocabId_platform;
+	}
+
+	/**
+	 * @return VocabId of Install method vocabulary
+	 */
+	public int getVocabIdInstMethod() {
+		return vocabId_instmethod;
+	}
+
+	/**
+	 * @return VocabId of Type vocabulary
+	 */
+	public int getVocabIdType() {
+		return vocabId_type;
+	}
+
+	/**
+	 * @param id
+	 *            ID of file to get
+	 * @return the file
+	 */
+	public File getFile(int id) {
+		return files.get(id);
 	}
 
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeMod.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeMod.java	(revision 599)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeMod.java	(revision 600)
@@ -4,4 +4,8 @@
 import java.util.HashSet;
 import java.util.Vector;
+
+import net.oni2.aeinstaller.backend.depot.DepotConfig;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.backend.mods.ECompatiblePlatform;
 
 import org.json.JSONArray;
@@ -74,15 +78,73 @@
 
 	/**
-	 * @return the taxonomyTerms
+	 * @return Types
 	 */
-	public HashMap<Integer, HashSet<Integer>> getTaxonomyTerms() {
-		return taxonomyTerms;
+	public HashSet<TaxonomyTerm> getTypes() {
+		HashSet<TaxonomyTerm> tt = new HashSet<TaxonomyTerm>();
+		for (int t : taxonomyTerms.get(DepotManager.getInstance()
+				.getVocabIdType())) {
+			tt.add(DepotManager.getInstance().getTaxonomyTerm(t));
+		}
+		return tt;
 	}
 
 	/**
-	 * @return the fields
+	 * @return Install method
 	 */
-	public HashMap<String, String> getFields() {
-		return fields;
+	public TaxonomyTerm getInstallMethod() {
+		return DepotManager.getInstance().getTaxonomyTerm(
+				taxonomyTerms
+						.get(DepotManager.getInstance().getVocabIdInstMethod())
+						.iterator().next());
+	}
+
+	/**
+	 * @return Compatible platform
+	 */
+	public ECompatiblePlatform getPlatform() {
+		TaxonomyTerm term = DepotManager.getInstance().getTaxonomyTerm(
+				taxonomyTerms
+						.get(DepotManager.getInstance().getVocabIdPlatform())
+						.iterator().next());
+
+		String validPlatform = term.getName();
+		if (validPlatform.equalsIgnoreCase(DepotConfig
+				.getTaxonomyName_Platform_Both()))
+			return ECompatiblePlatform.BOTH;
+		if (validPlatform.equalsIgnoreCase(DepotConfig
+				.getTaxonomyName_Platform_Win()))
+			return ECompatiblePlatform.WIN;
+		if (validPlatform.equalsIgnoreCase(DepotConfig
+				.getTaxonomyName_Platform_Mac()))
+			return ECompatiblePlatform.MACOS;
+
+		return null;
+	}
+
+	/**
+	 * @return Creator of mod
+	 */
+	public String getCreator() {
+		if (fields.get("creator") != null)
+			return fields.get("creator");
+		else
+			return "";
+	}
+
+	/**
+	 * @return Version of mod
+	 */
+	public String getVersion() {
+		if (fields.get("version") != null)
+			return fields.get("version");
+		else
+			return "";
+	}
+
+	/**
+	 * @return Package number
+	 */
+	public int getPackageNumber() {
+		return Integer.parseInt(fields.get("package_number"));
 	}
 }
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/EBSLInstallType.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/EBSLInstallType.java	(revision 600)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/EBSLInstallType.java	(revision 600)
@@ -0,0 +1,19 @@
+package net.oni2.aeinstaller.backend.mods;
+
+/**
+ * @author Christian Illy
+ */
+public enum EBSLInstallType {
+	/**
+	 * No BSL files
+	 */
+	NONE,
+	/**
+	 * Normal BSL install mode
+	 */
+	NORMAL,
+	/**
+	 * BSL addon install mode
+	 */
+	ADDON
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/ECompatiblePlatform.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/ECompatiblePlatform.java	(revision 600)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/ECompatiblePlatform.java	(revision 600)
@@ -0,0 +1,19 @@
+package net.oni2.aeinstaller.backend.mods;
+
+/**
+ * @author Christian Illy
+ */
+public enum ECompatiblePlatform {
+	/**
+	 * Only for Win
+	 */
+	WIN,
+	/**
+	 * Only for MacOS
+	 */
+	MACOS,
+	/**
+	 * Usable with both platforms
+	 */
+	BOTH
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/Mod.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/Mod.java	(revision 599)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/Mod.java	(revision 600)
@@ -1,5 +1,19 @@
 package net.oni2.aeinstaller.backend.mods;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashSet;
+
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.Settings;
+import net.oni2.aeinstaller.backend.Settings.Platform;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.backend.depot.model.NodeMod;
+import net.oni2.aeinstaller.backend.depot.model.TaxonomyTerm;
 
 /**
@@ -7,33 +21,282 @@
  */
 public class Mod implements Comparable<Mod> {
-	private double aeVersion;
-	private int node;
-	private int packageNumber;
-
-	public double getAEVersion() {
-		return aeVersion;
-	}
-
+	// TODO: Dependencies/Conflicts
+	
+	private String name = "";
+	private int packageNumber = 0;
+
+	private HashSet<Type> types = new HashSet<Type>();
+	private ECompatiblePlatform platform = null;
+	private String version = "";
+	private String creator = "";
+	private EBSLInstallType bslInstallType = null;
+	private String description = "";
+	private double aeVersion = 0;
+	private int zipSize = 0;
+	private NodeMod node = null;
+	private net.oni2.aeinstaller.backend.depot.model.File file = null;
+
+	private long localTimestamp = 0;
+
+	/**
+	 * Create a new Mod entry from a given Mod-Node
+	 * 
+	 * @param nm
+	 *            Mod-Node
+	 */
+	public Mod(NodeMod nm) {
+		node = nm;
+		name = nm.getTitle();
+		packageNumber = nm.getPackageNumber();
+		for (TaxonomyTerm tt : nm.getTypes()) {
+			Type t = ModManager.getInstance().getTypeByName(tt.getName());
+			types.add(t);
+			t.addEntry(this);
+		}
+		platform = nm.getPlatform();
+		version = nm.getVersion();
+		creator = nm.getCreator();
+		if (nm.getBody() != null)
+			description = nm.getBody().getSafe_value();
+		file = DepotManager.getInstance().getFile(
+				nm.getUploads().firstElement().getFid());
+		zipSize = file.getFilesize();
+
+		if (isLocalAvailable())
+			updateLocalData();
+	}
+
+	/**
+	 * Update information for local package existence
+	 */
+	public void updateLocalData() {
+		File config = new File(getLocalPath(), "Mod_Info.cfg");
+		File timestamp = new File(getLocalPath(), "aei.cfg");
+		if (config.exists()) {
+			try {
+				FileInputStream fstream = new FileInputStream(config);
+				InputStreamReader isr = new InputStreamReader(fstream);
+				BufferedReader br = new BufferedReader(isr);
+				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("AEInstallVersion")) {
+						aeVersion = Double.parseDouble(sVal);
+					} else if (sName.equalsIgnoreCase("NameOfMod")) {
+						if (node == null)
+							name = sVal;
+					} else if (sName.equalsIgnoreCase("Creator")) {
+						if (node == null)
+							creator = sVal;
+					} else if (sName.equalsIgnoreCase("HasBsl")) {
+						if (sVal.equalsIgnoreCase("addon"))
+							bslInstallType = EBSLInstallType.ADDON;
+						else if (sVal.equalsIgnoreCase("yes"))
+							bslInstallType = EBSLInstallType.NORMAL;
+						else
+							bslInstallType = EBSLInstallType.NONE;
+					} else if (sName.equalsIgnoreCase("ModVersion")) {
+						if (node == null)
+							version = sVal;
+					} else if (sName.equalsIgnoreCase("Readme")) {
+						if (node == null)
+							description = sVal.replaceAll("\\\\n", "<br>");
+					}
+				}
+				isr.close();
+			} catch (FileNotFoundException e) {
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		} else {
+			System.err.println("No config found for mod folder: "
+					+ getLocalPath().getPath());
+		}
+		if (timestamp.exists()) {
+			try {
+				FileInputStream fstream = new FileInputStream(timestamp);
+				InputStreamReader isr = new InputStreamReader(fstream);
+				BufferedReader br = new BufferedReader(isr);
+				String ts = br.readLine();
+				localTimestamp = Long.parseLong(ts);
+				isr.close();
+			} catch (FileNotFoundException e) {
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+
+	/**
+	 * Create a new Mod entry from the given local mod folder
+	 * 
+	 * @param folder
+	 *            Mod folder with Mod_Info.cfg
+	 */
+	public Mod(File folder) {
+		packageNumber = Integer.parseInt(folder.getName().substring(0, 5));
+		updateLocalData();
+
+		Type t = ModManager.getInstance().getTypeByName("-Local-");
+		types.add(t);
+		t.addEntry(this);
+
+		platform = ECompatiblePlatform.BOTH;
+	}
+
+	/**
+	 * @return has separate paths for win/mac/common or not
+	 */
 	public boolean hasSeparatePlatformDirs() {
 		return aeVersion >= 2;
 	}
 
+	/**
+	 * @return Path to local mod folder
+	 */
 	public File getLocalPath() {
-		// TODO
-		return null;
-	}
-	
-	public boolean newerAvailable() {
-		//TODO
-		return false;
-	}
-	
-	public boolean localAvailable() {
-		//TODO
-		return false;
-	}
-	
+		final String folderStart = String.format("%05d", packageNumber);
+
+		if (Paths.getModsPath().exists()) {
+			for (File f : Paths.getModsPath().listFiles(new FilenameFilter() {
+				@Override
+				public boolean accept(File d, String fn) {
+					return fn.startsWith(folderStart);
+				}
+			})) {
+				return f;
+			}
+		}
+
+		return new File(Paths.getModsPath(), folderStart);
+	}
+
+	/**
+	 * @return Is there a newer version on the depot?
+	 */
+	public boolean isNewerAvailable() {
+		if (node != null)
+			return node.getUploads().firstElement().getTimestamp() > localTimestamp;
+		else
+			return false;
+	}
+
+	/**
+	 * @return Mod exists within mods folder
+	 */
+	public boolean isLocalAvailable() {
+		return getLocalPath().exists();
+	}
+
+	/**
+	 * @return Name of mod
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the package number
+	 */
 	public int getPackageNumber() {
 		return packageNumber;
+	}
+
+	/**
+	 * @return Types of mod
+	 */
+	public HashSet<Type> getTypes() {
+		return types;
+	}
+
+	/**
+	 * @return Compatible platforms
+	 */
+	public ECompatiblePlatform getPlatform() {
+		return platform;
+	}
+
+	/**
+	 * @return Version of mod
+	 */
+	public String getVersion() {
+		return version;
+	}
+
+	/**
+	 * @return Creator of mod
+	 */
+	public String getCreator() {
+		return creator;
+	}
+
+	/**
+	 * @return Installation type of BSL files
+	 */
+	public EBSLInstallType getBSLInstallType() {
+		return bslInstallType;
+	}
+
+	/**
+	 * @return Description of mod
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * @return Size of Zip file on Depot
+	 */
+	public int getZipSize() {
+		return zipSize;
+	}
+
+	/**
+	 * @return Depot Mod-Node
+	 */
+	public NodeMod getNode() {
+		return node;
+	}
+
+	/**
+	 * @return Is a mod that is always installed?
+	 */
+	public boolean isDefaultMod() {
+		return packageNumber < 10000;
+	}
+
+	/**
+	 * @return Get the depot file entry
+	 */
+	public net.oni2.aeinstaller.backend.depot.model.File getFile() {
+		return file;
+	}
+
+	@Override
+	public String toString() {
+		return name;
+	}
+
+	/**
+	 * @return Is this mod valid on the running platform?
+	 */
+	public boolean validOnPlatform() {
+		ECompatiblePlatform plat = platform;
+		switch (plat) {
+			case BOTH:
+				return true;
+			case MACOS:
+				return (Settings.getPlatform() == Platform.MACOS);
+			case WIN:
+				return (Settings.getPlatform() == Platform.WIN)
+						|| (Settings.getPlatform() == Platform.LINUX);
+		}
+		return false;
 	}
 
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java	(revision 600)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java	(revision 600)
@@ -0,0 +1,112 @@
+package net.oni2.aeinstaller.backend.mods;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Vector;
+
+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;
+
+/**
+ * @author Christian Illy
+ */
+public class ModManager {
+	// Download mods
+	// Update mods
+
+	private static ModManager instance = new ModManager();
+
+	private HashMap<String, Type> types = new HashMap<String, Type>();
+	private HashMap<Integer, Mod> mods = new HashMap<Integer, Mod>();
+
+	/**
+	 * First initialization of ModManager
+	 */
+	public void init() {
+		types = new HashMap<String, Type>();
+		mods = new HashMap<Integer, Mod>();
+
+		types.put("-Local-", new Type("-Local-", null));
+
+		for (TaxonomyTerm tt : DepotManager.getInstance()
+				.getTaxonomyTermsByVocabulary(
+						DepotManager.getInstance().getVocabIdType())) {
+			types.put(tt.getName(), new Type(tt.getName(), tt));
+		}
+
+		HashMap<Integer, Mod> modFolders = new HashMap<Integer, Mod>();
+		if (Paths.getModsPath().exists()) {
+			for (File f : Paths.getModsPath().listFiles(new FileFilter() {
+				@Override
+				public boolean accept(File pathname) {
+					return pathname.isDirectory();
+				}
+			})) {
+				Mod m = new Mod(f);
+				modFolders.put(m.getPackageNumber(), m);
+			}
+		}
+
+		for (NodeMod nm : DepotManager.getInstance().getModPackageNodes()) {
+			if (nm.getUploads().size() == 1) {
+				Mod m = new Mod(nm);
+				mods.put(m.getPackageNumber(), m);
+				modFolders.remove(m.getPackageNumber());
+			}
+		}
+
+		for (Mod m : modFolders.values()) {
+			mods.put(m.getPackageNumber(), m);
+		}
+	}
+
+	public void refreshLocalMods() {
+		// TODO: evtl nur private e.g. als listener für downloads?
+	}
+
+	/**
+	 * @return Singleton instance
+	 */
+	public static ModManager getInstance() {
+		return instance;
+	}
+
+	Type getTypeByName(String name) {
+		return types.get(name);
+	}
+
+	/**
+	 * @return Collection of types which do have mods associated
+	 */
+	public Collection<Type> getTypesWithContent() {
+		Vector<Type> res = new Vector<Type>();
+		for (Type t : types.values()) {
+			if (t.getEntries().size() > 0)
+				res.add(t);
+		}
+		return res;
+	}
+
+	/**
+	 * @return Collection of mods
+	 */
+	public Collection<Mod> getMods() {
+		return mods.values();
+	}
+
+	/**
+	 * @return Mods which are always installed
+	 */
+	public Collection<Mod> getDefaultMods() {
+		Vector<Mod> res = new Vector<Mod>();
+		for (Mod m : mods.values()) {
+			if (m.isDefaultMod())
+				res.add(m);
+		}
+		return res;
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/Type.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/Type.java	(revision 600)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/Type.java	(revision 600)
@@ -0,0 +1,51 @@
+package net.oni2.aeinstaller.backend.mods;
+
+import java.util.HashSet;
+
+import net.oni2.aeinstaller.backend.depot.model.TaxonomyTerm;
+
+/**
+ * @author Christian Illy
+ */
+public class Type {
+	String name;
+	TaxonomyTerm depotTerm;
+
+	HashSet<Mod> entries = new HashSet<Mod>();
+
+	/**
+	 * Create a new local type declaration
+	 * 
+	 * @param name
+	 *            Name of type
+	 * @param tt
+	 *            Optional TaxTerm link
+	 */
+	public Type(String name, TaxonomyTerm tt) {
+		this.name = name;
+		this.depotTerm = tt;
+	}
+
+	void addEntry(Mod m) {
+		entries.add(m);
+	}
+
+	/**
+	 * @return Name of type
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return Entries for type
+	 */
+	public HashSet<Mod> getEntries() {
+		return entries;
+	}
+
+	@Override
+	public String toString() {
+		return String.format("%s (%d)", name, entries.size());
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/network/DrupalJSONQuery.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/network/DrupalJSONQuery.java	(revision 599)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/network/DrupalJSONQuery.java	(revision 600)
@@ -59,5 +59,4 @@
 					+ ".json", data);
 		} catch (UnsupportedEncodingException e) {
-			// TODO Auto-generated catch block
 			e.printStackTrace();
 		}
@@ -170,11 +169,8 @@
 			return jA;
 		} catch (JSONException e) {
-			// TODO Auto-generated catch block
 			e.printStackTrace();
 		} catch (UnsupportedEncodingException e) {
-			// TODO Auto-generated catch block
 			e.printStackTrace();
 		} catch (IOException e) {
-			// TODO Auto-generated catch block
 			e.printStackTrace();
 		} finally {
@@ -185,5 +181,4 @@
 					input.close();
 				} catch (IOException e) {
-					// TODO Auto-generated catch block
 					e.printStackTrace();
 				}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/oni/InstallProgressListener.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/oni/InstallProgressListener.java	(revision 600)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/oni/InstallProgressListener.java	(revision 600)
@@ -0,0 +1,18 @@
+package net.oni2.aeinstaller.backend.oni;
+
+/**
+ * @author Christian Illy
+ */
+public interface InstallProgressListener {
+	/**
+	 * Called when installation process advances
+	 * 
+	 * @param done
+	 *            Steps done
+	 * @param total
+	 *            Steps in total
+	 * @param step
+	 *            Name of step
+	 */
+	public void installProgressUpdate(int done, int total, String step);
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java	(revision 599)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java	(revision 600)
@@ -3,6 +3,10 @@
 import java.io.File;
 import java.io.FileFilter;
+import java.io.FileNotFoundException;
 import java.io.FilenameFilter;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -35,5 +39,7 @@
 	}
 
-	public static void install(TreeSet<Mod> mods) {
+	public static void install(TreeSet<Mod> mods,
+			InstallProgressListener listener) {
+
 		Vector<File> folders = new Vector<File>();
 		folders.add(Paths.getVanillaOnisPath());
@@ -59,40 +65,85 @@
 		}
 
-		for (File f : Paths.getModsPath().listFiles()) {
-			File oni = new File(f, "oni");
-			if (oni.exists())
-				folders.add(oni);
-		}
-		combineBinaryFiles(folders);
+		// for (File f : Paths.getModsPath().listFiles()) {
+		// File oni = new File(f, "oni");
+		// if (oni.exists())
+		// folders.add(oni);
+		// }
+		combineBinaryFiles(folders, listener);
 
 		// TODO: bsl()
 	}
 
-	private static void combineBinaryFiles(List<File> srcFolders) {
+	private static void combineBinaryFiles(List<File> srcFoldersFiles,
+			InstallProgressListener listener) {
 		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");
+			listener.installProgressUpdate(stepsDone, totalSteps,
+					"Cleaning up directories");
+
 			createEmptyPath(Paths.getEditionGDF());
-			HashMap<String, Vector<File>> levels = new HashMap<String, Vector<File>>();
-
-			for (File path : srcFolders) {
-				for (File levelF : path.listFiles()) {
-					if (!levels.containsKey(levelF.getName().toLowerCase()))
-						levels.put(levelF.getName().toLowerCase(),
-								new Vector<File>());
-					levels.get(levelF.getName().toLowerCase()).add(levelF);
-				}
-			}
-
+
+			log.println("Importing levels");
 			for (String l : levels.keySet()) {
-				System.out.println("Level " + l);
+				log.println("\tLevel " + l);
 				for (File f : levels.get(l)) {
-					System.out.println("    " + f.getPath());
-
-				}
-
-				OniSplit.importLevel(levels.get(l),
+					log.println("\t\t\t" + f.getPath());
+				}
+
+				Vector<String> res = OniSplit.packLevel(levels.get(l),
 						new File(Paths.getEditionGDF(), l + ".dat"));
-
-				System.out.println();
-			}
+				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
@@ -103,16 +154,47 @@
 	/**
 	 * Initializes the Edition core
+	 * 
+	 * @param listener
+	 *            Listener for status updates
 	 */
-	public static void initializeEdition() {
+	public static void initializeEdition(InstallProgressListener listener) {
 		File init = new File(Paths.getTempPath(), "init");
+
+		int totalSteps = 0;
+		int stepsDone = 0;
+
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+		for (@SuppressWarnings("unused")
+		File f : Paths.getVanillaGDF().listFiles(new FilenameFilter() {
+			@Override
+			public boolean accept(File dir, String name) {
+				return name.endsWith(".dat");
+			}
+		})) {
+			totalSteps++;
+		}
+		totalSteps = totalSteps * 2 + 2;
+
 		try {
+			File logFile = new File(Paths.getInstallerPath(),
+					"Initialization.log");
+			PrintWriter log = new PrintWriter(logFile);
+
+			Date start = new Date();
+			log.println("Initialization of Edition core started at "
+					+ sdf.format(start));
+			log.println("Cleaning directories");
+
+			listener.installProgressUpdate(stepsDone, totalSteps,
+					"Cleaning up directories");
 			createEmptyPath(Paths.getVanillaOnisPath());
 			createEmptyPath(init);
 			File level0Folder = new File(init, "level0_Final");
 			createEmptyPath(level0Folder);
-			File level0FolderVanilla = new File(Paths.getVanillaOnisPath(),
-					"level0_Final");
-			createEmptyPath(level0FolderVanilla);
-			createEmptyPath(new File(level0FolderVanilla, "characters"));
+
+			stepsDone++;
+
+			log.println("Exporting levels and moving files to level0");
 
 			for (File f : Paths.getVanillaGDF().listFiles(new FilenameFilter() {
@@ -127,29 +209,51 @@
 				int levelNumber = Integer.parseInt(fi.findInLine("[0-9]+"));
 
+				log.println("\t" + levelName + ":");
+				log.println("\t\tExporting");
+				listener.installProgressUpdate(stepsDone, totalSteps,
+						"Exporting vanilla level " + levelNumber);
+
 				// Edition/GameDataFolder/level*_Final/
 				File tempLevelFolder = new File(init, levelName);
 
 				// Export Vanilla-Level-Dat -> Temp/Level
-				OniSplit.export(tempLevelFolder, f);
-
+				Vector<String> res = OniSplit.export(tempLevelFolder, f);
+				if (res != null && res.size() > 0) {
+					for (String s : res)
+						log.println("\t\t\t" + s);
+				}
+
+				log.println("\t\tMoving files");
 				handleFileGlobalisation(tempLevelFolder, level0Folder,
-						level0FolderVanilla, levelNumber);
-			}
+						levelNumber);
+				stepsDone++;
+				log.println();
+			}
+
+			log.println("Reimporting levels");
 
 			for (File f : init.listFiles()) {
 				String levelName = f.getName();
 
-				// Edition/AEInstaller/vanilla/level*_Final/
-				File vanillaFolder = new File(Paths.getVanillaOnisPath(),
-						levelName);
-				vanillaFolder.mkdirs();
+				log.println("\t" + levelName);
+				listener.installProgressUpdate(stepsDone, totalSteps,
+						"Creating globalized " + levelName);
 
 				Vector<File> folders = new Vector<File>();
 				folders.add(f);
 
-				OniSplit.importLevel(folders, new File(vanillaFolder, levelName
-						+ ".oni"));
-			}
-
+				Vector<String> res = OniSplit.importLevel(folders, new File(
+						Paths.getVanillaOnisPath(), levelName + ".dat"));
+				if (res != null && res.size() > 0) {
+					for (String s : res)
+						log.println("\t\t" + s);
+				}
+
+				log.println();
+				stepsDone++;
+			}
+
+			listener.installProgressUpdate(stepsDone, totalSteps,
+					"Copying basic files");
 			// Copy Oni-configs
 			File persistVanilla = new File(Paths.getOniBasePath(),
@@ -167,4 +271,9 @@
 
 			// TODO: FileUtils.deleteDirectory(init);
+			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) {
 			e.printStackTrace();
@@ -185,5 +294,5 @@
 
 	private static void handleFileGlobalisation(File tempFolder,
-			File level0Folder, File level0FolderVanilla, int levelNumber) {
+			File level0Folder, int levelNumber) {
 		// Move AKEV and related files to subfolder so they're not globalized:
 		if (levelNumber != 0) {
Index: AE/installer2/src/net/oni2/aeinstaller/backend/oni/OniSplit.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/oni/OniSplit.java	(revision 599)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/oni/OniSplit.java	(revision 600)
@@ -8,9 +8,9 @@
 
 import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.QuickAppExecution;
 import net.oni2.aeinstaller.backend.Settings;
 import net.oni2.aeinstaller.backend.Settings.Architecture;
 import net.oni2.aeinstaller.backend.Settings.Platform;
 import net.oni2.aeinstaller.backend.WinRegistry;
-import net.oni2.aeinstaller.backend.app_launcher.QuickAppExecution;
 
 /**
@@ -83,6 +83,7 @@
 	 * @param input
 	 *            Dat file
-	 */
-	public static void export(File targetFolder, File input) {
+	 * @return OniSplit output
+	 */
+	public static Vector<String> export(File targetFolder, File input) {
 		if (!targetFolder.exists())
 			targetFolder.mkdir();
@@ -92,16 +93,12 @@
 		cmdLine.add(targetFolder.getPath());
 		cmdLine.add(input.getPath());
-		// System.out.println(cmdLine.toString());
-		Vector<String> res = null;
-		try {
-			res = QuickAppExecution.execute(cmdLine);
-		} catch (IOException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-		if (res != null) {
-			// check for errors
-			System.out.println(res.toString());
-		}
+		Vector<String> res = null;
+		try {
+			res = QuickAppExecution.execute(cmdLine);
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return res;
 	}
 
@@ -113,6 +110,7 @@
 	 * @param targetFile
 	 *            Target .dat-file
-	 */
-	public static void importLevel(Vector<File> sourceFolders, File targetFile) {
+	 * @return OniSplit output
+	 */
+	public static Vector<String> importLevel(Vector<File> sourceFolders, File targetFile) {
 		Vector<String> cmdLine = getProgramInvocation();
 		cmdLine.add(getImportParam());
@@ -120,16 +118,42 @@
 			cmdLine.add(f.getPath());
 		cmdLine.add(targetFile.getPath());
-		// System.out.println(cmdLine.toString());
-		Vector<String> res = null;
-		try {
-			res = QuickAppExecution.execute(cmdLine);
-		} catch (IOException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-		if (res != null) {
-			// check for errors
-			System.out.println(res.toString());
-		}
+		Vector<String> res = null;
+		try {
+			res = QuickAppExecution.execute(cmdLine);
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	/**
+	 * Pack a level to a .dat-file. More powerful variant which allows
+	 * specifying single .oni/.dat files
+	 * 
+	 * @param sourceFoldersFiles
+	 *            Folders (for recursive .oni import) or files (.dat and single
+	 *            .oni) to import
+	 * @param targetFile
+	 *            Target .dat-file
+	 * @return OniSplit output
+	 */
+	public static Vector<String> packLevel(Vector<File> sourceFoldersFiles,
+			File targetFile) {
+		Vector<String> cmdLine = getProgramInvocation();
+		cmdLine.add(getPackParam());
+		cmdLine.add(getPackTypeParam());
+		cmdLine.add("-out");
+		cmdLine.add(targetFile.getPath());
+		for (File f : sourceFoldersFiles)
+			cmdLine.add(f.getPath());
+		Vector<String> res = null;
+		try {
+			res = QuickAppExecution.execute(cmdLine);
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return res;
 	}
 
@@ -144,6 +168,7 @@
 	 * @param moveParameter
 	 *            e.g. overwrite, delete
-	 */
-	public static void move(File targetFolder, String input,
+	 * @return OniSplit output
+	 */
+	public static Vector<String> move(File targetFolder, String input,
 			String moveParameter) {
 		if (!targetFolder.exists())
@@ -162,8 +187,5 @@
 			e.printStackTrace();
 		}
-		if (res != null && res.size() > 0) {
-			// TODO: errors
-			System.out.println(res.toString());
-		}
+		return res;
 	}
 
@@ -175,9 +197,21 @@
 	}
 
+	private static String getPackParam() {
+		return "pack";
+	}
+
+	private static String getPackTypeParam() {
+		if (Settings.getPlatform() == Platform.MACOS)
+			return "-type:macintel";
+		else
+			return "-type:pc";
+	}
+
 	private static Vector<String> getProgramInvocation() {
 		Vector<String> res = new Vector<String>();
 		if (Settings.getPlatform() != Platform.WIN)
 			res.add("mono");
-		res.add(new File(Paths.getInstallerPath(), "Onisplit.exe").getPath());
+		res.add(new File(new File(Paths.getEditionBasePath(), "Tools"),
+				"Onisplit.exe").getPath());
 		return res;
 	}
