Index: AE/installer2/.classpath
===================================================================
--- AE/installer2/.classpath	(revision 591)
+++ AE/installer2/.classpath	(revision 591)
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/betterbeansbinding-core-1.3.0.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/betterbeansbinding-el-1.3.0.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/betterbeansbinding-swingbinding-1.3.0.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/commons-lang-2.4.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/javabuilder-core-1.1.0.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/javabuilder-swing-1.1.0.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/jsr305-1.3.7.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/miglayout-3.7.1.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/slf4j-api-1.6.1.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/slf4j-simple-1.6.1.jar"/>
+	<classpathentry kind="lib" path="lib/swingbuilder/snakeyaml-1.7.jar"/>
+	<classpathentry kind="lib" path="lib/JSON-java.jar"/>
+	<classpathentry kind="lib" path="lib/httpclient/httpclient-4.2.2.jar"/>
+	<classpathentry kind="lib" path="lib/httpclient/httpcore-4.2.2.jar"/>
+	<classpathentry kind="lib" path="lib/httpclient/commons-logging-1.1.1.jar"/>
+	<classpathentry kind="lib" path="lib/xstream-1.4.3.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
Index: AE/installer2/.project
===================================================================
--- AE/installer2/.project	(revision 591)
+++ AE/installer2/.project	(revision 591)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>AEInstaller2</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
Index: AE/installer2/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- AE/installer2/.settings/org.eclipse.jdt.core.prefs	(revision 591)
+++ AE/installer2/.settings/org.eclipse.jdt.core.prefs	(revision 591)
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
Index: AE/installer2/doc/conflict-resolving.txt
===================================================================
--- AE/installer2/doc/conflict-resolving.txt	(revision 591)
+++ AE/installer2/doc/conflict-resolving.txt	(revision 591)
@@ -0,0 +1,15 @@
+Multi-.Oni: No conflict, priority based resolution
+
+BSL:
+1. Mods containing different BSL-files for the same level (addon=no):
+"light" warning window
+
+2. Mods containing (partially) same BSL-files for the same level (addon=no):
+Conflict warning, if accepted take only files of higher priority mod
+
+3. Mods containing (partially) same BSL-files for the same level (addon=yes):
+no conflict, overwrite base files with addon-files
+
+4. As 3 with Addon on Addon:
+("Light" warning?) conflicting files are resolved by higher priority addon taking precedence
+
Index: AE/installer2/doc/drupal_node_types_json.txt
===================================================================
--- AE/installer2/doc/drupal_node_types_json.txt	(revision 591)
+++ AE/installer2/doc/drupal_node_types_json.txt	(revision 591)
@@ -0,0 +1,180 @@
+type=page:
+    "uid": "4",
+    "body": {"und": [{
+        "summary": null,
+        "safe_value": "<p><img src=\"/sites/default/images/Yogurt!-200px.png\" alt=\"A tasty favicon\" align=\"right\" /><u>What is the Mod Depot?<\/u><br />\n... The site is administered by Iritscen, who can be reached through mods{at}oni2{dot}net. The oni2.net domain is administered by Alloc.<\/p>",
+        "safe_summary": "",
+        "value": "<p><img src=\"/sites/default/images/Yogurt!-200px.png\" alt=\"A tasty favicon\" align=\"right\"><u>What is the Mod Depot?<\/u><br />\r\n... The site is administered by Iritscen, who can be reached through mods{at}oni2{dot}net. The oni2.net domain is administered by Alloc.<\/p>",
+        "format": "2"
+    }]},
+    "data": "a:1:{s:13:\"form_build_id\";s:37:\"form-134923bd25b368dc3c2dfa412f22a42a\";}",
+    "last_comment_uid": "1",
+    "type": "page",
+    "last_comment_name": null,
+    "changed": "1290977713",
+    "title": "About the Mod Depot",
+    "created": "1221420696",
+    "name": "Iritscen",
+    "path": "http://mods.oni2.net/node/1",
+    "revision_uid": "1",
+    "tnid": "0",
+    "vid": "1",
+    "comment_count": "0",
+    "status": "1",
+    "nid": "1",
+    "log": "",
+    "cid": "0",
+    "picture": "0",
+    "sticky": "0",
+    "promote": "0",
+    "last_comment_timestamp": "1221420696",
+    "revision_timestamp": "1290977713",
+    "translate": "0",
+    "language": "en",
+    "comment": "1"
+
+type=file:
+    "uid": "6",
+    "body": {"und": [{
+        "summary": null,
+        "safe_value": "A mod that adds a ...\n\nRequires Character Globalization (included with Edition)",
+        "safe_summary": "",
+        "value": "A mod that adds a level ...\r\nRequires Character Globalization (included with Edition)",
+        "format": "2"
+    }]},
+    "data": "a:1:{s:13:\"form_build_id\";s:37:\"form-9c92f19269ee6aa14969207bb41ec9cb\";}",
+    "last_comment_uid": "6",
+    "type": "file",
+    "last_comment_name": "",
+    "upload": {"und": [{
+        "timestamp": "1221588058",
+        "uid": "6",
+        "fid": "8",
+        "filesize": "173633",
+        "status": "1",
+        "description": "Arena.v.1.zip",
+        "filename": "Arena.v.1.zip",
+        "display": "1",
+        "filemime": "application/zip",
+        "uri": "private://Arena.v.1.zip"
+    }]},
+    "changed": "1250546844",
+    "title": "Arena of Pain (Win)",
+    "created": "1221588073",
+    "name": "Gumby",
+    "path": "http://mods.oni2.net/node/4",
+    "revision_uid": "1",
+    "tnid": "0",
+    "vid": "4",
+    "comment_count": 0,
+    "taxonomy_vocabulary_1": {"und": [{"tid": "2"}]},
+    "taxonomy_vocabulary_2": {"und": [{"tid": "21"}]},
+    "status": "1",
+    "nid": "4",
+    "taxonomy_vocabulary_3": {"und": [{"tid": "15"}]},
+    "log": "",
+    "cid": 0,
+    "picture": "0",
+    "sticky": "0",
+    "promote": "0",
+    "last_comment_timestamp": "1221588073",
+    "revision_timestamp": "1250546844",
+    "translate": "0",
+    "language": "en",
+    "comment": "0"
+
+    "vid":"218",
+    "uid":"42",
+    "title":"23615 Hanako",
+    "log":"",
+    "status":"1",
+    "comment":"2",
+    "promote":"0",
+    "sticky":"0",
+    "nid":"218",
+    "type":"file",
+    "language":"en",
+    "created":"1348876643",
+    "changed":"1349355480",
+    "tnid":"0",
+    "translate":"0",
+    "revision_timestamp":"1349355480",
+    "revision_uid":"42",
+    "taxonomy_vocabulary_3": {"und": [{"tid":"20"}]},
+    "taxonomy_vocabulary_2": {"und": [{"tid":"9"}]},
+    "taxonomy_vocabulary_1": {"und": [{"tid":"3"}]},
+    "body": {"und":[{
+        "value":"Adds New character Hanako, Hayate's female partner. Comes with 5 outfits. Chenille and heavy kick move by paradox, Running kick throw by Andrew. \r\n\r\nRequires Character Additives Package version 5 or above.\r\n\r\nUpdated Oct 4, 2012 fixed some LOD issues.",
+        "summary":null,
+        "format":"1",
+        "safe_value":"<p>Adds New character Hanako, Hayate's female partner. Comes with 5 outfits. Chenille and heavy kick move by paradox, Running kick throw by Andrew. </p>\n<p>Requires Character Additives Package version 5 or above.</p>\n<p>Updated Oct 4, 2012 fixed some LOD issues.</p>\n",
+        "safe_summary":""
+    }]},
+    "upload": {"und":[{
+        "fid":"585",
+        "display":"1",
+        "description":"23615Hanako.zip",
+        "uid":"42",
+        "filename":"23615Hanako.zip",
+        "uri":"private://23615Hanako.zip",
+        "filemime":"application/zip",
+        "filesize":"7890641",
+        "status":"1",
+        "timestamp":"1349353975"
+    }]},
+    "field_creator": {"und":[{
+        "value":"Samer",
+        "format":null,
+        "safe_value":"Samer"
+    }]},
+    "field_version": {"und":[{
+        "value":"1",
+        "format":null,
+        "safe_value":"1"
+    }]},
+    "cid":"0",
+    "last_comment_timestamp":"1348876643",
+    "last_comment_name":null,
+    "last_comment_uid":"42",
+    "comment_count":"0",
+    "name":"Samer",
+    "picture":"0",
+    "data":"a:1:{s:13:\"form_build_id\";s:37:\"form-f8d981ba47500e3df419bf009280cbb4\";}",
+    "path":"http://mods.oni2.net/node/218"
+
+
+
+type=story:
+    "uid": "4",
+    "body": {"und": [{
+        "summary": "This was ...\r\n\r\n<a href=\"http://www.simplici7y.com/\">Simplici7y<\/a> has a ...to me.",
+        "safe_value": "<p>This was already pretty ... to me.<\/p>\n",
+        "safe_summary": "<p>This was already pretty ...to me.<\/p>\n",
+        "value": "This was ...\r\n\r\n<a href=\"http://www.simplici7y.com/\">Simplici7y<\/a> has a... me.",
+        "format": "1"
+    }]},
+    "data": "a:1:{s:13:\"form_build_id\";s:37:\"form-134923bd25b368dc3c2dfa412f22a42a\";}",
+    "last_comment_uid": "4",
+    "type": "story",
+    "last_comment_name": null,
+    "changed": "1356050024",
+    "title": "Commenting is up",
+    "created": "1221591584",
+    "name": "Iritscen",
+    "path": "http://mods.oni2.net/node/5",
+    "revision_uid": "1",
+    "tnid": "0",
+    "vid": "5",
+    "comment_count": "0",
+    "status": "1",
+    "nid": "5",
+    "log": "",
+    "cid": "0",
+    "picture": "0",
+    "sticky": "0",
+    "promote": "1",
+    "last_comment_timestamp": "1221591584",
+    "revision_timestamp": "1356050024",
+    "translate": "0",
+    "language": "en",
+    "comment": "1"
Index: AE/installer2/doc/drupal_services.txt
===================================================================
--- AE/installer2/doc/drupal_services.txt	(revision 591)
+++ AE/installer2/doc/drupal_services.txt	(revision 591)
@@ -0,0 +1,18 @@
+http://mods.oni2.net/?q=api:
+
+GET /taxonomy_vocabulary(.json): All vocabs
+GET /taxonomy_vocabulary/$ID(.json): Single vocab
+POST /taxonomy_vocabulary/getTree(.json): Get a tree (data: vid=$ID) (basically all terms of this vocab)
+
+GET /taxonomy_term(.json): All terms
+GET /taxonomy_term/$ID(.json): Single term
+POST /taxonomy_term/selectNodes(.json): Get nodes with term (data: tid=$ID)
+
+GET /node(.json): All nodes
+GET /node/$ID(.json): Single node
+GET /node/$ID/files(.json): Get files (INCLUDING BINARY CONTENT!) of node
+GET /node/$ID/comments(.json): Get comments of node
+
+GET /file(.json): Index all files (no content)
+GET /file/$ID(.json): Single file (INCLUDING BINARY CONTENT! &file_contents=0 <- no content)
+
Index: AE/installer2/doc/outline.txt
===================================================================
--- AE/installer2/doc/outline.txt	(revision 591)
+++ AE/installer2/doc/outline.txt	(revision 591)
@@ -0,0 +1,67 @@
+http://wiki.oni2.net/Anniversary_Edition/Framework
+
+The globalization process
+When first running the Installer, this is a one-time process that takes the
+vanilla .dats in Oni's GDF and breaks them into thousands of .oni files in
+folders like Edition/GDF/level1_Final. Next, the Installer combines those
+folders of .oni files into singleton, large .oni files, one for each level, in
+Edition/install/VanillaDats/. Note that no data has been modded yet, just
+reformatted. This process of pre-compiling the data results in a large speed
+increase over previous recombination methods.
+
+The mod installation process
+Right after this, the Installer prompts you to choose some mods to install.
+When you hit the Install button, the Installer starts off by deleting the
+existing .dat/.raw(/.sep) files in Edition/GDF to start from a blank slate. It
+then iterates through each mod package you selected, and sends a command to
+OniSplit to combine the singleton .oni for that level (in VanillaDats) with the
+.oni files in whichever packages have files meant for that level. The final
+.dat/.raw(/.sep) files are then created in Edition/GDF. 
+
+-1) AEI-folder has to be able to be moved to another machine including
+downloaded mods, OniSplit etc so that it can be distributed as an offline
+install
+
+*) Open package folder from mod list (also menu options for other folders like
+gdf)
+
+0) OniSplit
+Download/Update from depot. Perhaps even without a shipped one
+(-> globalization after downloading onisplit)
+
+1) Dependencies
+Would be nice if in the future we could at least give a warning when a mod
+requires another that isn't available (and of course allow write dependencies
+for the new mods).This would help very much in some mods that require others.
+For example the china mod requires about other 3 packages, and it will crash if
+they aren't present. This would at least warn the user of that.
+
+2) Mod-Depot access
+Another thing that would be awesome but can be a little tough to program would
+be that the installer would show the latest mods from the depot with a short
+description, plus maybe the most downloaded mods. In addition install and
+download them directly from it would be even more interesting.
+
+3) Supply new tools
+I think the new version should contain the latest oni split version plus maybe
+the latest vago/ae tools too.
+* Onisplit latest version and scripts vago gui or/and demos's gui and lukas
+creator's oni levels unlock should be included.
+
+4) Switch supplied mods
+We should see which mods are more used right now that aren't included with the
+edition. For example I see that perceptible blood is kinda popular between the
+players, even if the edition already comes with another soft blood version.
+Dunno if we should replace that one with the perceptible blood (which is more
+anime style in my opinion).
+
+5) Update supplied mods
+There are a lot of outdated mods in the last Edition things like brutal AI,
+New Characters and Andrashi Melee System I think, which should be updated to
+the last version.
+
+6) Order in mod-list
+AE installer should list mods ordered by numbers rather than by alphabetical
+order. Arranging by package numbers makes related packages ordered after each
+other and makes it easier to identify which will overwrite which.
+ 
Index: AE/installer2/src/Images.properties
===================================================================
--- AE/installer2/src/Images.properties	(revision 591)
+++ AE/installer2/src/Images.properties	(revision 591)
@@ -0,0 +1,22 @@
+img.connect=/net/oni2/aeinstaller/images/tango/network-idle.png
+img.disconnect=/net/oni2/aeinstaller/images/tango/network-offline.png
+img.exit=/net/oni2/aeinstaller/images/tango/system-log-out.png
+img.manageExams=/net/oni2/aeinstaller/images/tango/document-open.png
+img.manageMembers=/net/oni2/aeinstaller/images/tango/contact-new.png
+img.updatesvn=/net/oni2/aeinstaller/images/tango/view-refresh.png
+img.settings=/net/oni2/aeinstaller/images/tango/preferences-system.png
+img.print=/net/oni2/aeinstaller/images/tango/document-print.png
+img.edit=/net/oni2/aeinstaller/images/tango/text-editor.png
+img.delete=/net/oni2/aeinstaller/images/tango/list-remove.png
+img.add=/net/oni2/aeinstaller/images/tango/list-add.png
+img.refresh=/net/oni2/aeinstaller/images/tango/go-jump.png
+img.openFile=/net/oni2/aeinstaller/images/tango/document-open.png
+img.sale=/net/oni2/aeinstaller/images/tango/accessories-calculator.png
+img.orders=/net/oni2/aeinstaller/images/tango/bookmark-new.png
+
+img.pause=/net/oni2/aeinstaller/images/open_icon_library/media-playback-pause-7.png
+img.stop=/net/oni2/aeinstaller/images/open_icon_library/media-playback-stop-7.png
+img.selectfolder=/net/oni2/aeinstaller/images/open_icon_library/folder-explore.png
+img.downloadFolder=/net/oni2/aeinstaller/images/open_icon_library/folder-games.png
+
+img.kdt=/net/oni2/aeinstaller/images/kdt.png
Index: AE/installer2/src/net/oni2/aeinstaller/AEInstaller2.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/AEInstaller2.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/AEInstaller2.java	(revision 591)
@@ -0,0 +1,105 @@
+package net.oni2.aeinstaller;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+
+import javax.swing.JFrame;
+import javax.swing.JToolBar;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+
+import net.oni2.aeinstaller.backend.Settings;
+import net.oni2.aeinstaller.backend.StuffToRefactorLater;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.gui.MainWin;
+
+import org.javabuilders.swing.SwingJavaBuilder;
+
+/**
+ * @author Christian Illy
+ * @version 0.1
+ */
+public class AEInstaller2 {
+
+	/**
+	 * @param args
+	 *            Command line arguments
+	 */
+	public static void main(String[] args) {
+		new File(Settings.getPrefsPath()).mkdirs();
+		new File(Settings.getDownloadPath()).mkdirs();
+
+		boolean debug = false;
+		for (String a : args)
+			if (a.equalsIgnoreCase("-debug"))
+				debug = true;
+		if (!debug) {
+			try {
+				System.setOut(new PrintStream(Settings.getPrefsPath()
+						+ "aei_output.log"));
+				System.setErr(new PrintStream(Settings.getPrefsPath()
+						+ "aei_error.log"));
+			} catch (FileNotFoundException e1) {
+				e1.printStackTrace();
+			}
+		}
+
+		Settings.deserializeFromFile();
+		Settings.setDebug(debug);
+		DepotManager.getInstance().loadFromFile(
+				new File(Settings.getDepotCacheFilename()));
+
+		SwingJavaBuilder.getConfig().addResourceBundle("Images");
+		SwingJavaBuilder.getConfig().setMarkInvalidResourceBundleKeys(true);
+		SwingJavaBuilder.getConfig().addType("JToolBarSeparator",
+				JToolBar.Separator.class);
+		// SwingJavaBuilder.getConfig().addType("ScreenshotBrowser",
+		// ScreenshotBrowser.class);
+		// SwingJavaBuilder.getConfig().addType("HTMLLinkLabel",
+		// HTMLLinkLabel.class);
+
+		System.setProperty("networkaddress.cache.ttl", "5");
+		System.setProperty("networkaddress.cache.negative.ttl", "1");
+
+		try {
+			String laf = Settings.getInstance().get("lookandfeel",
+					(String) null);
+			if (laf == null) {
+				for (LookAndFeelInfo lafInfo : UIManager
+						.getInstalledLookAndFeels()) {
+					if (lafInfo.getName().equals("Nimbus"))
+						laf = lafInfo.getClassName();
+				}
+			}
+			if (laf == null)
+				laf = UIManager.getSystemLookAndFeelClassName();
+			UIManager.setLookAndFeel(laf);
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		JFrame.setDefaultLookAndFeelDecorated(true);
+
+		// TODO
+		System.out.println("JarPath:   " + Settings.getJarPath());
+		System.out.println("PrefsPath: " + Settings.getPrefsPath());
+		System.out.println("DataPath:  " + Settings.getDataPath());
+		System.out.println("DownPath:  " + Settings.getDownloadPath());
+		System.out.println("TempPath:  " + Settings.getTempPath());
+		System.out.println("ValidPath: "
+				+ StuffToRefactorLater.verifyRunningDirectory());
+
+		SwingUtilities.invokeLater(new Runnable() {
+
+			public void run() {
+				try {
+					new MainWin().setVisible(true);
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		});
+
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/ColorCopy.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/ColorCopy.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/ColorCopy.java	(revision 591)
@@ -0,0 +1,17 @@
+package net.oni2.aeinstaller.backend;
+
+import java.awt.Color;
+
+/**
+ * @author Christian Illy
+ */
+public class ColorCopy {
+	/**
+	 * @param orig
+	 *            Color to copy
+	 * @return New color instance with same RGB but 100% opaque
+	 */
+	public static Color copyColor(Color orig) {
+		return new Color(orig.getRed(), orig.getGreen(), orig.getBlue());
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java	(revision 591)
@@ -0,0 +1,301 @@
+package net.oni2.aeinstaller.backend;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+
+/**
+ * Manages and stores programm settings
+ * 
+ * @author Christian Illy
+ */
+public class Settings implements Serializable {
+
+	private static final long serialVersionUID = 8067725289301601179L;
+
+	/**
+	 * @author Christian Illy
+	 */
+	public enum Platform {
+		/**
+		 * Running Windows
+		 */
+		WIN,
+		/**
+		 * Running MacOS
+		 */
+		MACOS,
+		/**
+		 * Running a Linux
+		 */
+		LINUX,
+		/**
+		 * Unknown OS
+		 */
+		UNKNOWN
+	}
+
+	private static Settings instance = new Settings();
+
+	private static boolean debugRun = false;
+
+	private HashMap<String, Object> prefs = new HashMap<String, Object>();
+
+	private boolean printNamesNotInMap = false;
+
+	/**
+	 * Get the singleton instance
+	 * 
+	 * @return Singleton instance
+	 */
+	public static Settings getInstance() {
+		return instance;
+	}
+
+	/**
+	 * @param debug
+	 *            Debug mode
+	 */
+	public static void setDebug(boolean debug) {
+		debugRun = debug;
+	}
+
+	/**
+	 * @return Is debug run
+	 */
+	public static boolean getDebug() {
+		return debugRun;
+	}
+
+	/**
+	 * @return The operating system running on
+	 */
+	public static Platform getPlatform() {
+		String os = System.getProperty("os.name").toLowerCase();
+		if (os.startsWith("win"))
+			return Platform.WIN;
+		if (os.startsWith("linux"))
+			return Platform.LINUX;
+		if (os.startsWith("mac"))
+			return Platform.MACOS;
+		return Platform.UNKNOWN;
+	}
+
+	/**
+	 * Get the Jar path
+	 * 
+	 * @return Path
+	 */
+	public static String getJarPath() {
+		String jarPath = Settings.class.getProtectionDomain().getCodeSource()
+				.getLocation().getPath();
+		String decodedPath = null;
+		try {
+			decodedPath = URLDecoder.decode(jarPath, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+		}
+		return new File(decodedPath).getParentFile().getPath() + "/";
+	}
+
+	/**
+	 * Get the preferences path
+	 * 
+	 * @return Path
+	 */
+	public static String getPrefsPath() {
+		return getJarPath();
+	}
+
+	/**
+	 * Get the path to store downloaded files
+	 * 
+	 * @return Download path
+	 */
+	public static String getDownloadPath() {
+		return getTempPath() + "oni_aei/";
+	}
+
+	/**
+	 * Get the path to store game information data
+	 * 
+	 * @return Data path
+	 */
+	public static String getDataPath() {
+		return getJarPath() + "mods/";
+	}
+
+	/**
+	 * Get the systems temp-path
+	 * 
+	 * @return Path
+	 */
+	public static String getTempPath() {
+		return new File(System.getProperty("java.io.tmpdir")).getPath() + "/";
+	}
+
+	/**
+	 * @return Mod Depot cache filename
+	 */
+	public static String getDepotCacheFilename() {
+		return Settings.getPrefsPath() + "ModDepotCache.xml";
+	}
+
+	private static String getSettingsFilename() {
+		return Settings.getPrefsPath() + "AEI-Settings.xml";
+	}
+
+	private static XStream getXStream() {
+		XStream xs = new XStream(new StaxDriver());
+		xs.alias("Settings", Settings.class);
+		return xs;
+	}
+
+	/**
+	 * Serializes the settings to disk
+	 */
+	public void serializeToFile() {
+		try {
+			FileOutputStream fos = new FileOutputStream(getSettingsFilename());
+			XStream xs = getXStream();
+			xs.toXML(this, fos);
+			fos.close();
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Deserializes the settings from disk
+	 */
+	public static void deserializeFromFile() {
+		try {
+			FileInputStream fis = new FileInputStream(getSettingsFilename());
+			XStream xs = getXStream();
+			Object obj = xs.fromXML(fis);
+			if (obj instanceof Settings)
+				instance = (Settings) obj;
+			fis.close();
+		} catch (FileNotFoundException e) {
+		} catch (IOException e) {
+		}
+	}
+
+	/**
+	 * Put a string value
+	 * 
+	 * @param key
+	 *            Key for value
+	 * @param value
+	 *            Value
+	 */
+	public void put(String key, String value) {
+		prefs.put(key, value);
+	}
+
+	/**
+	 * Put a boolean value
+	 * 
+	 * @param key
+	 *            Key for value
+	 * @param value
+	 *            Value
+	 */
+	public void put(String key, boolean value) {
+		prefs.put(key, value);
+	}
+
+	/**
+	 * Put a int value
+	 * 
+	 * @param key
+	 *            Key for value
+	 * @param value
+	 *            Value
+	 */
+	public void put(String key, int value) {
+		prefs.put(key, value);
+	}
+
+	/**
+	 * Get a string value
+	 * 
+	 * @param key
+	 *            Key for value
+	 * @param def
+	 *            Default return value if key does not exist
+	 * @return Value
+	 */
+	public String get(String key, String def) {
+		if (prefs.containsKey(key)) {
+			if (prefs.get(key) instanceof String)
+				return (String) (prefs.get(key));
+		}
+		if (printNamesNotInMap)
+			System.out.println("Settings: Key \"" + key
+					+ "\" not in Map, defaulting to \"" + def + "\".");
+		return def;
+	}
+
+	/**
+	 * Get a boolean value
+	 * 
+	 * @param key
+	 *            Key for value
+	 * @param def
+	 *            Default return value if key does not exist
+	 * @return Value
+	 */
+	public Boolean get(String key, Boolean def) {
+		if (prefs.containsKey(key)) {
+			if (prefs.get(key) instanceof Boolean)
+				return (Boolean) (prefs.get(key));
+		}
+		if (printNamesNotInMap)
+			System.out.println("Settings: Key \"" + key
+					+ "\" not in Map, defaulting to \"" + def + "\".");
+		return def;
+	}
+
+	/**
+	 * Get a int value
+	 * 
+	 * @param key
+	 *            Key for value
+	 * @param def
+	 *            Default return value if key does not exist
+	 * @return Value
+	 */
+	public int get(String key, int def) {
+		if (prefs.containsKey(key)) {
+			if (prefs.get(key) instanceof Integer)
+				return (Integer) (prefs.get(key));
+		}
+		if (printNamesNotInMap)
+			System.out.println("Settings: Key \"" + key
+					+ "\" not in Map, defaulting to \"" + def + "\".");
+		return def;
+	}
+
+	/**
+	 * Remove a value
+	 * 
+	 * @param key
+	 *            Key to value to remove
+	 */
+	public void removeValue(String key) {
+		prefs.remove(key);
+	}
+
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/StuffToRefactorLater.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/StuffToRefactorLater.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/StuffToRefactorLater.java	(revision 591)
@@ -0,0 +1,16 @@
+package net.oni2.aeinstaller.backend;
+
+import java.io.File;
+
+public class StuffToRefactorLater {
+	/**
+	 * 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() {
+		File jarPath = new File(Settings.getJarPath());
+		File parentPath = jarPath.getParentFile().getParentFile();
+		File gdf = new File(parentPath, "GameDataFolder");
+		return gdf.exists() && gdf.isDirectory();
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotCacheUpdateProgressListener.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotCacheUpdateProgressListener.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotCacheUpdateProgressListener.java	(revision 591)
@@ -0,0 +1,16 @@
+package net.oni2.aeinstaller.backend.depot;
+
+/**
+ * @author Christian Illy
+ */
+public interface DepotCacheUpdateProgressListener {
+	/**
+	 * @param stepName
+	 *            Current step name (e.g. "Taxonomy Vocabulary")
+	 * @param current
+	 *            Current progress in terms of requests
+	 * @param total
+	 *            Total requests to do
+	 */
+	public void cacheUpdateProgress(String stepName, int current, int total);
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotConfig.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotConfig.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotConfig.java	(revision 591)
@@ -0,0 +1,42 @@
+package net.oni2.aeinstaller.backend.depot;
+
+/**
+ * @author Christian Illy
+ */
+public class DepotConfig {
+	/**
+	 * Type-value of nodes which contain Mods
+	 */
+	public static final String NODETYPE_MOD = "mod";
+
+	/**
+	 * Vocabulary name for platform field
+	 */
+	public static final String PLATFORM_VOCAB = "Platform";
+	/**
+	 * Vocabulary name for installtype field
+	 */
+	public static final String INSTALLTYPE_VOCAB = "Install method";
+	/**
+	 * Vocabulary name for modtype field
+	 */
+	public static final String MODTYPE_VOCAB = "Mod type";
+	
+	/**
+	 * Taxonomy term name for platform value Win
+	 */
+	public static final String PLATFORM_WIN = "Windows";
+	/**
+	 * Taxonomy term name for platform value Mac
+	 */
+	public static final String PLATFORM_MAC = "Mac OS";
+	/**
+	 * Taxonomy term name for platform value Both
+	 */
+	public static final String PLATFORM_BOTH = "Both";
+
+	/**
+	 * Taxonomy term name for installtype Package
+	 */
+	public static final String INSTALLTYPE_PACKAGE = "Package";
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java	(revision 591)
@@ -0,0 +1,419 @@
+package net.oni2.aeinstaller.backend.depot;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Vector;
+
+import net.oni2.aeinstaller.backend.Settings;
+import net.oni2.aeinstaller.backend.Settings.Platform;
+import net.oni2.aeinstaller.backend.depot.model.File;
+import net.oni2.aeinstaller.backend.depot.model.Node;
+import net.oni2.aeinstaller.backend.depot.model.NodeField_Body;
+import net.oni2.aeinstaller.backend.depot.model.NodeField_Upload;
+import net.oni2.aeinstaller.backend.depot.model.NodeMod;
+import net.oni2.aeinstaller.backend.depot.model.TaxonomyTerm;
+import net.oni2.aeinstaller.backend.depot.model.TaxonomyVocabulary;
+import net.oni2.aeinstaller.backend.network.DrupalJSONQuery;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+
+/**
+ * @author Christian Illy
+ */
+public class DepotManager {
+	private static DepotManager instance = new DepotManager();
+
+	private HashMap<Integer, TaxonomyVocabulary> taxonomyVocabulary = new HashMap<Integer, TaxonomyVocabulary>();
+	private HashMap<Integer, TaxonomyTerm> taxonomyTerms = new HashMap<Integer, TaxonomyTerm>();
+
+	private HashMap<Integer, Node> nodes = new HashMap<Integer, Node>();
+	private HashMap<String, HashMap<Integer, Node>> nodesByType = new HashMap<String, HashMap<Integer, Node>>();
+
+	private HashMap<Integer, File> files = new HashMap<Integer, File>();
+
+	private int vocabId_type = -1;
+	private int vocabId_platform = -1;
+	private int vocabId_instmethod = -1;
+
+	/**
+	 * @return Singleton instance
+	 */
+	public static DepotManager getInstance() {
+		return instance;
+	}
+
+	/**
+	 * Update local Depot information cache
+	 * 
+	 * @param forceRefreshAll
+	 *            Force refreshing all data, even if it seems to be cached
+	 * @param listener
+	 *            Listener for update status
+	 */
+	public void updateInformation(boolean forceRefreshAll,
+			DepotCacheUpdateProgressListener listener) {
+		taxonomyTerms.clear();
+		taxonomyVocabulary.clear();
+
+		HashMap<Integer, Node> oldNodes = null;
+		HashMap<Integer, File> oldFiles = null;
+
+		if (forceRefreshAll) {
+			oldNodes = new HashMap<Integer, Node>();
+			oldFiles = new HashMap<Integer, File>();
+		} else {
+			oldNodes = nodes;
+			oldFiles = files;
+		}
+
+		nodes = new HashMap<Integer, Node>();
+		nodesByType = new HashMap<String, HashMap<Integer, Node>>();
+		files = new HashMap<Integer, File>();
+
+		try {
+			JSONArray ja;
+			JSONObject jo;
+			int page;
+
+			// Get taxonomy vocabulary
+			listener.cacheUpdateProgress("Updating taxonomy vocabulary", 0, 100);
+			page = 0;
+			do {
+				ja = DrupalJSONQuery.getIndex("taxonomy_vocabulary", page);
+				for (int i = 0; i < ja.length(); i++) {
+					jo = ja.getJSONObject(i);
+					TaxonomyVocabulary tv = new TaxonomyVocabulary(jo);
+					taxonomyVocabulary.put(tv.getVid(), tv);
+				}
+				page++;
+			} while (ja.length() > 0);
+
+			// Get taxonomy terms
+			listener.cacheUpdateProgress("Updating taxonomy terms", 0, 100);
+			page = 0;
+			do {
+				ja = DrupalJSONQuery.getIndex("taxonomy_term", page);
+				for (int i = 0; i < ja.length(); i++) {
+					jo = ja.getJSONObject(i);
+					TaxonomyTerm tt = new TaxonomyTerm(jo);
+					taxonomyTerms.put(tt.getTid(), tt);
+				}
+				page++;
+			} while (ja.length() > 0);
+
+			// Check nodes for new information
+			listener.cacheUpdateProgress("Checking for new/updated nodes", 1,
+					100);
+			HashSet<Integer> nodesToUpdate = new HashSet<Integer>();
+			page = 0;
+			do {
+				ja = DrupalJSONQuery.getIndex("node", page);
+				for (int i = 0; i < ja.length(); i++) {
+					jo = ja.getJSONObject(i);
+					int nid = jo.getInt("nid");
+					long changedRemote = jo.getLong("changed");
+					if (oldNodes.containsKey(nid)) {
+						if (changedRemote > oldNodes.get(nid).getChanged())
+							nodesToUpdate.add(nid);
+						else {
+							Node n = oldNodes.get(nid);
+							nodes.put(nid, n);
+							if (!nodesByType.containsKey(n.getType()))
+								nodesByType.put(n.getType(),
+										new HashMap<Integer, Node>());
+							nodesByType.get(n.getType()).put(nid, n);
+						}
+					} else {
+						nodesToUpdate.add(nid);
+					}
+				}
+				page++;
+			} while (ja.length() > 0);
+
+			// Check files for new stuff
+			listener.cacheUpdateProgress("Checking for new/updated files", 2,
+					100);
+			HashSet<Integer> filesToUpdate = new HashSet<Integer>();
+			page = 0;
+			do {
+				ja = DrupalJSONQuery.getIndex("file", page);
+				for (int i = 0; i < ja.length(); i++) {
+					jo = ja.getJSONObject(i);
+					int fid = jo.getInt("fid");
+					long changedRemote = jo.getLong("timestamp");
+					if (oldFiles.containsKey(fid)) {
+						if (changedRemote > oldFiles.get(fid).getTimestamp())
+							filesToUpdate.add(fid);
+						else
+							files.put(fid, oldFiles.get(fid));
+					} else {
+						filesToUpdate.add(fid);
+					}
+				}
+				page++;
+			} while (ja.length() > 0);
+
+			int total = nodesToUpdate.size() + filesToUpdate.size() + 3;
+			int step = 3;
+			// Update nodes with new information
+			for (int nid : nodesToUpdate) {
+				listener.cacheUpdateProgress("Updating nodes", step++, total);
+
+				ja = DrupalJSONQuery.getItem("node", nid, "");
+				jo = ja.getJSONObject(0);
+				String type = jo.getString("type");
+
+				Node n = null;
+				if (type.equalsIgnoreCase(DepotConfig.NODETYPE_MOD))
+					n = new NodeMod(jo);
+				else
+					n = new Node(jo);
+
+				nodes.put(nid, n);
+				if (!nodesByType.containsKey(type))
+					nodesByType.put(type, new HashMap<Integer, Node>());
+				nodesByType.get(type).put(nid, n);
+			}
+
+			// Update new files
+			for (int fid : filesToUpdate) {
+				listener.cacheUpdateProgress("Updating files", step++, total);
+
+				ja = DrupalJSONQuery.getItem("file", fid, "&file_contents=0");
+				jo = ja.getJSONObject(0);
+
+				File f = new File(jo);
+				files.put(fid, f);
+			}
+
+			vocabId_type = getVocabulary(DepotConfig.MODTYPE_VOCAB).getVid();
+			vocabId_platform = getVocabulary(DepotConfig.PLATFORM_VOCAB)
+					.getVid();
+			vocabId_instmethod = getVocabulary(DepotConfig.INSTALLTYPE_VOCAB)
+					.getVid();
+		} catch (JSONException e) {
+			e.printStackTrace();
+		} catch (Exception e) {
+			System.err.println(e.getMessage());
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * @return All TaxVocabs
+	 */
+	public Vector<TaxonomyVocabulary> getVocabulary() {
+		return new Vector<TaxonomyVocabulary>(taxonomyVocabulary.values());
+	}
+
+	/**
+	 * @param id
+	 *            Get taxonomy vocabulary by given ID
+	 * @return TaxVocab
+	 */
+	public TaxonomyVocabulary getVocabulary(int id) {
+		return taxonomyVocabulary.get(id);
+	}
+
+	/**
+	 * @param name
+	 *            Get taxonomy vocabulary by given name
+	 * @return TaxVocab
+	 */
+	public TaxonomyVocabulary getVocabulary(String name) {
+		for (TaxonomyVocabulary v : taxonomyVocabulary.values()) {
+			if (v.getName().equalsIgnoreCase(name))
+				return v;
+		}
+		return null;
+	}
+
+	/**
+	 * @param vocabId
+	 *            Get all taxonomy terms of a given vocabulary
+	 * @return TaxTerms
+	 */
+	public Vector<TaxonomyTerm> getTaxonomyTermsByVocabulary(int vocabId) {
+		Vector<TaxonomyTerm> res = new Vector<TaxonomyTerm>();
+		for (TaxonomyTerm t : taxonomyTerms.values()) {
+			if (t.getVid() == vocabId)
+				res.add(t);
+		}
+		return res;
+	}
+
+	/**
+	 * @param id
+	 *            Get taxonomy term by given ID
+	 * @return TaxTerm
+	 */
+	public TaxonomyTerm getTaxonomyTerm(int id) {
+		return taxonomyTerms.get(id);
+	}
+
+	/**
+	 * @param name
+	 *            Get taxonomy term by given name
+	 * @return TaxTerm
+	 */
+	public TaxonomyTerm getTaxonomyTerm(String name) {
+		for (TaxonomyTerm t : taxonomyTerms.values()) {
+			if (t.getName().equalsIgnoreCase(name))
+				return t;
+		}
+		return null;
+	}
+
+	/**
+	 * Get all nodes of given node type
+	 * 
+	 * @param nodeType
+	 *            Node type
+	 * @return Nodes of type nodeType
+	 */
+	public Vector<Node> getNodesByType(String nodeType) {
+		return new Vector<Node>(nodesByType.get(nodeType).values());
+	}
+
+	/**
+	 * @return Mod-Nodes
+	 */
+	public Vector<NodeMod> getModPackageNodes() {
+		int packageterm_id = getTaxonomyTerm(DepotConfig.INSTALLTYPE_PACKAGE)
+				.getTid();
+
+		Vector<Node> files = getNodesByType(DepotConfig.NODETYPE_MOD);
+		Vector<NodeMod> result = new Vector<NodeMod>();
+		for (Node n : files) {
+			if (n instanceof NodeMod) {
+				NodeMod nm = (NodeMod) n;
+				if (nm.getTaxonomyTerms().get(vocabId_instmethod)
+						.contains(packageterm_id))
+					result.add(nm);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * @param node
+	 *            Node to check validity on
+	 * @param platform
+	 *            Platform to check against
+	 * @return True if valid on platform
+	 */
+	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.PLATFORM_BOTH))
+			return true;
+
+		if ((platform == Platform.WIN) || (platform == Platform.LINUX))
+			return validPlatform.equalsIgnoreCase(DepotConfig.PLATFORM_WIN);
+		else if (platform == Platform.MACOS)
+			return validPlatform.equalsIgnoreCase(DepotConfig.PLATFORM_MAC);
+		else
+			return false;
+	}
+
+	/**
+	 * Checks if the given mod-node is of the given mod-type(s)
+	 * 
+	 * @param node
+	 *            Node to check
+	 * @param type
+	 *            Type(s) to check
+	 * @param or
+	 *            If false check if all given types are included in node. If
+	 *            true checks if either of the given types is included.
+	 * @return True if of given type(s)
+	 */
+	public boolean isModOfType(NodeMod node, HashSet<Integer> type, boolean or) {
+		boolean matching = true;
+		if (or)
+			matching = false;
+
+		for (int t : type) {
+			if (or)
+				matching |= node.getTaxonomyTerms().get(vocabId_type)
+						.contains(t);
+			else
+				matching &= node.getTaxonomyTerms().get(vocabId_type)
+						.contains(t);
+		}
+		return matching;
+	}
+
+	/**
+	 * Print stats about nodes and files
+	 */
+	public void printStats() {
+		System.out.println("Nodes by type:");
+		for (String t : nodesByType.keySet()) {
+			System.out.println("  " + t + ": " + nodesByType.get(t).size());
+		}
+
+		System.out.println("Files: " + files.size());
+	}
+
+	private XStream getXStream() {
+		XStream xs = new XStream(new StaxDriver());
+		xs.alias("Depot", DepotManager.class);
+		xs.alias("File", net.oni2.aeinstaller.backend.depot.model.File.class);
+		xs.alias("Node", Node.class);
+		xs.alias("NodeField_Body", NodeField_Body.class);
+		xs.alias("NodeField_Upload", NodeField_Upload.class);
+		xs.alias("NodeMod", NodeMod.class);
+		xs.alias("TaxonomyTerm", TaxonomyTerm.class);
+		xs.alias("TaxonomyVocabulary", TaxonomyVocabulary.class);
+		return xs;
+	}
+
+	/**
+	 * Save Depot cache instance to file
+	 * 
+	 * @param f
+	 *            File to write to
+	 */
+	public void saveToFile(java.io.File f) {
+		try {
+			FileOutputStream fos = new FileOutputStream(f);
+			XStream xs = getXStream();
+			xs.toXML(this, fos);
+			fos.close();
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Load Depot cache instance from file
+	 * 
+	 * @param f
+	 *            File to read from
+	 */
+	public void loadFromFile(java.io.File f) {
+		try {
+			FileInputStream fis = new FileInputStream(f);
+			XStream xs = getXStream();
+			Object obj = xs.fromXML(fis);
+			if (obj instanceof DepotManager)
+				instance = (DepotManager) obj;
+			fis.close();
+		} catch (FileNotFoundException e) {
+		} catch (IOException e) {
+		}
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/JSONHelpers.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/JSONHelpers.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/JSONHelpers.java	(revision 591)
@@ -0,0 +1,30 @@
+package net.oni2.aeinstaller.backend.depot;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author Christian Illy
+ */
+public class JSONHelpers {
+	/**
+	 * Return the string associated with the given key or null if the key does
+	 * not exist or does not contain a string
+	 * 
+	 * @param parent
+	 *            Parent JSON object
+	 * @param key
+	 *            Key to look for
+	 * @return Contained string or null
+	 */
+	public static String getStringOrNull(JSONObject parent, String key) {
+		try {
+			Object obj = parent.get(key);
+			if (obj instanceof String)
+				return (String) obj;
+		} catch (JSONException e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/File.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/File.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/File.java	(revision 591)
@@ -0,0 +1,112 @@
+package net.oni2.aeinstaller.backend.depot.model;
+
+import net.oni2.aeinstaller.backend.depot.JSONHelpers;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author Christian Illy
+ */
+public class File {
+	private int fid;
+	private int uid; // User ID of uploader
+	private String filename; // Filename
+	private String uri; // Internal URI
+	private String filemime; // Mimetype
+	private int filesize; // Bytes
+	private int status; // ???
+	private long timestamp; // Timestamp of upload
+	private String uri_full; // Full public URI
+	private String target_uri; // ???
+
+	/**
+	 * @param json
+	 *            JSON object of File to parse
+	 * @throws JSONException
+	 *             On key not found / wrong type
+	 */
+	public File(JSONObject json) throws JSONException {
+		fid = json.getInt("fid");
+		uid = json.getInt("uid");
+		filename = JSONHelpers.getStringOrNull(json, "filename");
+		uri = JSONHelpers.getStringOrNull(json, "uri");
+		filemime = JSONHelpers.getStringOrNull(json, "filemime");
+		filesize = json.getInt("filesize");
+		status = json.getInt("status");
+		timestamp = json.getLong("timestamp");
+		uri_full = JSONHelpers.getStringOrNull(json, "uri_full");
+		target_uri = JSONHelpers.getStringOrNull(json, "target_uri");
+	}
+
+	/**
+	 * @return the fid
+	 */
+	public int getFid() {
+		return fid;
+	}
+
+	/**
+	 * @return the uid
+	 */
+	public int getUid() {
+		return uid;
+	}
+
+	/**
+	 * @return the filename
+	 */
+	public String getFilename() {
+		return filename;
+	}
+
+	/**
+	 * @return the uri
+	 */
+	public String getUri() {
+		return uri;
+	}
+
+	/**
+	 * @return the filemime
+	 */
+	public String getFilemime() {
+		return filemime;
+	}
+
+	/**
+	 * @return the filesize
+	 */
+	public int getFilesize() {
+		return filesize;
+	}
+
+	/**
+	 * @return the status
+	 */
+	public int getStatus() {
+		return status;
+	}
+
+	/**
+	 * @return the timestamp
+	 */
+	public long getTimestamp() {
+		return timestamp;
+	}
+
+	/**
+	 * @return the uri_full
+	 */
+	public String getUri_full() {
+		return uri_full;
+	}
+
+	/**
+	 * @return the target_uri
+	 */
+	public String getTarget_uri() {
+		return target_uri;
+	}
+
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/Node.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/Node.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/Node.java	(revision 591)
@@ -0,0 +1,274 @@
+package net.oni2.aeinstaller.backend.depot.model;
+
+import net.oni2.aeinstaller.backend.depot.JSONHelpers;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author Christian Illy
+ */
+public class Node {
+	private int uid; // User ID of editor
+	private int nid; // Node ID
+
+	private NodeField_Body body;
+
+	private String data; // ???
+	private int last_comment_uid; // User ID of author of last comment
+	private String last_comment_name; // Name of author of last comment
+	private long last_comment_timestamp; // Timestamp of post time of last
+											// comment
+	private int comment_count; // Number of comments
+	private String type; // Type of node ("story", "page", "mod" ...)
+	private long changed; // Timestamp of last change
+	private String title; // Title of node
+	private long created; // Timestamp of node creation
+	private String name; // Name of user who created the node
+	private String path; // URI of node
+	private int revision_uid; // User ID of author of last revision
+	private int tnid; // ???
+	private int vid; // Revision VID of latest revision
+	private int status; // ??? perhaps published etc?
+	private String log; // ???
+	private int cid; // ???
+	private int picture; // ???
+	private int sticky; // Is sticky on news overview?
+	private int promote; // (???) Is promoted?
+	private long revision_timestamp; // Timestamp of last revision
+	private int translate; // ???
+	private String language; // Languagecode of content
+	private String comment; // ???
+
+	/**
+	 * @param json
+	 *            JSON object of Node to parse
+	 * @throws JSONException
+	 *             On key not found / wrong type
+	 */
+	public Node(JSONObject json) throws JSONException {
+		uid = json.getInt("uid");
+		nid = json.getInt("nid");
+
+		Object bodyObj = json.get("body");
+		if (bodyObj instanceof JSONObject) {
+			JSONObject jBody = ((JSONObject) bodyObj).getJSONArray("und").getJSONObject(0);
+			body = new NodeField_Body(jBody);
+		}
+		data = JSONHelpers.getStringOrNull(json, "data");
+		last_comment_uid = json.getInt("last_comment_uid");
+		last_comment_name = JSONHelpers.getStringOrNull(json,
+				"last_comment_name");
+		last_comment_timestamp = json.getLong("last_comment_timestamp");
+		comment_count = json.getInt("comment_count");
+		type = JSONHelpers.getStringOrNull(json, "type");
+		changed = json.getLong("changed");
+		title = JSONHelpers.getStringOrNull(json, "title");
+		created = json.getLong("created");
+		name = JSONHelpers.getStringOrNull(json, "name");
+		path = JSONHelpers.getStringOrNull(json, "path");
+		revision_uid = json.getInt("revision_uid");
+		tnid = json.getInt("tnid");
+		vid = json.getInt("vid");
+		status = json.getInt("status");
+		log = JSONHelpers.getStringOrNull(json, "log");
+		cid = json.getInt("cid");
+		picture = json.getInt("picture");
+		sticky = json.getInt("sticky");
+		promote = json.getInt("promote");
+		revision_timestamp = json.getLong("revision_timestamp");
+		translate = json.getInt("translate");
+		language = JSONHelpers.getStringOrNull(json, "language");
+		comment = JSONHelpers.getStringOrNull(json, "comment");
+	}
+
+	/**
+	 * @return the uid
+	 */
+	public int getUid() {
+		return uid;
+	}
+
+	/**
+	 * @return the nid
+	 */
+	public int getNid() {
+		return nid;
+	}
+
+	/**
+	 * @return the body
+	 */
+	public NodeField_Body getBody() {
+		return body;
+	}
+
+	/**
+	 * @return the data
+	 */
+	public String getData() {
+		return data;
+	}
+
+	/**
+	 * @return the last_comment_uid
+	 */
+	public int getLast_comment_uid() {
+		return last_comment_uid;
+	}
+
+	/**
+	 * @return the last_comment_name
+	 */
+	public String getLast_comment_name() {
+		return last_comment_name;
+	}
+
+	/**
+	 * @return the last_comment_timestamp
+	 */
+	public long getLast_comment_timestamp() {
+		return last_comment_timestamp;
+	}
+
+	/**
+	 * @return the comment_count
+	 */
+	public int getComment_count() {
+		return comment_count;
+	}
+
+	/**
+	 * @return the type
+	 */
+	public String getType() {
+		return type;
+	}
+
+	/**
+	 * @return the changed
+	 */
+	public long getChanged() {
+		return changed;
+	}
+
+	/**
+	 * @return the title
+	 */
+	public String getTitle() {
+		return title;
+	}
+
+	/**
+	 * @return the created
+	 */
+	public long getCreated() {
+		return created;
+	}
+
+	/**
+	 * @return the name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the path
+	 */
+	public String getPath() {
+		return path;
+	}
+
+	/**
+	 * @return the revision_uid
+	 */
+	public int getRevision_uid() {
+		return revision_uid;
+	}
+
+	/**
+	 * @return the tnid
+	 */
+	public int getTnid() {
+		return tnid;
+	}
+
+	/**
+	 * @return the vid
+	 */
+	public int getVid() {
+		return vid;
+	}
+
+	/**
+	 * @return the status
+	 */
+	public int getStatus() {
+		return status;
+	}
+
+	/**
+	 * @return the log
+	 */
+	public String getLog() {
+		return log;
+	}
+
+	/**
+	 * @return the cid
+	 */
+	public int getCid() {
+		return cid;
+	}
+
+	/**
+	 * @return the picture
+	 */
+	public int getPicture() {
+		return picture;
+	}
+
+	/**
+	 * @return the sticky
+	 */
+	public int getSticky() {
+		return sticky;
+	}
+
+	/**
+	 * @return the promote
+	 */
+	public int getPromote() {
+		return promote;
+	}
+
+	/**
+	 * @return the revision_timestamp
+	 */
+	public long getRevision_timestamp() {
+		return revision_timestamp;
+	}
+
+	/**
+	 * @return the translate
+	 */
+	public int getTranslate() {
+		return translate;
+	}
+
+	/**
+	 * @return the language
+	 */
+	public String getLanguage() {
+		return language;
+	}
+
+	/**
+	 * @return the comment
+	 */
+	public String getComment() {
+		return comment;
+	}
+
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Body.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Body.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Body.java	(revision 591)
@@ -0,0 +1,66 @@
+package net.oni2.aeinstaller.backend.depot.model;
+
+import net.oni2.aeinstaller.backend.depot.JSONHelpers;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author Christian Illy
+ */
+public class NodeField_Body {
+	private String summary;
+	private String value;
+	private String safe_summary;
+	private String safe_value;
+	private int format;
+
+	/**
+	 * @param json
+	 *            JSON object of body-entry to parse
+	 * @throws JSONException
+	 *             On key not found / wrong type
+	 */
+	public NodeField_Body(JSONObject json) throws JSONException {
+		summary = JSONHelpers.getStringOrNull(json, "summary");
+		value = JSONHelpers.getStringOrNull(json, "value");
+		safe_summary = JSONHelpers.getStringOrNull(json, "safe_summary");
+		safe_value = JSONHelpers.getStringOrNull(json, "safe_value");
+		format = json.getInt("format");
+	}
+
+	/**
+	 * @return the summary
+	 */
+	public String getSummary() {
+		return summary;
+	}
+
+	/**
+	 * @return the value
+	 */
+	public String getValue() {
+		return value;
+	}
+
+	/**
+	 * @return the safe_summary
+	 */
+	public String getSafe_summary() {
+		return safe_summary;
+	}
+
+	/**
+	 * @return the safe_value
+	 */
+	public String getSafe_value() {
+		return safe_value;
+	}
+
+	/**
+	 * @return the format
+	 */
+	public int getFormat() {
+		return format;
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Upload.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Upload.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Upload.java	(revision 591)
@@ -0,0 +1,112 @@
+package net.oni2.aeinstaller.backend.depot.model;
+
+import net.oni2.aeinstaller.backend.depot.JSONHelpers;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author Christian Illy
+ */
+public class NodeField_Upload {
+
+	private long timestamp; // Time of upload
+	private int uid; // User ID of uploader
+	private int fid; // File ID
+	private int filesize; // Size in bytes
+	private String status; // ???
+	private String description;// Description
+	private String filename; // Filename
+	private int display; // isVisible
+	private String filemime; // Mimetype
+	private String uri; // Private URI
+
+	/**
+	 * @param json
+	 *            JSON object of upload-entry to parse
+	 * @throws JSONException
+	 *             On key not found / wrong type
+	 */
+	public NodeField_Upload(JSONObject json) throws JSONException {
+		timestamp = json.getLong("timestamp");
+		uid = json.getInt("uid");
+		fid = json.getInt("fid");
+		filesize = json.getInt("filesize");
+		status = JSONHelpers.getStringOrNull(json, "status");
+		description = JSONHelpers.getStringOrNull(json, "description");
+		filename = JSONHelpers.getStringOrNull(json, "filename");
+		display = json.getInt("display");
+		filemime = JSONHelpers.getStringOrNull(json, "filemime");
+		uri = JSONHelpers.getStringOrNull(json, "uri");
+	}
+
+	/**
+	 * @return the timestamp
+	 */
+	public long getTimestamp() {
+		return timestamp;
+	}
+
+	/**
+	 * @return the uid
+	 */
+	public int getUid() {
+		return uid;
+	}
+
+	/**
+	 * @return the fid
+	 */
+	public int getFid() {
+		return fid;
+	}
+
+	/**
+	 * @return the filesize
+	 */
+	public int getFilesize() {
+		return filesize;
+	}
+
+	/**
+	 * @return the status
+	 */
+	public String getStatus() {
+		return status;
+	}
+
+	/**
+	 * @return the description
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * @return the filename
+	 */
+	public String getFilename() {
+		return filename;
+	}
+
+	/**
+	 * @return the display
+	 */
+	public int getDisplay() {
+		return display;
+	}
+
+	/**
+	 * @return the filemime
+	 */
+	public String getFilemime() {
+		return filemime;
+	}
+
+	/**
+	 * @return the uri
+	 */
+	public String getUri() {
+		return uri;
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeMod.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeMod.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeMod.java	(revision 591)
@@ -0,0 +1,86 @@
+package net.oni2.aeinstaller.backend.depot.model;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Vector;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author Christian Illy
+ */
+public class NodeMod extends Node {
+	private Vector<NodeField_Upload> uploads = new Vector<NodeField_Upload>();
+	private HashMap<Integer, HashSet<Integer>> taxonomyTerms = new HashMap<Integer, HashSet<Integer>>();
+	private HashMap<String, String> fields = new HashMap<String, String>();
+
+	/**
+	 * @param json
+	 *            JSON object of Mod-node to parse
+	 * @throws JSONException
+	 *             On key not found / wrong type
+	 */
+	public NodeMod(JSONObject json) throws JSONException {
+		super(json);
+
+		Object uploadObj = json.get("upload");
+		if (uploadObj instanceof JSONObject) {
+			JSONArray jUploads = ((JSONObject) uploadObj).getJSONArray("und");
+			for (int i = 0; i < jUploads.length(); i++) {
+				NodeField_Upload up = new NodeField_Upload(
+						jUploads.getJSONObject(i));
+				uploads.add(up);
+			}
+		}
+
+		for (Object key : json.keySet()) {
+			String keyS = ((String) key).toLowerCase();
+			Object val = json.get(keyS);
+			if (keyS.startsWith("field_")) {
+				String fName = keyS.substring(keyS.indexOf("_") + 1);
+				String value = "";
+				if (val instanceof JSONObject) {
+					value = ((JSONObject) val).getJSONArray("und")
+							.getJSONObject(0).getString("value");
+				}
+				fields.put(fName, value);
+			}
+
+			if (keyS.startsWith("taxonomy_vocabulary_")) {
+				int vid = Integer
+						.parseInt(keyS.substring(keyS.lastIndexOf("_") + 1));
+				HashSet<Integer> values = new HashSet<Integer>();
+				if (val instanceof JSONObject) {
+					JSONArray ja = ((JSONObject) val).getJSONArray("und");
+					for (int i = 0; i < ja.length(); i++) {
+						values.add(ja.getJSONObject(i).getInt("tid"));
+					}
+				}
+				taxonomyTerms.put(vid, values);
+			}
+		}
+	}
+
+	/**
+	 * @return the uploads
+	 */
+	public Vector<NodeField_Upload> getUploads() {
+		return uploads;
+	}
+
+	/**
+	 * @return the taxonomyTerms
+	 */
+	public HashMap<Integer, HashSet<Integer>> getTaxonomyTerms() {
+		return taxonomyTerms;
+	}
+
+	/**
+	 * @return the fields
+	 */
+	public HashMap<String, String> getFields() {
+		return fields;
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyTerm.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyTerm.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyTerm.java	(revision 591)
@@ -0,0 +1,87 @@
+package net.oni2.aeinstaller.backend.depot.model;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author Christian Illy
+ */
+public class TaxonomyTerm {
+	private int tid;
+	private int vid;
+	private String name;
+	private String description;
+	private String uri;
+
+	/**
+	 * Manually create a taxonomy term for internal use
+	 * 
+	 * @param tid
+	 *            ID
+	 * @param vid
+	 *            VocabID
+	 * @param name
+	 *            Term name
+	 */
+	public TaxonomyTerm(int tid, int vid, String name) {
+		this.tid = tid;
+		this.vid = vid;
+		this.name = name;
+		this.description = "";
+		this.uri = "";
+	}
+
+	/**
+	 * @param json
+	 *            JSONObject containing the item
+	 * @throws JSONException
+	 *             If a key can't be found
+	 */
+	public TaxonomyTerm(JSONObject json) throws JSONException {
+		tid = json.getInt("tid");
+		vid = json.getInt("vid");
+		name = json.getString("name");
+		description = json.getString("description");
+		uri = json.getString("uri");
+	}
+
+	/**
+	 * @return the term ID (tid)
+	 */
+	public int getTid() {
+		return tid;
+	}
+
+	/**
+	 * @return the vocabulary ID (vid)
+	 */
+	public int getVid() {
+		return vid;
+	}
+
+	/**
+	 * @return the name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the description
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * @return the uri
+	 */
+	public String getUri() {
+		return uri;
+	}
+	
+	@Override
+	public String toString() {
+		return getName();
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyVocabulary.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyVocabulary.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyVocabulary.java	(revision 591)
@@ -0,0 +1,73 @@
+package net.oni2.aeinstaller.backend.depot.model;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author Christian Illy
+ */
+public class TaxonomyVocabulary {
+	private int vid;
+	private String name;
+	private String machine_name;
+	private String description;
+	private int hierarchy;
+	private String uri;
+
+	/**
+	 * @param json
+	 *            JSONObject containing the item
+	 * @throws JSONException
+	 *             If a key can't be found
+	 */
+	public TaxonomyVocabulary(JSONObject json) throws JSONException {
+		vid = json.getInt("vid");
+		name = json.getString("name");
+		machine_name = json.getString("machine_name");
+		description = json.getString("description");
+		hierarchy = json.getInt("hierarchy");
+		uri = json.getString("uri");
+	}
+
+	/**
+	 * @return the vocabulary ID (vid)
+	 */
+	public int getVid() {
+		return vid;
+	}
+
+	/**
+	 * @return the name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the machine_name
+	 */
+	public String getMachine_name() {
+		return machine_name;
+	}
+
+	/**
+	 * @return the description
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * @return the hierarchy level
+	 */
+	public int getHierarchy() {
+		return hierarchy;
+	}
+
+	/**
+	 * @return the uri
+	 */
+	public String getUri() {
+		return uri;
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/network/DrupalJSONQuery.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/network/DrupalJSONQuery.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/network/DrupalJSONQuery.java	(revision 591)
@@ -0,0 +1,194 @@
+package net.oni2.aeinstaller.backend.network;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import net.oni2.aeinstaller.backend.Settings;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * @author Christian Illy
+ */
+public class DrupalJSONQuery {
+
+	private static String getDepotUrl() {
+		return Settings.getInstance().get("depot_api_url",
+				"http://mods.oni2.net/?q=api/");
+	}
+
+	/**
+	 * Execute an REST action through a HTTP POST query
+	 * 
+	 * @param resource
+	 *            Resource to run on
+	 * @param action
+	 *            Action to call
+	 * @param postData
+	 *            Fieldname / value pairs to include in POST data
+	 * @return JSON structure of item
+	 * @throws Exception
+	 *             on HTTP error
+	 */
+	public static JSONArray postAction(String resource, String action,
+			HashMap<String, String> postData) throws Exception {
+		try {
+			List<NameValuePair> nvps = new ArrayList<NameValuePair>();
+			for (String key : postData.keySet()) {
+				nvps.add(new BasicNameValuePair(key, postData.get(key)));
+			}
+			HttpEntity data = new UrlEncodedFormEntity(nvps);
+			return executeQuery(getDepotUrl() + resource + "/" + action
+					+ ".json", data);
+		} catch (UnsupportedEncodingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return null;
+	}
+
+	/**
+	 * Execute an REST references query through a HTTP GET query
+	 * 
+	 * @param resource
+	 *            Resource to run on
+	 * @param index
+	 *            Index of item to get the references from
+	 * @param refName
+	 *            Name of references type
+	 * @return JSON structure of item
+	 * @throws Exception
+	 *             on HTTP error
+	 */
+	public static JSONArray getReferences(String resource, int index,
+			String refName) throws Exception {
+		return executeQuery(
+				getDepotUrl() + resource + "/" + Integer.toString(index) + "/"
+						+ refName + ".json", null);
+	}
+
+	/**
+	 * Execute an REST item query through a HTTP GET query
+	 * 
+	 * @param resource
+	 *            Resource to run on
+	 * @param index
+	 *            Index of item to get
+	 * @param parameters
+	 *            Parameters to pass (must start with ampersand "&")
+	 * @return JSON structure of item
+	 * @throws Exception
+	 *             on HTTP error
+	 */
+	public static JSONArray getItem(String resource, int index,
+			String parameters) throws Exception {
+		return executeQuery(
+				getDepotUrl() + resource + "/" + Integer.toString(index)
+						+ ".json" + parameters, null);
+	}
+
+	/**
+	 * Execute an REST index query through a HTTP GET query
+	 * 
+	 * @param resource
+	 *            Resource to run on
+	 * @param page
+	 *            Number of page to get (for limited results, e.g. nodes), -1 to
+	 *            ignore
+	 * @return JSON structure of item
+	 * @throws Exception
+	 *             on HTTP error
+	 */
+	public static JSONArray getIndex(String resource, int page)
+			throws Exception {
+		String pageN = "";
+		if (page >= 0)
+			pageN = "&page=" + Integer.toString(page);
+		return executeQuery(getDepotUrl() + resource + ".json" + pageN, null);
+	}
+
+	private static JSONArray executeQuery(String url, HttpEntity postData)
+			throws Exception {
+		BufferedReader input = null;
+		HttpRequestBase httpQuery = null;
+
+		try {
+			DefaultHttpClient httpclient = new DefaultHttpClient();
+			if (postData == null) {
+				httpQuery = new HttpGet(url);
+			} else {
+				httpQuery = new HttpPost(url);
+				((HttpPost) httpQuery).setEntity(postData);
+			}
+
+			HttpResponse response = httpclient.execute(httpQuery);
+
+			int code = response.getStatusLine().getStatusCode();
+			if ((code > 299) || (code < 200)) {
+				throw new Exception(String.format(
+						"Error fetching content (HTTP status code %d).", code));
+			}
+
+			HttpEntity entity = response.getEntity();
+
+			input = new BufferedReader(new InputStreamReader(
+					entity.getContent()));
+			StringBuffer json = new StringBuffer();
+
+			char data[] = new char[1024];
+			int dataRead;
+			while ((dataRead = input.read(data, 0, 1024)) != -1) {
+				json.append(data, 0, dataRead);
+			}
+
+			EntityUtils.consume(entity);
+
+			JSONArray jA = null;
+			if (json.charAt(0) == '{') {
+				jA = new JSONArray();
+				jA.put(new JSONObject(json.toString()));
+			} else
+				jA = new JSONArray(json.toString());
+
+			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 {
+			if (httpQuery != null)
+				httpQuery.releaseConnection();
+			if (input != null) {
+				try {
+					input.close();
+				} catch (IOException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+		return null;
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloadListener.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloadListener.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloadListener.java	(revision 591)
@@ -0,0 +1,24 @@
+package net.oni2.aeinstaller.backend.network;
+
+/**
+ * Interface for listeners to status updates during file download
+ * 
+ * @author Christian Illy
+ */
+public interface FileDownloadListener {
+
+	/**
+	 * Called after checking out / updating a single file
+	 * 
+	 * @param source
+	 *            Source of event
+	 * @param state
+	 *            Current state of downloader
+	 * @param done
+	 *            Bytes done
+	 * @param total
+	 *            Total bytes for the download
+	 */
+	public void statusUpdate(FileDownloader source,
+			FileDownloader.EState state, int done, int total);
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java	(revision 591)
@@ -0,0 +1,258 @@
+package net.oni2.aeinstaller.backend.network;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashSet;
+
+/**
+ * @author Christian Illy
+ */
+public class FileDownloader implements Runnable {
+	/**
+	 * @author Christian Illy
+	 */
+	public enum EState {
+		/**
+		 * Downloader initialized but not started
+		 */
+		INIT,
+		/**
+		 * Download running
+		 */
+		RUNNING,
+		/**
+		 * Download suspended
+		 */
+		PAUSED,
+		/**
+		 * Download interrupted
+		 */
+		INTERRUPTED,
+		/**
+		 * Download finished successfully
+		 */
+		FINISHED,
+		/**
+		 * Aborted because of an error
+		 */
+		ERROR
+	};
+
+	private HashSet<FileDownloadListener> listeners = new HashSet<FileDownloadListener>();
+	private Thread t = null;
+	private URL url = null;
+	private File target = null;
+	private int size = -1;
+	private int downloaded = 0;
+
+	private EState state = EState.INIT;
+
+	/**
+	 * @param url
+	 *            URL of file to download
+	 * @param target
+	 *            Path of target file to save to
+	 * @throws IOException
+	 *             If url could not be opened
+	 */
+	public FileDownloader(String url, String target) throws IOException {
+		this.url = new URL(url);
+		this.target = new File(target);
+
+		URLConnection connection = this.url.openConnection();
+		connection.connect();
+		size = connection.getContentLength();
+	}
+
+	/**
+	 * @param listener
+	 *            Listener to add
+	 */
+	public void addListener(FileDownloadListener listener) {
+		listeners.add(listener);
+	}
+
+	/**
+	 * @param listener
+	 *            Listener to remove
+	 */
+	public void removeListener(FileDownloadListener listener) {
+		listeners.remove(listener);
+	}
+
+	/**
+	 * Start the download process
+	 */
+	public synchronized void start() {
+		if (t == null) {
+			t = new Thread(this);
+			t.start();
+			state = EState.RUNNING;
+		}
+	}
+
+	/**
+	 * @param suspend
+	 *            Suspend or resume
+	 */
+	public synchronized void suspend(boolean suspend) {
+		if ((state == EState.RUNNING) || (state == EState.PAUSED)) {
+			if (suspend)
+				state = EState.PAUSED;
+			else
+				state = EState.RUNNING;
+		}
+	}
+
+	/**
+	 * Stop (abort) download
+	 */
+	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();
+	}
+
+	private synchronized void updateStatus(int current, int total) {
+		downloaded = current;
+		for (FileDownloadListener l : listeners) {
+			l.statusUpdate(this, state, current, total);
+		}
+	}
+
+	@Override
+	public void run() {
+		int downloaded = 0;
+		int fileLength = Integer.MAX_VALUE;
+		String strLastModified = null;
+		String strEtag = null;
+		RandomAccessFile outFile = null;
+		try {
+			outFile = new RandomAccessFile(target, "rw");
+		} catch (FileNotFoundException e1) {
+			// TODO Auto-generated catch block
+			e1.printStackTrace();
+			state = EState.ERROR;
+			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();
+						}
+
+						// 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();
+						}
+					}
+					break;
+				default:
+					break;
+			}
+		}
+
+		try {
+			// Close streams.
+			outFile.close();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+
+		state = EState.FINISHED;
+		updateStatus(downloaded, fileLength);
+	}
+
+	/**
+	 * @return the target
+	 */
+	public File getTarget() {
+		return target;
+	}
+
+	/**
+	 * @return the size
+	 */
+	public int getSize() {
+		return size;
+	}
+
+	/**
+	 * @return the downloaded size
+	 */
+	public int getDownloaded() {
+		return downloaded;
+	}
+
+}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 591)
@@ -0,0 +1,245 @@
+package net.oni2.aeinstaller.gui;
+
+import java.awt.Frame;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.TreeMap;
+
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.RowSorter;
+import javax.swing.RowSorter.SortKey;
+import javax.swing.SortOrder;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableRowSorter;
+
+import net.oni2.aeinstaller.backend.Settings;
+import net.oni2.aeinstaller.backend.StuffToRefactorLater;
+import net.oni2.aeinstaller.backend.depot.DepotCacheUpdateProgressListener;
+import net.oni2.aeinstaller.backend.depot.DepotConfig;
+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.gui.modtable.ModTableFilter;
+import net.oni2.aeinstaller.gui.modtable.ModTableModel;
+import net.oni2.aeinstaller.gui.settings.SettingsDialog;
+
+import org.javabuilders.BuildResult;
+import org.javabuilders.annotations.DoInBackground;
+import org.javabuilders.event.BackgroundEvent;
+import org.javabuilders.swing.SwingJavaBuilder;
+
+/**
+ * @author Christian Illy
+ */
+public class MainWin extends JFrame implements ListSelectionListener {
+	private static final long serialVersionUID = -4027395051382659650L;
+
+	private ResourceBundle bundle = ResourceBundle.getBundle(getClass()
+			.getName());
+	@SuppressWarnings("unused")
+	private BuildResult result = SwingJavaBuilder.build(this, bundle);
+
+	private JComboBox cmbModTypes;
+	private JTable tblMods;
+	private ModTableModel model;
+	private TableRowSorter<ModTableModel> sorter;
+
+	/**
+	 * Constructor of main window.
+	 */
+	public MainWin() {
+		this.setTitle(bundle.getString("frame.title") + " - v"
+				+ bundle.getString("version"));
+
+		initTable();
+		initModTypeBox();
+	}
+
+	private void initModTypeBox() {
+		int vid = DepotManager.getInstance()
+				.getVocabulary(DepotConfig.MODTYPE_VOCAB).getVid();
+		TreeMap<String, TaxonomyTerm> terms = new TreeMap<String, TaxonomyTerm>();
+		terms.put(" ", new TaxonomyTerm(-1, vid, "-All-"));
+		for (TaxonomyTerm t : DepotManager.getInstance()
+				.getTaxonomyTermsByVocabulary(vid)) {
+			terms.put(t.getName(), t);
+		}
+		for (TaxonomyTerm t : terms.values()) {
+			cmbModTypes.addItem(t);
+		}
+		cmbModTypes.setSelectedIndex(0);
+	}
+
+	private void initTable() {
+		tblMods.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+		tblMods.getSelectionModel().addListSelectionListener(this);
+
+		model = new ModTableModel();
+
+		tblMods.setModel(model);
+
+		sorter = new TableRowSorter<ModTableModel>(model);
+		tblMods.setRowSorter(sorter);
+
+		sorter.setRowFilter(new ModTableFilter(-1));
+
+		sorter.setSortable(2, false);
+		sorter.setComparator(1, new Comparator<String>() {
+
+			@Override
+			public int compare(String o1, String o2) {
+				int i1 = Integer.parseInt(o1);
+				int i2 = Integer.parseInt(o2);
+				return i1 - i2;
+			}
+		});
+
+		List<RowSorter.SortKey> sortKeys = new ArrayList<RowSorter.SortKey>();
+		sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
+		sorter.setSortKeys(sortKeys);
+
+		for (int i = 0; i < model.getColumnCount(); i++) {
+			model.setColumnConstraints(i, tblMods.getColumnModel().getColumn(i));
+		}
+
+		// for (int i = 3; i > 0; i--) {
+		// tblMods.getColumnModel().removeColumn(tblMods.getColumnModel().getColumn(i));
+		// }
+	}
+
+	@SuppressWarnings("unused")
+	private boolean closeFrames() {
+		System.gc();
+		for (Frame f : Frame.getFrames()) {
+			if (f != this)
+				f.dispose();
+		}
+		return true;
+	}
+
+	private void exit() {
+		setVisible(false);
+		dispose();
+	}
+
+	@SuppressWarnings("unused")
+	private void saveLocalData() {
+		Settings.getInstance().serializeToFile();
+		DepotManager.getInstance().saveToFile(
+				new File(Settings.getDepotCacheFilename()));
+	}
+
+	@SuppressWarnings("unused")
+	private boolean validatePath() {
+		if (!StuffToRefactorLater.verifyRunningDirectory()) {
+			JOptionPane.showMessageDialog(this,
+					bundle.getString("invalidPath.text"),
+					bundle.getString("invalidPath.title"),
+					JOptionPane.ERROR_MESSAGE);
+			if (!Settings.getDebug()) {
+				exit();
+				return false;
+			}
+		}
+		return true;
+	}
+
+	@DoInBackground(progressMessage = "updateDepot.title", cancelable = false, indeterminateProgress = false)
+	private void execDepotUpdate(final BackgroundEvent evt) {
+		try {
+			DepotManager.getInstance().updateInformation(false,
+					new DepotCacheUpdateProgressListener() {
+
+						@Override
+						public void cacheUpdateProgress(String stepName,
+								int current, int total) {
+							evt.setProgressEnd(total);
+							evt.setProgressValue(current);
+							evt.setProgressMessage(stepName);
+						}
+					});
+			model.reloadData();
+			DepotManager.getInstance().printStats();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void checkUpdates() {
+		if (Settings.getInstance().get("notifyupdates", true)) {
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void focus() {
+		SwingUtilities.invokeLater(new Runnable() {
+
+			@Override
+			public void run() {
+				toFront();
+				repaint();
+			}
+		});
+
+	}
+
+	@SuppressWarnings("unused")
+	private void showSettings() {
+		SettingsDialog.openWindow();
+	}
+
+	@SuppressWarnings("unused")
+	private void modTypeSelection() {
+		TaxonomyTerm t = (TaxonomyTerm) cmbModTypes.getSelectedItem();
+		sorter.setRowFilter(new ModTableFilter(t.getTid()));
+	}
+
+	@SuppressWarnings("unused")
+	private void sortAlpha() {
+		SortOrder order = SortOrder.ASCENDING;
+		for (SortKey sk : sorter.getSortKeys()) {
+			if (sk.getColumn() == 0) {
+				if (sk.getSortOrder() == SortOrder.ASCENDING)
+					order = SortOrder.DESCENDING;
+			}
+		}
+		List<RowSorter.SortKey> sortKeys = new ArrayList<RowSorter.SortKey>();
+		sortKeys.add(new RowSorter.SortKey(0, order));
+		sorter.setSortKeys(sortKeys);
+	}
+
+	@SuppressWarnings("unused")
+	private void sortPackageNumber() {
+		SortOrder order = SortOrder.ASCENDING;
+		for (SortKey sk : sorter.getSortKeys()) {
+			if (sk.getColumn() == 1) {
+				if (sk.getSortOrder() == SortOrder.ASCENDING)
+					order = SortOrder.DESCENDING;
+			}
+		}
+		List<RowSorter.SortKey> sortKeys = new ArrayList<RowSorter.SortKey>();
+		sortKeys.add(new RowSorter.SortKey(1, order));
+		sorter.setSortKeys(sortKeys);
+	}
+
+	@Override
+	public void valueChanged(ListSelectionEvent arg0) {
+		int viewRow = tblMods.getSelectedRow();
+		if (viewRow < 0)
+			// TODO
+			return;
+		int modelRow = tblMods.convertRowIndexToModel(viewRow);
+		NodeMod mod = (NodeMod) model.getValueAt(modelRow, -1);
+		// TODO
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.properties	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.properties	(revision 591)
@@ -0,0 +1,21 @@
+# Main Frame
+frame.title=AE Installer 2
+buttonExit.title=Exit
+version=0.1
+
+menu.file=&Menu
+menu.settings=&Settings
+menu.settingsTooltip=Settings
+menu.exit=&Exit\tCtrl+Q
+menu.exitTooltip=Exit
+
+lblModTypes.text=Mod types: 
+
+
+updateDepot.title=Updating Mod Depot cache
+
+updatesAvailable.title=Updates available
+updatesAvailable.text=Some mods have newer versions available.
+
+invalidPath.title=Wrong directory
+invalidPath.text=This program has to be placed in the subfolder Edition/AEInstaller inside a vanilla Oni folder.\nThe full path of the .jar-file has to be:\nOniFolder/Edition/AEInstaller/AEInstaller2.jar
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.yml
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.yml	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.yml	(revision 591)
@@ -0,0 +1,44 @@
+JFrame:
+  name: frame
+  title: frame.title
+  size: 800x600
+  minimumSize: 400x300
+  locationRelativeTo: null
+  defaultCloseOperation: doNothingOnClose
+  onWindowOpened: [validatePath,execDepotUpdate,checkUpdates,focus]
+  onWindowClosing: [$confirm,closeFrames,saveLocalData,exit]
+  iconImage: img.kdt
+  content:
+    - Action(name=exitAction, text=menu.exit, toolTipText=menu.exitTooltip, icon=img.exit, onAction=[$confirm,closeFrames,saveLocalData,exit])
+    - Action(name=settings, text=menu.settings, toolTipText=menu.settingsTooltip, icon=img.settings, onAction=[showSettings])
+    - JMenuBar:
+        - JMenu(name=fileMenu, text=menu.file):
+            - JMenuItem(action=settings)
+            - JMenuItem(action=exitAction)
+    - JToolBar(name=tools, floatable=false, orientation=0):
+        - JButton(action=exitAction, hideActionText=true)
+        - JToolBarSeparator()
+        - JButton(action=settings, hideActionText=true)
+    - JPanel(name=contents):
+        - JPanel(name=panMods):
+            - JLabel(name=lblModTypes, text=lblModTypes.text)
+            - JComboBox(name=cmbModTypes, onAction=modTypeSelection)
+            - JScrollPane(name=scrollMods, vScrollBar=always, hScrollBar=never):
+                JTable(name=tblMods)
+            - JButton(name=btnSortAlpha, onAction=sortAlpha, text=A-Z)
+            - JButton(name=btnSortNumber, onAction=sortPackageNumber, text=0-9)
+            - MigLayout: |
+                 [grow]
+                 lblModTypes<,cmbModTypes           [min]
+                 scrollMods                         [grow]
+                 >btnSortAlpha=1<,btnSortNumber=1<  [min]
+        - MigLayout: |
+             [pref]
+             panMods        [grow]
+    - MigLayout:
+        layoutConstraints: wrap 1
+        columnConstraints: grow
+        rowConstraints: grow
+        constraints:
+            - tools: dock north
+            - contents: grow
Index: AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableFilter.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableFilter.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableFilter.java	(revision 591)
@@ -0,0 +1,43 @@
+package net.oni2.aeinstaller.gui.modtable;
+
+import java.util.HashSet;
+
+import javax.swing.RowFilter;
+
+import net.oni2.aeinstaller.backend.Settings;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.backend.depot.model.NodeMod;
+
+/**
+ * @author Christian Illy
+ */
+public class ModTableFilter extends RowFilter<ModTableModel, Integer> {
+	int type = -1;
+
+	/**
+	 * @param type
+	 *            Type of mods to show (-1 for all)
+	 */
+	public ModTableFilter(int type) {
+		super();
+		this.type = type;
+	}
+
+	@Override
+	public boolean include(
+			javax.swing.RowFilter.Entry<? extends ModTableModel, ? extends Integer> entry) {
+		NodeMod mod = (NodeMod) entry.getModel().getValueAt(
+				entry.getIdentifier(), -1);
+
+		if (!DepotManager.getInstance().isModValidOnPlatform(mod,
+				Settings.getPlatform()))
+			return false;
+
+		if (type < 0)
+			return true;
+
+		HashSet<Integer> types = new HashSet<Integer>();
+		types.add(type);
+		return DepotManager.getInstance().isModOfType(mod, types, false);
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.java	(revision 591)
@@ -0,0 +1,158 @@
+package net.oni2.aeinstaller.gui.modtable;
+
+import java.util.ResourceBundle;
+import java.util.Vector;
+
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableColumn;
+
+import net.oni2.aeinstaller.backend.depot.DepotConfig;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.backend.depot.model.NodeMod;
+
+/**
+ * @author Christian Illy
+ */
+public class ModTableModel extends AbstractTableModel {
+
+	private static final long serialVersionUID = -8278155705802697354L;
+
+	private ResourceBundle bundle = ResourceBundle.getBundle(getClass()
+			.getName());
+
+	private Vector<NodeMod> items;
+	private int vocabModTypeID = -1;
+	private int vocabPlatformID = -1;
+
+	/**
+	 * Create a new model
+	 */
+	public ModTableModel() {
+		reloadData();
+	}
+
+	@Override
+	public Object getValueAt(int row, int col) {
+		NodeMod node = items.get(row);
+		switch (col) {
+			case -1:
+				return node;
+			case 0:
+				return node.getTitle();
+			case 1:
+				return node.getFields().get("package_number");
+			case 2:
+				String type = "";
+				if (vocabModTypeID < 0) {
+					vocabModTypeID = DepotManager.getInstance()
+							.getVocabulary(DepotConfig.MODTYPE_VOCAB).getVid();
+				}
+				for (int tid : node.getTaxonomyTerms().get(vocabModTypeID)) {
+					if (type.length() > 0)
+						type += ", ";
+					type += DepotManager.getInstance().getTaxonomyTerm(tid)
+							.getName();
+				}
+				return type;
+			case 3:
+				if (vocabPlatformID < 0) {
+					vocabPlatformID = DepotManager.getInstance()
+							.getVocabulary(DepotConfig.PLATFORM_VOCAB).getVid();
+				}
+				int tid = node.getTaxonomyTerms().get(vocabPlatformID)
+						.iterator().next();
+				return DepotManager.getInstance().getTaxonomyTerm(tid)
+						.getName();
+		}
+		return null;
+	}
+
+	@Override
+	public String getColumnName(int col) {
+		switch (col) {
+			case 0:
+				return bundle.getString("mod.name");
+			case 1:
+				return bundle.getString("mod.package_number");
+			case 2:
+				return bundle.getString("mod.type");
+			case 3:
+				return bundle.getString("mod.platform");
+		}
+		return null;
+	}
+
+	@Override
+	public int getRowCount() {
+		return items.size();
+	}
+
+	@Override
+	public int getColumnCount() {
+		return 4;
+	}
+
+	@Override
+	public Class<?> getColumnClass(int col) {
+		switch (col) {
+			case 0:
+				return String.class;
+			case 1:
+				return Integer.class;
+			case 2:
+				return String.class;
+			case 3:
+				return String.class;
+		}
+		return null;
+	}
+
+	/**
+	 * Set the constraints on the columns size for the given column
+	 * 
+	 * @param colNum
+	 *            Column number
+	 * @param col
+	 *            Column object
+	 */
+	public void setColumnConstraints(int colNum, TableColumn col) {
+		int w;
+		switch (colNum) {
+			case 0:
+				col.setPreferredWidth(150);
+				break;
+			case 1:
+				w = 55;
+				col.setPreferredWidth(w);
+				col.setMinWidth(w);
+				col.setMaxWidth(w);
+				break;
+			case 2:
+				col.setPreferredWidth(100);
+				break;
+			case 3:
+				w = 70;
+				col.setPreferredWidth(w);
+				col.setMinWidth(w);
+				col.setMaxWidth(w);
+				break;
+		}
+	}
+
+	/**
+	 * Reload the nodes data after an update to the cache
+	 */
+	public void reloadData() {
+		items = DepotManager.getInstance().getModPackageNodes();
+	}
+
+	/**
+	 * Get the items vector
+	 * 
+	 * @return Items
+	 */
+	public Vector<NodeMod> getItems() {
+		return items;
+	}
+
+}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.properties	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.properties	(revision 591)
@@ -0,0 +1,4 @@
+mod.name=Name
+mod.type=Type
+mod.package_number=Pkg#
+mod.platform=Platform
Index: AE/installer2/src/net/oni2/aeinstaller/gui/settings/LaFComboModel.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/settings/LaFComboModel.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/settings/LaFComboModel.java	(revision 591)
@@ -0,0 +1,97 @@
+package net.oni2.aeinstaller.gui.settings;
+
+import java.util.HashSet;
+import java.util.Vector;
+
+import javax.swing.ComboBoxModel;
+import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+import javax.swing.event.ListDataListener;
+
+import net.oni2.aeinstaller.backend.Settings;
+
+/**
+ * Comboboxmodel for Look and Feel selection
+ * 
+ * @author Christian Illy
+ */
+public class LaFComboModel implements ComboBoxModel {
+
+	Vector<LookAndFeelInfo> items;
+	HashSet<ListDataListener> listeners;
+	int selected;
+
+	/**
+	 * Create a new LaF model
+	 */
+	public LaFComboModel() {
+		listeners = new HashSet<ListDataListener>();
+		items = new Vector<LookAndFeelInfo>();
+		for (LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels())
+			items.add(laf);
+
+		String laf = Settings.getInstance().get("lookandfeel",
+				UIManager.getSystemLookAndFeelClassName());
+
+		if (items.size() > 0)
+			selected = 0;
+		else
+			selected = -1;
+
+		for (int i = 0; i < items.size(); i++) {
+			String iLaF = items.get(i).getClassName();
+			if (iLaF.equals(laf)) {
+				selected = i;
+				break;
+			}
+		}
+	}
+
+	@Override
+	public int getSize() {
+		return items.size();
+	}
+
+	@Override
+	public Object getElementAt(int index) {
+		return items.get(index).getName();
+	}
+
+	/**
+	 * Get class name for the selected LaF
+	 * 
+	 * @return LaF class name
+	 */
+	public String getSelectedClassName() {
+		return items.get(selected).getClassName();
+	}
+
+	@Override
+	public void addListDataListener(ListDataListener arg0) {
+		listeners.add(arg0);
+	}
+
+	@Override
+	public void removeListDataListener(ListDataListener arg0) {
+		listeners.remove(arg0);
+	}
+
+	@Override
+	public Object getSelectedItem() {
+		if (selected >= 0)
+			return getElementAt(selected);
+		else
+			return null;
+	}
+
+	@Override
+	public void setSelectedItem(Object anItem) {
+		for (int i = 0; i < items.size(); i++) {
+			if (getElementAt(i).equals(anItem)) {
+				selected = i;
+				return;
+			}
+		}
+		selected = -1;
+	}
+}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.java	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.java	(revision 591)
@@ -0,0 +1,110 @@
+package net.oni2.aeinstaller.gui.settings;
+
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+import net.oni2.aeinstaller.backend.Settings;
+
+import org.javabuilders.BuildResult;
+import org.javabuilders.swing.SwingJavaBuilder;
+
+/**
+ * @author Christian Illy
+ */
+public class SettingsDialog extends JFrame {
+	private static final long serialVersionUID = -5719515325671846620L;
+
+	private ResourceBundle bundle = ResourceBundle.getBundle(getClass()
+			.getName());
+	@SuppressWarnings("unused")
+	private BuildResult result = SwingJavaBuilder.build(this, bundle);
+
+	private static SettingsDialog openWindow = null;
+
+	private JComboBox cmbLaF;
+	private LaFComboModel laFModel;
+
+	/**
+	 * Open the Settings dialog if not currently opened or brings the existing
+	 * one to front
+	 */
+	public static void openWindow() {
+		if (openWindow != null) {
+			SwingUtilities.invokeLater(new Runnable() {
+
+				@Override
+				public void run() {
+					openWindow.toFront();
+					openWindow.repaint();
+				}
+			});
+		} else {
+			new SettingsDialog().setVisible(true);
+		}
+	}
+
+	private SettingsDialog() {
+		openWindow = this;
+		setMinimumSize(new Dimension(500, (int) getSize().getHeight() + 0));
+
+		AbstractAction closeAction = new AbstractAction() {
+
+			private static final long serialVersionUID = 1L;
+
+			public void actionPerformed(ActionEvent arg0) {
+				dispose();
+			}
+		};
+		KeyStroke ksCtrlW = KeyStroke
+				.getKeyStroke('W', KeyEvent.CTRL_DOWN_MASK);
+		getRootPane()
+				.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
+				.put(ksCtrlW, "close");
+		getRootPane().getActionMap().put("close", closeAction);
+
+		initValues();
+	}
+
+	private void initValues() {
+		Settings set = Settings.getInstance();
+
+		laFModel = new LaFComboModel();
+		cmbLaF.setModel(laFModel);
+	}
+
+	@SuppressWarnings("unused")
+	private void close() {
+		openWindow = null;
+	}
+
+	@SuppressWarnings("unused")
+	private boolean save() {
+		Settings set = Settings.getInstance();
+
+		String oldLaf = set.get("lookandfeel",
+				UIManager.getSystemLookAndFeelClassName());
+		String newLaf = laFModel.getSelectedClassName();
+
+		if (!newLaf.equals(oldLaf)) {
+			set.put("lookandfeel", newLaf);
+			JOptionPane.showMessageDialog(this,
+					bundle.getString("newLaF.text"),
+					bundle.getString("newLaF.title"),
+					JOptionPane.INFORMATION_MESSAGE);
+		}
+
+		return true;
+	}
+
+}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.properties	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.properties	(revision 591)
@@ -0,0 +1,16 @@
+frame.title=AE Installer: Settings
+btnOk=Save
+btnCancel=Cancel
+
+panUI=Look and feel
+lblLaF=GUI theme:
+
+panSetupDefaults=Default values for the installations
+lblCreateDesktop=Link auf Desktop erstellen:
+lblDesktopFolder=Desktop-Ordner:
+txtDesktopFolder=Relativ zum Desktop-Ordner. Z.B. "." um die Verknüpfungen direkt auf dem Desktop abzulegen, "Spiele" um die Verknüpfungen in einen Unterordner Spiele auf dem Desktop zu legen.
+lblUnattended=<html>Schnelles Setup (obige<br>Einstellungen nicht im Setup änderbar):</html> 
+
+
+newLaF.text=A new GUI theme was selected.\nPlease restart the application in order to apply the changes.
+newLaF.title=Restart to apply theme
Index: AE/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.yml
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.yml	(revision 591)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.yml	(revision 591)
@@ -0,0 +1,32 @@
+JFrame:
+  name: frame
+  title: frame.title
+  size: packed
+  locationRelativeTo: null
+  defaultCloseOperation: disposeOnClose
+  onWindowClosed: [close]
+  iconImage: img.kdt
+  content:
+    - JButton(name=btnOk, text=btnOk, onAction=[save,dispose])
+    - JButton(name=btnCancel, text=btnCancel, onAction=[dispose])
+    - JPanel(name=panSetupDefaults, groupTitle=panSetupDefaults):
+      - JLabel(name=lblCreateDesktop, text=lblCreateDesktop)
+      - JCheckBox(name=chkCreateDesktop)
+      - JLabel(name=lblDesktopFolder, text=lblDesktopFolder)
+      - JTextField(name=txtDesktopFolder, toolTipText=txtDesktopFolder)
+      - MigLayout: |
+           [min]                [grow]
+           >lblCreateDesktop    chkCreateDesktop         [pref]
+           >lblDesktopFolder    txtDesktopFolder         [pref]
+    - JPanel(name=panUI, groupTitle=panUI):
+      - JLabel(name=lblLaF, text=lblLaF)
+      - JComboBox(name=cmbLaF, minimumSize=150x10)
+      - MigLayout: |
+           [min]        [grow]
+           >lblLaF      cmbLaF    [pref]
+    - MigLayout: |
+         [grow]
+         panSetupDefaults        [pref]
+         panUI                   [pref]
+         >btnOk+*=1,btnCancel=1  [min]
+  
