Index: java/installer2/.classpath
===================================================================
--- java/installer2/.classpath	(revision 722)
+++ java/installer2/.classpath	(revision 722)
@@ -0,0 +1,30 @@
+<?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="/home/ci/Development/Oni/_Libraries/swingbuilder/betterbeansbinding-core-1.3.0.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/betterbeansbinding-el-1.3.0.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/betterbeansbinding-swingbinding-1.3.0.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/commons-lang-2.4.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/javabuilder-core-1.1.0.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/javabuilder-swing-1.1.0.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/jsr305-1.3.7.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/miglayout-3.7.1.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/slf4j-api-1.6.1.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/slf4j-simple-1.6.1.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/swingbuilder/snakeyaml-1.7.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/JSON-java.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/httpclient/httpclient-4.2.2.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/httpclient/httpcore-4.2.2.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/httpclient/commons-logging-1.1.1.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/xstream-1.4.3.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/macify.jar"/>
+	<classpathentry kind="lib" path="/home/ci/Development/Oni/_Libraries/commons-io-2.4.jar">
+		<attributes>
+			<attribute name="javadoc_location" value="jar:platform:/resource/OniJavaLibraries/commons-io-2.4-javadoc.jar!/"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry combineaccessrules="false" kind="src" path="/ProgramSettings"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/PlatformTools"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
Index: java/installer2/.project
===================================================================
--- java/installer2/.project	(revision 722)
+++ java/installer2/.project	(revision 722)
@@ -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: java/installer2/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- java/installer2/.settings/org.eclipse.jdt.core.prefs	(revision 722)
+++ java/installer2/.settings/org.eclipse.jdt.core.prefs	(revision 722)
@@ -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: java/installer2/.settings/org.eclipse.ltk.core.refactoring.prefs
===================================================================
--- java/installer2/.settings/org.eclipse.ltk.core.refactoring.prefs	(revision 722)
+++ java/installer2/.settings/org.eclipse.ltk.core.refactoring.prefs	(revision 722)
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false
Index: java/installer2/doc/AEI1-stuff_onisplit-commands.txt
===================================================================
--- java/installer2/doc/AEI1-stuff_onisplit-commands.txt	(revision 722)
+++ java/installer2/doc/AEI1-stuff_onisplit-commands.txt	(revision 722)
@@ -0,0 +1,30 @@
+filter mod-types from combobox with no nodes
+
+save current installed config
+
+install
+select all (only of current type?)
+revert - select mods that are currently installed
+
+
+globalization:
+
+installation:
+- remove current .dat/.raw/.sep from GDF
+- remove current BSL-folders?
+- combine vanilla+default-mods+selected-mods
+- combine BSL
+
+
+orig:
+globalization: installer.cpp/h - globalizeData() in #31
+installation: installer.cpp/h - recompileAll() in #500
+
+
+
+
+check .net>2.0:
+win: http://msdn.microsoft.com/en-us/kb/kbarticle.aspx?id=318785
+mac: which mono
+
+http://www.microsoft.com/downloads/details.aspx?familyid=0856eacb-4362-4b0d-8edd-aab15c5e04f5
Index: java/installer2/doc/conflict-resolving.txt
===================================================================
--- java/installer2/doc/conflict-resolving.txt	(revision 722)
+++ java/installer2/doc/conflict-resolving.txt	(revision 722)
@@ -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: java/installer2/doc/drupal_node_types_json.txt
===================================================================
--- java/installer2/doc/drupal_node_types_json.txt	(revision 722)
+++ java/installer2/doc/drupal_node_types_json.txt	(revision 722)
@@ -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: java/installer2/doc/drupal_services.txt
===================================================================
--- java/installer2/doc/drupal_services.txt	(revision 722)
+++ java/installer2/doc/drupal_services.txt	(revision 722)
@@ -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: java/installer2/doc/nodes-with-multiple-files.txt
===================================================================
--- java/installer2/doc/nodes-with-multiple-files.txt	(revision 722)
+++ java/installer2/doc/nodes-with-multiple-files.txt	(revision 722)
@@ -0,0 +1,40 @@
+Node 210, Files 2, Platform Both, Type Tool, InstMeth (none)
+Node 203, Files 2, Platform Windows, Type Tool, InstMeth (none)
+Node 184, Files 2, Platform Both, Type Tool, InstMeth (none)
+Node 172, Files 2, Platform Windows, Type Tool, InstMeth File swap
+Node 129, Files 2, Platform Both, Type Tool, InstMeth (none)
+Node 118, Files 2, Platform Windows, Type Tool, InstMeth (none)
+Node 72, Files 3, Platform Windows, Type Tool, InstMeth (none)
+Node 38, Files 5, Platform Both, Type Tool, InstMeth (none)
+
+Node 144, Files 3, Platform Both, Type Script, InstMeth (none)
+Node 114, Files 2, Platform Both, Type Script, InstMeth File swap
+Node 94, Files 3, Platform Both, Type Script, InstMeth File swap
+
+Node 222, Files 3, Platform Both, Type Character, InstMeth Package
+Node 214, Files 2, Platform Both, Type Texture, InstMeth Package
+Node 208, Files 2, Platform Both, Type Level, InstMeth Package
+Node 198, Files 1, Platform Both, Type Level, InstMeth Package
+Node 194, Files 1, Platform Both, Type Level, InstMeth Package
+Node 193, Files 2, Platform Both, Type Character, InstMeth Package
+Node 191, Files 2, Platform Both, Type Character, InstMeth Package
+Node 183, Files 2, Platform Windows, Type Character, InstMeth Package
+Node 181, Files 3, Platform Both, Type Level, InstMeth Package
+Node 180, Files 2, Platform Windows, Type Character, InstMeth Package
+Node 177, Files 2, Platform Both, Type Other, InstMeth Package
+Node 147, Files 1, Platform Windows, Type Character, InstMeth Package
+Node 134, Files 2, Platform Both, Type Character, InstMeth Package
+Node 123, Files 1, Platform Both, Type Level, InstMeth Package
+Node 121, Files 2, Platform Both, Type Character, InstMeth Package
+Node 108, Files 1, Platform Both, Type Character, InstMeth Package
+Node 101, Files 3, Platform Windows, Type Character, InstMeth Package
+Node 82, Files 1, Platform Both, Type Texture, InstMeth Package
+Node 63, Files 1, Platform Both, Type Script, InstMeth Package
+
+
+if you filter out those which only have multiple files cause of win+mac and old versions you get the following *packages* with multiple files:
+Node 214, Files 2, Platform Both, Type Texture, InstMeth Package - Split into two packages, lighter = created by VR/Samer #+1
+Node 193, Files 2, Platform Both, Type Character, InstMeth Package - Split into two, shapeshifted = #+1
+Node 181, Files 3, Platform Both, Type Level, InstMeth Package - split, dependency, delete noblue?
+Node 134, Files 2, Platform Both, Type Character, InstMeth Package - Split into two packages, #+1
+Node 101, Files 3, Platform Windows, Type Character, InstMeth Package - merge + separate package #+1
Index: java/installer2/locales/net/oni2/aeinstaller/localization/CorePackagesDialog_de.properties
===================================================================
--- java/installer2/locales/net/oni2/aeinstaller/localization/CorePackagesDialog_de.properties	(revision 722)
+++ java/installer2/locales/net/oni2/aeinstaller/localization/CorePackagesDialog_de.properties	(revision 722)
@@ -0,0 +1,1 @@
+frame.title=AE Installer: Kernpakete
Index: java/installer2/locales/net/oni2/aeinstaller/localization/Downloader_de.properties
===================================================================
--- java/installer2/locales/net/oni2/aeinstaller/localization/Downloader_de.properties	(revision 722)
+++ java/installer2/locales/net/oni2/aeinstaller/localization/Downloader_de.properties	(revision 722)
@@ -0,0 +1,15 @@
+frame.title=AE Installer: Mods herunterladen
+
+btnAbort.title=Abbrechen
+lblName.title=Aktuelle Datei:
+lblIsDep.title= (Abhängigkeit)
+lblElapsed.title=Vergangen:
+lblRemaining.title=Verbleibend:
+lblDownloaded.title=Heruntergeladen:
+lblTotal.title=Gesamtgröße:
+lblRate.title=Geschwindigkeit:
+
+abort.text=Wenn du den Download jetzt abbrichst wird die Installation als gesamtes abgebrochen.\nWirklich abbrechen?
+abort.title=Wirklich abbrechen?
+
+unpacking=keine - entpacke Dateien
Index: java/installer2/locales/net/oni2/aeinstaller/localization/Global_de.properties
===================================================================
--- java/installer2/locales/net/oni2/aeinstaller/localization/Global_de.properties	(revision 722)
+++ java/installer2/locales/net/oni2/aeinstaller/localization/Global_de.properties	(revision 722)
@@ -0,0 +1,11 @@
+offlineMode.title=Offline-Modus
+offlineMode.text=AEI läuft im Offline-Modus.\nAktualisierungen oder Downloads sind nicht möglich.\nBitte starte AEI neu, wenn du eine Verbindung zum Internet hast, damit du wieder Aktualisierungen und Downloads ausführen kannst.
+
+invalidPath.title=Falscher Ordner
+invalidPath.text=Dieses Programm muss im Unterordner Edition/AEInstaller innerhalb einer regulären Oni-Installation liegen.\nDer volle Pfad zu der .jar-Datei sollte so aussehen:\nOniFolder/Edition/AEInstaller/AEInstaller2.jar
+
+dotNetMissing.title=.NET ist nicht installiert
+dotNetMissing.text=.NET, welches für dieses Programm benötigt wird, ist nicht installiert.<br>Bitte lade es herunter und installiere es:<br>%1
+
+offlineModeStartup.title=Offline-Modus
+offlineModeStartup.text=Es konnte keine Verbindung zum ModDepot hergestellt werden.\nAEI wird im Offline-Modus ausgeführt.\nAktualisierungen oder Downloads sind nicht möglich.
Index: java/installer2/locales/net/oni2/aeinstaller/localization/MainWin_de.properties
===================================================================
--- java/installer2/locales/net/oni2/aeinstaller/localization/MainWin_de.properties	(revision 722)
+++ java/installer2/locales/net/oni2/aeinstaller/localization/MainWin_de.properties	(revision 722)
@@ -0,0 +1,99 @@
+# Main Frame
+buttonExit.title=Beenden
+
+menu.main=&Programm
+menu.settings=&Einstellungen
+menu.settingsTooltip=Einstellungen
+menu.about=Über
+menu.aboutTooltip=Über
+menu.exit=&Beenden\tCtrl+Q
+menu.exitTooltip=Beenden
+
+menu.file=&Datei
+menu.runOniFull=&Oni starten (Vollbild)
+menu.runOniFullTooltip=Starte Oni im Vollbildmodus
+menu.runOniWin=Oni starten (&Fenster)
+menu.runOniWinTooltip=Starte Oni im Fenstermodus
+menu.openEditionFolder=Edition-Ordner öffnen
+menu.openEditionFolderTooltip=Öffne den Ordner der Edition im Dateiexplorer
+menu.loadConfig=Mod-Auswahl &laden...
+menu.loadConfigTooltip=Lade eine Auswahl von Mods aus einer Datei
+menu.saveConfig=&Speichere die aktuelle Mod-Auswahl...
+menu.saveConfigTooltip=Speichere die aktuelle Mod-Auswahl in eine Datei
+
+menu.corePackages=&Zeige Kernpakete
+menu.corePackagesTooltip=Zeige welche Kernpakete genutzt werden
+menu.reglobalize=&Baue die Kerndaten neu auf
+menu.reglobalizeTooltip=Baue die Kerndaten neu auf
+menu.update=Überprüfe auf Aktualisierungen
+menu.updateTooltip=Überprüfe auf Aktualisierungen für Pakete die bereits heruntergeladen wurden
+
+menu.tools=&Tools
+menu.manageTools=&Tools verwalten
+menu.manageToolsTooltip=Installiere/Entferne Tools
+
+btnUnSelectAll.text=Alle an/abwählen
+btnRevertSelection.text=Auswahl zurücksetzen
+btnRevertSelection.tooltip=Wähle Mods, die zur Zeit installiert sind
+btnInstall.text=&Installieren
+btnInstall.tooltip=Installiere die ausgewählten Mods
+lblModTypes.text=Mod Typ: 
+lblShowOnly.text=Zeige:
+radAll.text=Alle
+radOnline.text=Online
+radLocal.text=Heruntergeladen
+lblShowFilter.text=Filter:
+lblSelectedMods.text=Mods für Installation ausgewählt:
+lblDownloadSize.text=Größe der herunterzuladenden Dateien: 
+
+updateDepot.title=Aktualisiere Mod Depot Cache
+
+rebuildCore.title=Kerndaten neu aufbauen
+rebuildCore.text=Das neuaufbauen der Kerndaten der Edition dauert einige Minuten.\nDieser Vorgang ist außerdem normalerweise nicht nötig.\nWillst du wirklich fortfahren? 
+
+installDependencies.title=Unerfüllte Abhängigkeiten
+installDependencies.text=Es gibt zur Zeit unerfüllte Abhängigkeiten für einige Mods.\n\nDie folgenden Mods sind bereits heruntergeladen und werden ausgewählt:\n%s\n\n\
+Diese Mods werden zusätzlich heruntergeladen:\n%s\n\n\
+Die Größe der herunterzuladenden Dateien beträgt: %s.\n\Fortfahren?
+installDependencies.none=<Keine>
+installIncompatibilities.title=Inkompatible Mods
+installIncompatibilities.text=Die folgenden Mods sind inkompatibel:\n\n%s\n\nDie Installation wird abgebrochen.
+installDone.title=Installation abgeschlossen
+installDone.text=Du kannst AE Oni nun spielen.
+installDoneDeps.text=Zusätzlich zu den manuell gewählten Mods wurden\nfolgende Mods installiert um Abhängigkeiten zu erfüllen:\n%s\n\nDu kannst AE Oni nun spielen.
+
+updatesAvailable.title=Aktualisierungen verfügbar
+updatesAvailable.text=Für die folgenden Mods und Tools gibt es neuere Versionen im Mod Depot.<br>Wähle die Pakete, die du aktualisieren willst.
+updatesAvailableSize.text=Größe der herunterzuladenden Dateien: %s.<br>Jetzt aktualisieren?
+updatesNotAvailable.title=Keine Aktualisierungen verfügbar
+updatesNotAvailable.text=Es gibt zur Zeit keine Aktualisierungen für die Mods und Tools, die du heruntergeladen hast.
+checkOnStartup.text=Beim Start auf Aktualisierungen prüfen?
+
+noOniSplit.title=OniSplit nicht vorhanden
+noOniSplit.text=Die Edition ist noch nicht initialisiert und OniSplit ist nicht vorhanden (Offline-Modus?).\nKann hier nicht fortfahren, beende.
+askInitialize.title=Initialisiere Edition
+askInitialize.text=Die Edition ist noch nicht initialisiert.\nWenn du jetzt nicht initialisierst wird der Installer beendet.\nEdition nun initialisieren?
+
+initializingEdition.title=Initialisiere Edition Kerndaten
+installing.title=Installiere Mods
+checkCorePackages.title=Überprüfe Kernpakete
+coreToolsInstall.title=Installiere Kernpakete
+corePackagesUpdated.title=Kernpakete aktualisiert
+corePackagesUpdated.text=Die folgenden Kernpakete wurden aktualisiert:%s
+
+doUpdate.title=Aktualisiere Pakete
+
+jreNotFound.text=Dieses Tool benötigt eine JRE aber es wurde keine gefunden.
+jreNotFound.title=JRE nicht gefunden
+
+dotNetNotFound.text=Dieses Tool benötigt eine .NET Runtime aber es wurde keine gefunden.
+dotNetNotFound.title=.NET Runtime nicht gefunden
+
+wineNotFound.text=Wine wird benötigt um ein Windows-Programm unter Linux auszuführen, konnte aber nicht gefunden werden.
+wineNotFound.title=Wine nicht gefunden
+
+oniExeNotFound.text=Oni's Programmdatei wurde nicht gefunden.
+oniExeNotFound.title=Oni nicht gefunden
+
+exeNotFound.text=Programmdatei wurde nicht gefunden.
+exeNotFound.title=Programmdatei nicht gefunden
Index: java/installer2/locales/net/oni2/aeinstaller/localization/ModTable_de.properties
===================================================================
--- java/installer2/locales/net/oni2/aeinstaller/localization/ModTable_de.properties	(revision 722)
+++ java/installer2/locales/net/oni2/aeinstaller/localization/ModTable_de.properties	(revision 722)
@@ -0,0 +1,29 @@
+mod.name=Name
+mod.package_number=Pkg#
+mod.install=Installieren
+mod.creator=Entwickler
+mod.state=Status
+mod.added=Hinzugefügt
+mod.date=Letzte Änderung
+
+filterTo.ALL=Alles
+filterTo.NAME=Mod Name
+filterTo.CREATOR=Entwickler
+filterTo.DESCRIPTION=Beschreibung
+
+state.installed=Ist <b>I</b>nstalliert
+state.updatable=<b>U</b>pdate verfügbar
+state.downloaded=Ist <b>D</b>ownloaded
+yes=Ja
+no=Nein
+
+openModFolder.text=Ordner des Mods öffnen
+openDepotPage.text=Depot-Seite des Mods im Browser öffnen
+downloadPackage.text=Lade Paket (erneut) herunter
+deletePackage.text=Lösche lokales Paket
+
+deletePackageLocalOnly.title=Nur lokal vorhandenes Paket
+deletePackageLocalOnly.text=Dieses Paket scheint nicht im Depot zu existieren.\nLöschen dieses Paketes im AEI ist aus Sicherheitsgründen verboten.
+
+deletePackageConfirm.title=Paketverzeichnis löschen
+deletePackageConfirm.text=Willst du das heruntergeladene Paket wirklich löschen?
Index: java/installer2/locales/net/oni2/aeinstaller/localization/PackageInfoBox_de.properties
===================================================================
--- java/installer2/locales/net/oni2/aeinstaller/localization/PackageInfoBox_de.properties	(revision 722)
+++ java/installer2/locales/net/oni2/aeinstaller/localization/PackageInfoBox_de.properties	(revision 722)
@@ -0,0 +1,9 @@
+lblTitle.text=Name:
+lblCreator.text=Entwickler:
+lblTypes.text=Typen:
+lblPlatform.text=Plattform:
+lblPackageNumber.text=Paketnummer:
+lblVersionNumber.text=Versionsnummer:
+lblFiles.text=Anzahl der Dateien:
+lblDescription.text=Beschreibung:
+lblDownloadSize.text=Downloadgröße:
Index: java/installer2/locales/net/oni2/aeinstaller/localization/SettingsDialog_de.properties
===================================================================
--- java/installer2/locales/net/oni2/aeinstaller/localization/SettingsDialog_de.properties	(revision 722)
+++ java/installer2/locales/net/oni2/aeinstaller/localization/SettingsDialog_de.properties	(revision 722)
@@ -0,0 +1,21 @@
+frame.title=AE Installer: Einstellungen
+btnOk=Speichern
+btnCancel=Abbrechen
+
+panUI=Aussehen
+lblLaF=Oberflächendesign:
+
+panCommon=Allgemeine Einstellungen
+lblNotifyOnStart=Benachrichtige über Aktualisierungen beim Start:
+lblNotifyDepsAfterInstall=Benachrichtige über Abhängigkeiten nach der Installation:
+lblNotifyDepsAfterInstall.tooltip=<html>\
+Normalerweise werden unerfüllte Abhängigkeiten angezeigt <b>bevor</b> die<br>\
+Installation fortgesetzt wird, damit du die Wahl hast den Download abzubrechen.<br>\
+Wenn du diese Option wählst wird die Installation komplett durchgeführt,<br>\
+auch wenn dafür weitere Pakete heruntergeladen werden. Du wirst dann am <b>Ende</b><br>\
+informiert, welche zusätzlichen Pakete installiert wurden.</html>
+lblCopyIntro=Kopiere Vorspannvideo bei der Installation:
+lblCopyOutro=Kopiere Abspannvideo bei der Installation:
+
+newLaF.text=Ein neues Oberflächendesign wurde gewählt.\nBitte die Anwendung neu starten damit die Änderung wirksam wird.
+newLaF.title=Neustarten um Änderung anzuwenden
Index: java/installer2/locales/net/oni2/aeinstaller/localization/ToolManager_de.properties
===================================================================
--- java/installer2/locales/net/oni2/aeinstaller/localization/ToolManager_de.properties	(revision 722)
+++ java/installer2/locales/net/oni2/aeinstaller/localization/ToolManager_de.properties	(revision 722)
@@ -0,0 +1,6 @@
+frame.title=AE Installer: Tool Verwaltung
+
+btnInstall.text=Installiere
+btnInstall.tooltip=Installiere dieses Tool
+btnInstall.un.text=Entferne
+btnInstall.un.tooltip=Entferne dieses Tool
Index: java/installer2/src/net/oni2/aeinstaller/AEInstaller.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 722)
@@ -0,0 +1,2 @@
+appname=AE Installer 2
+appversion=0.99v
Index: java/installer2/src/net/oni2/aeinstaller/AEInstaller2.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/AEInstaller2.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/AEInstaller2.java	(revision 722)
@@ -0,0 +1,262 @@
+package net.oni2.aeinstaller;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ResourceBundle;
+
+import javax.imageio.ImageIO;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JToolBar;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.UIManager.LookAndFeelInfo;
+
+import net.oni2.SettingsManager;
+import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.SizeFormatter;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.backend.oni.Installer;
+import net.oni2.aeinstaller.backend.oni.OniSplit;
+import net.oni2.aeinstaller.gui.HTMLLinkLabel;
+import net.oni2.aeinstaller.gui.MainWin;
+import net.oni2.platformtools.PlatformInformation;
+import net.oni2.platformtools.PlatformInformation.Platform;
+import net.oni2.platformtools.applicationinvoker.DotNet;
+
+import org.javabuilders.swing.SwingJavaBuilder;
+import org.simplericity.macify.eawt.Application;
+import org.simplericity.macify.eawt.DefaultApplication;
+
+/**
+ * @author Christian Illy
+ */
+public class AEInstaller2 {
+
+	private static ResourceBundle imagesBundle;
+	private static ResourceBundle basicBundle;
+	private static ResourceBundle globalBundle;
+
+	private static Application app = null;
+
+	private static void initMacOS() {
+		System.setProperty("apple.laf.useScreenMenuBar", "true");
+		System.setProperty("com.apple.mrj.application.apple.menu.about.name",
+				basicBundle.getString("appname"));
+		app = new DefaultApplication();
+
+		URL icon = AEInstaller2.class.getResource(imagesBundle
+				.getString("img.ae"));
+		try {
+			BufferedImage img = ImageIO.read(icon);
+			app.setApplicationIconImage(img);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+	
+	private static void initBundles() {
+		File localesDir = CaseInsensitiveFile.getCaseInsensitiveFile(Paths.getInstallerPath(), "locales");
+		if (localesDir.isDirectory())
+			addClassPath(localesDir);
+		imagesBundle = ResourceBundle.getBundle("net.oni2.aeinstaller.Images");
+		basicBundle = ResourceBundle
+				.getBundle("net.oni2.aeinstaller.AEInstaller");
+		globalBundle = ResourceBundle
+				.getBundle("net.oni2.aeinstaller.localization.Global");
+	}
+
+	private static void addClassPath(File dir) {
+		try {
+			URL u = dir.toURI().toURL();
+			URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader
+					.getSystemClassLoader();
+			Class<URLClassLoader> urlClass = URLClassLoader.class;
+			Method method = urlClass.getDeclaredMethod("addURL",
+					new Class[] { URL.class });
+			method.setAccessible(true);
+			method.invoke(urlClassLoader, new Object[] { u });
+		} catch (MalformedURLException e) {
+		} catch (SecurityException e) {
+		} catch (NoSuchMethodException e) {
+		} catch (IllegalArgumentException e) {
+		} catch (IllegalAccessException e) {
+		} catch (InvocationTargetException e) {
+		}
+	}
+
+	/**
+	 * @param args
+	 *            Command line arguments
+	 */
+	public static void main(String[] args) {
+		Paths.getPrefsPath().mkdirs();
+		Paths.getDownloadPath().mkdirs();
+
+		boolean debug = false;
+		boolean noCacheUpdate = false;
+		for (String a : args) {
+			if (a.equalsIgnoreCase("-debug"))
+				debug = true;
+			if (a.equalsIgnoreCase("-nocacheupdate"))
+				noCacheUpdate = true;
+		}
+		if (!debug) {
+			try {
+				PrintStream ps = new PrintStream(new File(Paths.getPrefsPath(),
+						"aei_output.log"));
+				System.setOut(ps);
+				System.setErr(ps);
+			} catch (FileNotFoundException e1) {
+				e1.printStackTrace();
+			}
+		}
+		
+		initBundles();
+
+		if (PlatformInformation.getPlatform() == Platform.MACOS)
+			initMacOS();
+
+		SettingsManager.setDebug(debug);
+		SettingsManager.deserializeFromFile(Paths.getSettingsFilename());
+		SettingsManager.setDebug(debug);
+		SettingsManager.getInstance().setNoCacheUpdateMode(noCacheUpdate);
+
+		SwingJavaBuilder.getConfig().addResourceBundle(imagesBundle);
+		SwingJavaBuilder.getConfig().addResourceBundle(basicBundle);
+		SwingJavaBuilder.getConfig().addResourceBundle(globalBundle);
+		SwingJavaBuilder.getConfig().setMarkInvalidResourceBundleKeys(true);
+		SwingJavaBuilder.getConfig().addType("JToolBarSeparator",
+				JToolBar.Separator.class);
+
+		System.setProperty("networkaddress.cache.ttl", "5");
+		System.setProperty("networkaddress.cache.negative.ttl", "1");
+
+		try {
+			String laf = SettingsManager.getInstance().get("lookandfeel",
+					(String) null);
+			if (laf == null) {
+				if (PlatformInformation.getPlatform() != Platform.LINUX) {
+					laf = UIManager.getSystemLookAndFeelClassName();
+				} else {
+					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);
+
+		System.out.println(basicBundle.getString("appname") + " "
+				+ basicBundle.getString("appversion"));
+		System.out.println("JarPath:   " + Paths.getInstallerPath());
+		System.out.println("PrefsPath: " + Paths.getPrefsPath());
+		System.out.println("DataPath:  " + Paths.getModsPath());
+		System.out.println("DownPath:  " + Paths.getDownloadPath());
+		System.out.println("TempPath:  " + Paths.getTempPath());
+		System.out.println("ValidPath: " + Installer.verifyRunningDirectory());
+		System.out.println("Platform:  " + PlatformInformation.getPlatform());
+		System.out.println("Architect: " + PlatformInformation.getArchitecture());
+		System.out.println(".NET:      " + DotNet.isInstalled());
+		System.out.println("OniSplit:  " + OniSplit.isOniSplitInstalled());
+		System.out.println("Globalized:" + Installer.isEditionInitialized());
+
+		// TODO: Check available space
+		System.out
+				.println("Free space on temp: "
+						+ SizeFormatter.format(Paths.getTempPath()
+								.getUsableSpace(), 3));
+		System.out.println("Free space on Jar:  "
+				+ SizeFormatter.format(Paths.getInstallerPath()
+						.getUsableSpace(), 3));
+		System.out.println();
+
+		if (!DotNet.isInstalled()) {
+			HTMLLinkLabel hll = new HTMLLinkLabel();
+			String dlUrl = "";
+			switch (PlatformInformation.getPlatform()) {
+				case WIN:
+					switch (PlatformInformation.getArchitecture()) {
+						case X86:
+							dlUrl = "http://download.microsoft.com/download/c/6/e/c6e88215-0178-4c6c-b5f3-158ff77b1f38/NetFx20SP2_x86.exe";
+							break;
+						case AMD64:
+							dlUrl = "http://download.microsoft.com/download/c/6/e/c6e88215-0178-4c6c-b5f3-158ff77b1f38/NetFx20SP2_x64.exe";
+							break;
+					}
+					break;
+				default:
+					dlUrl = "http://www.go-mono.com/mono-downloads/download.html";
+			}
+			hll.setText(globalBundle
+					.getString("dotNetMissing.text")
+					.replaceAll(
+							"%1",
+							String.format("<a href=\"%s\">%s</a>", dlUrl, dlUrl)));
+			JOptionPane.showMessageDialog(null, hll,
+					globalBundle.getString("dotNetMissing.title"),
+					JOptionPane.ERROR_MESSAGE);
+			return;
+		}
+
+		if (!Installer.verifyRunningDirectory()) {
+			JOptionPane.showMessageDialog(null,
+					globalBundle.getString("invalidPath.text"),
+					globalBundle.getString("invalidPath.title"),
+					JOptionPane.ERROR_MESSAGE);
+			if (!SettingsManager.isDebug()) {
+				return;
+			}
+		}
+
+		boolean offline = false;
+		for (String a : args)
+			if (a.equalsIgnoreCase("-offline"))
+				offline = true;
+		if (!offline) {
+			offline = !DepotManager.getInstance().checkConnection();
+		}
+		if (offline) {
+			JOptionPane.showMessageDialog(null,
+					globalBundle.getString("offlineModeStartup.text"),
+					globalBundle.getString("offlineModeStartup.title"),
+					JOptionPane.INFORMATION_MESSAGE);
+		}
+		SettingsManager.getInstance().setOfflineMode(offline);
+
+		SwingUtilities.invokeLater(new Runnable() {
+			public void run() {
+				try {
+					MainWin mw = new MainWin();
+					if (app != null) {
+						app.addAboutMenuItem();
+						app.setEnabledAboutMenu(true);
+						app.addPreferencesMenuItem();
+						app.setEnabledPreferencesMenu(true);
+						app.addApplicationListener(mw);
+					}
+					mw.setVisible(true);
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		});
+
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/DepotPackageCheck.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/DepotPackageCheck.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/DepotPackageCheck.java	(revision 722)
@@ -0,0 +1,61 @@
+package net.oni2.aeinstaller;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+import net.oni2.aeinstaller.backend.depot.DepotConfig;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.backend.depot.model.Node;
+import net.oni2.aeinstaller.backend.depot.model.NodeMod;
+
+/**
+ * @author Christian Illy
+ */
+public class DepotPackageCheck {
+
+	/**
+	 * @param args
+	 *            Arguments
+	 */
+	public static void main(String[] args) {
+		System.out.println("Reading Depot data:");
+		DepotManager.getInstance().updateInformation();
+		System.out.println("\nReading done");
+		System.out.println();
+		DepotManager.getInstance().printStats();
+
+		System.out.println();
+
+		printModsWithFilesNot1();
+	}
+
+	private static void printModsWithFilesNot1() {
+		System.out.println("Mod-Nodes with files != 1:");
+
+		HashMap<String, HashSet<NodeMod>> foundNodes = new HashMap<String, HashSet<NodeMod>>();
+		for (Node n : DepotManager.getInstance().getNodesByType(
+				DepotConfig.getNodeType_Mod())) {
+			NodeMod nm = (NodeMod) n;
+			if (nm.getUploads().size() != 1) {
+				if (!foundNodes.containsKey(nm.getInstallMethod().getName()))
+					foundNodes.put(nm.getInstallMethod().getName(),
+							new HashSet<NodeMod>());
+				foundNodes.get(nm.getInstallMethod().getName()).add(nm);
+			}
+		}
+
+		for (String inst : foundNodes.keySet()) {
+			System.out.format("Inst method '%s':\n", inst);
+			for (NodeMod nm : foundNodes.get(inst)) {
+				System.out
+						.format("  Node %3d, Files %d, Platform %5s, Type %11s, Submitter %10s, Title \"%s\"\n",
+								nm.getNid(), nm.getUploads().size(), nm
+										.getPlatform().toString(), nm
+										.getTypes().toString(), nm.getName(),
+								nm.getTitle());
+			}
+			System.out.println();
+		}
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/Images.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/Images.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/Images.properties	(revision 722)
@@ -0,0 +1,22 @@
+img.openFile=/net/oni2/aeinstaller/images/tango/document-open.png
+img.saveFile=/net/oni2/aeinstaller/images/tango/document-save-as.png
+img.settings=/net/oni2/aeinstaller/images/tango/preferences-system.png
+img.exit=/net/oni2/aeinstaller/images/tango/system-log-out.png
+img.undo=/net/oni2/aeinstaller/images/tango/edit-undo.png
+img.undo16=/net/oni2/aeinstaller/images/tango/edit-undo16.png
+img.redo=/net/oni2/aeinstaller/images/tango/edit-redo.png
+img.refresh=/net/oni2/aeinstaller/images/tango/view-refresh.png
+
+img.stop=/net/oni2/aeinstaller/images/open_icon_library/media-playback-stop-7.png
+img.about=/net/oni2/aeinstaller/images/open_icon_library/help-about-3.png
+img.tools=/net/oni2/aeinstaller/images/open_icon_library/tools-hammer_and_nails.png
+img.install=/net/oni2/aeinstaller/images/open_icon_library/run-build-install-root.png
+img.uninstall=/net/oni2/aeinstaller/images/open_icon_library/edit-delete-6.png
+img.folder=/net/oni2/aeinstaller/images/open_icon_library/folder-open-3.png
+img.update=/net/oni2/aeinstaller/images/open_icon_library/system-software-update-2.png
+img.unSelect16=/net/oni2/aeinstaller/images/open_icon_library/select-2.png
+
+img.ae=/net/oni2/aeinstaller/images/AElogo.png
+img.oni=/net/oni2/aeinstaller/images/oni.png
+img.transparent=/net/oni2/aeinstaller/images/transparent.png
+img.core=/net/oni2/aeinstaller/images/core-package.png
Index: java/installer2/src/net/oni2/aeinstaller/backend/CaseInsensitiveFile.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/CaseInsensitiveFile.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/CaseInsensitiveFile.java	(revision 722)
@@ -0,0 +1,34 @@
+package net.oni2.aeinstaller.backend;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+/**
+ * @author Christian Illy
+ */
+public class CaseInsensitiveFile {
+	/**
+	 * Get a File for the given parent path and a child name. Return the name as
+	 * passed to the function if either parent or a file named that way does not
+	 * exist.
+	 * 
+	 * @param parent
+	 *            Parent path
+	 * @param name
+	 *            Name of file
+	 * @return File
+	 */
+	public static File getCaseInsensitiveFile(File parent, final String name) {
+		if (parent.exists()) {
+			for (File f : parent.listFiles(new FilenameFilter() {
+				@Override
+				public boolean accept(File dir, String fname) {
+					return fname.equalsIgnoreCase(name);
+				}
+			})) {
+				return f;
+			}
+		}
+		return new File(parent, name);
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/ColorCopy.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/ColorCopy.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/ColorCopy.java	(revision 722)
@@ -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: java/installer2/src/net/oni2/aeinstaller/backend/ImageResizer.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/ImageResizer.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/ImageResizer.java	(revision 722)
@@ -0,0 +1,30 @@
+package net.oni2.aeinstaller.backend;
+
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+
+import javax.swing.ImageIcon;
+
+/**
+ * @author Christian Illy
+ */
+public class ImageResizer {
+	/**
+	 * @param src
+	 *            Source image
+	 * @param width
+	 *            New width
+	 * @param height
+	 *            New height
+	 * @return Resized image icon
+	 */
+	public static ImageIcon resizeImage(ImageIcon src, int width, int height) {
+		Image img = src.getImage();
+		BufferedImage bi = new BufferedImage(img.getWidth(null),
+				img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
+		Graphics g = bi.createGraphics();
+		g.drawImage(img, 0, 0, width, height, null);
+		return new ImageIcon(bi);
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/Paths.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/Paths.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/Paths.java	(revision 722)
@@ -0,0 +1,131 @@
+package net.oni2.aeinstaller.backend;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+
+import net.oni2.SettingsManager;
+
+/**
+ * @author Christian Illy
+ */
+public class Paths {
+
+	/**
+	 * @return Mod Depot cache filename
+	 */
+	public static File getDepotCacheFilename() {
+		return new File(getPrefsPath(), "ModDepotCache.xml");
+	}
+
+	/**
+	 * @return Settings filename of AEI
+	 */
+	public static File getSettingsFilename() {
+		return new File(getPrefsPath(), "AEI-Settings.xml");
+	}
+
+	/**
+	 * Get the Jar path
+	 * 
+	 * @return Path
+	 */
+	public static File getInstallerPath() {
+		if (SettingsManager.isDebug()) {
+			String wd = System.getProperty("user.dir");
+			return new File(wd);
+		} else {
+			String jarPath = SettingsManager.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();
+		}
+	}
+
+	/**
+	 * Get the preferences path
+	 * 
+	 * @return Path
+	 */
+	public static File getPrefsPath() {
+		return getInstallerPath();
+	}
+
+	/**
+	 * Get the path to store downloaded files
+	 * 
+	 * @return Download path
+	 */
+	public static File getDownloadPath() {
+		return new File(getTempPath(), "downloads");
+	}
+
+	/**
+	 * Get the path to store mods
+	 * 
+	 * @return Data path
+	 */
+	public static File getModsPath() {
+		return new File(getInstallerPath(), "packages");
+	}
+
+	/**
+	 * Get the path where vanilla .oni-files are stored
+	 * 
+	 * @return Vanilla .oni's path
+	 */
+	public static File getVanillaOnisPath() {
+		return new File(getInstallerPath(), "vanilla");
+	}
+
+	/**
+	 * Get the base path of Oni
+	 * 
+	 * @return Vanilla Oni path
+	 */
+	public static File getOniBasePath() {
+		return getInstallerPath().getParentFile().getParentFile();
+	}
+
+	/**
+	 * Get the base path of the Edition
+	 * 
+	 * @return Edition path
+	 */
+	public static File getEditionBasePath() {
+		return getInstallerPath().getParentFile();
+	}
+
+	/**
+	 * Get the path where the vanilla Oni GDF is located
+	 * 
+	 * @return Vanilla Oni GDF
+	 */
+	public static File getVanillaGDF() {
+		return CaseInsensitiveFile.getCaseInsensitiveFile(getOniBasePath(), "GameDataFolder");
+	}
+
+	/**
+	 * Get the path where the Edition GDF is located
+	 * 
+	 * @return Edition GDF
+	 */
+	public static File getEditionGDF() {
+		return new File(getEditionBasePath(), "GameDataFolder");
+	}
+
+	/**
+	 * Get the systems temp-path
+	 * 
+	 * @return Path
+	 */
+	public static File getTempPath() {
+		return new File(System.getProperty("java.io.tmpdir"), "oni_aei");
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/SizeFormatter.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/SizeFormatter.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/SizeFormatter.java	(revision 722)
@@ -0,0 +1,35 @@
+package net.oni2.aeinstaller.backend;
+
+/**
+ * @author Christian Illy
+ */
+public class SizeFormatter {
+	/**
+	 * @param sizeVal
+	 *            Size in Byte
+	 * @param digits
+	 *            Number of digits
+	 * @return Formatted value
+	 */
+	public static String format(long sizeVal, int digits) {
+		String names[] = { "B", "KiB", "MiB", "GiB" };
+		int nameInd = 0;
+		float size = sizeVal;
+		while (size > 1024) {
+			nameInd++;
+			size /= 1024;
+		}
+		if (size < 10)
+			return String.format(
+					"%1." + String.valueOf(Math.max(digits - 1, 0)) + "f %s",
+					size, names[nameInd]);
+		else if (size < 100)
+			return String.format(
+					"%1." + String.valueOf(Math.max(digits - 2, 0)) + "f %s",
+					size, names[nameInd]);
+		else
+			return String.format(
+					"%1." + String.valueOf(Math.max(digits - 3, 0)) + "f %s",
+					size, names[nameInd]);
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/WinRegistry.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/WinRegistry.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/WinRegistry.java	(revision 722)
@@ -0,0 +1,502 @@
+package net.oni2.aeinstaller.backend;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.prefs.Preferences;
+
+/**
+ * @author Unknown
+ */
+public class WinRegistry {
+	/**
+	 * Constant for accessing HKCU
+	 */
+	public static final int HKEY_CURRENT_USER = 0x80000001;
+	/**
+	 * Constant for accessing HKLM
+	 */
+	public static final int HKEY_LOCAL_MACHINE = 0x80000002;
+	/**
+	 * Constant for successful accesses
+	 */
+	public static final int REG_SUCCESS = 0;
+	/**
+	 * Constant for not found
+	 */
+	public static final int REG_NOTFOUND = 2;
+	/**
+	 * Constant for access denied
+	 */
+	public static final int REG_ACCESSDENIED = 5;
+
+	/**
+	 * Access 32bit registry view when running as 64bit application
+	 */
+	public static final int KEY_WOW64_32KEY = 0x0200;
+	/**
+	 * Access 64bit registry view when running as 32bit application
+	 */
+	public static final int KEY_WOW64_64KEY = 0x0100;
+
+	private static final int KEY_ALL_ACCESS = 0xf003f;
+	private static final int KEY_READ = 0x20019;
+	private static Preferences userRoot = Preferences.userRoot();
+	private static Preferences systemRoot = Preferences.systemRoot();
+	private static Class<? extends Preferences> userClass = userRoot.getClass();
+	private static Method regOpenKey = null;
+	private static Method regCloseKey = null;
+	private static Method regQueryValueEx = null;
+	private static Method regEnumValue = null;
+	private static Method regQueryInfoKey = null;
+	private static Method regEnumKeyEx = null;
+	private static Method regCreateKeyEx = null;
+	private static Method regSetValueEx = null;
+	private static Method regDeleteKey = null;
+	private static Method regDeleteValue = null;
+
+	private static boolean usable = false;
+
+	static {
+		try {
+			regOpenKey = userClass.getDeclaredMethod("WindowsRegOpenKey",
+					new Class[] { int.class, byte[].class, int.class });
+			regOpenKey.setAccessible(true);
+			regCloseKey = userClass.getDeclaredMethod("WindowsRegCloseKey",
+					new Class[] { int.class });
+			regCloseKey.setAccessible(true);
+			regQueryValueEx = userClass.getDeclaredMethod(
+					"WindowsRegQueryValueEx", new Class[] { int.class,
+							byte[].class });
+			regQueryValueEx.setAccessible(true);
+			regEnumValue = userClass.getDeclaredMethod("WindowsRegEnumValue",
+					new Class[] { int.class, int.class, int.class });
+			regEnumValue.setAccessible(true);
+			regQueryInfoKey = userClass.getDeclaredMethod(
+					"WindowsRegQueryInfoKey1", new Class[] { int.class });
+			regQueryInfoKey.setAccessible(true);
+			regEnumKeyEx = userClass.getDeclaredMethod("WindowsRegEnumKeyEx",
+					new Class[] { int.class, int.class, int.class });
+			regEnumKeyEx.setAccessible(true);
+			regCreateKeyEx = userClass.getDeclaredMethod(
+					"WindowsRegCreateKeyEx", new Class[] { int.class,
+							byte[].class });
+			regCreateKeyEx.setAccessible(true);
+			regSetValueEx = userClass.getDeclaredMethod("WindowsRegSetValueEx",
+					new Class[] { int.class, byte[].class, byte[].class });
+			regSetValueEx.setAccessible(true);
+			regDeleteValue = userClass.getDeclaredMethod(
+					"WindowsRegDeleteValue", new Class[] { int.class,
+							byte[].class });
+			regDeleteValue.setAccessible(true);
+			regDeleteKey = userClass.getDeclaredMethod("WindowsRegDeleteKey",
+					new Class[] { int.class, byte[].class });
+			regDeleteKey.setAccessible(true);
+			usable = true;
+		} catch (Exception e) {
+			// e.printStackTrace();
+		}
+	}
+
+	private WinRegistry() {
+	}
+
+	/**
+	 * Read a value from key and value name
+	 * 
+	 * @param hkey
+	 *            HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
+	 * @param key
+	 *            Key to access
+	 * @param valueName
+	 *            Name of value to read
+	 * @param wow64
+	 *            0 for standard registry access (32-bits for 32-bit app,
+	 *            64-bits for 64-bits app) or KEY_WOW64_32KEY to force access to
+	 *            32-bit registry view, or KEY_WOW64_64KEY to force access to
+	 *            64-bit registry view
+	 * @return the value
+	 * @throws Exception
+	 *             If registry access impossible
+	 * @throws IllegalArgumentException
+	 *             On illegal arguments
+	 * @throws IllegalAccessException
+	 *             On illegal access
+	 * @throws InvocationTargetException
+	 *             On reflection problems
+	 */
+	public static String readString(int hkey, String key, String valueName,
+			int wow64) throws Exception, IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException {
+		if (!usable)
+			throw new Exception(
+					"Registry access not supported (not a Windows OS?).");
+		if (hkey == HKEY_LOCAL_MACHINE) {
+			return readString(systemRoot, hkey, key, valueName, wow64);
+		} else if (hkey == HKEY_CURRENT_USER) {
+			return readString(userRoot, hkey, key, valueName, wow64);
+		} else {
+			throw new IllegalArgumentException("hkey=" + hkey);
+		}
+	}
+
+	/**
+	 * Read value(s) and value name(s) form given key
+	 * 
+	 * @param hkey
+	 *            HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
+	 * @param key
+	 *            Key to access
+	 * @param wow64
+	 *            0 for standard registry access (32-bits for 32-bit app,
+	 *            64-bits for 64-bits app) or KEY_WOW64_32KEY to force access to
+	 *            32-bit registry view, or KEY_WOW64_64KEY to force access to
+	 *            64-bit registry view
+	 * @return the value name(s) plus the value(s)
+	 * @throws Exception
+	 *             If registry access impossible
+	 * @throws IllegalArgumentException
+	 *             On illegal arguments
+	 * @throws IllegalAccessException
+	 *             On illegal access
+	 * @throws InvocationTargetException
+	 *             On reflection problems
+	 */
+	public static Map<String, String> readStringValues(int hkey, String key,
+			int wow64) throws Exception, IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException {
+		if (!usable)
+			throw new Exception(
+					"Registry access not supported (not a Windows OS?).");
+		if (hkey == HKEY_LOCAL_MACHINE) {
+			return readStringValues(systemRoot, hkey, key, wow64);
+		} else if (hkey == HKEY_CURRENT_USER) {
+			return readStringValues(userRoot, hkey, key, wow64);
+		} else {
+			throw new IllegalArgumentException("hkey=" + hkey);
+		}
+	}
+
+	/**
+	 * Read the value name(s) from a given key
+	 * 
+	 * @param hkey
+	 *            HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
+	 * @param key
+	 *            Key to access
+	 * @param wow64
+	 *            0 for standard registry access (32-bits for 32-bit app,
+	 *            64-bits for 64-bits app) or KEY_WOW64_32KEY to force access to
+	 *            32-bit registry view, or KEY_WOW64_64KEY to force access to
+	 *            64-bit registry view
+	 * @return the value name(s)
+	 * @throws Exception
+	 *             If registry access impossible
+	 * @throws IllegalArgumentException
+	 *             On illegal arguments
+	 * @throws IllegalAccessException
+	 *             On illegal access
+	 * @throws InvocationTargetException
+	 *             On reflection problems
+	 */
+	public static List<String> readStringSubKeys(int hkey, String key, int wow64)
+			throws Exception, IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException {
+		if (!usable)
+			throw new Exception(
+					"Registry access not supported (not a Windows OS?).");
+		if (hkey == HKEY_LOCAL_MACHINE) {
+			return readStringSubKeys(systemRoot, hkey, key, wow64);
+		} else if (hkey == HKEY_CURRENT_USER) {
+			return readStringSubKeys(userRoot, hkey, key, wow64);
+		} else {
+			throw new IllegalArgumentException("hkey=" + hkey);
+		}
+	}
+
+	/**
+	 * Create a key
+	 * 
+	 * @param hkey
+	 *            HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
+	 * @param key
+	 *            Key to access
+	 * @throws Exception
+	 *             If registry access impossible
+	 * @throws IllegalArgumentException
+	 *             On illegal arguments
+	 * @throws IllegalAccessException
+	 *             On illegal access
+	 * @throws InvocationTargetException
+	 *             On reflection problems
+	 */
+	public static void createKey(int hkey, String key) throws Exception,
+			IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException {
+		int[] ret;
+		if (!usable)
+			throw new Exception(
+					"Registry access not supported (not a Windows OS?).");
+		if (hkey == HKEY_LOCAL_MACHINE) {
+			ret = createKey(systemRoot, hkey, key);
+			regCloseKey
+					.invoke(systemRoot, new Object[] { new Integer(ret[0]) });
+		} else if (hkey == HKEY_CURRENT_USER) {
+			ret = createKey(userRoot, hkey, key);
+			regCloseKey.invoke(userRoot, new Object[] { new Integer(ret[0]) });
+		} else {
+			throw new IllegalArgumentException("hkey=" + hkey);
+		}
+		if (ret[1] != REG_SUCCESS) {
+			throw new IllegalArgumentException("rc=" + ret[1] + "  key=" + key);
+		}
+	}
+
+	/**
+	 * Write a value in a given key/value name
+	 * 
+	 * @param hkey
+	 *            HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
+	 * @param key
+	 *            Key to access
+	 * @param valueName
+	 *            Name of value to write to
+	 * @param value
+	 *            String to write
+	 * @param wow64
+	 *            0 for standard registry access (32-bits for 32-bit app,
+	 *            64-bits for 64-bits app) or KEY_WOW64_32KEY to force access to
+	 *            32-bit registry view, or KEY_WOW64_64KEY to force access to
+	 *            64-bit registry view
+	 * @throws Exception
+	 *             If registry access impossible
+	 * @throws IllegalArgumentException
+	 *             On illegal arguments
+	 * @throws IllegalAccessException
+	 *             On illegal access
+	 * @throws InvocationTargetException
+	 *             On reflection problems
+	 */
+	public static void writeStringValue(int hkey, String key, String valueName,
+			String value, int wow64) throws Exception,
+			IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException {
+		if (!usable)
+			throw new Exception(
+					"Registry access not supported (not a Windows OS?).");
+		if (hkey == HKEY_LOCAL_MACHINE) {
+			writeStringValue(systemRoot, hkey, key, valueName, value, wow64);
+		} else if (hkey == HKEY_CURRENT_USER) {
+			writeStringValue(userRoot, hkey, key, valueName, value, wow64);
+		} else {
+			throw new IllegalArgumentException("hkey=" + hkey);
+		}
+	}
+
+	/**
+	 * Delete a given key
+	 * 
+	 * @param hkey
+	 *            HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
+	 * @param key
+	 *            Key to delete
+	 * @throws Exception
+	 *             If registry access impossible
+	 * @throws IllegalArgumentException
+	 *             On illegal arguments
+	 * @throws IllegalAccessException
+	 *             On illegal access
+	 * @throws InvocationTargetException
+	 *             On reflection problems
+	 */
+	public static void deleteKey(int hkey, String key) throws Exception,
+			IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException {
+		int rc = -1;
+		if (!usable)
+			throw new Exception(
+					"Registry access not supported (not a Windows OS?).");
+		if (hkey == HKEY_LOCAL_MACHINE) {
+			rc = deleteKey(systemRoot, hkey, key);
+		} else if (hkey == HKEY_CURRENT_USER) {
+			rc = deleteKey(userRoot, hkey, key);
+		}
+		if (rc != REG_SUCCESS) {
+			throw new IllegalArgumentException("rc=" + rc + "  key=" + key);
+		}
+	}
+
+	/**
+	 * delete a value from a given key/value name
+	 * 
+	 * @param hkey
+	 *            HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
+	 * @param key
+	 *            Key to access
+	 * @param value
+	 *            Name of value to delete
+	 * @param wow64
+	 *            0 for standard registry access (32-bits for 32-bit app,
+	 *            64-bits for 64-bits app) or KEY_WOW64_32KEY to force access to
+	 *            32-bit registry view, or KEY_WOW64_64KEY to force access to
+	 *            64-bit registry view
+	 * @throws Exception
+	 *             If registry access impossible
+	 * @throws IllegalArgumentException
+	 *             On illegal arguments
+	 * @throws IllegalAccessException
+	 *             On illegal access
+	 * @throws InvocationTargetException
+	 *             On reflection problems
+	 */
+	public static void deleteValue(int hkey, String key, String value, int wow64)
+			throws Exception, IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException {
+		if (!usable)
+			throw new Exception(
+					"Registry access not supported (not a Windows OS?).");
+		int rc = -1;
+		if (hkey == HKEY_LOCAL_MACHINE) {
+			rc = deleteValue(systemRoot, hkey, key, value, wow64);
+		} else if (hkey == HKEY_CURRENT_USER) {
+			rc = deleteValue(userRoot, hkey, key, value, wow64);
+		}
+		if (rc != REG_SUCCESS) {
+			throw new IllegalArgumentException("rc=" + rc + "  key=" + key
+					+ "  value=" + value);
+		}
+	}
+
+	// ========================================================================
+	private static int deleteValue(Preferences root, int hkey, String key,
+			String value, int wow64) throws IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException {
+		int[] handles = (int[]) regOpenKey.invoke(root, new Object[] {
+				new Integer(hkey), toCstr(key),
+				new Integer(KEY_ALL_ACCESS | wow64) });
+		if (handles[1] != REG_SUCCESS) {
+			return handles[1]; // can be REG_NOTFOUND, REG_ACCESSDENIED
+		}
+		int rc = ((Integer) regDeleteValue.invoke(root, new Object[] {
+				new Integer(handles[0]), toCstr(value) })).intValue();
+		regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) });
+		return rc;
+	}
+
+	// ========================================================================
+	private static int deleteKey(Preferences root, int hkey, String key)
+			throws IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException {
+		int rc = ((Integer) regDeleteKey.invoke(root, new Object[] {
+				new Integer(hkey), toCstr(key) })).intValue();
+		return rc; // can REG_NOTFOUND, REG_ACCESSDENIED, REG_SUCCESS
+	}
+
+	// ========================================================================
+	private static String readString(Preferences root, int hkey, String key,
+			String value, int wow64) throws IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException {
+		int[] handles = (int[]) regOpenKey
+				.invoke(root, new Object[] { new Integer(hkey), toCstr(key),
+						new Integer(KEY_READ | wow64) });
+		if (handles[1] != REG_SUCCESS) {
+			return null;
+		}
+		byte[] valb = (byte[]) regQueryValueEx.invoke(root, new Object[] {
+				new Integer(handles[0]), toCstr(value) });
+		regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) });
+		return (valb != null ? new String(valb).trim() : null);
+	}
+
+	// ========================================================================
+	private static Map<String, String> readStringValues(Preferences root,
+			int hkey, String key, int wow64) throws Exception,
+			IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException {
+		HashMap<String, String> results = new HashMap<String, String>();
+		int[] handles = (int[]) regOpenKey
+				.invoke(root, new Object[] { new Integer(hkey), toCstr(key),
+						new Integer(KEY_READ | wow64) });
+		if (handles[1] != REG_SUCCESS) {
+			return null;
+		}
+		int[] info = (int[]) regQueryInfoKey.invoke(root,
+				new Object[] { new Integer(handles[0]) });
+
+		int count = info[2]; // count
+		int maxlen = info[4]; // value length max
+		for (int index = 0; index < count; index++) {
+			byte[] name = (byte[]) regEnumValue.invoke(root, new Object[] {
+					new Integer(handles[0]), new Integer(index),
+					new Integer(maxlen + 1) });
+			String value = readString(hkey, key, new String(name), wow64);
+			results.put(new String(name).trim(), value);
+		}
+		regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) });
+		return results;
+	}
+
+	// ========================================================================
+	private static List<String> readStringSubKeys(Preferences root, int hkey,
+			String key, int wow64) throws IllegalArgumentException,
+			IllegalAccessException, InvocationTargetException {
+		List<String> results = new ArrayList<String>();
+		int[] handles = (int[]) regOpenKey
+				.invoke(root, new Object[] { new Integer(hkey), toCstr(key),
+						new Integer(KEY_READ | wow64) });
+		if (handles[1] != REG_SUCCESS) {
+			return null;
+		}
+		int[] info = (int[]) regQueryInfoKey.invoke(root,
+				new Object[] { new Integer(handles[0]) });
+
+		int count = info[0]; // Fix: info[2] was being used here with wrong
+								// results. Suggested by davenpcj, confirmed by
+								// Petrucio
+		int maxlen = info[3]; // value length max
+		for (int index = 0; index < count; index++) {
+			byte[] name = (byte[]) regEnumKeyEx.invoke(root, new Object[] {
+					new Integer(handles[0]), new Integer(index),
+					new Integer(maxlen + 1) });
+			results.add(new String(name).trim());
+		}
+		regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) });
+		return results;
+	}
+
+	// ========================================================================
+	private static int[] createKey(Preferences root, int hkey, String key)
+			throws IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException {
+		return (int[]) regCreateKeyEx.invoke(root, new Object[] {
+				new Integer(hkey), toCstr(key) });
+	}
+
+	// ========================================================================
+	private static void writeStringValue(Preferences root, int hkey,
+			String key, String valueName, String value, int wow64)
+			throws IllegalArgumentException, IllegalAccessException,
+			InvocationTargetException {
+		int[] handles = (int[]) regOpenKey.invoke(root, new Object[] {
+				new Integer(hkey), toCstr(key),
+				new Integer(KEY_ALL_ACCESS | wow64) });
+		regSetValueEx.invoke(root, new Object[] { new Integer(handles[0]),
+				toCstr(valueName), toCstr(value) });
+		regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) });
+	}
+
+	// ========================================================================
+	// utility
+	private static byte[] toCstr(String str) {
+		byte[] result = new byte[str.length() + 1];
+
+		for (int i = 0; i < str.length(); i++) {
+			result[i] = (byte) str.charAt(i);
+		}
+		result[str.length()] = 0;
+		return result;
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/depot/DepotConfig.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/DepotConfig.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/DepotConfig.java	(revision 722)
@@ -0,0 +1,100 @@
+package net.oni2.aeinstaller.backend.depot;
+
+import java.util.TreeSet;
+
+import net.oni2.SettingsManager;
+
+/**
+ * @author Christian Illy
+ */
+public class DepotConfig {
+
+	/**
+	 * @return Type-value of nodes which contain Mods
+	 */
+	public static String getNodeType_Mod() {
+		return "mod";
+	}
+
+	/**
+	 * @return Vocabulary name for platform field
+	 */
+	static String getVocabularyName_Platform() {
+		return "Platform";
+	}
+
+	/**
+	 * @return Vocabulary name for installtype field
+	 */
+	static String getVocabularyName_InstallType() {
+		return "Install method";
+	}
+
+	/**
+	 * @return Vocabulary name for modtype field
+	 */
+	static String getVocabularyName_ModType() {
+		return "Mod type";
+	}
+
+	/**
+	 * @return Taxonomy term name for platform value Win
+	 */
+	public static String getTaxonomyName_Platform_Win() {
+		return "Windows";
+	}
+
+	/**
+	 * @return Taxonomy term name for platform value Mac
+	 */
+	public static String getTaxonomyName_Platform_Mac() {
+		return "Mac OS";
+	}
+
+	/**
+	 * @return Taxonomy term name for platform value Both
+	 */
+	public static String getTaxonomyName_Platform_Both() {
+		return "Both";
+	}
+
+	/**
+	 * @return Taxonomy term name for installtype Package
+	 */
+	static String getTaxonomyName_InstallType_Package() {
+		return "Package";
+	}
+
+	/**
+	 * @return Taxonomy term names for mods of type Tool
+	 */
+	public static TreeSet<String> getTaxonomyName_ModType_Tool() {
+		TreeSet<String> res = new TreeSet<String>();
+		res.add("Tool");
+		res.add("Patch");
+		return res;
+	}
+
+	/**
+	 * @return First package number that's not a core tool/mod. Everything
+	 *         below is considered a core package
+	 */
+	public static int getCoreNumberLimit() {
+		return 8000;
+	}
+
+	/**
+	 * @return URL of Depot
+	 */
+	static String getDepotUrl() {
+		return SettingsManager.getInstance().get("depot_url", "http://mods.oni2.net/");
+	}
+
+	/**
+	 * @return URL of Depot API
+	 */
+	static String getDepotApiUrl() {
+		return SettingsManager.getInstance().get("depot_api_url",
+				"http://mods.oni2.net/?q=api/");
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java	(revision 722)
@@ -0,0 +1,376 @@
+package net.oni2.aeinstaller.backend.depot;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.UnknownHostException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import net.oni2.aeinstaller.backend.Paths;
+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.FileDownloader;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.impl.client.DefaultHttpClient;
+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 = loadFromFile();
+
+	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
+	 */
+	public void updateInformation() {
+		try {
+			java.io.File zipName = new java.io.File(Paths.getDownloadPath(),
+					"jsoncache.zip");
+			FileDownloader fd = new FileDownloader(DepotConfig.getDepotUrl()
+					+ "jsoncache/jsoncache.zip?ts="
+					+ (new Date().getTime() / 1000), zipName);
+			fd.start();
+			while (fd.getState() != net.oni2.aeinstaller.backend.network.FileDownloader.EState.FINISHED) {
+				Thread.sleep(50);
+			}
+
+			ZipFile zf = null;
+			try {
+				zf = new ZipFile(zipName);
+				for (Enumeration<? extends ZipEntry> e = zf.entries(); e
+						.hasMoreElements();) {
+					ZipEntry ze = e.nextElement();
+					if (!ze.isDirectory()) {
+						BufferedReader input = new BufferedReader(
+								new InputStreamReader(zf.getInputStream(ze)));
+						StringBuffer json = new StringBuffer();
+
+						char data[] = new char[1024];
+						int dataRead;
+						while ((dataRead = input.read(data, 0, 1024)) != -1) {
+							json.append(data, 0, dataRead);
+						}
+
+						if (ze.getName().toLowerCase().contains("vocabulary")) {
+							initVocabulary(new JSONArray(json.toString()));
+						}
+						if (ze.getName().toLowerCase().contains("terms")) {
+							initTerms(new JSONArray(json.toString()));
+						}
+						if (ze.getName().toLowerCase().contains("nodes")) {
+							initNodes(new JSONArray(json.toString()));
+						}
+						if (ze.getName().toLowerCase().contains("files")) {
+							initFiles(new JSONArray(json.toString()));
+						}
+					}
+				}
+			} finally {
+				if (zf != null)
+					zf.close();
+				zipName.delete();
+			}
+
+			vocabId_type = getVocabulary(
+					DepotConfig.getVocabularyName_ModType()).getVid();
+			vocabId_platform = getVocabulary(
+					DepotConfig.getVocabularyName_Platform()).getVid();
+			vocabId_instmethod = getVocabulary(
+					DepotConfig.getVocabularyName_InstallType()).getVid();
+
+			saveToFile();
+		} catch (JSONException e) {
+			e.printStackTrace();
+		} catch (IOException e1) {
+			e1.printStackTrace();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+	}
+
+	private void initFiles(JSONArray ja) throws JSONException {
+		files = new HashMap<Integer, File>();
+		JSONObject jo;
+		for (int i = 0; i < ja.length(); i++) {
+			jo = ja.getJSONObject(i);
+			int fid = jo.getInt("fid");
+
+			File f = new File(jo);
+			files.put(fid, f);
+		}
+	}
+
+	private void initNodes(JSONArray ja) throws JSONException {
+		nodes = new HashMap<Integer, Node>();
+		nodesByType = new HashMap<String, HashMap<Integer, Node>>();
+		JSONObject jo;
+		for (int i = 0; i < ja.length(); i++) {
+			jo = ja.getJSONObject(i);
+
+			int nid = jo.getInt("nid");
+			String type = jo.getString("type");
+
+			Node n = null;
+			if (type.equalsIgnoreCase(DepotConfig.getNodeType_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);
+		}
+	}
+
+	private void initTerms(JSONArray ja) throws JSONException {
+		taxonomyTerms.clear();
+		JSONObject jo;
+		for (int i = 0; i < ja.length(); i++) {
+			jo = ja.getJSONObject(i);
+			TaxonomyTerm tt = new TaxonomyTerm(jo);
+			taxonomyTerms.put(tt.getTid(), tt);
+		}
+	}
+
+	private void initVocabulary(JSONArray ja) throws JSONException {
+		taxonomyVocabulary.clear();
+		JSONObject jo;
+		for (int i = 0; i < ja.length(); i++) {
+			jo = ja.getJSONObject(i);
+			TaxonomyVocabulary tv = new TaxonomyVocabulary(jo);
+			taxonomyVocabulary.put(tv.getVid(), tv);
+		}
+	}
+
+	/**
+	 * @return Can we connect to the Depot?
+	 */
+	public boolean checkConnection() {
+		HttpRequestBase httpQuery = null;
+
+		try {
+			DefaultHttpClient httpclient = new DefaultHttpClient();
+			httpQuery = new HttpHead(DepotConfig.getDepotUrl());
+
+			HttpResponse response = httpclient.execute(httpQuery);
+
+			int code = response.getStatusLine().getStatusCode();
+
+			return (code >= 200) && (code <= 299);
+		} catch (UnknownHostException e) {
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		} finally {
+			if (httpQuery != null)
+				httpQuery.releaseConnection();
+		}
+		return false;
+	}
+
+	private 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
+	 */
+	private 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;
+	}
+
+	/**
+	 * @return All defined types
+	 */
+	public Vector<TaxonomyTerm> getTypes() {
+		return getTaxonomyTermsByVocabulary(getVocabIdType());
+	}
+
+	/**
+	 * @param id
+	 *            Get taxonomy term by given ID
+	 * @return TaxTerm
+	 */
+	public TaxonomyTerm getTaxonomyTerm(int id) {
+		return taxonomyTerms.get(id);
+	}
+
+	/**
+	 * 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() {
+		Vector<NodeMod> result = new Vector<NodeMod>();
+		String instMethName = DepotConfig.getTaxonomyName_InstallType_Package();
+
+		Vector<Node> files = getNodesByType(DepotConfig.getNodeType_Mod());
+		for (Node n : files) {
+			if (n instanceof NodeMod) {
+				NodeMod nm = (NodeMod) n;
+				if (nm.getInstallMethod().getName()
+						.equalsIgnoreCase(instMethName)) {
+					try {
+						nm.getPackageNumber();
+						result.add(nm);
+					} catch (NumberFormatException e) {
+						System.err.println("Node " + nm.getNid()
+								+ " does not have a package number!!!");
+					}
+				}
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * @return VocabId of Platform vocabulary
+	 */
+	public int getVocabIdPlatform() {
+		return vocabId_platform;
+	}
+
+	/**
+	 * @return VocabId of Install method vocabulary
+	 */
+	public int getVocabIdInstMethod() {
+		return vocabId_instmethod;
+	}
+
+	/**
+	 * @return VocabId of Type vocabulary
+	 */
+	public int getVocabIdType() {
+		return vocabId_type;
+	}
+
+	/**
+	 * @param id
+	 *            ID of file to get
+	 * @return the file
+	 */
+	public File getFile(int id) {
+		return files.get(id);
+	}
+
+	/**
+	 * 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 static 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
+	 */
+	private void saveToFile() {
+		try {
+			FileOutputStream fos = new FileOutputStream(
+					Paths.getDepotCacheFilename());
+			XStream xs = getXStream();
+			xs.toXML(this, fos);
+			fos.close();
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	private static DepotManager loadFromFile() {
+		try {
+			FileInputStream fis = new FileInputStream(
+					Paths.getDepotCacheFilename());
+			XStream xs = getXStream();
+			Object obj = xs.fromXML(fis);
+			fis.close();
+			if (obj instanceof DepotManager)
+				return (DepotManager) obj;
+		} catch (FileNotFoundException e) {
+		} catch (IOException e) {
+		}
+		return new DepotManager();
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/depot/JSONHelpers.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/JSONHelpers.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/JSONHelpers.java	(revision 722)
@@ -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: java/installer2/src/net/oni2/aeinstaller/backend/depot/model/File.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/model/File.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/model/File.java	(revision 722)
@@ -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: java/installer2/src/net/oni2/aeinstaller/backend/depot/model/Node.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/model/Node.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/model/Node.java	(revision 722)
@@ -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: java/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Body.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Body.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Body.java	(revision 722)
@@ -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: java/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Upload.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Upload.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeField_Upload.java	(revision 722)
@@ -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: java/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeMod.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeMod.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/model/NodeMod.java	(revision 722)
@@ -0,0 +1,157 @@
+package net.oni2.aeinstaller.backend.depot.model;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Vector;
+
+import net.oni2.aeinstaller.backend.depot.DepotConfig;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.backend.packages.ECompatiblePlatform;
+
+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<TaxonomyTerm>> taxonomyTerms = new HashMap<Integer, HashSet<TaxonomyTerm>>();
+	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));
+				if (up.getDisplay() != 0) {
+					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<TaxonomyTerm> values = new HashSet<TaxonomyTerm>();
+				if (val instanceof JSONObject) {
+					JSONArray ja = ((JSONObject) val).getJSONArray("und");
+					for (int i = 0; i < ja.length(); i++) {
+						values.add(DepotManager.getInstance().getTaxonomyTerm(
+								ja.getJSONObject(i).getInt("tid")));
+					}
+				}
+				taxonomyTerms.put(vid, values);
+			}
+		}
+	}
+
+	/**
+	 * @return the uploads
+	 */
+	public Vector<NodeField_Upload> getUploads() {
+		return uploads;
+	}
+
+	/**
+	 * @return Types
+	 */
+	public HashSet<TaxonomyTerm> getTypes() {
+		return taxonomyTerms.get(DepotManager.getInstance().getVocabIdType());
+	}
+
+	/**
+	 * @return Install method
+	 */
+	public TaxonomyTerm getInstallMethod() {
+		return taxonomyTerms
+				.get(DepotManager.getInstance().getVocabIdInstMethod())
+				.iterator().next();
+	}
+
+	/**
+	 * @return Compatible platform
+	 */
+	public ECompatiblePlatform getPlatform() {
+		TaxonomyTerm term = taxonomyTerms
+				.get(DepotManager.getInstance().getVocabIdPlatform())
+				.iterator().next();
+
+		String validPlatform = term.getName();
+		if (validPlatform.equalsIgnoreCase(DepotConfig
+				.getTaxonomyName_Platform_Both()))
+			return ECompatiblePlatform.BOTH;
+		if (validPlatform.equalsIgnoreCase(DepotConfig
+				.getTaxonomyName_Platform_Win()))
+			return ECompatiblePlatform.WIN;
+		if (validPlatform.equalsIgnoreCase(DepotConfig
+				.getTaxonomyName_Platform_Mac()))
+			return ECompatiblePlatform.MACOS;
+
+		return null;
+	}
+
+	/**
+	 * @return Creator of mod
+	 */
+	public String getCreator() {
+		if (fields.get("creator") != null)
+			return fields.get("creator");
+		else
+			return "";
+	}
+
+	/**
+	 * @return Version of mod
+	 */
+	public String getVersion() {
+		if (fields.get("version") != null)
+			return fields.get("version");
+		else
+			return "";
+	}
+
+	/**
+	 * @return Package number
+	 */
+	public int getPackageNumber() {
+		return Integer.parseInt(fields.get("package_number"));
+	}
+
+	/**
+	 * @return Is this mod a tool?
+	 */
+	public boolean isTool() {
+		HashSet<TaxonomyTerm> types = getTypes();
+		for (String s : DepotConfig.getTaxonomyName_ModType_Tool()) {
+			for (TaxonomyTerm tt : types)
+				if (tt.getName().equalsIgnoreCase(s))
+					return true;
+		}
+		return false;
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyTerm.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyTerm.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyTerm.java	(revision 722)
@@ -0,0 +1,69 @@
+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;
+
+	/**
+	 * @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: java/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyVocabulary.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyVocabulary.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/depot/model/TaxonomyVocabulary.java	(revision 722)
@@ -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: java/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloadListener.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloadListener.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloadListener.java	(revision 722)
@@ -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: java/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java	(revision 722)
@@ -0,0 +1,260 @@
+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 fileLength = Integer.MAX_VALUE;
+	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, File target) throws IOException {
+		this.url = new URL(url);
+		this.target = target;
+	}
+
+	/**
+	 * @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;
+			updateStatus(downloaded, fileLength);
+		}
+	}
+
+	/**
+	 * Stop (abort) download
+	 */
+	public synchronized void stop() {
+		if (state != EState.FINISHED) {
+			state = EState.INTERRUPTED;
+			if (t != null) {
+				try {
+					t.join();
+				} catch (InterruptedException e) {
+					e.printStackTrace();
+				}
+				t = null;
+			}
+			updateStatus(0, 1);
+			if (target.exists())
+				target.delete();
+		}
+	}
+
+	/**
+	 * @return current state
+	 */
+	public EState getState() {
+		return state;
+	}
+
+	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;
+		String strLastModified = null;
+		String strEtag = null;
+		RandomAccessFile outFile = null;
+		try {
+			outFile = new RandomAccessFile(target, "rw");
+		} catch (FileNotFoundException e1) {
+			e1.printStackTrace();
+			state = EState.ERROR;
+			updateStatus(downloaded, fileLength);
+			return;
+		}
+
+		try {
+			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();
+							connection.setRequestProperty("Cache-Control", "no-cache");
+							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 % 50) == 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;
+				}
+			}
+		} finally {
+			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 downloaded size
+	 */
+	public int getDownloaded() {
+		return downloaded;
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/InstallProgressListener.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/InstallProgressListener.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/InstallProgressListener.java	(revision 722)
@@ -0,0 +1,18 @@
+package net.oni2.aeinstaller.backend.oni;
+
+/**
+ * @author Christian Illy
+ */
+public interface InstallProgressListener {
+	/**
+	 * Called when installation process advances
+	 * 
+	 * @param done
+	 *            Steps done
+	 * @param total
+	 *            Steps in total
+	 * @param step
+	 *            Name of step
+	 */
+	public void installProgressUpdate(int done, int total, String step);
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/Installer.java	(revision 722)
@@ -0,0 +1,937 @@
+package net.oni2.aeinstaller.backend.oni;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Scanner;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import net.oni2.SettingsManager;
+import net.oni2.aeinstaller.AEInstaller2;
+import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.packages.EBSLInstallType;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.PackageManager;
+import net.oni2.platformtools.PlatformInformation;
+import net.oni2.platformtools.PlatformInformation.Platform;
+import net.oni2.platformtools.applicationinvoker.ApplicationInvocationResult;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.RegexFileFilter;
+import org.apache.commons.io.filefilter.TrueFileFilter;
+import org.javabuilders.swing.SwingJavaBuilder;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+
+/**
+ * @author Christian Illy
+ */
+public class Installer {
+	private static FileFilter dirFileFilter = new FileFilter() {
+		@Override
+		public boolean accept(File pathname) {
+			return pathname.isDirectory();
+		}
+	};
+
+	/**
+	 * @return Is Edition Core initialized
+	 */
+	public static boolean isEditionInitialized() {
+		return Paths.getVanillaOnisPath().exists();
+	}
+
+	private static void createEmptyPath(File path) throws IOException {
+		if (path.exists())
+			FileUtils.deleteDirectory(path);
+		path.mkdirs();
+	}
+
+	/**
+	 * @return list of currently installed mods
+	 */
+	public static Vector<Integer> getInstalledMods() {
+		File installCfg = new File(Paths.getEditionGDF(), "installed_mods.xml");
+		return PackageManager.getInstance().loadModSelection(installCfg);
+	}
+
+	/**
+	 * @return Currently installed tools
+	 */
+	@SuppressWarnings("unchecked")
+	public static TreeSet<Integer> getInstalledTools() {
+		File installCfg = new File(Paths.getInstallerPath(),
+				"installed_tools.xml");
+		TreeSet<Integer> res = new TreeSet<Integer>();
+		try {
+			if (installCfg.exists()) {
+				FileInputStream fis = new FileInputStream(installCfg);
+				XStream xs = new XStream(new StaxDriver());
+				Object obj = xs.fromXML(fis);
+				if (obj instanceof TreeSet<?>)
+					res = (TreeSet<Integer>) obj;
+				fis.close();
+			}
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	private static void writeInstalledTools(TreeSet<Integer> tools) {
+		File installCfg = new File(Paths.getInstallerPath(),
+				"installed_tools.xml");
+		try {
+			FileOutputStream fos = new FileOutputStream(installCfg);
+			XStream xs = new XStream(new StaxDriver());
+			xs.toXML(tools, fos);
+			fos.close();
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * @param tools
+	 *            Tools to (un)install
+	 * @param uninstall
+	 *            Uninstall tools or install?
+	 */
+	public static void installTools(TreeSet<Package> tools, boolean uninstall) {
+		TreeSet<Integer> installed = getInstalledTools();
+		for (Package m : tools) {
+			if (!uninstall || installed.contains(m.getPackageNumber())) {
+				File plain = CaseInsensitiveFile.getCaseInsensitiveFile(
+						m.getLocalPath(), "plain");
+				if (plain.exists()) {
+					if (m.hasSeparatePlatformDirs()) {
+						File plainCommon = CaseInsensitiveFile
+								.getCaseInsensitiveFile(plain, "common");
+						File plainMac = CaseInsensitiveFile
+								.getCaseInsensitiveFile(plain, "mac_only");
+						File plainWin = CaseInsensitiveFile
+								.getCaseInsensitiveFile(plain, "win_only");
+						if (plainCommon.exists())
+							copyRemoveToolsFiles(plainCommon,
+									Paths.getEditionBasePath(), uninstall);
+						if (PlatformInformation.getPlatform() == Platform.MACOS
+								&& plainMac.exists())
+							copyRemoveToolsFiles(plainMac,
+									Paths.getEditionBasePath(), uninstall);
+						else if (plainWin.exists())
+							copyRemoveToolsFiles(plainWin,
+									Paths.getEditionBasePath(), uninstall);
+					} else {
+						copyRemoveToolsFiles(plain, Paths.getEditionBasePath(),
+								uninstall);
+					}
+				}
+			}
+			if (uninstall)
+				installed.remove(m.getPackageNumber());
+			else
+				installed.add(m.getPackageNumber());
+		}
+		writeInstalledTools(installed);
+	}
+
+	private static void copyRemoveToolsFiles(File srcFolder, File targetFolder,
+			boolean remove) {
+		for (File f : srcFolder.listFiles()) {
+			try {
+				if (f.isDirectory())
+					copyRemoveToolsFiles(f,
+							CaseInsensitiveFile.getCaseInsensitiveFile(
+									targetFolder, f.getName()), remove);
+				else {
+					File targetFile = CaseInsensitiveFile
+							.getCaseInsensitiveFile(targetFolder, f.getName());
+					if (remove) {
+						if (targetFile.exists())
+							targetFile.delete();
+					} else {
+						if (!targetFile.getName().equals(f.getName()))
+							targetFile.delete();
+						FileUtils.copyFileToDirectory(f, targetFolder);
+					}
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+		if (remove)
+			if (targetFolder.list().length == 0)
+				targetFolder.delete();
+	}
+
+	/**
+	 * Install the given set of mods
+	 * 
+	 * @param mods
+	 *            Mods to install
+	 * @param listener
+	 *            Listener for install progress updates
+	 */
+	public static void install(TreeSet<Package> mods,
+			InstallProgressListener listener) {
+		File logFile = new File(Paths.getInstallerPath(), "Installation.log");
+		PrintWriter log = null;
+		try {
+			log = new PrintWriter(logFile);
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		}
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+		Date start = new Date();
+		log.println("Installation of mods started at " + sdf.format(start));
+
+		log.println();
+		log.println("AEI2 version: "
+				+ SwingJavaBuilder.getConfig().getResource("appversion"));
+		log.println("Installed tools:");
+		for (Package t : PackageManager.getInstance().getInstalledTools()) {
+			log.println(String.format(" - %s (%s)", t.getName(), t.getVersion()));
+		}
+		log.println("Installing mods:");
+		for (Package m : mods) {
+			log.println(String.format(" - %s (%s)", m.getName(), m.getVersion()));
+		}
+		log.println();
+
+		Paths.getEditionGDF().mkdirs();
+		for (File f : Paths.getEditionGDF().listFiles(new FilenameFilter() {
+			public boolean accept(File arg0, String arg1) {
+				String s = arg1.toLowerCase();
+				return s.endsWith(".dat")
+						|| s.endsWith(".raw")
+						|| s.endsWith(".sep")
+						|| (s.equals("intro.bik") && !SettingsManager.getInstance()
+								.get("copyintro", false))
+						|| (s.equals("outro.bik") && !SettingsManager.getInstance()
+								.get("copyoutro", false));
+			}
+		})) {
+			f.delete();
+		}
+		File IGMD = new File(Paths.getEditionGDF(), "IGMD");
+		if (IGMD.exists()) {
+			for (File f : IGMD.listFiles(new FileFilter() {
+				@Override
+				public boolean accept(File pathname) {
+					return pathname.isDirectory();
+				}
+			})) {
+				File ignore = CaseInsensitiveFile.getCaseInsensitiveFile(f,
+						"ignore.txt");
+				if (!ignore.exists()) {
+					try {
+						FileUtils.deleteDirectory(f);
+					} catch (IOException e) {
+						e.printStackTrace();
+					}
+				}
+			}
+		}
+
+		File installCfg = new File(Paths.getEditionGDF(), "installed_mods.xml");
+		PackageManager.getInstance().saveModSelection(installCfg, mods);
+
+		TreeSet<Integer> unlockLevels = new TreeSet<Integer>();
+
+		Vector<File> foldersOni = new Vector<File>();
+		foldersOni.add(Paths.getVanillaOnisPath());
+
+		Vector<File> foldersPatches = new Vector<File>();
+
+		for (Package m : mods) {
+			for (int lev : m.getUnlockLevels())
+				unlockLevels.add(lev);
+
+			File oni = CaseInsensitiveFile.getCaseInsensitiveFile(
+					m.getLocalPath(), "oni");
+			if (oni.exists()) {
+				if (m.hasSeparatePlatformDirs()) {
+					File oniCommon = CaseInsensitiveFile
+							.getCaseInsensitiveFile(oni, "common");
+					File oniMac = CaseInsensitiveFile.getCaseInsensitiveFile(
+							oni, "mac_only");
+					File oniWin = CaseInsensitiveFile.getCaseInsensitiveFile(
+							oni, "win_only");
+					if (oniCommon.exists())
+						foldersOni.add(oniCommon);
+					if (PlatformInformation.getPlatform() == Platform.MACOS
+							&& oniMac.exists())
+						foldersOni.add(oniMac);
+					else if (oniWin.exists())
+						foldersOni.add(oniWin);
+				} else {
+					foldersOni.add(oni);
+				}
+			}
+
+			File patches = CaseInsensitiveFile.getCaseInsensitiveFile(
+					m.getLocalPath(), "patches");
+			if (patches.exists()) {
+				if (m.hasSeparatePlatformDirs()) {
+					File patchesCommon = CaseInsensitiveFile
+							.getCaseInsensitiveFile(patches, "common");
+					File patchesMac = CaseInsensitiveFile
+							.getCaseInsensitiveFile(patches, "mac_only");
+					File patchesWin = CaseInsensitiveFile
+							.getCaseInsensitiveFile(patches, "win_only");
+					if (patchesCommon.exists())
+						foldersPatches.add(patchesCommon);
+					if (PlatformInformation.getPlatform() == Platform.MACOS
+							&& patchesMac.exists())
+						foldersPatches.add(patchesMac);
+					else if (patchesWin.exists())
+						foldersPatches.add(patchesWin);
+				} else {
+					foldersPatches.add(patches);
+				}
+			}
+		}
+
+		TreeMap<String, Vector<File>> levels = new TreeMap<String, Vector<File>>();
+		for (File path : foldersOni) {
+			for (File levelF : path.listFiles()) {
+				String fn = levelF.getName().toLowerCase();
+				String levelN = null;
+				if (levelF.isDirectory()) {
+					levelN = fn;
+				} else if (fn.endsWith(".dat")) {
+					levelN = fn.substring(0, fn.lastIndexOf('.')).toLowerCase();
+				}
+				if (levelN != null) {
+					if (!levels.containsKey(levelN))
+						levels.put(levelN, new Vector<File>());
+					levels.get(levelN).add(levelF);
+				}
+			}
+		}
+
+		applyPatches(levels, foldersPatches, listener, log);
+
+		combineBinaryFiles(levels, listener, log);
+		combineBSLFolders(mods, listener, log);
+
+		copyVideos(log);
+
+		if (unlockLevels.size() > 0) {
+			unlockLevels(unlockLevels, log);
+		}
+
+		log.println();
+		Date end = new Date();
+		log.println("Initialization ended at " + sdf.format(end));
+		log.println("Process took "
+				+ ((end.getTime() - start.getTime()) / 1000) + " seconds");
+		log.close();
+	}
+
+	private static void combineBSLFolders(TreeSet<Package> mods,
+			InstallProgressListener listener, PrintWriter log) {
+		listener.installProgressUpdate(95, 100, "Installing BSL files");
+		log.println("Installing BSL files");
+
+		HashMap<EBSLInstallType, Vector<Package>> modsToInclude = new HashMap<EBSLInstallType, Vector<Package>>();
+		modsToInclude.put(EBSLInstallType.NORMAL, new Vector<Package>());
+		modsToInclude.put(EBSLInstallType.ADDON, new Vector<Package>());
+
+		for (Package m : mods.descendingSet()) {
+			File bsl = CaseInsensitiveFile.getCaseInsensitiveFile(
+					m.getLocalPath(), "bsl");
+			if (bsl.exists()) {
+				if (m.hasSeparatePlatformDirs()) {
+					File bslCommon = CaseInsensitiveFile
+							.getCaseInsensitiveFile(bsl, "common");
+					File bslMac = CaseInsensitiveFile.getCaseInsensitiveFile(
+							bsl, "mac_only");
+					File bslWin = CaseInsensitiveFile.getCaseInsensitiveFile(
+							bsl, "win_only");
+					if ((PlatformInformation.getPlatform() == Platform.MACOS && bslMac
+							.exists())
+							|| ((PlatformInformation.getPlatform() == Platform.WIN || PlatformInformation
+									.getPlatform() == Platform.LINUX) && bslWin
+									.exists()) || bslCommon.exists()) {
+						modsToInclude.get(m.getBSLInstallType()).add(m);
+					}
+				} else {
+					modsToInclude.get(m.getBSLInstallType()).add(m);
+				}
+			}
+		}
+
+		for (Package m : modsToInclude.get(EBSLInstallType.NORMAL)) {
+			copyBSL(m, false);
+		}
+		Vector<Package> addons = modsToInclude.get(EBSLInstallType.ADDON);
+		for (int i = addons.size() - 1; i >= 0; i--) {
+			copyBSL(addons.get(i), true);
+		}
+	}
+
+	private static void copyBSL(Package sourceMod, boolean addon) {
+		File targetBaseFolder = new File(Paths.getEditionGDF(), "IGMD");
+		if (!targetBaseFolder.exists())
+			targetBaseFolder.mkdir();
+
+		Vector<File> sources = new Vector<File>();
+		File bsl = CaseInsensitiveFile.getCaseInsensitiveFile(
+				sourceMod.getLocalPath(), "bsl");
+		if (sourceMod.hasSeparatePlatformDirs()) {
+			File bslCommon = CaseInsensitiveFile.getCaseInsensitiveFile(bsl,
+					"common");
+			File bslMac = CaseInsensitiveFile.getCaseInsensitiveFile(bsl,
+					"mac_only");
+			File bslWin = CaseInsensitiveFile.getCaseInsensitiveFile(bsl,
+					"win_only");
+			if (PlatformInformation.getPlatform() == Platform.MACOS && bslMac.exists()) {
+				for (File f : bslMac.listFiles(dirFileFilter)) {
+					File targetBSL = new File(targetBaseFolder, f.getName());
+					if (addon || !targetBSL.exists())
+						sources.add(f);
+				}
+			}
+			if ((PlatformInformation.getPlatform() == Platform.WIN || PlatformInformation
+					.getPlatform() == Platform.LINUX) && bslWin.exists()) {
+				for (File f : bslWin.listFiles(dirFileFilter)) {
+					File targetBSL = new File(targetBaseFolder, f.getName());
+					if (addon || !targetBSL.exists())
+						sources.add(f);
+				}
+			}
+			if (bslCommon.exists()) {
+				for (File f : bslCommon.listFiles(dirFileFilter)) {
+					File targetBSL = new File(targetBaseFolder, f.getName());
+					if (addon || !targetBSL.exists())
+						sources.add(f);
+				}
+			}
+		} else {
+			for (File f : bsl.listFiles(dirFileFilter)) {
+				File targetBSL = new File(targetBaseFolder, f.getName());
+				if (addon || !targetBSL.exists())
+					sources.add(f);
+			}
+		}
+
+		System.out.println("For mod: " + sourceMod.getName()
+				+ " install BSL folders: " + sources.toString());
+		for (File f : sources) {
+			File targetPath = new File(targetBaseFolder, f.getName());
+			if (!targetPath.exists())
+				targetPath.mkdir();
+			if (!(CaseInsensitiveFile.getCaseInsensitiveFile(targetPath,
+					"ignore.txt").exists())) {
+				for (File fbsl : f.listFiles()) {
+					if (fbsl.getName().toLowerCase().endsWith(".bsl")) {
+						File targetFile = new File(targetPath, fbsl.getName());
+						if (addon || !targetFile.exists()) {
+							try {
+								FileUtils.copyFile(fbsl, targetFile);
+							} catch (IOException e) {
+								e.printStackTrace();
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	private static void applyPatches(
+			TreeMap<String, Vector<File>> oniLevelFolders,
+			List<File> patchFolders, InstallProgressListener listener,
+			PrintWriter log) {
+		log.println();
+		log.println("Applying XML patches");
+		listener.installProgressUpdate(0, 1, "Applying XML patches");
+
+		long startMS = new Date().getTime();
+
+		String tmpFolderName = "installrun_temp-"
+				+ new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss")
+						.format(new Date());
+		File tmpFolder = new File(Paths.getTempPath(), tmpFolderName);
+		tmpFolder.mkdir();
+
+		HashMap<String, Vector<File>> patches = new HashMap<String, Vector<File>>();
+		for (File patchFolder : patchFolders) {
+			for (File levelFolder : patchFolder.listFiles(dirFileFilter)) {
+				String lvlName = levelFolder.getName().toLowerCase();
+				for (File f : FileUtils.listFiles(levelFolder,
+						new String[] { "oni-patch" }, true)) {
+					if (!patches.containsKey(lvlName))
+						patches.put(lvlName, new Vector<File>());
+					patches.get(lvlName).add(f);
+				}
+			}
+		}
+
+		for (String level : patches.keySet()) {
+			File levelFolder = new File(tmpFolder, level);
+			levelFolder.mkdir();
+
+			log.println("\t\tPatches for " + level);
+
+			Vector<String> exportPatterns = new Vector<String>();
+			// Get files to be patched from vanilla.dat
+			for (File patch : patches.get(level)) {
+				String patternWildcard = patch.getName();
+				patternWildcard = patternWildcard.substring(0,
+						patternWildcard.indexOf(".oni-patch"));
+				patternWildcard = patternWildcard.replace('-', '*');
+				exportPatterns.add(patternWildcard);
+			}
+			for (File srcFolder : oniLevelFolders.get(level)) {
+				if (srcFolder.isFile()) {
+					if (srcFolder.getPath().toLowerCase().contains("vanilla")) {
+						// Extract from .dat
+						ApplicationInvocationResult res = OniSplit.export(levelFolder,
+								srcFolder, exportPatterns);
+						logAppOutput(res, log);
+					}
+				}
+			}
+
+			// Get files to be patched from packages
+			for (File patch : patches.get(level)) {
+				String patternWildcard = patch.getName();
+				patternWildcard = patternWildcard.substring(0,
+						patternWildcard.indexOf(".oni-patch"));
+				patternWildcard = patternWildcard.replace('-', '*');
+				patternWildcard = patternWildcard + ".oni";
+				Vector<String> patterns = new Vector<String>();
+				patterns.add(patternWildcard);
+				final Pattern patternRegex = Pattern.compile(
+						patternWildcard.replaceAll("\\*", ".\\*"),
+						Pattern.CASE_INSENSITIVE);
+
+				for (File srcFolder : oniLevelFolders.get(level)) {
+					if (srcFolder.isFile()) {
+						if (!srcFolder.getPath().toLowerCase()
+								.contains("vanilla")) {
+							// Extract from .dat
+							ApplicationInvocationResult res = OniSplit.export(
+									levelFolder, srcFolder, patterns);
+							logAppOutput(res, log);
+						}
+					} else {
+						// Copy from folder with overwrite
+						for (File f : FileUtils.listFiles(srcFolder,
+								new RegexFileFilter(patternRegex),
+								TrueFileFilter.TRUE)) {
+							try {
+								FileUtils.copyFileToDirectory(f, levelFolder);
+							} catch (IOException e) {
+								e.printStackTrace();
+							}
+						}
+					}
+				}
+			}
+
+			// Extract files to XML
+			File levelFolderXML = new File(levelFolder, "xml");
+			Vector<File> files = new Vector<File>();
+			files.add(new File(levelFolder, "*.oni"));
+			ApplicationInvocationResult res = OniSplit.convertOniToXML(levelFolderXML,
+					files);
+			logAppOutput(res, log);
+
+			// Apply patches in levelFolderXML
+			for (File patch : patches.get(level)) {
+				String patternWildcard = patch.getName();
+				patternWildcard = patternWildcard.substring(0,
+						patternWildcard.indexOf(".oni-patch"));
+				patternWildcard = patternWildcard + ".xml";
+				patternWildcard = patternWildcard.replace('-', '*');
+
+				res = XMLTools.patch(patch, new File(levelFolderXML,
+						patternWildcard));
+				logAppOutput(res, log);
+			}
+
+			// Create .oni files from XML
+			files.clear();
+			files.add(new File(levelFolderXML, "*.xml"));
+			res = OniSplit.convertXMLtoOni(levelFolder, files);
+			logAppOutput(res, log);
+
+			// Remove XML folder as import will only require .oni's
+			// try {
+			// FileUtils.deleteDirectory(levelFolderXML);
+			// } catch (IOException e) {
+			// e.printStackTrace();
+			// }
+
+			oniLevelFolders.get(level).add(levelFolder);
+		}
+
+		log.println("Applying XML patches took "
+				+ (new Date().getTime() - startMS) + " ms");
+	}
+
+	private static void combineBinaryFiles(
+			TreeMap<String, Vector<File>> oniLevelFolders,
+			InstallProgressListener listener, PrintWriter log) {
+		long startMS = new Date().getTime();
+
+		int totalSteps = 0;
+		int stepsDone = 0;
+
+		for (@SuppressWarnings("unused")
+		String s : oniLevelFolders.keySet())
+			totalSteps++;
+		totalSteps++;
+
+		log.println();
+		log.println("Importing levels");
+		for (String l : oniLevelFolders.keySet()) {
+			log.println("\tLevel " + l);
+			listener.installProgressUpdate(stepsDone, totalSteps,
+					"Installing level " + l);
+
+			ApplicationInvocationResult res = OniSplit.packLevel(oniLevelFolders.get(l),
+					new File(Paths.getEditionGDF(), sanitizeLevelName(l)
+							+ ".dat"));
+			logAppOutput(res, log);
+
+			stepsDone++;
+		}
+
+		log.println("Importing levels took " + (new Date().getTime() - startMS)
+				+ " ms");
+		log.println();
+	}
+
+	private static void copyVideos(PrintWriter log) {
+		if (SettingsManager.getInstance().get("copyintro", false)) {
+			File src = new File(Paths.getVanillaGDF(), "intro.bik");
+			log.println("Copying intro");
+			if (src.exists()) {
+				try {
+					FileUtils.copyFileToDirectory(src, Paths.getEditionGDF());
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+		} else {
+			log.println("NOT copying intro");
+		}
+		if (SettingsManager.getInstance().get("copyoutro", true)) {
+			File src = new File(Paths.getVanillaGDF(), "outro.bik");
+			log.println("Copying outro");
+			if (src.exists()) {
+				try {
+					FileUtils.copyFileToDirectory(src, Paths.getEditionGDF());
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+		} else {
+			log.println("NOT copying outro");
+		}
+	}
+
+	private static void unlockLevels(TreeSet<Integer> unlockLevels,
+			PrintWriter log) {
+		File dat = new File(Paths.getEditionBasePath(), "persist.dat");
+		log.println("Unlocking levels: " + unlockLevels.toString());
+		if (!dat.exists()) {
+			InputStream is = AEInstaller2.class
+					.getResourceAsStream("/net/oni2/aeinstaller/resources/persist.dat");
+			try {
+				FileUtils.copyInputStreamToFile(is, dat);
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+		PersistDat save = new PersistDat(dat);
+		HashSet<Integer> currentlyUnlocked = save.getUnlockedLevels();
+		currentlyUnlocked.addAll(unlockLevels);
+		save.setUnlockedLevels(currentlyUnlocked);
+		save.close();
+	}
+
+	/**
+	 * Initializes the Edition core
+	 * 
+	 * @param listener
+	 *            Listener for status updates
+	 */
+	public static void initializeEdition(InstallProgressListener listener) {
+		File init = new File(Paths.getTempPath(), "init");
+
+		int totalSteps = 0;
+		int stepsDone = 0;
+
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+		for (@SuppressWarnings("unused")
+		File f : Paths.getVanillaGDF().listFiles(new FilenameFilter() {
+			@Override
+			public boolean accept(File dir, String name) {
+				return name.endsWith(".dat");
+			}
+		})) {
+			totalSteps++;
+		}
+		totalSteps = totalSteps * 2 + 2;
+
+		try {
+			File logFile = new File(Paths.getInstallerPath(),
+					"Initialization.log");
+			PrintWriter log = new PrintWriter(logFile);
+
+			Date start = new Date();
+			log.println("Initialization of Edition core started at "
+					+ sdf.format(start));
+			log.println("Cleaning directories");
+
+			listener.installProgressUpdate(stepsDone, totalSteps,
+					"Cleaning up directories");
+			createEmptyPath(Paths.getVanillaOnisPath());
+			createEmptyPath(init);
+			File level0Folder = new File(init, "level0_Final");
+			createEmptyPath(level0Folder);
+
+			stepsDone++;
+
+			log.println("Exporting levels and moving files to level0");
+
+			for (File f : Paths.getVanillaGDF().listFiles(new FilenameFilter() {
+				@Override
+				public boolean accept(File dir, String name) {
+					return name.endsWith(".dat");
+				}
+			})) {
+				String levelName = f.getName().substring(0,
+						f.getName().indexOf('.'));
+				Scanner fi = new Scanner(levelName);
+				int levelNumber = Integer.parseInt(fi.findInLine("[0-9]+"));
+
+				log.println("\t" + levelName + ":");
+				log.println("\t\tExporting");
+				listener.installProgressUpdate(stepsDone, totalSteps,
+						"Exporting vanilla level " + levelNumber);
+
+				// Edition/GameDataFolder/level*_Final/
+				File tempLevelFolder = new File(init, levelName);
+
+				// Export Vanilla-Level-Dat -> Temp/Level
+				ApplicationInvocationResult res = OniSplit.export(tempLevelFolder, f);
+				logAppOutput(res, log);
+
+				log.println("\t\tMoving files");
+				handleFileGlobalisation(tempLevelFolder, level0Folder,
+						levelNumber);
+				stepsDone++;
+				log.println();
+			}
+
+			log.println("Reimporting levels");
+
+			for (File f : init.listFiles()) {
+				String levelName = f.getName();
+
+				log.println("\t" + levelName);
+				listener.installProgressUpdate(stepsDone, totalSteps,
+						"Creating globalized " + levelName);
+
+				Vector<File> folders = new Vector<File>();
+				folders.add(f);
+
+				ApplicationInvocationResult res = OniSplit
+						.importLevel(folders,
+								new File(Paths.getVanillaOnisPath(), levelName
+										+ ".dat"));
+				logAppOutput(res, log);
+
+				log.println();
+				stepsDone++;
+			}
+
+			listener.installProgressUpdate(stepsDone, totalSteps,
+					"Copying basic files");
+			// Copy Oni-configs
+			File persistVanilla = new File(Paths.getOniBasePath(),
+					"persist.dat");
+			File persistEdition = new File(Paths.getEditionBasePath(),
+					"persist.dat");
+			File keyConfVanilla = new File(Paths.getOniBasePath(),
+					"key_config.txt");
+			File keyConfEdition = new File(Paths.getEditionBasePath(),
+					"key_config.txt");
+			if (persistVanilla.exists() && !persistEdition.exists())
+				FileUtils.copyFile(persistVanilla, persistEdition);
+			if (keyConfVanilla.exists() && !keyConfEdition.exists())
+				FileUtils.copyFile(keyConfVanilla, keyConfEdition);
+
+			FileUtils.deleteDirectory(init);
+
+			Date end = new Date();
+			log.println("Initialization ended at " + sdf.format(end));
+			log.println("Process took "
+					+ ((end.getTime() - start.getTime()) / 1000) + " seconds");
+			log.close();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	private static void moveFileToTargetOrDelete(File source, File target) {
+		if (source.equals(target))
+			return;
+		if (!target.exists()) {
+			if (!source.renameTo(target)) {
+				System.err.println("File " + source.getPath() + " not moved!");
+			}
+		} else if (!source.delete()) {
+			System.err.println("File " + source.getPath() + " not deleted!");
+		}
+	}
+
+	private static void handleFileGlobalisation(File tempFolder,
+			File level0Folder, int levelNumber) {
+		// Move AKEV and related files to subfolder so they're not globalized:
+		if (levelNumber != 0) {
+			File akevFolder = new File(tempFolder, "AKEV");
+			akevFolder.mkdir();
+			OniSplit.move(akevFolder, tempFolder.getPath() + "/AKEV*.oni",
+					"overwrite");
+		}
+
+		for (File f : tempFolder.listFiles(new FileFilter() {
+			@Override
+			public boolean accept(File pathname) {
+				return pathname.isFile();
+			}
+		})) {
+			// Move matching files to subfolder NoGlobal:
+			if (f.getName().startsWith("TXMPfail")
+					|| f.getName().startsWith("TXMPlevel")
+					|| (f.getName().startsWith("TXMP") && f.getName().contains(
+							"intro"))
+					|| f.getName().startsWith("TXMB")
+					|| f.getName().equals("M3GMpowerup_lsi.oni")
+					|| f.getName().equals("TXMPlsi_icon.oni")
+					|| (f.getName().startsWith("TXMB") && f.getName().contains(
+							"splash_screen.oni"))) {
+				File noGlobal = new File(tempFolder, "NoGlobal");
+				noGlobal.mkdir();
+				File noGlobalFile = new File(noGlobal, f.getName());
+				moveFileToTargetOrDelete(f, noGlobalFile);
+			}
+			// Move matching files to level0_Animations/level0_TRAC
+			else if (f.getName().startsWith("TRAC")) {
+				File level0File = new File(level0Folder, f.getName());
+				moveFileToTargetOrDelete(f, level0File);
+			}
+			// Move matching files to level0_Animations/level0_TRAM
+			else if (f.getName().startsWith("TRAM")) {
+				File level0File = new File(level0Folder, f.getName());
+				moveFileToTargetOrDelete(f, level0File);
+			}
+			// Move matching files to level0_Textures
+			else if (f.getName().startsWith("ONSK")
+					|| f.getName().startsWith("TXMP")) {
+				File level0File = new File(level0Folder, f.getName());
+				moveFileToTargetOrDelete(f, level0File);
+			}
+			// Move matching files to *VANILLA*/level0_Characters
+			else if (f.getName().startsWith("ONCC")
+					|| f.getName().startsWith("TRBS")
+					|| f.getName().startsWith("ONCV")
+					|| f.getName().startsWith("ONVL")
+					|| f.getName().startsWith("TRMA")
+					|| f.getName().startsWith("TRSC")
+					|| f.getName().startsWith("TRAS")) {
+				File level0File = new File(level0Folder, f.getName());
+				moveFileToTargetOrDelete(f, level0File);
+			}
+			// Move matching files to level0_Sounds
+			else if (f.getName().startsWith("OSBD")
+					|| f.getName().startsWith("SNDD")) {
+				File level0File = new File(level0Folder, f.getName());
+				moveFileToTargetOrDelete(f, level0File);
+			}
+			// Move matching files to level0_Particles
+			else if (f.getName().startsWith("BINA3")
+					|| f.getName().startsWith("M3GMdebris")
+					|| f.getName().equals("M3GMtoxic_bubble.oni")
+					|| f.getName().startsWith("M3GMelec")
+					|| f.getName().startsWith("M3GMrat")
+					|| f.getName().startsWith("M3GMjet")
+					|| f.getName().startsWith("M3GMbomb_")
+					|| f.getName().equals("M3GMbarab_swave.oni")
+					|| f.getName().equals("M3GMbloodyfoot.oni")) {
+				File level0File = new File(level0Folder, f.getName());
+				moveFileToTargetOrDelete(f, level0File);
+			}
+			// Move matching files to Archive (aka delete them)
+			else if (f.getName().startsWith("AGDB")
+					|| f.getName().startsWith("TRCM")) {
+				f.delete();
+			}
+			// Move matching files to /level0_Final/
+			else if (f.getName().startsWith("ONWC")) {
+				File level0File = new File(level0Folder, f.getName());
+				moveFileToTargetOrDelete(f, level0File);
+			}
+		}
+	}
+
+	private static String sanitizeLevelName(String ln) {
+		int ind = ln.indexOf("_");
+		String res = ln.substring(0, ind + 1);
+		res += ln.substring(ind + 1, ind + 2).toUpperCase();
+		res += ln.substring(ind + 2);
+		return res;
+	}
+
+	/**
+	 * Verify that the Edition is within a subfolder to vanilla Oni
+	 * (..../Oni/Edition/AEInstaller)
+	 * 
+	 * @return true if GDF can be found in the parent's parent-path
+	 */
+	public static boolean verifyRunningDirectory() {
+		return Paths.getVanillaGDF().exists()
+				&& Paths.getVanillaGDF().isDirectory();
+	}
+
+	private static void logAppOutput(ApplicationInvocationResult result, PrintWriter log) {
+		if (result != null) {
+			log.println("\t\t\tCalled:");
+			for (String s : result.cmdLine)
+				log.println("\t\t\t\t" + s);
+			log.println("\t\t\tReturned: " + result.errorCode);
+			for (String s : result.output)
+				log.println("\t\t\t\t" + s);
+			log.println("\t\t\tDuration: " + result.time + " ms");
+			log.println();
+		}
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/OniLauncher.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/OniLauncher.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/OniLauncher.java	(revision 722)
@@ -0,0 +1,68 @@
+package net.oni2.aeinstaller.backend.oni;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Vector;
+
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.platformtools.PlatformInformation;
+import net.oni2.platformtools.applicationinvoker.ApplicationInvoker;
+import net.oni2.platformtools.applicationinvoker.EExeType;
+import net.oni2.platformtools.applicationinvoker.ERuntimeNotInstalledException;
+
+/**
+ * @author Christian Illy
+ */
+public class OniLauncher {
+
+	private static File getOniExe() {
+		File exe = null;
+		switch (PlatformInformation.getPlatform()) {
+			case WIN:
+				exe = new File(Paths.getEditionBasePath(), "Oni.exe");
+				break;
+			case MACOS:
+				exe = new File(Paths.getEditionBasePath(),
+						"Oni.app/Contents/MacOS/Oni");
+				break;
+			case LINUX:
+				exe = new File(Paths.getEditionBasePath(), "Oni.exe");
+				break;
+			default:
+		}
+		if ((exe != null) && !exe.exists())
+			exe = null;
+		return exe;
+	}
+
+	private static EExeType getOniExeType() {
+		switch (PlatformInformation.getPlatform()) {
+			case MACOS:
+				return EExeType.OSBINARY;
+			default:
+				return EExeType.WINEXE;
+		}
+	}
+
+	/**
+	 * @param windowed
+	 *            Run in windowed mode
+	 * @throws FileNotFoundException
+	 *             If Oni's executable was not found
+	 * @throws ERuntimeNotInstalledException
+	 *             If Linux and Wine not found
+	 */
+	public static void launch(boolean windowed) throws FileNotFoundException,
+			ERuntimeNotInstalledException {
+		File exe = getOniExe();
+		if (exe == null)
+			throw new FileNotFoundException("Oni's executable was not found");
+		Vector<String> params = new Vector<String>();
+		params.add("-debugfiles");
+		if (windowed)
+			params.add("-noswitch");
+		ApplicationInvoker.execute(getOniExeType(), exe.getParentFile(), exe,
+				params);
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/OniSplit.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/OniSplit.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/OniSplit.java	(revision 722)
@@ -0,0 +1,254 @@
+package net.oni2.aeinstaller.backend.oni;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Vector;
+
+import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.platformtools.PlatformInformation;
+import net.oni2.platformtools.PlatformInformation.Platform;
+import net.oni2.platformtools.applicationinvoker.ApplicationInvoker;
+import net.oni2.platformtools.applicationinvoker.ApplicationInvocationResult;
+import net.oni2.platformtools.applicationinvoker.EExeType;
+import net.oni2.platformtools.applicationinvoker.ERuntimeNotInstalledException;
+
+/**
+ * @author Christian Illy
+ */
+public class OniSplit {
+
+	/**
+	 * @return Is Onisplit installed?
+	 */
+	public static boolean isOniSplitInstalled() {
+		return getProgramFile().exists();
+	}
+
+	/**
+	 * Export given dat-file to target folder
+	 * 
+	 * @param targetFolder
+	 *            Target folder
+	 * @param input
+	 *            Dat file
+	 * @return OniSplit output
+	 */
+	public static ApplicationInvocationResult export(File targetFolder, File input) {
+		return export(targetFolder, input, null);
+	}
+
+	/**
+	 * Export named resources from given dat-file to target folder
+	 * 
+	 * @param targetFolder
+	 *            Target folder
+	 * @param input
+	 *            Dat file
+	 * @param patterns
+	 *            Filename patterns for files to export
+	 * @return OniSplit output
+	 */
+	public static ApplicationInvocationResult export(File targetFolder, File input,
+			Vector<String> patterns) {
+		if (!targetFolder.exists())
+			targetFolder.mkdir();
+
+		Vector<String> params = new Vector<String>();
+		if (patterns == null)
+			params.add("-export");
+		else {
+			for (String p : patterns)
+				params.add("-export:" + p);
+		}
+		params.add(targetFolder.getPath());
+		params.add(input.getPath());
+		ApplicationInvocationResult res = null;
+		try {
+			res = ApplicationInvoker.executeAndWait(EExeType.DOTNET, null, getProgramFile(), params);
+		} catch (IOException e) {
+			e.printStackTrace();
+		} catch (ERuntimeNotInstalledException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	/**
+	 * Import given folder to a .dat-file
+	 * 
+	 * @param sourceFolders
+	 *            Folders containing .oni-files
+	 * @param targetFile
+	 *            Target .dat-file
+	 * @return OniSplit output
+	 */
+	public static ApplicationInvocationResult importLevel(Vector<File> sourceFolders,
+			File targetFile) {
+		Vector<String> params = new Vector<String>();
+		params.add(getImportParam());
+		for (File f : sourceFolders)
+			params.add(f.getPath());
+		params.add(targetFile.getPath());
+		ApplicationInvocationResult res = null;
+		try {
+			res = ApplicationInvoker.executeAndWait(EExeType.DOTNET, null, getProgramFile(), params);
+		} catch (IOException e) {
+			e.printStackTrace();
+		} catch (ERuntimeNotInstalledException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	/**
+	 * Pack a level to a .dat-file. More powerful variant which allows
+	 * specifying single .oni/.dat files
+	 * 
+	 * @param sourceFoldersFiles
+	 *            Folders (for recursive .oni import) or files (.dat and single
+	 *            .oni) to import
+	 * @param targetFile
+	 *            Target .dat-file
+	 * @return OniSplit output
+	 */
+	public static ApplicationInvocationResult packLevel(Vector<File> sourceFoldersFiles,
+			File targetFile) {
+		Vector<String> params = new Vector<String>();
+		params.add(getPackParam());
+		params.add(getPackTypeParam());
+		params.add("-out");
+		params.add(targetFile.getPath());
+		for (File f : sourceFoldersFiles)
+			params.add(f.getPath());
+		ApplicationInvocationResult res = null;
+		try {
+			res = ApplicationInvoker.executeAndWait(EExeType.DOTNET, null, getProgramFile(), params);
+		} catch (IOException e) {
+			e.printStackTrace();
+		} catch (ERuntimeNotInstalledException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	/**
+	 * Move files from one location to another using OniSplit so relations are
+	 * handled
+	 * 
+	 * @param targetFolder
+	 *            Target folder for files
+	 * @param input
+	 *            Files to move, can contain wildcards
+	 * @param moveParameter
+	 *            e.g. overwrite, delete
+	 * @return OniSplit output
+	 */
+	public static ApplicationInvocationResult move(File targetFolder, String input,
+			String moveParameter) {
+		if (!targetFolder.exists())
+			targetFolder.mkdir();
+
+		Vector<String> params = new Vector<String>();
+		params.add("-move"
+				+ (moveParameter != null ? ":" + moveParameter : ""));
+		params.add(targetFolder.getPath());
+		params.add(input);
+		ApplicationInvocationResult res = null;
+		try {
+			res = ApplicationInvoker.executeAndWait(EExeType.DOTNET, null, getProgramFile(), params);
+		} catch (IOException e) {
+			e.printStackTrace();
+		} catch (ERuntimeNotInstalledException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	/**
+	 * Convert given .oni-files to XML and put them in target folder
+	 * 
+	 * @param targetFolder
+	 *            Target folder
+	 * @param inputFiles
+	 *            .oni files
+	 * @return OniSplit output
+	 */
+	public static ApplicationInvocationResult convertOniToXML(File targetFolder,
+			Vector<File> inputFiles) {
+		if (!targetFolder.exists())
+			targetFolder.mkdirs();
+
+		Vector<String> params = new Vector<String>();
+		params.add("-extract:xml");
+		params.add(targetFolder.getPath());
+		for (File f : inputFiles) {
+			params.add(f.getPath());
+		}
+		ApplicationInvocationResult res = null;
+		try {
+			res = ApplicationInvoker.executeAndWait(EExeType.DOTNET, null, getProgramFile(), params);
+		} catch (IOException e) {
+			e.printStackTrace();
+		} catch (ERuntimeNotInstalledException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	/**
+	 * Convert given XML-files to .oni and put them in target folder
+	 * 
+	 * @param targetFolder
+	 *            Target folder
+	 * @param inputFiles
+	 *            XML-files
+	 * @return OniSplit output
+	 */
+	public static ApplicationInvocationResult convertXMLtoOni(File targetFolder,
+			Vector<File> inputFiles) {
+		if (!targetFolder.exists())
+			targetFolder.mkdirs();
+
+		Vector<String> params = new Vector<String>();
+		params.add("-create");
+		params.add(targetFolder.getPath());
+		for (File f : inputFiles) {
+			params.add(f.getPath());
+		}
+		ApplicationInvocationResult res = null;
+		try {
+			res = ApplicationInvoker.executeAndWait(EExeType.DOTNET, null, getProgramFile(), params);
+		} catch (IOException e) {
+			e.printStackTrace();
+		} catch (ERuntimeNotInstalledException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	private static String getImportParam() {
+		if (PlatformInformation.getPlatform() == Platform.MACOS)
+			return "-import:sep";
+		else
+			return "-import:nosep";
+	}
+
+	private static String getPackParam() {
+		return "pack";
+	}
+
+	private static String getPackTypeParam() {
+		if (PlatformInformation.getPlatform() == Platform.MACOS)
+			return "-type:macintel";
+		else
+			return "-type:pc";
+	}
+
+	private static File getProgramFile() {
+		File toolsPath = CaseInsensitiveFile.getCaseInsensitiveFile(
+				Paths.getEditionBasePath(), "Tools");
+		return CaseInsensitiveFile.getCaseInsensitiveFile(toolsPath,
+				"Onisplit.exe");
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/PersistDat.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/PersistDat.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/PersistDat.java	(revision 722)
@@ -0,0 +1,90 @@
+package net.oni2.aeinstaller.backend.oni;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.HashSet;
+
+/**
+ * @author Christian Illy
+ */
+public class PersistDat {
+	private RandomAccessFile dat = null;
+
+	/**
+	 * Open the given persist.dat
+	 * 
+	 * @param dat
+	 *            Path to persist.dat
+	 */
+	public PersistDat(File dat) {
+		try {
+			this.dat = new RandomAccessFile(dat, "rw");
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * @return Currently unlocked levels in persist.dat
+	 */
+	public HashSet<Integer> getUnlockedLevels() {
+		HashSet<Integer> result = new HashSet<Integer>();
+		if (dat != null) {
+			try {
+				dat.seek(8);
+				for (int i = 0; i < 32; i++) {
+					int val = dat.read();
+					for (int j = 0; j < 8; j++) {
+						if ((val & (1 << j)) > 0) {
+							result.add(i * 8 + j);
+						}
+					}
+				}
+			} catch (IOException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * @param unlock
+	 *            Levels to be unlocked in persist.dat
+	 */
+	public void setUnlockedLevels(HashSet<Integer> unlock) {
+		if (dat != null) {
+			if (unlock != null) {
+				try {
+					dat.seek(8);
+					for (int i = 0; i < 32; i++) {
+						int val = 0;
+						for (int j = 0; j < 8; j++) {
+							if (unlock.contains(i * 8 + j))
+								val |= 1 << j;
+						}
+						dat.write(val);
+					}
+				} catch (IOException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Close file
+	 */
+	public void close() {
+		if (dat != null) {
+			try {
+				dat.close();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/XMLTools.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/XMLTools.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/XMLTools.java	(revision 722)
@@ -0,0 +1,53 @@
+package net.oni2.aeinstaller.backend.oni;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Vector;
+
+import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.platformtools.applicationinvoker.ApplicationInvoker;
+import net.oni2.platformtools.applicationinvoker.ApplicationInvocationResult;
+import net.oni2.platformtools.applicationinvoker.EExeType;
+import net.oni2.platformtools.applicationinvoker.ERuntimeNotInstalledException;
+
+/**
+ * @author Christian Illy
+ */
+public class XMLTools {
+
+	/**
+	 * Patch the given XML file with the given patch
+	 * 
+	 * @param patch
+	 *            Patchfile
+	 * @param source
+	 *            File to patch
+	 * 
+	 * @return XMLTools output
+	 */
+	public static ApplicationInvocationResult patch(File patch, File source) {
+		Vector<String> params = new Vector<String>();
+		// xmlTools.exe patchfile -filename:PATCH -forceinfiles:TOPATCH
+		params.add("patchfile");
+		params.add("-filename:" + patch.getPath());
+		params.add("-forceinfiles:" + source.getPath());
+		ApplicationInvocationResult res = null;
+		try {
+			res = ApplicationInvoker.executeAndWait(EExeType.DOTNET, null,
+					getProgramFile(), params);
+		} catch (IOException e) {
+			e.printStackTrace();
+		} catch (ERuntimeNotInstalledException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	private static File getProgramFile() {
+		File toolsPath = CaseInsensitiveFile.getCaseInsensitiveFile(
+				Paths.getEditionBasePath(), "Tools");
+		return CaseInsensitiveFile.getCaseInsensitiveFile(toolsPath,
+				"xmlTools.exe");
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/EBSLInstallType.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/EBSLInstallType.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/EBSLInstallType.java	(revision 722)
@@ -0,0 +1,15 @@
+package net.oni2.aeinstaller.backend.packages;
+
+/**
+ * @author Christian Illy
+ */
+public enum EBSLInstallType {
+	/**
+	 * Normal BSL install mode
+	 */
+	NORMAL,
+	/**
+	 * BSL addon install mode
+	 */
+	ADDON
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/ECompatiblePlatform.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/ECompatiblePlatform.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/ECompatiblePlatform.java	(revision 722)
@@ -0,0 +1,19 @@
+package net.oni2.aeinstaller.backend.packages;
+
+/**
+ * @author Christian Illy
+ */
+public enum ECompatiblePlatform {
+	/**
+	 * Only for Win
+	 */
+	WIN,
+	/**
+	 * Only for MacOS
+	 */
+	MACOS,
+	/**
+	 * Usable with both platforms
+	 */
+	BOTH
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/Mod_Info.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/Mod_Info.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/Mod_Info.java	(revision 722)
@@ -0,0 +1,245 @@
+package net.oni2.aeinstaller.backend.packages;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashSet;
+
+import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.platformtools.applicationinvoker.EExeType;
+
+/**
+ * @author Christian Illy
+ */
+public class Mod_Info {
+	private double aeVersion = 0;
+	private String name = "";
+	private String creator = "";
+	private EBSLInstallType bslInstallType = EBSLInstallType.NORMAL;
+	private String version = "";
+	private String description = "";
+
+	private HashSet<Integer> incompatibilities = new HashSet<Integer>();
+	private HashSet<Integer> dependencies = new HashSet<Integer>();
+	private HashSet<Integer> unlockLevel = new HashSet<Integer>();
+
+	private File exeFile = null;
+	private EExeType exeType = EExeType.OSBINARY;
+	private File iconFile = null;
+	private String workingDir = "Base";
+
+	/**
+	 * @param f
+	 *            Mod_Info.cfg
+	 * @param packageNumber
+	 *            Package number this Mod_Info belongs to
+	 */
+	public Mod_Info(File f, int packageNumber) {
+		InputStreamReader isr = null;
+		try {
+			FileInputStream fstream = new FileInputStream(f);
+			isr = new InputStreamReader(fstream);
+			BufferedReader br = new BufferedReader(isr);
+			String strLine;
+			while ((strLine = br.readLine()) != null) {
+				if (strLine.indexOf("->") < 1)
+					continue;
+				int pos = strLine.indexOf("//");
+				while (pos >= 0) {
+					if ((pos < 6)
+							|| !strLine.substring(pos - 5, pos).equals("http:")) {
+						strLine = strLine.substring(0, pos);
+						break;
+					} else {
+						pos = strLine.indexOf("//", pos + 1);
+					}
+				}
+				String[] split = strLine.split("->", 2);
+				String sName = split[0].trim();
+				String sVal = split[1].trim();
+				if (sName.equalsIgnoreCase("AEInstallVersion")) {
+					aeVersion = Double.parseDouble(sVal);
+				} else if (sName.equalsIgnoreCase("NameOfMod")) {
+					name = sVal;
+				} else if (sName.equalsIgnoreCase("Creator")) {
+					creator = sVal;
+				} else if (sName.equalsIgnoreCase("HasBsl")) {
+					if (sVal.equalsIgnoreCase("addon"))
+						bslInstallType = EBSLInstallType.ADDON;
+				} else if (sName.equalsIgnoreCase("ModVersion")) {
+					version = sVal;
+				} else if (sName.equalsIgnoreCase("Readme")) {
+					description = "<p>" + sVal.replaceAll("\\\\n", "<br>")
+							+ "</p>";
+				} else if (sName.equalsIgnoreCase("DependsOn")) {
+					String[] depsS = sVal.split(",");
+					for (String s : depsS) {
+						try {
+							int dep = Integer.parseInt(s);
+							dependencies.add(dep);
+						} catch (NumberFormatException e) {
+							System.err
+									.format("Mod_Info of %05d does contain a non-number dependency: '%s'\n",
+											packageNumber, s);
+						}
+					}
+				} else if (sName.equalsIgnoreCase("IncompatibleWith")) {
+					String[] confS = sVal.split(",");
+					for (String s : confS) {
+						try {
+							int conf = Integer.parseInt(s);
+							incompatibilities.add(conf);
+						} catch (NumberFormatException e) {
+							System.err
+									.format("Mod_Info of %05d does contain a non-number incompatibility: '%s'\n",
+											packageNumber, s);
+						}
+					}
+				} else if (sName.equalsIgnoreCase("UnlockLevel")) {
+					String[] levelsS = sVal.split(",");
+					for (String s : levelsS) {
+						try {
+							int level = Integer.parseInt(s);
+							unlockLevel.add(level);
+						} catch (NumberFormatException e) {
+							System.err
+									.format("Mod_Info of %05d does contain a non-number UnlockLevel value: '%s'\n",
+											packageNumber, s);
+						}
+					}
+				} else if (sName.equalsIgnoreCase("ExeName")) {
+					exeFile = CaseInsensitiveFile.getCaseInsensitiveFile(
+							Paths.getEditionBasePath(), sVal);
+				} else if (sName.equalsIgnoreCase("ExeType")) {
+					if (sVal.equalsIgnoreCase("OSBinary"))
+						exeType = EExeType.OSBINARY;
+					else if (sVal.equalsIgnoreCase("WinExe"))
+						exeType = EExeType.WINEXE;
+					else if (sVal.equalsIgnoreCase("DotNet"))
+						exeType = EExeType.DOTNET;
+					else if (sVal.equalsIgnoreCase("Jar"))
+						exeType = EExeType.JAR;
+				} else if (sName.equalsIgnoreCase("WorkingDir")) {
+					workingDir = sVal;
+				} else if (sName.equalsIgnoreCase("IconName")) {
+					iconFile = CaseInsensitiveFile.getCaseInsensitiveFile(
+							Paths.getEditionBasePath(), sVal);
+				}
+			}
+			if (exeFile != null) {
+				if (exeFile.getName().toLowerCase().endsWith(".jar"))
+					exeType = EExeType.JAR;
+				else if (exeType == EExeType.OSBINARY && exeFile.getName().toLowerCase().endsWith(".exe"))
+					exeType = EExeType.WINEXE;
+			}
+		} catch (FileNotFoundException e) {
+		} catch (IOException e) {
+			e.printStackTrace();
+		} finally {
+			if (isr != null) {
+				try {
+					isr.close();
+				} catch (IOException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	/**
+	 * @return the aeVersion
+	 */
+	public double getAeVersion() {
+		return aeVersion;
+	}
+
+	/**
+	 * @return the name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the creator
+	 */
+	public String getCreator() {
+		return creator;
+	}
+
+	/**
+	 * @return the bslInstallType
+	 */
+	public EBSLInstallType getBslInstallType() {
+		return bslInstallType;
+	}
+
+	/**
+	 * @return the version
+	 */
+	public String getVersion() {
+		return version;
+	}
+
+	/**
+	 * @return the description
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * @return the incompatibilities
+	 */
+	public HashSet<Integer> getIncompatibilities() {
+		return incompatibilities;
+	}
+
+	/**
+	 * @return the dependencies
+	 */
+	public HashSet<Integer> getDependencies() {
+		return dependencies;
+	}
+
+	/**
+	 * @return the unlockLevel
+	 */
+	public HashSet<Integer> getUnlockLevel() {
+		return unlockLevel;
+	}
+
+	/**
+	 * @return the exeFile
+	 */
+	public File getExeFile() {
+		return exeFile;
+	}
+
+	/**
+	 * @return the exeType
+	 */
+	public EExeType getExeType() {
+		return exeType;
+	}
+
+	/**
+	 * @return the iconFile
+	 */
+	public File getIconFile() {
+		return iconFile;
+	}
+
+	/**
+	 * @return the workingDir
+	 */
+	public String getWorkingDir() {
+		return workingDir;
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/Package.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/Package.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/Package.java	(revision 722)
@@ -0,0 +1,456 @@
+package net.oni2.aeinstaller.backend.packages;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashSet;
+
+import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
+import net.oni2.aeinstaller.backend.Paths;
+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.backend.oni.Installer;
+import net.oni2.platformtools.PlatformInformation;
+import net.oni2.platformtools.PlatformInformation.Platform;
+import net.oni2.platformtools.applicationinvoker.EExeType;
+
+import org.apache.commons.io.FileUtils;
+
+/**
+ * @author Christian Illy
+ */
+public class Package implements Comparable<Package> {
+	private String name = "";
+	private int packageNumber = 0;
+
+	private HashSet<Type> types = new HashSet<Type>();
+	private boolean tool = false;
+	private ECompatiblePlatform platform = null;
+	private String version = "";
+	private String submitter = "";
+	private String creator = "";
+	private EBSLInstallType bslInstallType = EBSLInstallType.NORMAL;
+	private String description = "";
+	private double aeVersion = 0;
+	private int zipSize = 0;
+	private NodeMod node = null;
+	private net.oni2.aeinstaller.backend.depot.model.File file = null;
+
+	private File exeFile = null;
+	private EExeType exeType = EExeType.OSBINARY;
+	private File iconFile = null;
+	private String workingDir = "Base";
+
+	private HashSet<Integer> incompatibilities = new HashSet<Integer>();
+	private HashSet<Integer> dependencies = new HashSet<Integer>();
+	private HashSet<Integer> unlockLevel = new HashSet<Integer>();
+
+	private long localTimestamp = 0;
+
+	/**
+	 * Create a new Package entry from a given Mod-Node
+	 * 
+	 * @param nm
+	 *            Mod-Node
+	 */
+	public Package(NodeMod nm) {
+		node = nm;
+		name = nm.getTitle();
+		packageNumber = nm.getPackageNumber();
+		platform = nm.getPlatform();
+		tool = nm.isTool();
+		for (TaxonomyTerm tt : nm.getTypes()) {
+			Type t = PackageManager.getInstance().getTypeByName(tt.getName());
+			types.add(t);
+			if (!tool && !isCorePackage() && isValidOnPlatform())
+				t.addEntry(this);
+		}
+		version = nm.getVersion();
+		submitter = nm.getName();
+		creator = nm.getCreator();
+		if (nm.getBody() != null) {
+			description = nm.getBody().getSafe_value();
+			if (!description.toLowerCase().startsWith("<p>"))
+				description = "<p>" + description + "</p>";
+		}
+		file = DepotManager.getInstance().getFile(
+				nm.getUploads().firstElement().getFid());
+		zipSize = file.getFilesize();
+
+		if (isLocalAvailable())
+			updateLocalData();
+	}
+
+	private void clearLocalOnlyInfo() {
+		aeVersion = 0;
+		bslInstallType = EBSLInstallType.NORMAL;
+
+		dependencies = new HashSet<Integer>();
+		incompatibilities = new HashSet<Integer>();
+		unlockLevel = new HashSet<Integer>();
+
+		exeFile = null;
+		workingDir = null;
+		iconFile = null;
+	}
+
+	/**
+	 * Update information for local package existence
+	 */
+	public void updateLocalData() {
+		File config = CaseInsensitiveFile.getCaseInsensitiveFile(getLocalPath(), "Mod_Info.cfg");
+		File aeicfg = new File(getLocalPath(), "aei.cfg");
+		File plain = CaseInsensitiveFile.getCaseInsensitiveFile(getLocalPath(), "plain");
+		if (config.exists()) {
+			Mod_Info mi = new Mod_Info(config, packageNumber);
+
+			aeVersion = mi.getAeVersion();
+			bslInstallType = mi.getBslInstallType();
+			if (node == null) {
+				name = mi.getName();
+				creator = mi.getCreator();
+				version = mi.getVersion();
+				description = mi.getDescription();
+			}
+
+			dependencies = mi.getDependencies();
+			incompatibilities = mi.getIncompatibilities();
+			unlockLevel = mi.getUnlockLevel();
+
+			exeFile = mi.getExeFile();
+			exeType = mi.getExeType();
+			workingDir = mi.getWorkingDir();
+			iconFile = mi.getIconFile();
+		} else {
+			clearLocalOnlyInfo();
+			System.err.println("No config found for mod folder: "
+					+ getLocalPath().getPath());
+		}
+		if (aeicfg.exists()) {
+			try {
+				FileInputStream fstream = new FileInputStream(aeicfg);
+				InputStreamReader isr = new InputStreamReader(fstream);
+				BufferedReader br = new BufferedReader(isr);
+				String strLine;
+				while ((strLine = br.readLine()) != null) {
+					if (strLine.indexOf("->") < 1)
+						continue;
+					if (strLine.indexOf("//") >= 0)
+						strLine = strLine.substring(0, strLine.indexOf("//"));
+					String[] split = strLine.split("->", 2);
+					String sName = split[0].trim();
+					String sVal = split[1].trim();
+					if (sName.equalsIgnoreCase("Timestamp")) {
+						localTimestamp = Long.parseLong(sVal);
+					}
+				}
+				isr.close();
+			} catch (FileNotFoundException e) {
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+		if (node == null)
+			tool = plain.exists();
+	}
+
+	/**
+	 * Create a new Mod entry from the given local mod folder
+	 * 
+	 * @param folder
+	 *            Mod folder with Mod_Info.cfg
+	 */
+	public Package(File folder) {
+		packageNumber = Integer.parseInt(folder.getName().substring(0, 5));
+		updateLocalData();
+
+		platform = ECompatiblePlatform.BOTH;
+	}
+
+	/**
+	 * @return has separate paths for win/mac/common or not
+	 */
+	public boolean hasSeparatePlatformDirs() {
+		return aeVersion >= 2;
+	}
+
+	private String getSanitizedPathName() {
+		return name.replaceAll("[^a-zA-Z0-9_.-]", "_");
+	}
+
+	/**
+	 * @return Path to local mod folder
+	 */
+	public File getLocalPath() {
+		final String folderStart = String.format("%05d", packageNumber);
+
+		if (Paths.getModsPath().exists()) {
+			for (File f : Paths.getModsPath().listFiles(new FilenameFilter() {
+				@Override
+				public boolean accept(File d, String fn) {
+					return fn.startsWith(folderStart);
+				}
+			})) {
+				return f;
+			}
+		}
+
+		return new File(Paths.getModsPath(), folderStart
+				+ getSanitizedPathName());
+	}
+
+	/**
+	 * @return Is there a newer version on the depot?
+	 */
+	public boolean isNewerAvailable() {
+		if (file != null)
+			return file.getTimestamp() > localTimestamp;
+		else
+			return false;
+	}
+
+	/**
+	 * @return Mod exists within mods folder
+	 */
+	public boolean isLocalAvailable() {
+		return getLocalPath().exists();
+	}
+
+	/**
+	 * @return Is mod installed?
+	 */
+	public boolean isInstalled() {
+		if (tool)
+			return Installer.getInstalledTools().contains(packageNumber);
+		else
+			return PackageManager.getInstance().isModInstalled(this);
+	}
+
+	/**
+	 * @return Name of mod
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return the package number
+	 */
+	public int getPackageNumber() {
+		return packageNumber;
+	}
+
+	/**
+	 * @return the package number as 5 digit string
+	 */
+	public String getPackageNumberString() {
+		return String.format("%05d", packageNumber);
+	}
+
+	/**
+	 * @return Types of mod
+	 */
+	public HashSet<Type> getTypes() {
+		return types;
+	}
+
+	/**
+	 * @return Is this mod actually a tool?
+	 */
+	public boolean isTool() {
+		return tool;
+	}
+
+	/**
+	 * @return Compatible platforms
+	 */
+	public ECompatiblePlatform getPlatform() {
+		return platform;
+	}
+
+	/**
+	 * @return Version of mod
+	 */
+	public String getVersion() {
+		return version;
+	}
+
+	/**
+	 * @return Submitter of mod
+	 */
+	public String getSubmitter() {
+		return submitter;
+	}
+
+	/**
+	 * @return Creator of mod
+	 */
+	public String getCreator() {
+		return creator;
+	}
+
+	/**
+	 * @return Installation type of BSL files
+	 */
+	public EBSLInstallType getBSLInstallType() {
+		return bslInstallType;
+	}
+
+	/**
+	 * @return Description of mod
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * @return Size of Zip file on Depot
+	 */
+	public int getZipSize() {
+		return zipSize;
+	}
+
+	/**
+	 * @return Is a package that is always installed?
+	 */
+	public boolean isCorePackage() {
+		return packageNumber < DepotConfig.getCoreNumberLimit();
+	}
+
+	/**
+	 * @return Get the depot file entry
+	 */
+	public net.oni2.aeinstaller.backend.depot.model.File getFile() {
+		return file;
+	}
+
+	/**
+	 * @return Get the depot Node
+	 */
+	public NodeMod getNode() {
+		return node;
+	}
+
+	/**
+	 * @return Depot page URI
+	 */
+	public URI getUrl() {
+		if (node == null)
+			return null;
+		if (node.getPath() == null)
+			return null;
+		URI res = null;
+		try {
+			res = new URI(node.getPath());
+		} catch (URISyntaxException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	@Override
+	public String toString() {
+		return name;
+	}
+
+	/**
+	 * @return the incompabitilities
+	 */
+	public HashSet<Integer> getIncompabitilities() {
+		return incompatibilities;
+	}
+
+	/**
+	 * @return the dependencies
+	 */
+	public HashSet<Integer> getDependencies() {
+		return dependencies;
+	}
+
+	/**
+	 * @return the levels this mod will unlock
+	 */
+	public HashSet<Integer> getUnlockLevels() {
+		return unlockLevel;
+	}
+
+	/**
+	 * @return Executable name of this tool
+	 */
+	public File getExeFile() {
+		return exeFile;
+	}
+
+	/**
+	 * @return Executable type of this tool
+	 */
+	public EExeType getExeType() {
+		return exeType;
+	}
+
+	/**
+	 * @return Icon file of this tool
+	 */
+	public File getIconFile() {
+		return iconFile;
+	}
+
+	/**
+	 * @return Working directory of this tool
+	 */
+	public File getWorkingDir() {
+		if (workingDir.equalsIgnoreCase("Exe")) {
+			if (exeFile != null)
+				return exeFile.getParentFile();
+			else
+				return Paths.getEditionGDF();
+		} else if (workingDir.equalsIgnoreCase("GDF"))
+			return Paths.getEditionGDF();
+		else
+			return Paths.getEditionBasePath();
+	}
+
+	/**
+	 * @return Is this mod valid on the running platform?
+	 */
+	public boolean isValidOnPlatform() {
+		switch (platform) {
+			case BOTH:
+				return true;
+			case MACOS:
+				return (PlatformInformation.getPlatform() == Platform.MACOS);
+			case WIN:
+				return (PlatformInformation.getPlatform() == Platform.WIN)
+						|| (PlatformInformation.getPlatform() == Platform.LINUX);
+		}
+		return false;
+	}
+
+	/**
+	 * Delete the local package folder
+	 */
+	public void deleteLocalPackage() {
+		if (getLocalPath().exists()) {
+			try {
+				FileUtils.deleteDirectory(getLocalPath());
+				updateLocalData();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+
+	@Override
+	public int compareTo(Package o) {
+		return getPackageNumber() - o.getPackageNumber();
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/PackageManager.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/PackageManager.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/PackageManager.java	(revision 722)
@@ -0,0 +1,344 @@
+package net.oni2.aeinstaller.backend.packages;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.backend.depot.model.NodeMod;
+import net.oni2.aeinstaller.backend.depot.model.TaxonomyTerm;
+import net.oni2.aeinstaller.backend.oni.Installer;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+
+/**
+ * @author Christian Illy
+ */
+public class PackageManager {
+	private static PackageManager instance = new PackageManager();
+
+	private HashMap<String, Type> types = new HashMap<String, Type>();
+	private HashMap<Integer, Package> mods = new HashMap<Integer, Package>();
+	private HashMap<Integer, Package> tools = new HashMap<Integer, Package>();
+
+	private Vector<Integer> currentlyInstalled = new Vector<Integer>();
+
+	/**
+	 * @param f
+	 *            Mod selection file
+	 * @return Mod selection
+	 */
+	@SuppressWarnings("unchecked")
+	public Vector<Integer> loadModSelection(File f) {
+		Vector<Integer> res = new Vector<Integer>();
+		try {
+			if (f.exists()) {
+				FileInputStream fis = new FileInputStream(f);
+				XStream xs = new XStream(new StaxDriver());
+				Object obj = xs.fromXML(fis);
+				if (obj instanceof Vector<?>)
+					res = (Vector<Integer>) obj;
+				fis.close();
+			}
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return res;
+	}
+
+	/**
+	 * @param f
+	 *            Mod selection file
+	 * @param mods
+	 *            Selected mods
+	 */
+	public void saveModSelection(File f, TreeSet<Package> mods) {
+		try {
+			Vector<Integer> installed = new Vector<Integer>();
+			for (Package m : mods) {
+				installed.add(m.getPackageNumber());
+			}
+			FileOutputStream fos = new FileOutputStream(f);
+			XStream xs = new XStream(new StaxDriver());
+			xs.toXML(installed, fos);
+			fos.close();
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * First initialization of ModManager
+	 */
+	public void init() {
+		types = new HashMap<String, Type>();
+		mods = new HashMap<Integer, Package>();
+
+		Type localType = new Type("-Local-", null);
+		types.put("-Local-", localType);
+
+		for (TaxonomyTerm tt : DepotManager.getInstance().getTypes()) {
+			types.put(tt.getName(), new Type(tt.getName(), tt));
+		}
+
+		HashMap<Integer, Package> modFolders = new HashMap<Integer, Package>();
+		if (Paths.getModsPath().exists()) {
+			for (File f : Paths.getModsPath().listFiles(new FileFilter() {
+				@Override
+				public boolean accept(File pathname) {
+					return pathname.isDirectory();
+				}
+			})) {
+				Package m = new Package(f);
+				modFolders.put(m.getPackageNumber(), m);
+			}
+		}
+
+		for (NodeMod nm : DepotManager.getInstance().getModPackageNodes()) {
+			if (nm.getUploads().size() == 1 && nm.getStatus() == 1) {
+				Package m = new Package(nm);
+				if (nm.isTool())
+					tools.put(m.getPackageNumber(), m);
+				else
+					mods.put(m.getPackageNumber(), m);
+				modFolders.remove(m.getPackageNumber());
+			}
+		}
+
+		for (Package m : modFolders.values()) {
+			if (!m.isCorePackage()) {
+				localType.addEntry(m);
+				m.getTypes().add(localType);
+			}
+			if (m.isTool())
+				tools.put(m.getPackageNumber(), m);
+			else
+				mods.put(m.getPackageNumber(), m);
+		}
+
+		updateInstalledMods();
+	}
+
+	/**
+	 * Update the list of currently installed mods
+	 */
+	public void updateInstalledMods() {
+		currentlyInstalled = Installer.getInstalledMods();
+		if (currentlyInstalled == null)
+			currentlyInstalled = new Vector<Integer>();
+	}
+
+	/**
+	 * @return Singleton instance
+	 */
+	public static PackageManager getInstance() {
+		return instance;
+	}
+
+	Type getTypeByName(String name) {
+		return types.get(name);
+	}
+
+	/**
+	 * @return Collection of types which do have mods associated
+	 */
+	public Collection<Type> getTypesWithContent() {
+		Vector<Type> res = new Vector<Type>();
+		for (Type t : types.values()) {
+			if (t.getEntries().size() > 0)
+				res.add(t);
+		}
+		return res;
+	}
+
+	/**
+	 * @return Collection of mods valid on this platform and not core package
+	 */
+	public Collection<Package> getModsValidAndNotCore() {
+		Vector<Package> res = new Vector<Package>();
+		for (Package m : mods.values())
+			if (m.isValidOnPlatform() && !m.isCorePackage())
+				res.add(m);
+		return res;
+	}
+
+	/**
+	 * @return Mods which are always installed and valid on this platform
+	 */
+	public TreeSet<Package> getCoreMods() {
+		TreeSet<Package> res = new TreeSet<Package>();
+		for (Package m : mods.values()) {
+			if (m.isValidOnPlatform() && m.isCorePackage())
+				res.add(m);
+		}
+		return res;
+	}
+
+	/**
+	 * @return Mods which are already locally available
+	 */
+	public TreeSet<Package> getLocalAvailableMods() {
+		TreeSet<Package> res = new TreeSet<Package>();
+		for (Package m : mods.values()) {
+			if (m.isLocalAvailable())
+				res.add(m);
+		}
+		return res;
+	}
+
+	/**
+	 * @return Mods which can be updated
+	 */
+	public TreeSet<Package> getUpdatableMods() {
+		TreeSet<Package> res = new TreeSet<Package>();
+		for (Package m : getLocalAvailableMods()) {
+			if (m.isNewerAvailable())
+				res.add(m);
+		}
+		return res;
+	}
+
+	/**
+	 * @return Collection of tools valid on this platform and not core
+	 */
+	public Collection<Package> getTools() {
+		Vector<Package> res = new Vector<Package>();
+		for (Package m : tools.values())
+			if (m.isValidOnPlatform() && !m.isCorePackage())
+				res.add(m);
+		return res;
+	}
+
+	/**
+	 * @return Tools which are always installed and valid on this platform
+	 */
+	public TreeSet<Package> getCoreTools() {
+		TreeSet<Package> res = new TreeSet<Package>();
+		for (Package m : tools.values()) {
+			if (m.isValidOnPlatform() && m.isCorePackage())
+				res.add(m);
+		}
+		return res;
+	}
+
+	/**
+	 * @return Tools which are already locally available
+	 */
+	public TreeSet<Package> getLocalAvailableTools() {
+		TreeSet<Package> res = new TreeSet<Package>();
+		for (Package m : tools.values()) {
+			if (m.isLocalAvailable())
+				res.add(m);
+		}
+		return res;
+	}
+
+	/**
+	 * @return Tools which can be updated
+	 */
+	public TreeSet<Package> getUpdatableTools() {
+		TreeSet<Package> res = new TreeSet<Package>();
+		for (Package m : getLocalAvailableTools()) {
+			if (m.isNewerAvailable())
+				res.add(m);
+		}
+		return res;
+	}
+
+	/**
+	 * @return Currently installed tools
+	 */
+	public TreeSet<Package> getInstalledTools() {
+		TreeSet<Package> res = new TreeSet<Package>();
+		for (int n : Installer.getInstalledTools()) {
+			res.add(getPackageByNumber(n));
+		}
+		return res;
+	}
+
+	/**
+	 * Get a mod/tool by its package number
+	 * 
+	 * @param number
+	 *            Package number
+	 * @return Mod/tool or null
+	 */
+	private Package getPackageByNumber(int number) {
+		if (mods.containsKey(number))
+			return mods.get(number);
+		if (tools.containsKey(number))
+			return tools.get(number);
+		return null;
+	}
+
+	/**
+	 * Check for unresolved dependencies within the given mods
+	 * 
+	 * @param mods
+	 *            Mods to check
+	 * @return Unmet dependencies
+	 */
+	public HashMap<Package, HashSet<Package>> checkDependencies(TreeSet<Package> mods) {
+		HashMap<Package, HashSet<Package>> res = new HashMap<Package, HashSet<Package>>();
+
+		for (Package m : mods) {
+			for (int depNum : m.getDependencies()) {
+				Package other = getPackageByNumber(depNum);
+				if (!mods.contains(other)) {
+					if (!res.containsKey(m))
+						res.put(m, new HashSet<Package>());
+					res.get(m).add(other);
+				}
+			}
+		}
+
+		return res;
+	}
+
+	/**
+	 * Check for incompabitilites between given mods
+	 * 
+	 * @param mods
+	 *            Mods to check
+	 * @return Incompatible mods
+	 */
+	public HashMap<Package, HashSet<Package>> checkIncompabitilites(TreeSet<Package> mods) {
+		HashMap<Package, HashSet<Package>> res = new HashMap<Package, HashSet<Package>>();
+
+		for (Package m : mods) {
+			for (int confNum : m.getIncompabitilities()) {
+				Package other = getPackageByNumber(confNum);
+				if (mods.contains(other)) {
+					if (!res.containsKey(m))
+						res.put(m, new HashSet<Package>());
+					res.get(m).add(other);
+				}
+			}
+		}
+
+		return res;
+	}
+
+	/**
+	 * @param m
+	 *            Mod to check
+	 * @return Is mod installed?
+	 */
+	boolean isModInstalled(Package m) {
+		return currentlyInstalled.contains(m.getPackageNumber());
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/Type.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/Type.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/Type.java	(revision 722)
@@ -0,0 +1,52 @@
+package net.oni2.aeinstaller.backend.packages;
+
+import java.util.HashSet;
+
+import net.oni2.aeinstaller.backend.depot.model.TaxonomyTerm;
+
+/**
+ * @author Christian Illy
+ */
+public class Type {
+	private String name;
+	@SuppressWarnings("unused")
+	private TaxonomyTerm depotTerm;
+
+	private HashSet<Package> entries = new HashSet<Package>();
+
+	/**
+	 * Create a new local type declaration
+	 * 
+	 * @param name
+	 *            Name of type
+	 * @param tt
+	 *            Optional TaxTerm link
+	 */
+	public Type(String name, TaxonomyTerm tt) {
+		this.name = name;
+		this.depotTerm = tt;
+	}
+
+	void addEntry(Package m) {
+		entries.add(m);
+	}
+
+	/**
+	 * @return Name of type
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @return Entries for type
+	 */
+	public HashSet<Package> getEntries() {
+		return entries;
+	}
+
+	@Override
+	public String toString() {
+		return String.format("%s (%d)", name, entries.size());
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownload.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownload.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownload.java	(revision 722)
@@ -0,0 +1,186 @@
+package net.oni2.aeinstaller.backend.packages.download;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.network.FileDownloadListener;
+import net.oni2.aeinstaller.backend.network.FileDownloader;
+import net.oni2.aeinstaller.backend.network.FileDownloader.EState;
+import net.oni2.aeinstaller.backend.packages.unpack.UnpackListener;
+import net.oni2.aeinstaller.backend.packages.unpack.Unpacker;
+
+/**
+ * @author Christian Illy
+ */
+public class ModDownload implements FileDownloadListener, UnpackListener {
+	/**
+	 * @author Christian Illy
+	 */
+	public enum ModDownloadState {
+		/**
+		 * Downloader initialized but not started
+		 */
+		INIT,
+		/**
+		 * Download running
+		 */
+		RUNNING,
+		/**
+		 * Aborted because of an error
+		 */
+		ERROR,
+		/**
+		 * Download interrupted
+		 */
+		INTERRUPTED,
+		/**
+		 * Download finished successfully
+		 */
+		DOWNLOADED,
+		/**
+		 * Package unzipped successfully
+		 */
+		UNPACKED
+	};
+
+	private Package mod;
+	private FileDownloader downloader;
+	private Unpacker unpacker;
+	private File zipFile;
+	private File targetFolder;
+	private ModDownloadListener listener;
+	private int size;
+
+	private ModDownloadState state = ModDownloadState.INIT;
+
+	/**
+	 * Create a mod download
+	 * 
+	 * @param mod
+	 *            Mod to download
+	 * @param listener
+	 *            Listener for progress
+	 */
+	public ModDownload(Package mod, ModDownloadListener listener) {
+		this.mod = mod;
+		this.listener = listener;
+
+		zipFile = new File(Paths.getDownloadPath(),
+				mod.getPackageNumberString() + ".zip");
+		targetFolder = mod.getLocalPath();
+		try {
+			downloader = new FileDownloader(mod.getFile().getUri_full(),
+					zipFile);
+			downloader.addListener(this);
+			unpacker = new Unpacker(zipFile, targetFolder, this);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * @return Size of this download
+	 */
+	public int getSize() {
+		return mod.getZipSize();
+	}
+
+	/**
+	 * Start this download
+	 */
+	public void start() {
+		state = ModDownloadState.RUNNING;
+		downloader.start();
+	}
+
+	/**
+	 * Abort this download
+	 */
+	public void abort() {
+		switch (state) {
+			case UNPACKED:
+			case INIT:
+			case ERROR:
+			case INTERRUPTED:
+				break;
+			case RUNNING:
+				downloader.stop();
+				break;
+			case DOWNLOADED:
+				unpacker.stop();
+				break;
+		}
+		state = ModDownloadState.INTERRUPTED;
+	}
+
+	/**
+	 * @return the mod object handled by this download
+	 */
+	public Package getMod() {
+		return mod;
+	}
+
+	private void writeTimestamp() {
+		File logFile = new File(targetFolder, "aei.cfg");
+		PrintWriter log = null;
+		try {
+			log = new PrintWriter(logFile);
+			log.println("Timestamp -> " + mod.getFile().getTimestamp());
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		}
+		if (log != null)
+			log.close();
+	}
+
+	@Override
+	public void statusUpdate(FileDownloader source, EState state, int done,
+			int total) {
+		switch (state) {
+			case INIT:
+				break;
+			case RUNNING:
+				listener.modDownloadStatusUpdate(this, this.state, done, total);
+				break;
+			case PAUSED:
+				break;
+			case INTERRUPTED:
+				break;
+			case ERROR:
+				this.state = ModDownloadState.ERROR;
+				listener.modDownloadStatusUpdate(this, this.state, done, total);
+				break;
+			case FINISHED:
+				this.state = ModDownloadState.DOWNLOADED;
+				listener.modDownloadStatusUpdate(this, this.state, done, total);
+				this.size = done;
+				unpacker.start();
+				break;
+		}
+	}
+
+	@Override
+	public void statusUpdate(Unpacker source,
+			net.oni2.aeinstaller.backend.packages.unpack.Unpacker.EState state) {
+		switch (state) {
+			case INIT:
+				break;
+			case RUNNING:
+				break;
+			case INTERRUPTED:
+				this.state = ModDownloadState.INTERRUPTED;
+				listener.modDownloadStatusUpdate(this, this.state, size, size);
+				break;
+			case FINISHED:
+				this.state = ModDownloadState.UNPACKED;
+				writeTimestamp();
+				zipFile.delete();
+				listener.modDownloadStatusUpdate(this, this.state, size, size);
+				break;
+		}
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownloadListener.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownloadListener.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownloadListener.java	(revision 722)
@@ -0,0 +1,23 @@
+package net.oni2.aeinstaller.backend.packages.download;
+
+import net.oni2.aeinstaller.backend.packages.download.ModDownload.ModDownloadState;
+
+/**
+ * @author Christian Illy
+ */
+public interface ModDownloadListener {
+	/**
+	 * Called for progress changes within the mod download/unpack process
+	 * 
+	 * @param source
+	 *            Source of event
+	 * @param state
+	 *            Current state
+	 * @param done
+	 *            Bytes downloaded
+	 * @param total
+	 *            Bytes total
+	 */
+	public void modDownloadStatusUpdate(ModDownload source,
+			ModDownloadState state, int done, int total);
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownloader.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownloader.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownloader.java	(revision 722)
@@ -0,0 +1,172 @@
+package net.oni2.aeinstaller.backend.packages.download;
+
+import java.util.Date;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.download.ModDownload.ModDownloadState;
+
+/**
+ * @author Christian Illy
+ */
+public class ModDownloader implements ModDownloadListener {
+	/**
+	 * @author Christian Illy
+	 */
+	public enum State {
+		/**
+		 * Downloads running
+		 */
+		RUNNING,
+		/**
+		 * Aborted because of an error
+		 */
+		ERROR,
+		/**
+		 * Downloads interrupted
+		 */
+		INTERRUPTED,
+		/**
+		 * When the last file was downloaded and only unpacking is left
+		 */
+		LAST_FILE_DOWNLOADED,
+		/**
+		 * Everything completed
+		 */
+		FINISHED
+	};
+
+	private int currentDownload = -1;
+	private int unpacked = 0;
+	private Vector<ModDownload> downloads = new Vector<ModDownload>();
+	private int totalSize = 0;
+	private int downloadedComplete = 0;
+	private int downloadedCurrent = 0;
+	private long startMS;
+	private State state = State.RUNNING;
+	private ModDownloaderListener listener;
+
+	/**
+	 * Create a mods-download-process
+	 * 
+	 * @param mods
+	 *            Mods to download
+	 * @param listener
+	 *            Listener for status updates
+	 */
+	public ModDownloader(TreeSet<Package> mods, ModDownloaderListener listener) {
+		this.listener = listener;
+		for (Package m : mods) {
+			downloads.add(new ModDownload(m, this));
+			totalSize += m.getZipSize();
+		}
+		startMS = new Date().getTime();
+		startNextDownload();
+	}
+
+	private void startNextDownload() {
+		if (currentDownload >= 0)
+			downloadedComplete += downloads.get(currentDownload).getSize();
+		currentDownload++;
+		downloadedCurrent = 0;
+		if ((state == State.RUNNING) && (currentDownload < downloads.size())) {
+			downloads.get(currentDownload).start();
+		} else if (state == State.RUNNING) {
+			state = State.LAST_FILE_DOWNLOADED;
+			notifyListener();
+		} else {
+			notifyListener();
+		}
+	}
+
+	private int getTimeElapsed() {
+		int total = (int) (new Date().getTime() - startMS);
+		return total;
+	}
+
+	private int getDownloadSpeed() {
+		int elap = getTimeElapsed();
+		long down = downloadedComplete + downloadedCurrent;
+		if (elap > 0)
+			return (int)(down * 1000 / elap);
+		else
+			return 1;
+	}
+
+	private int getTimeRemaining() {
+		int remainingSize = totalSize
+				- (downloadedComplete + downloadedCurrent);
+		return remainingSize / getDownloadSpeed();
+	}
+
+	private void notifyListener() {
+		if (currentDownload < downloads.size()) {
+			listener.updateStatus(this,
+					downloads.get(currentDownload).getMod(), state, unpacked,
+					downloads.size(), downloadedComplete + downloadedCurrent,
+					totalSize, getTimeElapsed() / 1000, getTimeRemaining(),
+					getDownloadSpeed());
+		} else {
+			listener.updateStatus(this, null, state, unpacked,
+					downloads.size(), downloadedComplete + downloadedCurrent,
+					totalSize, getTimeElapsed() / 1000, getTimeRemaining(),
+					getDownloadSpeed());
+		}
+	}
+
+	/**
+	 * @return total download size
+	 */
+	public int getTotalSize() {
+		return totalSize;
+	}
+
+	/**
+	 * @return Is this process finished
+	 */
+	public boolean isFinished() {
+		return state == State.FINISHED;
+	}
+
+	@Override
+	public void modDownloadStatusUpdate(ModDownload source,
+			ModDownloadState state, int done, int total) {
+		switch (state) {
+			case RUNNING:
+				downloadedCurrent = done;
+				notifyListener();
+				break;
+			case ERROR:
+				this.state = State.ERROR;
+				break;
+			case DOWNLOADED:
+				if (source == downloads.get(currentDownload))
+					startNextDownload();
+				break;
+			case UNPACKED:
+				source.getMod().updateLocalData();
+				unpacked++;
+				if (unpacked >= downloads.size())
+					this.state = State.FINISHED;
+				notifyListener();
+				break;
+			case INIT:
+				break;
+			case INTERRUPTED:
+				break;
+		}
+	}
+
+	/**
+	 * Abort download process
+	 */
+	public void abort() {
+		if (currentDownload < downloads.size()) {
+			state = State.INTERRUPTED;
+			ModDownload md = downloads.get(currentDownload);
+			md.abort();
+		}
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownloaderListener.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownloaderListener.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/download/ModDownloaderListener.java	(revision 722)
@@ -0,0 +1,37 @@
+package net.oni2.aeinstaller.backend.packages.download;
+
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.download.ModDownloader.State;
+
+/**
+ * @author Christian Illy
+ */
+public interface ModDownloaderListener {
+	/**
+	 * Callback for progress updates on mod downloads
+	 * 
+	 * @param source
+	 *            Event source
+	 * @param currentDownload
+	 *            Currently downloading mod
+	 * @param state
+	 *            Current state
+	 * @param filesDown
+	 *            Downloaded(+unpacked) files
+	 * @param filesTotal
+	 *            Files in total to handle
+	 * @param bytesDown
+	 *            Bytes downloaded
+	 * @param bytesTotal
+	 *            Bytes in total to handle
+	 * @param duration
+	 *            Duration of downloads in seconds
+	 * @param remaining
+	 *            Remaining time in seconds
+	 * @param speed
+	 *            Average download speed in B/s
+	 */
+	public void updateStatus(ModDownloader source, Package currentDownload,
+			State state, int filesDown, int filesTotal, int bytesDown,
+			int bytesTotal, int duration, int remaining, int speed);
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/unpack/UnpackListener.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/unpack/UnpackListener.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/unpack/UnpackListener.java	(revision 722)
@@ -0,0 +1,18 @@
+package net.oni2.aeinstaller.backend.packages.unpack;
+
+import net.oni2.aeinstaller.backend.packages.unpack.Unpacker.EState;
+
+/**
+ * @author Christian Illy
+ */
+public interface UnpackListener {
+	/**
+	 * Called for progress changes within the unpacker
+	 * 
+	 * @param source
+	 *            Source of event
+	 * @param state
+	 *            Current state
+	 */
+	public void statusUpdate(Unpacker source, EState state);
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/packages/unpack/Unpacker.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/packages/unpack/Unpacker.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/backend/packages/unpack/Unpacker.java	(revision 722)
@@ -0,0 +1,193 @@
+package net.oni2.aeinstaller.backend.packages.unpack;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.io.FileUtils;
+
+/**
+ * @author Christian Illy
+ */
+public class Unpacker implements Runnable {
+	/**
+	 * @author Christian Illy
+	 */
+	public enum EState {
+		/**
+		 * Unpacker initialized but not started
+		 */
+		INIT,
+		/**
+		 * Unpack running
+		 */
+		RUNNING,
+		/**
+		 * Unpack interrupted
+		 */
+		INTERRUPTED,
+		/**
+		 * Unpack finished successfully
+		 */
+		FINISHED,
+	};
+
+	private UnpackListener listener;
+
+	private File zip;
+	private File target;
+
+	private Thread t = null;
+
+	private EState state = EState.INIT;
+
+	/**
+	 * Initialize a new AE package unpacker
+	 * 
+	 * @param zipFile
+	 *            AE zip package
+	 * @param targetFolder
+	 *            Target folder
+	 * @param listener
+	 *            Listener for progress updates
+	 */
+	public Unpacker(File zipFile, File targetFolder, UnpackListener listener) {
+		this.listener = listener;
+		zip = zipFile;
+		target = targetFolder;
+	}
+
+	/**
+	 * Start the unpack process
+	 */
+	public synchronized void start() {
+		if (t == null) {
+			t = new Thread(this);
+			t.start();
+			state = EState.RUNNING;
+			updateStatus();
+		}
+	}
+
+	/**
+	 * Stop (abort) the process
+	 */
+	public synchronized void stop() {
+		if (state != EState.FINISHED) {
+			state = EState.INTERRUPTED;
+			if (t != null) {
+				try {
+					t.join();
+				} catch (InterruptedException e) {
+					e.printStackTrace();
+				}
+				t = null;
+			}
+			updateStatus();
+			if (state != EState.FINISHED) {
+				if (target.exists()) {
+					try {
+						FileUtils.deleteDirectory(target);
+					} catch (IOException e) {
+						e.printStackTrace();
+					}
+				}
+			}
+		}
+	}
+
+	private synchronized void updateStatus() {
+		listener.statusUpdate(this, state);
+	}
+
+	@Override
+	public void run() {
+		try {
+			switch (state) {
+				case INTERRUPTED:
+					return;
+				case RUNNING:
+					ZipFile zf = null;
+					try {
+						int pathStart = 0;
+						String pathStartName = "";
+
+						zf = new ZipFile(zip);
+
+						if (target.exists())
+							FileUtils.deleteDirectory(target);
+						target.mkdirs();
+
+						for (Enumeration<? extends ZipEntry> e = zf.entries(); e
+								.hasMoreElements();) {
+							ZipEntry ze = e.nextElement();
+							if (ze.getName().toLowerCase()
+									.endsWith("/mod_info.cfg")
+									|| ze.getName().toLowerCase()
+											.equals("mod_info.cfg")) {
+								pathStart = ze.getName().toLowerCase()
+										.indexOf("mod_info.cfg");
+								pathStartName = ze.getName().substring(0,
+										pathStart);
+							}
+						}
+
+						for (Enumeration<? extends ZipEntry> e = zf.entries(); e
+								.hasMoreElements();) {
+							if (state == EState.INTERRUPTED)
+								return;
+							ZipEntry ze = e.nextElement();
+							if (!ze.isDirectory()) {
+								if (ze.getName().startsWith(pathStartName)) {
+									if (!(ze.getName().endsWith("aei.cfg") || ze
+											.getName().endsWith(".DS_Store"))) {
+										File targetFile = new File(target, ze
+												.getName().substring(pathStart));
+										File parent = targetFile
+												.getParentFile();
+										parent.mkdirs();
+
+										InputStream in = zf.getInputStream(ze);
+
+										int read = 0;
+										byte[] data = new byte[1024];
+										FileOutputStream fileOut = new FileOutputStream(
+												targetFile);
+										while ((read = in.read(data, 0, 1024)) != -1) {
+											fileOut.write(data, 0, read);
+										}
+										fileOut.close();
+									}
+								}
+							}
+						}
+					} catch (ZipException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					} catch (IOException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					} finally {
+						try {
+							if (zf != null)
+								zf.close();
+						} catch (IOException e) {
+							e.printStackTrace();
+						}
+					}
+					break;
+				default:
+					break;
+			}
+		} finally {
+		}
+
+		state = EState.FINISHED;
+		updateStatus();
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/HTMLLinkLabel.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/HTMLLinkLabel.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/HTMLLinkLabel.java	(revision 722)
@@ -0,0 +1,78 @@
+package net.oni2.aeinstaller.gui;
+
+import java.awt.Color;
+import java.awt.Desktop;
+import java.awt.Font;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import javax.swing.JEditorPane;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.UIDefaults;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.DefaultCaret;
+
+import net.oni2.aeinstaller.backend.ColorCopy;
+
+/**
+ * @author Christian Illy
+ */
+public class HTMLLinkLabel extends JEditorPane {
+	private static final long serialVersionUID = 2416829757362043910L;
+
+	private String prefix;
+	private String suffix;
+
+	/**
+	 * Create a new HTMLLinkLabel
+	 */
+	public HTMLLinkLabel() {
+		super();
+
+		((DefaultCaret) this.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
+
+		setContentType("text/html");
+
+		JLabel label = new JLabel();
+		Font font = label.getFont();
+
+		StringBuffer style = new StringBuffer("font-family:" + font.getFamily()
+				+ ";");
+		style.append("font-weight:" + (font.isBold() ? "bold" : "normal") + ";");
+		style.append("font-size:" + font.getSize() + "pt;");
+
+		prefix = "<html><body style=\"" + style + "\">";
+		suffix = "</body></html>";
+
+		addHyperlinkListener(new HyperlinkListener() {
+
+			@Override
+			public void hyperlinkUpdate(HyperlinkEvent e) {
+				if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED))
+					try {
+						Desktop.getDesktop().browse(e.getURL().toURI());
+					} catch (IOException e1) {
+						e1.printStackTrace();
+					} catch (URISyntaxException e1) {
+						e1.printStackTrace();
+					}
+			}
+		});
+		setEditable(false);
+
+		Color bgColor = ColorCopy.copyColor(new JFrame().getBackground());
+		UIDefaults defaults = new UIDefaults();
+		defaults.put("EditorPane[Enabled].backgroundPainter", bgColor);
+		putClientProperty("Nimbus.Overrides", defaults);
+		putClientProperty("Nimbus.Overrides.InheritDefaults", true);
+		setBackground(bgColor);
+	}
+
+	@Override
+	public void setText(String t) {
+		super.setText(prefix + t + suffix);
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/MainWin.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 722)
@@ -0,0 +1,932 @@
+package net.oni2.aeinstaller.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Desktop;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.ResourceBundle;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import javax.swing.AbstractAction;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.ToolTipManager;
+import javax.swing.filechooser.FileFilter;
+
+import net.oni2.SettingsManager;
+import net.oni2.aeinstaller.AEInstaller2;
+import net.oni2.aeinstaller.backend.ImageResizer;
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.SizeFormatter;
+import net.oni2.aeinstaller.backend.depot.DepotManager;
+import net.oni2.aeinstaller.backend.oni.InstallProgressListener;
+import net.oni2.aeinstaller.backend.oni.Installer;
+import net.oni2.aeinstaller.backend.oni.OniLauncher;
+import net.oni2.aeinstaller.backend.oni.OniSplit;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.PackageManager;
+import net.oni2.aeinstaller.backend.packages.Type;
+import net.oni2.aeinstaller.backend.packages.download.ModDownloader;
+import net.oni2.aeinstaller.backend.packages.download.ModDownloader.State;
+import net.oni2.aeinstaller.backend.packages.download.ModDownloaderListener;
+import net.oni2.aeinstaller.gui.about.AboutDialog;
+import net.oni2.aeinstaller.gui.corepackages.CorePackagesDialog;
+import net.oni2.aeinstaller.gui.downloadwindow.Downloader;
+import net.oni2.aeinstaller.gui.modtable.EApplyFilterTo;
+import net.oni2.aeinstaller.gui.modtable.ModInstallSelectionListener;
+import net.oni2.aeinstaller.gui.modtable.ModSelectionListener;
+import net.oni2.aeinstaller.gui.modtable.ModTable;
+import net.oni2.aeinstaller.gui.modtable.ModTable.ETableContentType;
+import net.oni2.aeinstaller.gui.packageinfobox.PackageInfoBox;
+import net.oni2.aeinstaller.gui.settings.SettingsDialog;
+import net.oni2.aeinstaller.gui.toolmanager.ToolManager;
+import net.oni2.platformtools.PlatformInformation;
+import net.oni2.platformtools.PlatformInformation.Platform;
+import net.oni2.platformtools.applicationinvoker.ApplicationInvoker;
+import net.oni2.platformtools.applicationinvoker.ERuntimeNotInstalledException;
+
+import org.javabuilders.BuildResult;
+import org.javabuilders.annotations.DoInBackground;
+import org.javabuilders.event.BackgroundEvent;
+import org.javabuilders.swing.SwingJavaBuilder;
+import org.simplericity.macify.eawt.ApplicationEvent;
+import org.simplericity.macify.eawt.ApplicationListener;
+
+/**
+ * @author Christian Illy
+ */
+public class MainWin extends JFrame implements ApplicationListener,
+		ModInstallSelectionListener, ModSelectionListener {
+	private static final long serialVersionUID = -4027395051382659650L;
+
+	private ResourceBundle bundle = ResourceBundle
+			.getBundle("net.oni2.aeinstaller.localization."
+					+ getClass().getSimpleName());
+	@SuppressWarnings("unused")
+	private BuildResult result = SwingJavaBuilder.build(this, bundle);
+
+	private JMenu mainMenu;
+	private JMenu toolsMenu;
+	private Vector<JMenuItem> toolsMenuItems = new Vector<JMenuItem>();
+
+	private JSplitPane contents;
+
+	private JComboBox cmbModTypes;
+	private JRadioButton radAll;
+	private JRadioButton radOnline;
+	private JRadioButton radLocal;
+	private JTextField txtShowFilter;
+	private JComboBox cmbShowFilterTo;
+	private JScrollPane scrollMods;
+	private ModTable tblMods;
+	private JLabel lblSelectedModsVal;
+	private JLabel lblDownloadSizeVal;
+
+	private PackageInfoBox pkgInfo;
+
+	private JButton btnInstall;
+
+	private TreeSet<Package> execCoreUpdates = new TreeSet<Package>();
+	private TreeSet<Package> execUpdates = null;
+
+	private enum EInstallState {
+		DONE,
+		READY,
+		ABORTED,
+		OFFLINE,
+		INCOMPATIBLE,
+		CHECKING
+	};
+
+	private EInstallState installState = EInstallState.DONE;
+	private TreeSet<Package> installMods = null;
+	private TreeSet<Package> installDeps = null;
+
+	/**
+	 * Constructor of main window.
+	 */
+	public MainWin() {
+		this.setTitle(SwingJavaBuilder.getConfig().getResource("appname")
+				+ " - v"
+				+ SwingJavaBuilder.getConfig().getResource("appversion"));
+
+		tblMods = new ModTable(ETableContentType.MODS);
+		tblMods.setVisible(false);
+		scrollMods.setViewportView(tblMods);
+
+		contents.setDividerLocation(SettingsManager.getInstance().get(
+				"win_main_divloc", 550));
+		contents.setResizeWeight(0.4);
+
+		if (PlatformInformation.getPlatform() == Platform.MACOS) {
+			mainMenu.setVisible(false);
+		}
+
+		ToolTipManager.sharedInstance().setInitialDelay(250);
+
+		getRootPane().setDefaultButton(btnInstall);
+		lblSelectedModsVal.setText("0");
+		lblDownloadSizeVal.setText(SizeFormatter.format(0, 2));
+		radAll.setSelected(true);
+
+		for (EApplyFilterTo f : EApplyFilterTo.values()) {
+			cmbShowFilterTo.addItem(f);
+		}
+		txtShowFilter.addKeyListener(new KeyAdapter() {
+			@Override
+			public void keyReleased(KeyEvent e) {
+				super.keyReleased(e);
+				updateTableFilter();
+			}
+		});
+
+		tblMods.addModSelectionListener(this);
+		tblMods.addDownloadSizeListener(this);
+
+		setSize(SettingsManager.getInstance().get("win_main_width", 950),
+				SettingsManager.getInstance().get("win_main_height", 600));
+		setLocationRelativeTo(null);
+	}
+
+	private void initModTypeBox() {
+		cmbModTypes.removeAllItems();
+
+		TreeMap<String, Type> types = new TreeMap<String, Type>();
+		for (Type t : PackageManager.getInstance().getTypesWithContent()) {
+			types.put(t.getName(), t);
+		}
+		cmbModTypes.addItem("-All-");
+		for (Type t : types.values()) {
+			cmbModTypes.addItem(t);
+		}
+		cmbModTypes.setSelectedIndex(0);
+	}
+
+	private void exit() {
+		dispose();
+		System.exit(0);
+	}
+
+	private void saveLocalData() {
+		SettingsManager.getInstance().put("win_main_divloc",
+				contents.getDividerLocation());
+		SettingsManager.getInstance().put("win_main_width", getWidth());
+		SettingsManager.getInstance().put("win_main_height", getHeight());
+		SettingsManager.getInstance().serializeToFile(
+				Paths.getSettingsFilename());
+	}
+
+	@DoInBackground(progressMessage = "updateDepot.title", cancelable = false, indeterminateProgress = false)
+	private void execDepotUpdate(final BackgroundEvent evt) {
+		if (!SettingsManager.getInstance().isOfflineMode()
+				&& !SettingsManager.getInstance().isNoCacheUpdateMode()) {
+			long start = new Date().getTime();
+
+			try {
+				DepotManager.getInstance().updateInformation();
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+
+			System.out.println("Took: " + (new Date().getTime() - start)
+					+ " msec");
+		}
+
+		PackageManager.getInstance().init();
+		tblMods.reloadData();
+		initModTypeBox();
+
+		tblMods.setVisible(true);
+	}
+
+	@SuppressWarnings("unused")
+	private void checkUpdates(Object evtSource) {
+		if ((evtSource != this)
+				|| SettingsManager.getInstance().get("notifyupdates", true)) {
+			if (SettingsManager.getInstance().isOfflineMode()) {
+				if (evtSource != this) {
+					JOptionPane.showMessageDialog(
+							this,
+							SwingJavaBuilder.getConfig().getResource(
+									"offlineMode.text"),
+							SwingJavaBuilder.getConfig().getResource(
+									"offlineMode.title"),
+							JOptionPane.WARNING_MESSAGE);
+				}
+			} else {
+				TreeSet<Package> mods = PackageManager.getInstance()
+						.getUpdatableMods();
+				TreeSet<Package> tools = PackageManager.getInstance()
+						.getUpdatableTools();
+				JPanel panPackages = new JPanel(new GridLayout(0, 1));
+				execUpdates = new TreeSet<Package>();
+				execUpdates.addAll(mods);
+				execUpdates.addAll(tools);
+				final JLabel lblSize = new JLabel("<html>"
+						+ String.format(
+								bundle.getString("updatesAvailableSize.text"),
+								SizeFormatter.format(0, 3)) + "</html>");
+				int size = 0;
+				for (final Package m : mods) {
+					size += m.getZipSize();
+					JCheckBox check = new JCheckBox("Mod: " + m.getName()
+							+ " (" + SizeFormatter.format(m.getZipSize(), 1)
+							+ ")");
+					check.setSelected(true);
+					check.addItemListener(new ItemListener() {
+						@Override
+						public void itemStateChanged(ItemEvent e) {
+							if (e.getStateChange() == ItemEvent.SELECTED)
+								execUpdates.add(m);
+							else
+								execUpdates.remove(m);
+							int s = 0;
+							for (Package p : execUpdates)
+								s += p.getZipSize();
+							lblSize.setText("<html>"
+									+ String.format(
+											bundle.getString("updatesAvailableSize.text"),
+											SizeFormatter.format(s, 3))
+									+ "</html>");
+						}
+					});
+					panPackages.add(check);
+				}
+				for (final Package m : tools) {
+					size += m.getZipSize();
+					JCheckBox check = new JCheckBox("Tool: " + m.getName()
+							+ " (" + SizeFormatter.format(m.getZipSize(), 1)
+							+ ")");
+					check.setSelected(true);
+					check.addItemListener(new ItemListener() {
+						@Override
+						public void itemStateChanged(ItemEvent e) {
+							if (e.getStateChange() == ItemEvent.SELECTED)
+								execUpdates.add(m);
+							else
+								execUpdates.remove(m);
+							int s = 0;
+							for (Package p : execUpdates)
+								s += p.getZipSize();
+							lblSize.setText("<html>"
+									+ String.format(
+											bundle.getString("updatesAvailableSize.text"),
+											SizeFormatter.format(s, 3))
+									+ "</html>");
+						}
+					});
+					panPackages.add(check);
+				}
+				lblSize.setText("<html>"
+						+ String.format(
+								bundle.getString("updatesAvailableSize.text"),
+								SizeFormatter.format(size, 3)) + "</html>");
+				if (size > 0) {
+					// Build info dialog content
+					JPanel packages = new JPanel(new BorderLayout(0, 7));
+					JLabel lblIntro = new JLabel("<html>"
+							+ bundle.getString("updatesAvailable.text")
+							+ "</html>");
+					packages.add(lblIntro, BorderLayout.NORTH);
+					packages.add(panPackages, BorderLayout.CENTER);
+					packages.add(lblSize, BorderLayout.SOUTH);
+
+					JPanel pan = new JPanel(new BorderLayout(0, 25));
+					pan.add(packages, BorderLayout.CENTER);
+					JCheckBox checkFutureUpdates = new JCheckBox(
+							bundle.getString("checkOnStartup.text"));
+					checkFutureUpdates.setSelected(SettingsManager
+							.getInstance().get("notifyupdates", true));
+					checkFutureUpdates.addItemListener(new ItemListener() {
+						@Override
+						public void itemStateChanged(ItemEvent evt) {
+							SettingsManager.getInstance().put("notifyupdates",
+									evt.getStateChange() == ItemEvent.SELECTED);
+						}
+					});
+					pan.add(checkFutureUpdates, BorderLayout.SOUTH);
+
+					// Show dialog
+					int res = JOptionPane.showConfirmDialog(this, pan,
+							bundle.getString("updatesAvailable.title"),
+							JOptionPane.YES_NO_OPTION,
+							JOptionPane.QUESTION_MESSAGE);
+					if (res == JOptionPane.NO_OPTION) {
+						execUpdates = null;
+					}
+				} else {
+					if (evtSource != this) {
+						JOptionPane.showMessageDialog(this,
+								bundle.getString("updatesNotAvailable.text"),
+								bundle.getString("updatesNotAvailable.title"),
+								JOptionPane.INFORMATION_MESSAGE);
+					}
+				}
+			}
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void doUpdate() {
+		if (execUpdates != null && execUpdates.size() > 0) {
+			Downloader dl = new Downloader(execUpdates, null);
+			try {
+				dl.setVisible(true);
+				if (dl.isFinished()) {
+					TreeSet<Integer> installed = Installer.getInstalledTools();
+					TreeSet<Package> tools = new TreeSet<Package>();
+					for (Package m : execUpdates)
+						if (m.isTool()
+								&& installed.contains(m.getPackageNumber()))
+							tools.add(m);
+					if (tools.size() > 0) {
+						Installer.installTools(tools, false);
+					}
+				}
+			} finally {
+				dl.dispose();
+			}
+		}
+		execUpdates = null;
+	}
+
+	@SuppressWarnings("unused")
+	private void focus() {
+		SwingUtilities.invokeLater(new Runnable() {
+
+			@Override
+			public void run() {
+				toFront();
+				repaint();
+			}
+		});
+
+	}
+
+	@SuppressWarnings("unused")
+	private void showCorePackagesDialog() {
+		new CorePackagesDialog().setVisible(true);
+	}
+
+	private void showSettings() {
+		new SettingsDialog().setVisible(true);
+	}
+
+	private void showAbout() {
+		new AboutDialog().setVisible(true);
+	}
+
+	private JFileChooser getConfigOpenSaveDialog(boolean save) {
+		JFileChooser fc = new JFileChooser();
+		fc.setCurrentDirectory(Paths.getEditionBasePath());
+		if (save)
+			fc.setDialogType(JFileChooser.SAVE_DIALOG);
+		else
+			fc.setDialogType(JFileChooser.OPEN_DIALOG);
+		fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+		fc.setFileFilter(new FileFilter() {
+			@Override
+			public String getDescription() {
+				return "XML files";
+			}
+
+			@Override
+			public boolean accept(File arg0) {
+				return (arg0.isDirectory())
+						|| (arg0.getName().toLowerCase().endsWith(".xml"));
+			}
+		});
+		fc.setMultiSelectionEnabled(false);
+		return fc;
+	}
+
+	@SuppressWarnings("unused")
+	private void loadConfig() {
+		JFileChooser fc = getConfigOpenSaveDialog(false);
+		int res = fc.showOpenDialog(this);
+		if (res == JFileChooser.APPROVE_OPTION) {
+			if (fc.getSelectedFile().exists())
+				tblMods.reloadSelection(fc.getSelectedFile());
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void saveConfig() {
+		JFileChooser fc = getConfigOpenSaveDialog(true);
+		int res = fc.showSaveDialog(this);
+		if (res == JFileChooser.APPROVE_OPTION) {
+			File f = fc.getSelectedFile();
+			if (!f.getName().endsWith(".xml"))
+				f = new File(f.getParentFile(), f.getName() + ".xml");
+			PackageManager.getInstance().saveModSelection(f,
+					tblMods.getSelectedMods());
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private boolean reglobalizeVerify() {
+		int res = JOptionPane.showConfirmDialog(this,
+				bundle.getString("rebuildCore.text"),
+				bundle.getString("rebuildCore.title"),
+				JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+		return res == JOptionPane.YES_OPTION;
+	}
+
+	@DoInBackground(progressMessage = "initializingEdition.title", cancelable = false, indeterminateProgress = false)
+	private void reglobalize(final BackgroundEvent evt) {
+		Installer.initializeEdition(new InstallProgressListener() {
+			@Override
+			public void installProgressUpdate(int done, int total, String step) {
+				evt.setProgressEnd(total);
+				evt.setProgressValue(done);
+				evt.setProgressMessage(step);
+			}
+		});
+	}
+
+	@SuppressWarnings("unused")
+	private void tools() {
+		new ToolManager().setVisible(true);
+	}
+
+	@SuppressWarnings("unused")
+	private void refreshToolsMenu() {
+		for (JMenuItem i : toolsMenuItems) {
+			toolsMenu.remove(i);
+		}
+		toolsMenuItems.clear();
+		for (final Package m : PackageManager.getInstance().getInstalledTools()) {
+			File exe = m.getExeFile();
+			if (exe != null && exe.exists()) {
+				JMenuItem item = new JMenuItem();
+				ImageIcon ico = null;
+				if (m.getIconFile() != null && m.getIconFile().exists()) {
+					ico = new ImageIcon(m.getIconFile().getPath());
+				} else {
+					URL icon = AEInstaller2.class
+							.getResource("images/transparent.png");
+					ico = new ImageIcon(icon);
+				}
+				ico = ImageResizer.resizeImage(ico, 32, 32);
+				item.setAction(new AbstractAction(m.getName(), ico) {
+					private static final long serialVersionUID = 1L;
+
+					@Override
+					public void actionPerformed(ActionEvent evt) {
+						try {
+							ApplicationInvoker.execute(m.getExeType(),
+									m.getWorkingDir(), m.getExeFile(), null);
+						} catch (ERuntimeNotInstalledException e) {
+							JOptionPane.showMessageDialog(null,
+									bundle.getString("exeNotFound.text"),
+									bundle.getString("exeNotFound.title"),
+									JOptionPane.ERROR_MESSAGE);
+							e.printStackTrace();
+						} catch (FileNotFoundException e) {
+							if (e.getMessage().contains("JRE"))
+								JOptionPane.showMessageDialog(null,
+										bundle.getString("jreNotFound.text"),
+										bundle.getString("jreNotFound.title"),
+										JOptionPane.ERROR_MESSAGE);
+							if (e.getMessage().contains(".NET"))
+								JOptionPane.showMessageDialog(
+										null,
+										bundle.getString("dotNetNotFound.text"),
+										bundle.getString("dotNetNotFound.title"),
+										JOptionPane.ERROR_MESSAGE);
+							if (e.getMessage().contains("Wine"))
+								JOptionPane.showMessageDialog(null,
+										bundle.getString("wineNotFound.text"),
+										bundle.getString("wineNotFound.title"),
+										JOptionPane.ERROR_MESSAGE);
+							e.printStackTrace();
+						}
+					}
+				});
+				toolsMenuItems.add(item);
+				toolsMenu.add(item);
+			}
+		}
+	}
+
+	private void revertSelection() {
+		tblMods.revertSelection();
+	}
+
+	@SuppressWarnings("unused")
+	private void unSelectAll() {
+		tblMods.unSelectAll();
+	}
+
+	@DoInBackground(progressMessage = "checkCorePackages.title", cancelable = false, indeterminateProgress = false)
+	private void checkCorePackages(final BackgroundEvent evt) {
+		if (!SettingsManager.getInstance().isOfflineMode()) {
+			for (Package m : PackageManager.getInstance().getCoreTools()) {
+				if (m.isNewerAvailable()) {
+					execCoreUpdates.add(m);
+				}
+			}
+			for (Package m : PackageManager.getInstance().getCoreMods()) {
+				if (m.isNewerAvailable()) {
+					execCoreUpdates.add(m);
+				}
+			}
+			if (execCoreUpdates.size() > 0) {
+				ModDownloader m = new ModDownloader(execCoreUpdates,
+						new ModDownloaderListener() {
+							@Override
+							public void updateStatus(ModDownloader source,
+									Package currentDownload, State state,
+									int filesDown, int filesTotal,
+									int bytesDown, int bytesTotal,
+									int duration, int remaining, int speed) {
+								evt.setProgressEnd(filesTotal);
+								evt.setProgressValue(filesDown);
+							}
+						});
+				while (!m.isFinished()) {
+					try {
+						Thread.sleep(10);
+					} catch (InterruptedException e) {
+						e.printStackTrace();
+					}
+				}
+			}
+			evt.setProgressMessage(bundle.getString("coreToolsInstall.title"));
+			Installer.installTools(PackageManager.getInstance().getCoreTools(),
+					false);
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void infoCorePackages() {
+		if (execCoreUpdates.size() > 0) {
+			String packages = "";
+			for (Package m : execCoreUpdates) {
+				packages += String.format("\n - %s (%s)", m.getName(),
+						m.getVersion());
+			}
+			JOptionPane.showMessageDialog(this, String.format(
+					bundle.getString("corePackagesUpdated.text"), packages),
+					bundle.getString("corePackagesUpdated.title"),
+					JOptionPane.INFORMATION_MESSAGE);
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void install() {
+		TreeSet<Package> mods = new TreeSet<Package>();
+		mods.addAll(PackageManager.getInstance().getCoreMods());
+		mods.addAll(tblMods.getSelectedMods());
+
+		installDeps = new TreeSet<Package>();
+
+		installState = EInstallState.CHECKING;
+
+		while (installState == EInstallState.CHECKING) {
+			TreeSet<Package> toDownload = new TreeSet<Package>();
+			for (Package m : mods) {
+				if (!m.isLocalAvailable())
+					toDownload.add(m);
+			}
+
+			if (toDownload.size() > 0
+					&& SettingsManager.getInstance().isOfflineMode()) {
+				installState = EInstallState.OFFLINE;
+				break;
+			}
+
+			if (toDownload.size() > 0) {
+				Downloader dl = new Downloader(toDownload, installDeps);
+				try {
+					dl.setVisible(true);
+					if (!dl.isFinished()) {
+						installState = EInstallState.ABORTED;
+						break;
+					}
+				} finally {
+					dl.dispose();
+				}
+			}
+
+			HashMap<Package, HashSet<Package>> dependencies = PackageManager
+					.getInstance().checkDependencies(mods);
+			if (dependencies.size() > 0) {
+				for (HashSet<Package> hm : dependencies.values()) {
+					installDeps.addAll(hm);
+				}
+
+				int size = 0;
+				String depsLocalString = "";
+				String depsDownloadString = "";
+				for (Package m : dependencies.keySet()) {
+					for (Package mDep : dependencies.get(m)) {
+						if (!mods.contains(mDep)) {
+							mods.add(mDep);
+							if (!mDep.isLocalAvailable()) {
+								size += mDep.getZipSize();
+								if (depsDownloadString.length() > 0)
+									depsDownloadString += "\n";
+								depsDownloadString += " - " + mDep.getName();
+							} else {
+								if (depsLocalString.length() > 0)
+									depsLocalString += "\n";
+								depsLocalString += " - " + mDep.getName();
+							}
+						}
+					}
+				}
+
+				if (depsLocalString.length() == 0)
+					depsLocalString = bundle
+							.getString("installDependencies.none");
+				if (depsDownloadString.length() == 0)
+					depsDownloadString = bundle
+							.getString("installDependencies.none");
+
+				if (!SettingsManager.getInstance().get(
+						"notifyDepsAfterInstall", false)) {
+					int res = JOptionPane.showConfirmDialog(this, String
+							.format(bundle
+									.getString("installDependencies.text"),
+									depsLocalString, depsDownloadString,
+									SizeFormatter.format(size, 3)), bundle
+							.getString("installDependencies.title"),
+							JOptionPane.YES_NO_OPTION,
+							JOptionPane.INFORMATION_MESSAGE);
+
+					if (res == JOptionPane.NO_OPTION) {
+						installState = EInstallState.ABORTED;
+						break;
+					}
+				}
+			} else {
+				HashMap<Package, HashSet<Package>> incompatibilities = PackageManager
+						.getInstance().checkIncompabitilites(mods);
+				if (incompatibilities.size() > 0) {
+					installState = EInstallState.INCOMPATIBLE;
+
+					String incompatString = "";
+					for (Package m : incompatibilities.keySet()) {
+						if (incompatString.length() > 0)
+							incompatString += "\n";
+						incompatString += m.getName() + ": ";
+						String confMods = "";
+						for (Package mConf : incompatibilities.get(m)) {
+							if (confMods.length() > 0)
+								confMods += ", ";
+							confMods += mConf.getName();
+						}
+						incompatString += confMods;
+					}
+
+					JOptionPane.showMessageDialog(this, String.format(
+							bundle.getString("installIncompatibilities.text"),
+							incompatString), bundle
+							.getString("installIncompatibilities.title"),
+							JOptionPane.ERROR_MESSAGE);
+					break;
+				} else {
+					installState = EInstallState.READY;
+				}
+			}
+		}
+
+		if (installState == EInstallState.READY) {
+			installMods = new TreeSet<Package>();
+			TreeSet<Package> actuallyTools = new TreeSet<Package>();
+
+			for (Package m : mods) {
+				if (m.isTool())
+					actuallyTools.add(m);
+				else
+					installMods.add(m);
+			}
+
+			if (actuallyTools.size() > 0) {
+				Installer.installTools(actuallyTools, false);
+			}
+		}
+	}
+
+	@DoInBackground(progressMessage = "installing.title", cancelable = false, indeterminateProgress = false)
+	private void installExec(final BackgroundEvent evt) {
+		if (installState == EInstallState.READY) {
+			Installer.install(installMods, new InstallProgressListener() {
+				@Override
+				public void installProgressUpdate(int done, int total,
+						String step) {
+					evt.setProgressEnd(total);
+					evt.setProgressValue(done);
+					evt.setProgressMessage(step);
+				}
+			});
+			installState = EInstallState.DONE;
+		}
+		installMods = null;
+	}
+
+	@SuppressWarnings("unused")
+	private void installDone() {
+		PackageManager.getInstance().updateInstalledMods();
+		switch (installState) {
+			case DONE:
+				revertSelection();
+				if (installDeps.size() > 0
+						&& SettingsManager.getInstance().get(
+								"notifyDepsAfterInstall", false)) {
+					String installedDeps = "";
+					for (Package m : installDeps) {
+						if (installedDeps.length() > 0)
+							installedDeps += "\n";
+						installedDeps += " - " + m.getName();
+					}
+					JOptionPane.showMessageDialog(this, String.format(
+							bundle.getString("installDoneDeps.text"),
+							installedDeps), bundle
+							.getString("installDone.title"),
+							JOptionPane.INFORMATION_MESSAGE);
+				} else {
+					JOptionPane.showMessageDialog(this,
+							bundle.getString("installDone.text"),
+							bundle.getString("installDone.title"),
+							JOptionPane.INFORMATION_MESSAGE);
+				}
+				break;
+			case OFFLINE:
+				JOptionPane.showMessageDialog(
+						this,
+						SwingJavaBuilder.getConfig().getResource(
+								"offlineMode.text"),
+						SwingJavaBuilder.getConfig().getResource(
+								"offlineMode.title"),
+						JOptionPane.WARNING_MESSAGE);
+				break;
+			default:
+				break;
+		}
+		installDeps = null;
+	}
+
+	@Override
+	public void modSelectionChanged(ModTable source, Package m) {
+		pkgInfo.updateInfo(m);
+	}
+
+	private void updateTableFilter() {
+		Object o = cmbModTypes.getSelectedItem();
+		Type t = null;
+		if (o instanceof Type)
+			t = (Type) o;
+		int downloadState = 0;
+		if (radOnline.isSelected())
+			downloadState = 1;
+		if (radLocal.isSelected())
+			downloadState = 2;
+		tblMods.setFilter(t, downloadState, txtShowFilter.getText(),
+				(EApplyFilterTo) cmbShowFilterTo.getSelectedItem());
+	}
+
+	@Override
+	public void modInstallSelectionChanged(int newSize, int newCount) {
+		lblSelectedModsVal.setText(String.valueOf(newCount));
+		lblDownloadSizeVal.setText(SizeFormatter.format(newSize, 2));
+	}
+
+	@SuppressWarnings("unused")
+	private void checkInitialize() {
+		if (!Installer.isEditionInitialized()) {
+			if (!OniSplit.isOniSplitInstalled()) {
+				JOptionPane.showMessageDialog(this,
+						bundle.getString("noOniSplit.text"),
+						bundle.getString("noOniSplit.title"),
+						JOptionPane.ERROR_MESSAGE);
+				exit();
+			} else {
+				int res = JOptionPane
+						.showConfirmDialog(this,
+								bundle.getString("askInitialize.text"),
+								bundle.getString("askInitialize.title"),
+								JOptionPane.YES_NO_OPTION,
+								JOptionPane.QUESTION_MESSAGE);
+				if (res == JOptionPane.NO_OPTION) {
+					saveLocalData();
+					exit();
+				}
+			}
+		}
+	}
+
+	@DoInBackground(progressMessage = "initializingEdition.title", cancelable = false, indeterminateProgress = false)
+	private void initialize(final BackgroundEvent evt) {
+		if (!Installer.isEditionInitialized()) {
+			Installer.initializeEdition(new InstallProgressListener() {
+				@Override
+				public void installProgressUpdate(int done, int total,
+						String step) {
+					evt.setProgressEnd(total);
+					evt.setProgressValue(done);
+					evt.setProgressMessage(step);
+				}
+			});
+		}
+	}
+
+	private void oni(boolean windowed) {
+		try {
+			OniLauncher.launch(windowed);
+		} catch (FileNotFoundException e) {
+			JOptionPane.showMessageDialog(this,
+					bundle.getString("oniExeNotFound.text"),
+					bundle.getString("oniExeNotFound.title"),
+					JOptionPane.ERROR_MESSAGE);
+			e.printStackTrace();
+		} catch (ERuntimeNotInstalledException e) {
+			JOptionPane.showMessageDialog(this,
+					bundle.getString("wineNotFound.text"),
+					bundle.getString("wineNotFound.title"),
+					JOptionPane.ERROR_MESSAGE);
+			e.printStackTrace();
+		}
+	}
+
+	@SuppressWarnings("unused")
+	private void oniFull() {
+		oni(false);
+	}
+
+	@SuppressWarnings("unused")
+	private void oniWin() {
+		oni(true);
+	}
+
+	@SuppressWarnings("unused")
+	private void openEditionFolder() {
+		try {
+			Desktop.getDesktop().open(Paths.getEditionBasePath());
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	@Override
+	public void handleAbout(ApplicationEvent event) {
+		event.setHandled(true);
+		showAbout();
+	}
+
+	@Override
+	public void handleOpenApplication(ApplicationEvent event) {
+	}
+
+	@Override
+	public void handleOpenFile(ApplicationEvent event) {
+	}
+
+	@Override
+	public void handlePreferences(ApplicationEvent event) {
+		showSettings();
+	}
+
+	@Override
+	public void handlePrintFile(ApplicationEvent event) {
+	}
+
+	@Override
+	public void handleQuit(ApplicationEvent event) {
+		event.setHandled(true);
+		saveLocalData();
+		exit();
+	}
+
+	@Override
+	public void handleReOpenApplication(ApplicationEvent event) {
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/MainWin.yml
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/MainWin.yml	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/MainWin.yml	(revision 722)
@@ -0,0 +1,87 @@
+JFrame:
+  name: frame
+  title: appname
+  size: 950x600
+  minimumSize: 400x300
+  locationRelativeTo: null
+  defaultCloseOperation: doNothingOnClose
+  onWindowOpened: [execDepotUpdate,checkCorePackages,infoCorePackages,checkInitialize,initialize,checkUpdates,doUpdate,refreshToolsMenu,focus]
+  onWindowClosing: [saveLocalData,exit]
+  iconImage: img.ae
+  content:
+    - Action(name=exitAction, text=menu.exit, toolTipText=menu.exitTooltip, icon=img.exit, onAction=[saveLocalData,exit])
+    - Action(name=settings, text=menu.settings, toolTipText=menu.settingsTooltip, icon=img.settings, onAction=[showSettings])
+    - Action(name=about, text=menu.about, toolTipText=menu.aboutTooltip, icon=img.about, onAction=[showAbout])
+    - Action(name=runOniFull, text=menu.runOniFull, toolTipText=menu.runOniFullTooltip, icon=img.oni, onAction=[oniFull])
+    - Action(name=runOniWin, text=menu.runOniWin, toolTipText=menu.runOniWinTooltip, icon=img.oni, onAction=[oniWin])
+    - Action(name=openEditionFolder, text=menu.openEditionFolder, toolTipText=menu.openEditionFolderTooltip, icon=img.folder, onAction=[openEditionFolder])
+    - Action(name=loadConfig, text=menu.loadConfig, toolTipText=menu.loadConfigTooltip, icon=img.openFile, onAction=[loadConfig])
+    - Action(name=saveConfig, text=menu.saveConfig, toolTipText=menu.saveConfigTooltip, icon=img.saveFile, onAction=[saveConfig])
+    - Action(name=reglobalize, text=menu.reglobalize, toolTipText=menu.reglobalizeTooltip, icon=img.refresh, onAction=[reglobalizeVerify,reglobalize])
+    - Action(name=tools, text=menu.manageTools, toolTipText=menu.manageToolsTooltip, icon=img.tools, onAction=[tools,refreshToolsMenu])
+    - Action(name=update, text=menu.update, toolTipText=menu.updateTooltip, icon=img.update, onAction=[checkUpdates,doUpdate])
+    - Action(name=corePackages, text=menu.corePackages, toolTipText=menu.corePackagesTooltip, icon=img.core, onAction=[showCorePackagesDialog])
+    - JMenuBar:
+        - JMenu(name=mainMenu, text=menu.main):
+            - JMenuItem(action=settings)
+            - JMenuItem(action=about)
+            - JMenuItem(action=exitAction)
+        - JMenu(name=fileMenu, text=menu.file):
+            - JMenuItem(action=runOniFull)
+            - JMenuItem(action=runOniWin)
+            - JSeparator()
+            - JMenuItem(action=update)
+            - JSeparator()
+            - JMenuItem(action=loadConfig)
+            - JMenuItem(action=saveConfig)
+            - JSeparator()
+            - JMenuItem(action=openEditionFolder)
+            - JSeparator()
+            - JMenuItem(action=corePackages)
+            - JMenuItem(action=reglobalize)
+        - JMenu(name=toolsMenu, text=menu.tools):
+            - JMenuItem(name=manageToolsItem, action=tools)
+            - JSeparator()
+    - JToolBar(name=toolbar, floatable=false, orientation=0):
+        - JButton(action=exitAction, hideActionText=true)
+        - JToolBarSeparator()
+        - JButton(action=settings, hideActionText=true)
+        - JButton(action=about, hideActionText=true)
+    - JSplitPane(name=contents, orientation=horizontalSplit, continuousLayout=true):
+        - JPanel(name=panMods):
+            - JLabel(name=lblModTypes, text=lblModTypes.text)
+            - JComboBox(name=cmbModTypes, onAction=updateTableFilter)
+            - JLabel(name=lblShowOnly, text=lblShowOnly.text)
+            - JRadioButton(name=radAll, text=radAll.text, onAction=updateTableFilter)
+            - JRadioButton(name=radOnline, text=radOnline.text, onAction=updateTableFilter)
+            - JRadioButton(name=radLocal, text=radLocal.text, onAction=updateTableFilter)
+            - ButtonGroup: [radAll,radOnline,radLocal]
+            - JLabel(name=lblShowFilter, text=lblShowFilter.text)
+            - JTextField(name=txtShowFilter)
+            - JComboBox(name=cmbShowFilterTo, onAction=updateTableFilter)
+            - JScrollPane(name=scrollMods, vScrollBar=always, hScrollBar=never)
+            - JButton(name=btnUnSelectAll, icon=img.unSelect16, text=btnUnSelectAll.text, onAction=[unSelectAll])
+            - JButton(name=btnRevertSelection, icon=img.undo16, text=btnRevertSelection.text, toolTipText=btnRevertSelection.tooltip, onAction=[revertSelection])
+            - JButton(name=btnInstall, icon=img.install, text=btnInstall.text, toolTipText=btnInstall.tooltip, onAction=[install,installExec,installDone])
+            - JLabel(name=lblSelectedMods, text=lblSelectedMods.text)
+            - JLabel(name=lblSelectedModsVal)
+            - JLabel(name=lblDownloadSize, text=lblDownloadSize.text)
+            - JLabel(name=lblDownloadSizeVal)
+            - MigLayout: |
+                 [min]           [grow]                           [min]
+                 >lblModTypes    cmbModTypes+*                                     [min]
+                 >lblShowOnly    radAll+*,radOnline,radLocal                       [min]
+                 >lblShowFilter  txtShowFilter+*>,cmbShowFilterTo<                 [min]
+                 scrollMods+*                                                      [grow]
+                 >btnUnSelectAll+*,btnRevertSelection                              [min]
+                 lblSelectedMods+2,lblSelectedModsVal             >btnInstall+*+*  [min]
+                 lblDownloadSize+2,lblDownloadSizeVal                              [min]
+        - PackageInfoBox(name=pkgInfo)
+    - MigLayout:
+        layoutConstraints: wrap 1
+        columnConstraints: grow
+        rowConstraints: grow
+        constraints:
+            - contents: grow
+
+#            - tools: dock north
Index: java/installer2/src/net/oni2/aeinstaller/gui/about/AboutDialog.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/about/AboutDialog.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/about/AboutDialog.java	(revision 722)
@@ -0,0 +1,61 @@
+package net.oni2.aeinstaller.gui.about;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.KeyStroke;
+
+import net.oni2.aeinstaller.gui.HTMLLinkLabel;
+
+import org.javabuilders.BuildResult;
+import org.javabuilders.swing.SwingJavaBuilder;
+
+/**
+ * @author Christian Illy
+ */
+public class AboutDialog extends JDialog {
+	private static final long serialVersionUID = 1632257865019785612L;
+
+	private ResourceBundle bundle = ResourceBundle.getBundle(getClass()
+			.getName());
+	@SuppressWarnings("unused")
+	private BuildResult result = SwingJavaBuilder.build(this, bundle);
+
+	private JLabel lblAppversion;
+	private HTMLLinkLabel txtLinks;
+
+	/**
+	 * Open the dialog
+	 */
+	public AboutDialog() {
+		lblAppversion.setText("v"
+				+ SwingJavaBuilder.getConfig().getResource("appversion"));
+
+		txtLinks.setText(bundle.getString("Links"));
+
+		pack();
+
+		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);
+
+		setLocationRelativeTo(null);
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/about/AboutDialog.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/about/AboutDialog.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/about/AboutDialog.properties	(revision 722)
@@ -0,0 +1,11 @@
+frame.title=AE Installer: About
+btnClose=Close
+
+AEIBy=by Alloc, Iritscen and Gumby
+
+panAE=AE Credits:
+AENames=<html>Demos_Kratos:<br>EdT:<br>Geyser:<br>Gumby:<br>Iritscen:<br>Loser:<br>Neo:<br>Paradox:<br>RossyMiles:<br>Script10k:<br>SFeLi:<br>SSG:</html>
+AEWork=<html>OniSplit GUI (Win)<br>OniSplit GUI (Mac)<br>Original creator of the Edition<br>AE Framework<br>Project management<br>Documenting Oni's game data<br>OniSplit, documenting Oni, tech support<br>Documenting Oni's game data<br>Daodan DLL port to C<br>xmlTools<br>Original Daodan DLL<br>Documenting Oni's game data</html>
+
+panLinks=Links:
+Links=<a href="http://oni.bungie.org/community/forum/index.php">Oni Central Forum</a> (oni.bungie.org/community/forum)<br><a href="http://oni.bungie.org/">Oni Community Portal</a> (oni.bungie.org)<br><a href="http://mods.oni2.net/">Oni Mod Depot</a> (mods.oni2.net)<br><a href="http://wiki.oni2.net/">Oni Galore Wiki</a> (wiki.oni2.net)
Index: java/installer2/src/net/oni2/aeinstaller/gui/about/AboutDialog.yml
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/about/AboutDialog.yml	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/about/AboutDialog.yml	(revision 722)
@@ -0,0 +1,35 @@
+JDialog:
+  name: frame
+  title: frame.title
+  resizable: false
+  locationRelativeTo: null
+  defaultCloseOperation: disposeOnClose
+  iconImage: img.ae
+  modalityType: applicationModal
+  content:
+    - JButton(name=btnClose, text=btnClose, onAction=[dispose])
+    - JPanel(name=panAEI):
+      - JLabel(name=lblAppname, text=appname)
+      - JLabel(name=lblAppversion)
+      - JLabel(name=lblAEIBy, text=AEIBy)
+      - MigLayout: |
+           [grow]
+           lblAppname<,lblAppversion<   [min]
+           lblAEIBy                     [min]
+    - JPanel(name=panAE, groupTitle=panAE):
+      - JLabel(name=lblNames, text=AENames, horizontalAlignment=right)
+      - JLabel(name=lblWork, text=AEWork)
+      - MigLayout: |
+           [min]      [grow]
+           >lblNames  lblWork [min]
+    - JPanel(name=panLinks, groupTitle=panLinks):
+      - HTMLLinkLabel(name=txtLinks)
+      - MigLayout: |
+           [grow]
+           txtLinks [pref]
+    - MigLayout: |
+         [grow]
+         panAEI     [pref]
+         panAE      [pref]
+         panLinks   [pref]
+         |btnClose  [min]
Index: java/installer2/src/net/oni2/aeinstaller/gui/corepackages/CorePackagesDialog.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/corepackages/CorePackagesDialog.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/corepackages/CorePackagesDialog.java	(revision 722)
@@ -0,0 +1,86 @@
+package net.oni2.aeinstaller.gui.corepackages;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.KeyStroke;
+
+import net.oni2.SettingsManager;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.gui.modtable.ModSelectionListener;
+import net.oni2.aeinstaller.gui.modtable.ModTable;
+import net.oni2.aeinstaller.gui.modtable.ModTable.ETableContentType;
+import net.oni2.aeinstaller.gui.packageinfobox.PackageInfoBox;
+
+import org.javabuilders.BuildResult;
+import org.javabuilders.swing.SwingJavaBuilder;
+
+/**
+ * @author Christian Illy
+ */
+public class CorePackagesDialog extends JDialog implements ModSelectionListener {
+	private static final long serialVersionUID = -5444213842599816301L;
+
+	private ResourceBundle bundle = ResourceBundle
+			.getBundle("net.oni2.aeinstaller.localization."
+					+ getClass().getSimpleName());
+	@SuppressWarnings("unused")
+	private BuildResult result = SwingJavaBuilder.build(this, bundle);
+
+	private JSplitPane contents;
+
+	private JScrollPane scrollTools;
+	private ModTable tblTools;
+
+	private PackageInfoBox pkgInfo;
+
+	/**
+	 * Open the dialog
+	 */
+	public CorePackagesDialog() {
+		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);
+
+		contents.setDividerLocation(SettingsManager.getInstance().get("win_core_divloc", 550));
+		contents.setResizeWeight(0.4);
+
+		tblTools = new ModTable(ETableContentType.CORE);
+		tblTools.reloadData();
+		scrollTools.setViewportView(tblTools);
+
+		tblTools.addModSelectionListener(this);
+
+		setSize(SettingsManager.getInstance().get("win_core_width", 950), SettingsManager.getInstance().get("win_core_height", 600));
+		setLocationRelativeTo(null);
+	}
+
+	@Override
+	public void modSelectionChanged(ModTable source, Package mod) {
+		pkgInfo.updateInfo(mod);
+	}
+
+	@SuppressWarnings("unused")
+	private void closing() {
+		SettingsManager.getInstance().put("win_core_divloc", contents.getDividerLocation());
+		SettingsManager.getInstance().put("win_core_width", getWidth());
+		SettingsManager.getInstance().put("win_core_height", getHeight());
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/corepackages/CorePackagesDialog.yml
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/corepackages/CorePackagesDialog.yml	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/corepackages/CorePackagesDialog.yml	(revision 722)
@@ -0,0 +1,21 @@
+JDialog:
+  name: frame
+  title: frame.title
+  size: 900x500
+  locationRelativeTo: null
+  defaultCloseOperation: disposeOnClose
+  iconImage: img.ae
+  modalityType: applicationModal
+  onWindowClosing: [closing]
+  content:
+    - JSplitPane(name=contents, orientation=horizontalSplit, continuousLayout=true):
+        - JPanel(name=panTools):
+            - JScrollPane(name=scrollTools, vScrollBar=always, hScrollBar=never)
+            - MigLayout: |
+                 [grow]
+                 scrollTools   [grow]
+        - PackageInfoBox(name=pkgInfo)
+    - MigLayout: |
+         [grow]
+         contents [grow]
+  
Index: java/installer2/src/net/oni2/aeinstaller/gui/downloadwindow/Downloader.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/downloadwindow/Downloader.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/downloadwindow/Downloader.java	(revision 722)
@@ -0,0 +1,131 @@
+package net.oni2.aeinstaller.gui.downloadwindow;
+
+import java.util.ResourceBundle;
+import java.util.TreeSet;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JProgressBar;
+
+import net.oni2.aeinstaller.backend.SizeFormatter;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.download.ModDownloader;
+import net.oni2.aeinstaller.backend.packages.download.ModDownloaderListener;
+import net.oni2.aeinstaller.backend.packages.download.ModDownloader.State;
+
+import org.javabuilders.BuildResult;
+import org.javabuilders.swing.SwingJavaBuilder;
+
+/**
+ * @author Christian Illy
+ */
+public class Downloader extends JDialog implements ModDownloaderListener {
+	private static final long serialVersionUID = 9097967828001263776L;
+
+	private ResourceBundle bundle = ResourceBundle
+			.getBundle("net.oni2.aeinstaller.localization."
+					+ getClass().getSimpleName());
+	@SuppressWarnings("unused")
+	private BuildResult result = SwingJavaBuilder.build(this, bundle);
+
+	private JLabel lblNameVal;
+	private JLabel lblIsDep;
+	private JLabel lblElapsedVal;
+	private JLabel lblRemainingVal;
+	private JLabel lblDownloadedVal;
+	private JLabel lblTotalVal;
+	private JLabel lblRateVal;
+	private JProgressBar progress;
+
+	private JButton btnAbort;
+
+	private ModDownloader downloader;
+	private TreeSet<Package> dependencies = new TreeSet<Package>();
+
+	/**
+	 * @param mods
+	 *            Mods to download
+	 * @param dependencies
+	 *            List of mods that only are auto-resolved dependencies
+	 */
+	public Downloader(TreeSet<Package> mods, TreeSet<Package> dependencies) {
+		super();
+
+		setResizable(false);
+		setSize(500, (int) getSize().getHeight());
+
+		if (dependencies != null)
+			this.dependencies = dependencies;
+
+		downloader = new ModDownloader(mods, this);
+		progress.setMaximum(downloader.getTotalSize());
+		progress.setStringPainted(true);
+		progress.setToolTipText(String.format("%d / %d files downloaded", 0,
+				mods.size()));
+
+		lblDownloadedVal.setText(SizeFormatter.format(0, 3));
+		lblTotalVal.setText(SizeFormatter.format(downloader.getTotalSize(), 3));
+	}
+
+	private void close() {
+		if (!downloader.isFinished())
+			downloader.abort();
+		setVisible(false);
+	}
+
+	@SuppressWarnings("unused")
+	private boolean confirm() {
+		int res = JOptionPane.showConfirmDialog(this,
+				bundle.getString("abort.text"),
+				bundle.getString("abort.title"), JOptionPane.YES_NO_OPTION,
+				JOptionPane.WARNING_MESSAGE);
+		return res == JOptionPane.YES_OPTION;
+	}
+
+	private String formatTime(int sec) {
+		int min = sec / 60;
+		sec = sec % 60;
+		return String.format("%02d:%02d", min, sec);
+	}
+
+	@Override
+	public void updateStatus(ModDownloader source, Package currentDownload,
+			State state, int filesDown, int filesTotal, int bytesDown,
+			int bytesTotal, int duration, int remaining, int speed) {
+		if (state == ModDownloader.State.FINISHED) {
+			close();
+		} else {
+			if (state == State.LAST_FILE_DOWNLOADED)
+				btnAbort.setEnabled(false);
+
+			progress.setValue(bytesDown);
+			progress.setToolTipText(String.format("%d / %d files downloaded",
+					filesDown, filesTotal));
+
+			if (currentDownload != null) {
+				lblNameVal.setText(currentDownload.getName());
+				lblIsDep.setVisible(dependencies.contains(currentDownload));
+			} else {
+				lblNameVal.setText(bundle.getString("unpacking"));
+				lblIsDep.setVisible(false);
+			}
+
+			lblElapsedVal.setText(formatTime(duration));
+			lblRemainingVal.setText(formatTime(remaining));
+
+			lblDownloadedVal.setText(SizeFormatter.format(bytesDown, 3));
+
+			lblRateVal.setText(SizeFormatter.format(speed, 3) + "/s");
+		}
+	}
+
+	/**
+	 * @return were all downloads finished?
+	 */
+	public boolean isFinished() {
+		return downloader.isFinished();
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/downloadwindow/Downloader.yml
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/downloadwindow/Downloader.yml	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/downloadwindow/Downloader.yml	(revision 722)
@@ -0,0 +1,33 @@
+JDialog:
+  name: frame
+  title: frame.title
+  size: packed
+  locationRelativeTo: null
+  defaultCloseOperation: doNothingOnClose
+  onWindowClosing: [confirm,close]
+  iconImage: img.ae
+  modalityType: applicationModal
+  content:
+    - JButton(name=btnAbort, text=btnAbort.title, icon=img.stop, onAction=[confirm,close])
+    - JLabel(name=lblName, text=lblName.title)
+    - JLabel(name=lblNameVal, font=14pt)
+    - JLabel(name=lblIsDep, text=lblIsDep.title, foreground=darkred, visible=false)
+    - JLabel(name=lblElapsed, text=lblElapsed.title)
+    - JLabel(name=lblElapsedVal)
+    - JLabel(name=lblRemaining, text=lblRemaining.title)
+    - JLabel(name=lblRemainingVal)
+    - JLabel(name=lblRate, text=lblRate.title)
+    - JLabel(name=lblRateVal)
+    - JLabel(name=lblDownloaded, text=lblDownloaded.title)
+    - JLabel(name=lblDownloadedVal)
+    - JLabel(name=lblTotal, text=lblTotal.title)
+    - JLabel(name=lblTotalVal)
+    - JProgressBar(name=progress)
+    - MigLayout: |
+         [grow]
+         lblName<,lblNameVal<,lblIsDep<                                  [min]
+         lblElapsed=1,lblElapsedVal=2,lblRemaining=1,lblRemainingVal=2   [min]
+         lblDownloaded=1,lblDownloadedVal=2,lblTotal=1,lblTotalVal=2     [min]
+         lblRate=1,lblRateVal=2                                          [min]
+         progress                                                        [min]
+         >btnAbort^                                                      [min]
Index: java/installer2/src/net/oni2/aeinstaller/gui/modtable/EApplyFilterTo.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/modtable/EApplyFilterTo.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/modtable/EApplyFilterTo.java	(revision 722)
@@ -0,0 +1,36 @@
+package net.oni2.aeinstaller.gui.modtable;
+
+import java.util.ResourceBundle;
+
+/**
+ * @author Christian Illy
+ */
+public enum EApplyFilterTo {
+	/**
+	 * Filter on all fields
+	 */
+	ALL,
+	/**
+	 * Filter only on name field
+	 */
+	NAME,
+	/**
+	 * Filter only on creator field
+	 */
+	CREATOR,
+	/**
+	 * Filter only on description field
+	 */
+	DESCRIPTION;
+
+	private ResourceBundle bundle = ResourceBundle
+			.getBundle("net.oni2.aeinstaller.localization.ModTable");
+
+	/**
+	 * @return Name of Filter
+	 */
+	public String toString() {
+		return bundle.getString("filterTo." + super.toString());
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModInstallSelectionListener.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModInstallSelectionListener.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModInstallSelectionListener.java	(revision 722)
@@ -0,0 +1,14 @@
+package net.oni2.aeinstaller.gui.modtable;
+
+/**
+ * @author Christian Illy
+ */
+public interface ModInstallSelectionListener {
+	/**
+	 * @param newSize
+	 *            New size of files to download
+	 * @param newCount
+	 *            New number of mods selected for installation
+	 */
+	public void modInstallSelectionChanged(int newSize, int newCount);
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModSelectionListener.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModSelectionListener.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModSelectionListener.java	(revision 722)
@@ -0,0 +1,18 @@
+package net.oni2.aeinstaller.gui.modtable;
+
+import net.oni2.aeinstaller.backend.packages.Package;
+
+/**
+ * @author Christian Illy
+ */
+public interface ModSelectionListener {
+	/**
+	 * Called on changes to the mod selection
+	 * 
+	 * @param source
+	 *            Source of event
+	 * @param mod
+	 *            New selected mod
+	 */
+	public void modSelectionChanged(ModTable source, Package mod);
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTable.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTable.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTable.java	(revision 722)
@@ -0,0 +1,539 @@
+package net.oni2.aeinstaller.gui.modtable;
+
+import java.awt.Desktop;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPopupMenu;
+import javax.swing.JTable;
+import javax.swing.JViewport;
+import javax.swing.ListSelectionModel;
+import javax.swing.RowSorter;
+import javax.swing.SortOrder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.RowSorterEvent;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableRowSorter;
+
+import net.oni2.SettingsManager;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.Type;
+import net.oni2.aeinstaller.gui.downloadwindow.Downloader;
+
+/**
+ * @author Christian Illy
+ */
+public class ModTable extends JTable {
+	private static final long serialVersionUID = 1L;
+
+	private ResourceBundle bundle = ResourceBundle
+			.getBundle("net.oni2.aeinstaller.localization.ModTable");
+
+	/**
+	 * @author Christian Illy
+	 */
+	public enum ETableContentType {
+		/**
+		 * Table showing mods
+		 */
+		MODS,
+		/**
+		 * Table showing tools
+		 */
+		TOOLS,
+		/**
+		 * Table showing core packages
+		 */
+		CORE
+	};
+
+	private HashSet<ModSelectionListener> modSelListeners = new HashSet<ModSelectionListener>();
+
+	private ModTableModel model;
+	private TableRowSorter<ModTableModel> sorter;
+
+	private ETableContentType contentType = ETableContentType.MODS;
+
+	/**
+	 * Create a new ModTable
+	 * 
+	 * @param contentType
+	 *            Content to show
+	 */
+	public ModTable(ETableContentType contentType) {
+		super();
+
+		this.contentType = contentType;
+
+		setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+		getSelectionModel().addListSelectionListener(this);
+		addMouseListener(new MouseEventHandler());
+		addKeyListener(new KeyEventHandler());
+		// To get checkbox-cells with background of row
+		((JComponent) getDefaultRenderer(Boolean.class)).setOpaque(true);
+
+		model = new ModTableModel(contentType);
+
+		setModel(model);
+
+		sorter = new TableRowSorter<ModTableModel>(model);
+		setRowSorter(sorter);
+
+		setFilter(null, 0, null, EApplyFilterTo.ALL);
+
+		List<RowSorter.SortKey> sortKeys = new ArrayList<RowSorter.SortKey>();
+
+		int sortCol = SettingsManager.getInstance().get("modSortColumn", 1);
+		SortOrder sortOrder = SortOrder.valueOf(SettingsManager.getInstance().get(
+				"modSortOrder", "ASCENDING"));
+
+		sortKeys.add(new RowSorter.SortKey(sortCol, sortOrder));
+		sorter.setSortKeys(sortKeys);
+
+		for (int i = 0; i < model.getColumnCount(); i++) {
+			model.setColumnConstraints(i, getColumnModel().getColumn(i));
+		}
+
+		getTableHeader().addMouseListener(new HeaderMouseEventHandler());
+
+		if (contentType != ETableContentType.MODS) {
+			getColumnModel().removeColumn(getColumnModel().getColumn(0));
+		}
+	}
+
+	@Override
+	public String getToolTipText(MouseEvent e) {
+		int r = rowAtPoint(e.getPoint());
+		int c = columnAtPoint(e.getPoint());
+		if (r >= 0 && r < getRowCount()) {
+			int modelCol = convertColumnIndexToModel(c);
+			if (modelCol == 4) {
+				final Package mod = (Package) getValueAt(r, -1);
+
+				String tt = "<html>";
+				tt += String.format("%s: %s<br>",
+						bundle.getString("state.installed"),
+						bundle.getString((mod.isInstalled() ? "yes" : "no")));
+				tt += String.format(
+						"%s: %s<br>",
+						bundle.getString("state.updatable"),
+						bundle.getString((mod.isLocalAvailable()
+								&& mod.isNewerAvailable() ? "yes" : "no")));
+				tt += String.format("%s: %s</html>", bundle
+						.getString("state.downloaded"), bundle.getString((mod
+						.isLocalAvailable() ? "yes" : "no")));
+				return tt;
+			}
+		}
+		return super.getToolTipText(e);
+	}
+
+	/**
+	 * @param listener
+	 *            Listener to add
+	 */
+	public void addModSelectionListener(ModSelectionListener listener) {
+		modSelListeners.add(listener);
+	}
+
+	/**
+	 * @param listener
+	 *            Listener to remove
+	 */
+	public void removeModSelectionListener(ModSelectionListener listener) {
+		modSelListeners.remove(listener);
+	}
+
+	private void notifyModSelectionListeners(Package m) {
+		for (ModSelectionListener l : modSelListeners) {
+			l.modSelectionChanged(this, m);
+		}
+	}
+
+	/**
+	 * @param listener
+	 *            Listener to add
+	 */
+	public void addDownloadSizeListener(ModInstallSelectionListener listener) {
+		model.addDownloadSizeListener(listener);
+	}
+
+	/**
+	 * @param listener
+	 *            Listener to remove
+	 */
+	public void removeDownloadSizeListener(ModInstallSelectionListener listener) {
+		model.removeDownloadSizeListener(listener);
+	}
+
+	/**
+	 * Reload the nodes data after an update to the cache
+	 */
+	public void reloadData() {
+		model.reloadData();
+	}
+
+	/**
+	 * Revert the selection to the mods that are currently installed
+	 */
+	public void revertSelection() {
+		model.revertSelection();
+	}
+
+	/**
+	 * Reload the selection after a config was loaded
+	 * 
+	 * @param config
+	 *            Config to load
+	 */
+	public void reloadSelection(File config) {
+		model.reloadSelection(config);
+	}
+
+	/**
+	 * @return Mods selected for installation
+	 */
+	public TreeSet<Package> getSelectedMods() {
+		return model.getSelectedMods();
+	}
+
+	/**
+	 * @param type
+	 *            Type of mods to show (null for all)
+	 * @param downloadState
+	 *            Show only: 0 = all, 1 = online, 2 = downloaded
+	 * @param filterString
+	 *            String to filter on
+	 * @param filterTo
+	 *            Fields to use string filter on
+	 */
+	public void setFilter(Type type, int downloadState, String filterString,
+			EApplyFilterTo filterTo) {
+		sorter.setRowFilter(new ModTableFilter(type, downloadState,
+				filterString, filterTo, contentType == ETableContentType.CORE,
+				false));
+	}
+
+	@Override
+	public void sorterChanged(RowSorterEvent evt) {
+		super.sorterChanged(evt);
+		if (evt.getType() == RowSorterEvent.Type.SORT_ORDER_CHANGED) {
+			@SuppressWarnings("unchecked")
+			RowSorter<ModTableModel> rs = (RowSorter<ModTableModel>) getRowSorter();
+			List<? extends RowSorter.SortKey> keys = rs.getSortKeys();
+			if (keys.size() > 0) {
+				int col = keys.get(0).getColumn();
+				SortOrder so = keys.get(0).getSortOrder();
+				SettingsManager.getInstance().put("modSortColumn", col);
+				SettingsManager.getInstance().put("modSortOrder", so.toString());
+			}
+		}
+	}
+
+	/**
+	 * Select/Unselect all currently visible items depending on the current
+	 * state of selection
+	 */
+	public void unSelectAll() {
+		boolean isAll = true;
+		for (int i = 0; i < getRowCount(); i++) {
+			int modRow = convertRowIndexToModel(i);
+			boolean inst = (Boolean) model.getValueAt(modRow, 0);
+			if (!inst) {
+				isAll = false;
+				break;
+			}
+		}
+
+		for (int i = 0; i < getRowCount(); i++) {
+			int modRow = convertRowIndexToModel(i);
+			model.setValueAt(!isAll, modRow, 0);
+		}
+		invalidate();
+		repaint();
+	}
+
+	@Override
+	public void valueChanged(ListSelectionEvent e) {
+		super.valueChanged(e);
+		int viewRow = getSelectedRow();
+		if (viewRow < 0) {
+			notifyModSelectionListeners(null);
+		} else {
+			Package mod = (Package) getValueAt(viewRow, -1);
+			notifyModSelectionListeners(mod);
+		}
+	}
+
+	private class MouseEventHandler extends MouseAdapter {
+		private void mouseEventProcessing(MouseEvent e) {
+			int r = rowAtPoint(e.getPoint());
+			if (r >= 0 && r < getRowCount())
+				setRowSelectionInterval(r, r);
+			else
+				clearSelection();
+
+			int rowindex = getSelectedRow();
+			if (rowindex >= 0) {
+				if (e.isPopupTrigger() && e.getComponent() instanceof JTable) {
+					final Package mod = (Package) getValueAt(rowindex, -1);
+
+					JPopupMenu popup = new JPopupMenu();
+
+					if (mod.isLocalAvailable()) {
+						// Open package folder item
+						JMenuItem openModFolder = new JMenuItem(
+								bundle.getString("openModFolder.text"));
+						openModFolder.addActionListener(new ActionListener() {
+							@Override
+							public void actionPerformed(ActionEvent arg0) {
+								try {
+									Desktop.getDesktop().open(
+											mod.getLocalPath());
+								} catch (IOException e) {
+									e.printStackTrace();
+								}
+							}
+						});
+						popup.add(openModFolder);
+					}
+
+					if (mod.getUrl() != null) {
+						// Open Depot page item
+						JMenuItem openDepotPage = new JMenuItem(
+								bundle.getString("openDepotPage.text"));
+						openDepotPage.addActionListener(new ActionListener() {
+							@Override
+							public void actionPerformed(ActionEvent arg0) {
+								try {
+									Desktop.getDesktop().browse(mod.getUrl());
+								} catch (IOException e) {
+									e.printStackTrace();
+								}
+							}
+						});
+						popup.add(openDepotPage);
+					}
+
+					if (mod.getFile() != null) {
+						// Download package
+						JMenuItem downloadPackage = new JMenuItem(
+								bundle.getString("downloadPackage.text"));
+						downloadPackage.addActionListener(new ActionListener() {
+							@Override
+							public void actionPerformed(ActionEvent arg0) {
+								TreeSet<Package> toDo = new TreeSet<Package>();
+								TreeSet<Package> deps = new TreeSet<Package>();
+								toDo.add(mod);
+								Downloader dl = new Downloader(toDo, deps);
+								try {
+									dl.setVisible(true);
+								} finally {
+									dl.dispose();
+								}
+								invalidate();
+								repaint();
+							}
+						});
+						popup.add(downloadPackage);
+					}
+
+					if (mod.isLocalAvailable()
+							&& contentType != ETableContentType.CORE) {
+						// Delete package folder item
+						JMenuItem deleteModFolder = new JMenuItem(
+								bundle.getString("deletePackage.text"));
+						deleteModFolder.addActionListener(new ActionListener() {
+							@Override
+							public void actionPerformed(ActionEvent arg0) {
+								if (mod.getNode() == null) {
+									JOptionPane.showMessageDialog(
+											null,
+											bundle.getString("deletePackageLocalOnly.text"),
+											bundle.getString("deletePackageLocalOnly.title"),
+											JOptionPane.INFORMATION_MESSAGE);
+								} else {
+									int res = JOptionPane.showConfirmDialog(
+											null,
+											bundle.getString("deletePackageConfirm.text"),
+											bundle.getString("deletePackageConfirm.title"),
+											JOptionPane.YES_NO_OPTION,
+											JOptionPane.WARNING_MESSAGE);
+									if (res == JOptionPane.YES_OPTION) {
+										mod.deleteLocalPackage();
+										invalidate();
+										repaint();
+									}
+								}
+							}
+						});
+						popup.add(deleteModFolder);
+					}
+
+					if (popup.getSubElements().length > 0)
+						popup.show(e.getComponent(), e.getX(), e.getY());
+				}
+			}
+		}
+
+		@Override
+		public void mousePressed(MouseEvent e) {
+			mouseEventProcessing(e);
+		}
+
+		@Override
+		public void mouseReleased(MouseEvent e) {
+			mouseEventProcessing(e);
+		}
+	}
+
+	private class KeyEventHandler extends KeyAdapter {
+		private void goToRow(int row) {
+			setRowSelectionInterval(row, row);
+			JViewport viewport = (JViewport) getParent();
+			Rectangle rect = getCellRect(row, 0, true);
+			Rectangle r2 = viewport.getVisibleRect();
+			scrollRectToVisible(new Rectangle(rect.x, rect.y,
+					(int) r2.getWidth(), (int) r2.getHeight()));
+		}
+
+		@Override
+		public void keyTyped(KeyEvent e) {
+			super.keyTyped(e);
+
+			if (e.getModifiers() == 0) {
+				String key = String.valueOf(e.getKeyChar()).toLowerCase();
+				int row = getSelectedRow();
+				if (row == (getRowCount() - 1))
+					row = -1;
+				for (int i = row + 1; i < getRowCount(); i++) {
+					Package m = (Package) getValueAt(i, -1);
+					if (m.getName().toLowerCase().startsWith(key)) {
+						goToRow(i);
+						return;
+					}
+				}
+				if (row > 0) {
+					for (int i = 0; i < row; i++) {
+						Package m = (Package) getValueAt(i, -1);
+						if (m.getName().toLowerCase().startsWith(key)) {
+							goToRow(i);
+							return;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	private class HeaderMouseEventHandler extends MouseAdapter {
+		private Vector<TableColumn> columns = new Vector<TableColumn>();
+		private TableColumnModel tcm = getColumnModel();
+
+		public HeaderMouseEventHandler() {
+			super();
+
+			for (int i = 1; i < tcm.getColumnCount(); i++) {
+				columns.add(tcm.getColumn(i));
+			}
+
+			for (int i = 1; i < columns.size(); i++) {
+				TableColumn tc = columns.get(i);
+				if (!SettingsManager.getInstance().get(
+						String.format("modShowColumn%02d", tc.getModelIndex()),
+						true))
+					tcm.removeColumn(tc);
+			}
+		}
+
+		private TableColumn getColumn(String name) {
+			for (TableColumn tc : columns) {
+				if (tc.getHeaderValue().equals(name)) {
+					return tc;
+				}
+			}
+			return null;
+		}
+
+		private int headerContains(TableColumn tc) {
+			for (int col = 0; col < tcm.getColumnCount(); col++) {
+				if (tcm.getColumn(col).equals(tc))
+					return col;
+			}
+			return -1;
+		}
+
+		private void mouseEventProcessing(MouseEvent e) {
+			if (e.isPopupTrigger() && e.getComponent() instanceof JTableHeader) {
+				JPopupMenu popup = new JPopupMenu();
+
+				ActionListener al = new ActionListener() {
+					@Override
+					public void actionPerformed(ActionEvent e) {
+						JCheckBoxMenuItem itm = (JCheckBoxMenuItem) e
+								.getSource();
+						TableColumn col = getColumn(itm.getText());
+						if (itm.isSelected()) {
+							tcm.addColumn(col);
+							for (int i = columns.indexOf(col) - 1; i >= 0; i--) {
+								int pos = headerContains(columns.get(i));
+								if (pos >= 0) {
+									tcm.moveColumn(tcm.getColumnCount() - 1,
+											pos + 1);
+									break;
+								}
+							}
+						} else {
+							tcm.removeColumn(col);
+						}
+						SettingsManager.getInstance().put(
+								String.format("modShowColumn%02d",
+										col.getModelIndex()), itm.isSelected());
+					}
+				};
+
+				for (int i = 1; i < columns.size(); i++) {
+					JCheckBoxMenuItem itm = new JCheckBoxMenuItem(
+							(String) columns.get(i).getHeaderValue());
+					itm.setSelected(headerContains(columns.get(i)) >= 0);
+
+					itm.addActionListener(al);
+					popup.add(itm);
+				}
+
+				if (popup.getSubElements().length > 0)
+					popup.show(e.getComponent(), e.getX(), e.getY());
+			}
+		}
+
+		@Override
+		public void mousePressed(MouseEvent e) {
+			mouseEventProcessing(e);
+		}
+
+		@Override
+		public void mouseReleased(MouseEvent e) {
+			mouseEventProcessing(e);
+		}
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableFilter.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableFilter.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableFilter.java	(revision 722)
@@ -0,0 +1,97 @@
+package net.oni2.aeinstaller.gui.modtable;
+
+import javax.swing.RowFilter;
+
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.Type;
+
+/**
+ * @author Christian Illy
+ */
+public class ModTableFilter extends RowFilter<ModTableModel, Integer> {
+	private Type type = null;
+	private int downloadState = 0;
+	private EApplyFilterTo filterTo = EApplyFilterTo.ALL;
+	private String filterString = null;
+
+	private boolean showCorePackages = false;
+	private boolean showInvalidPlatform = false;
+
+	/**
+	 * @param type
+	 *            Type of mods to show (null for all)
+	 * @param downloadState
+	 *            Show only: 0 = all, 1 = online, 2 = downloaded
+	 * @param filterString
+	 *            Filter for the given string
+	 * @param filterTo
+	 *            Use the string filter only on the named field
+	 * @param showCorePackages
+	 *            Show core packages in table
+	 * @param showInvalidPlatform
+	 *            Show packages not valid on this platform
+	 */
+	public ModTableFilter(Type type, int downloadState, String filterString,
+			EApplyFilterTo filterTo, boolean showCorePackages,
+			boolean showInvalidPlatform) {
+		super();
+		this.type = type;
+		this.downloadState = downloadState;
+		if (filterString != null)
+			this.filterString = filterString.toLowerCase();
+		this.filterTo = filterTo;
+		this.showCorePackages = showCorePackages;
+		this.showInvalidPlatform = showInvalidPlatform;
+	}
+
+	@Override
+	public boolean include(
+			RowFilter.Entry<? extends ModTableModel, ? extends Integer> entry) {
+		Package mod = (Package) entry.getModel().getValueAt(
+				entry.getIdentifier(), -1);
+
+		if (mod.isCorePackage() && !showCorePackages)
+			return false;
+
+		if (!mod.isValidOnPlatform() && !showInvalidPlatform)
+			return false;
+
+		boolean result = true;
+		if (type != null)
+			result &= mod.getTypes().contains(type);
+		switch (downloadState) {
+			case 1:
+				result &= !mod.isLocalAvailable();
+				break;
+			case 2:
+				result &= mod.isLocalAvailable();
+				break;
+		}
+		if (filterString != null && filterString.length() > 1) {
+			switch (filterTo) {
+				case ALL:
+					result &= mod.getName().toLowerCase()
+							.contains(filterString)
+							|| mod.getCreator().toLowerCase()
+									.contains(filterString)
+							|| mod.getDescription().toLowerCase()
+									.contains(filterString);
+					break;
+				case CREATOR:
+					result &= mod.getCreator().toLowerCase()
+							.contains(filterString);
+					break;
+				case DESCRIPTION:
+					result &= mod.getDescription().toLowerCase()
+							.contains(filterString);
+					break;
+				case NAME:
+					result &= mod.getName().toLowerCase()
+							.contains(filterString);
+					break;
+			}
+		}
+
+		return result;
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/modtable/ModTableModel.java	(revision 722)
@@ -0,0 +1,309 @@
+package net.oni2.aeinstaller.gui.modtable;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.ResourceBundle;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableColumn;
+
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.PackageManager;
+import net.oni2.aeinstaller.gui.modtable.ModTable.ETableContentType;
+
+/**
+ * @author Christian Illy
+ */
+public class ModTableModel extends AbstractTableModel {
+
+	private static final long serialVersionUID = -8278155705802697354L;
+
+	private ResourceBundle bundle = ResourceBundle
+			.getBundle("net.oni2.aeinstaller.localization.ModTable");
+
+	private Vector<Package> items = new Vector<Package>();
+	private Vector<Boolean> install = new Vector<Boolean>();
+
+	private HashSet<ModInstallSelectionListener> listeners = new HashSet<ModInstallSelectionListener>();
+
+	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+
+	private ETableContentType contentType = ETableContentType.MODS;
+
+	/**
+	 * Create a new model
+	 * 
+	 * @param contentType
+	 *            Content type to show
+	 */
+	public ModTableModel(ETableContentType contentType) {
+		this.contentType = contentType;
+	}
+
+	@Override
+	public Object getValueAt(int row, int col) {
+		Package mod = items.get(row);
+		switch (col) {
+			case -1:
+				return mod;
+			case 0:
+				return install.get(row);
+			case 1:
+				return mod.getName();
+			case 2:
+				return mod.getPackageNumberString();
+			case 3:
+				return mod.getCreator();
+			case 4:
+				String res = "";
+				res += (mod.isInstalled() ? "I" : "_");
+				res += (mod.isLocalAvailable() && mod.isNewerAvailable() ? "U"
+						: "_");
+				res += (mod.isLocalAvailable() ? "D" : "_");
+				return res;
+			case 5:
+				if (mod.getNode() != null)
+					return sdf.format(new Date(
+							mod.getNode().getCreated() * 1000));
+				else
+					return null;
+			case 6:
+				if (mod.getFile() != null)
+					return sdf.format(new Date(
+							mod.getFile().getTimestamp() * 1000));
+				else
+					return null;
+		}
+		return null;
+	}
+
+	@Override
+	public String getColumnName(int col) {
+		switch (col) {
+			case 0:
+				return bundle.getString("mod.install");
+			case 1:
+				return bundle.getString("mod.name");
+			case 2:
+				return bundle.getString("mod.package_number");
+			case 3:
+				return bundle.getString("mod.creator");
+			case 4:
+				return bundle.getString("mod.state");
+			case 5:
+				return bundle.getString("mod.added");
+			case 6:
+				return bundle.getString("mod.date");
+		}
+		return null;
+	}
+
+	@Override
+	public int getRowCount() {
+		return items.size();
+	}
+
+	@Override
+	public int getColumnCount() {
+		return 7;
+	}
+
+	@Override
+	public Class<?> getColumnClass(int col) {
+		switch (col) {
+			case 0:
+				return Boolean.class;
+			case 1:
+				return String.class;
+			case 2:
+				return String.class;
+			case 3:
+				return String.class;
+			case 4:
+				return String.class;
+			case 5:
+				return String.class;
+			case 6:
+				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:
+				w = 70;
+				col.setPreferredWidth(w);
+				col.setMinWidth(w);
+				col.setMaxWidth(w);
+				break;
+			case 1:
+				col.setPreferredWidth(150);
+				break;
+			case 2:
+				w = 60;
+				col.setPreferredWidth(w);
+				col.setMinWidth(w);
+				col.setMaxWidth(w);
+				break;
+			case 3:
+				col.setPreferredWidth(90);
+				break;
+			case 4:
+				w = 60;
+				col.setPreferredWidth(w);
+				col.setMinWidth(w);
+				col.setMaxWidth(w);
+				break;
+			case 5:
+				w = 100;
+				col.setPreferredWidth(w);
+				col.setMinWidth(w);
+				col.setMaxWidth(w);
+				break;
+			case 6:
+				w = 100;
+				col.setPreferredWidth(w);
+				col.setMinWidth(w);
+				col.setMaxWidth(w);
+				break;
+		}
+	}
+
+	/**
+	 * Reload the nodes data after an update to the cache
+	 */
+	public void reloadData() {
+		items.clear();
+		switch (contentType) {
+			case MODS:
+				items.addAll(PackageManager.getInstance()
+						.getModsValidAndNotCore());
+				break;
+			case TOOLS:
+				items.addAll(PackageManager.getInstance().getTools());
+				break;
+			case CORE:
+				items.addAll(PackageManager.getInstance().getCoreTools());
+				items.addAll(PackageManager.getInstance().getCoreMods());
+				break;
+		}
+		revertSelection();
+	}
+
+	/**
+	 * Revert the selection to the mods that are currently installed
+	 */
+	public void revertSelection() {
+		install.clear();
+		int count = 0;
+		for (int i = 0; i < items.size(); i++) {
+			boolean installed = items.get(i).isInstalled();
+			install.add(i, installed);
+			if (installed)
+				count++;
+		}
+		notifyDownloadSize(0, count);
+		fireTableDataChanged();
+	}
+
+	/**
+	 * Reload the selection after a config was loaded
+	 * 
+	 * @param config
+	 *            Config to load
+	 */
+	public void reloadSelection(File config) {
+		if (contentType == ETableContentType.MODS) {
+			Vector<Integer> selected = PackageManager.getInstance()
+					.loadModSelection(config);
+			install.clear();
+			for (int i = 0; i < items.size(); i++) {
+				install.add(i,
+						selected.contains(items.get(i).getPackageNumber()));
+			}
+			fireTableDataChanged();
+		}
+	}
+
+	/**
+	 * Get the items vector
+	 * 
+	 * @return Items
+	 */
+	public Vector<Package> getItems() {
+		return items;
+	}
+
+	/**
+	 * @return Mods selected for installation
+	 */
+	public TreeSet<Package> getSelectedMods() {
+		TreeSet<Package> res = new TreeSet<Package>();
+		for (int i = 0; i < items.size(); i++) {
+			if (install.get(i))
+				res.add(items.get(i));
+		}
+		return res;
+	}
+
+	@Override
+	public boolean isCellEditable(int rowIndex, int columnIndex) {
+		return columnIndex == 0;
+	}
+
+	private void notifyDownloadSize(int size, int count) {
+		for (ModInstallSelectionListener dsl : listeners)
+			dsl.modInstallSelectionChanged(size, count);
+	}
+
+	@Override
+	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+		super.setValueAt(aValue, rowIndex, columnIndex);
+		if (columnIndex == 0) {
+			install.set(rowIndex, (Boolean) aValue);
+
+			int size = 0;
+			int count = 0;
+			for (int i = 0; i < items.size(); i++) {
+				if (install.get(i)) {
+					count++;
+					Package m = items.get(i);
+					if (!m.isLocalAvailable())
+						size += m.getZipSize();
+				}
+			}
+			notifyDownloadSize(size, count);
+		}
+	}
+
+	/**
+	 * @param lis
+	 *            Listener to receive download size changed events
+	 */
+	public void addDownloadSizeListener(ModInstallSelectionListener lis) {
+		listeners.add(lis);
+	}
+
+	/**
+	 * @param lis
+	 *            Listener to no longer receive download size changed events
+	 */
+	public void removeDownloadSizeListener(ModInstallSelectionListener lis) {
+		listeners.remove(lis);
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/packageinfobox/PackageInfoBox.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/packageinfobox/PackageInfoBox.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/packageinfobox/PackageInfoBox.java	(revision 722)
@@ -0,0 +1,79 @@
+package net.oni2.aeinstaller.gui.packageinfobox;
+
+import java.awt.Insets;
+import java.util.ResourceBundle;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.oni2.aeinstaller.backend.SizeFormatter;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.Type;
+import net.oni2.aeinstaller.gui.HTMLLinkLabel;
+
+import org.javabuilders.BuildResult;
+import org.javabuilders.swing.SwingJavaBuilder;
+
+/**
+ * @author Christian Illy
+ */
+public class PackageInfoBox extends JPanel {
+	private static final long serialVersionUID = 233098603653577693L;
+
+	private ResourceBundle bundle = ResourceBundle
+			.getBundle("net.oni2.aeinstaller.localization."
+					+ getClass().getSimpleName());
+	@SuppressWarnings("unused")
+	private BuildResult result = SwingJavaBuilder.build(this, bundle);
+
+	private JLabel lblTitleVal;
+	private JLabel lblCreatorVal;
+	private JLabel lblTypesVal;
+	private JLabel lblPlatformVal;
+	private JLabel lblPackageNumberVal;
+	private JLabel lblVersionNumberVal;
+	private HTMLLinkLabel lblDescriptionVal;
+	private JLabel lblDownloadSizeVal;
+
+	/**
+	 * Create a package info box
+	 */
+	public PackageInfoBox() {
+		super();
+		lblDescriptionVal.setMargin(new Insets(-15, 0, 0, 0));
+	}
+
+	/**
+	 * @param mod
+	 *            Mod info to show
+	 */
+	public void updateInfo(Package mod) {
+		lblTitleVal.setText("");
+		lblCreatorVal.setText("");
+		lblDescriptionVal.setText("");
+		lblTypesVal.setText("");
+		lblPlatformVal.setText("");
+		lblPackageNumberVal.setText("");
+		lblVersionNumberVal.setText("");
+		lblDownloadSizeVal.setText("");
+
+		if (mod != null) {
+			lblTitleVal.setText(mod.getName());
+			lblCreatorVal.setText(mod.getCreator());
+			lblDescriptionVal.setText(mod.getDescription());
+
+			String types = "";
+			for (Type t : mod.getTypes()) {
+				if (types.length() > 0)
+					types += ", ";
+				types += t.getName();
+			}
+			lblTypesVal.setText(types);
+			lblPlatformVal.setText(mod.getPlatform().toString());
+			lblPackageNumberVal.setText(mod.getPackageNumberString());
+			lblVersionNumberVal.setText(mod.getVersion());
+			lblDownloadSizeVal
+					.setText(SizeFormatter.format(mod.getZipSize(), 3));
+		}
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/packageinfobox/PackageInfoBox.yml
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/packageinfobox/PackageInfoBox.yml	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/packageinfobox/PackageInfoBox.yml	(revision 722)
@@ -0,0 +1,30 @@
+JPanel:
+  name: panel
+  content:
+    - JLabel(name=lblTitle, text=lblTitle.text)
+    - JLabel(name=lblTitleVal)
+    - JLabel(name=lblCreator, text=lblCreator.text)
+    - JLabel(name=lblCreatorVal)
+    - JLabel(name=lblTypes, text=lblTypes.text)
+    - JLabel(name=lblTypesVal)
+    - JLabel(name=lblPlatform, text=lblPlatform.text)
+    - JLabel(name=lblPlatformVal)
+    - JLabel(name=lblPackageNumber, text=lblPackageNumber.text)
+    - JLabel(name=lblPackageNumberVal)
+    - JLabel(name=lblVersionNumber, text=lblVersionNumber.text)
+    - JLabel(name=lblVersionNumberVal)
+    - JLabel(name=lblDescription, text=lblDescription.text)
+    - JScrollPane(name=scrollDescription, vScrollBar=always, hScrollBar=asNeeded):
+        HTMLLinkLabel(name=lblDescriptionVal)
+    - JLabel(name=lblDownloadSize, text=lblDownloadSize.text)
+    - JLabel(name=lblDownloadSizeVal)
+    - MigLayout: |
+         [min]             [grow]
+         >lblTitle         lblTitleVal         [min]
+         >lblCreator       lblCreatorVal       [min]
+         >lblTypes         lblTypesVal         [min]
+         >lblPlatform      lblPlatformVal      [min]
+         >lblPackageNumber lblPackageNumberVal [min]
+         >lblVersionNumber lblVersionNumberVal [min]
+         >^lblDescription  scrollDescription   [grow]
+         >lblDownloadSize  lblDownloadSizeVal  [min]
Index: java/installer2/src/net/oni2/aeinstaller/gui/settings/LaFComboModel.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/settings/LaFComboModel.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/settings/LaFComboModel.java	(revision 722)
@@ -0,0 +1,98 @@
+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.SettingsManager;
+
+/**
+ * Comboboxmodel for Look and Feel selection
+ * 
+ * @author Christian Illy
+ */
+public class LaFComboModel implements ComboBoxModel {
+
+	private Vector<LookAndFeelInfo> items;
+	private HashSet<ListDataListener> listeners;
+	private 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 = SettingsManager.getInstance().get("lookandfeel",
+				UIManager.getLookAndFeel().getClass().getName());
+
+		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: java/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.java	(revision 722)
@@ -0,0 +1,103 @@
+package net.oni2.aeinstaller.gui.settings;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.KeyStroke;
+import javax.swing.UIManager;
+
+import net.oni2.SettingsManager;
+
+import org.javabuilders.BuildResult;
+import org.javabuilders.swing.SwingJavaBuilder;
+
+/**
+ * @author Christian Illy
+ */
+public class SettingsDialog extends JDialog {
+	private static final long serialVersionUID = -5719515325671846620L;
+
+	private ResourceBundle bundle = ResourceBundle
+			.getBundle("net.oni2.aeinstaller.localization."
+					+ getClass().getSimpleName());
+	@SuppressWarnings("unused")
+	private BuildResult result = SwingJavaBuilder.build(this, bundle);
+
+	private JComboBox cmbLaF;
+	private LaFComboModel laFModel;
+
+	private JCheckBox chkNotifyOnStart;
+	private JCheckBox chkNotifyDepsAfterInstall;
+	private JCheckBox chkCopyIntro;
+	private JCheckBox chkCopyOutro;
+
+	/**
+	 * Open the settings
+	 */
+	public SettingsDialog() {
+		setResizable(false);
+
+		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();
+
+		setLocationRelativeTo(null);
+	}
+
+	private void initValues() {
+		SettingsManager set = SettingsManager.getInstance();
+
+		laFModel = new LaFComboModel();
+		cmbLaF.setModel(laFModel);
+
+		chkNotifyOnStart.setSelected(set.get("notifyupdates", true));
+		chkNotifyDepsAfterInstall.setSelected(set.get("notifyDepsAfterInstall", false));
+		chkCopyIntro.setSelected(set.get("copyintro", false));
+		chkCopyOutro.setSelected(set.get("copyoutro", true));
+	}
+
+	@SuppressWarnings("unused")
+	private boolean save() {
+		SettingsManager set = SettingsManager.getInstance();
+
+		set.put("notifyupdates", chkNotifyOnStart.isSelected());
+		set.put("notifyDepsAfterInstall", chkNotifyDepsAfterInstall.isSelected());
+		set.put("copyintro", chkCopyIntro.isSelected());
+		set.put("copyoutro", chkCopyOutro.isSelected());
+
+		String oldLaf = set.get("lookandfeel", UIManager.getLookAndFeel()
+				.getClass().getName());
+		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: java/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.yml
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.yml	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/settings/SettingsDialog.yml	(revision 722)
@@ -0,0 +1,38 @@
+JDialog:
+  name: frame
+  title: frame.title
+  size: packed
+  locationRelativeTo: null
+  defaultCloseOperation: disposeOnClose
+  iconImage: img.ae
+  modalityType: applicationModal
+  content:
+    - JButton(name=btnOk, text=btnOk, onAction=[save,dispose])
+    - JButton(name=btnCancel, text=btnCancel, onAction=[dispose])
+    - JPanel(name=panCommon, groupTitle=panCommon):
+      - JLabel(name=lblNotifyOnStart, text=lblNotifyOnStart)
+      - JCheckBox(name=chkNotifyOnStart)
+      - JLabel(name=lblNotifyDepsAfterInstall, text=lblNotifyDepsAfterInstall, toolTipText=lblNotifyDepsAfterInstall.tooltip)
+      - JCheckBox(name=chkNotifyDepsAfterInstall, toolTipText=lblNotifyDepsAfterInstall.tooltip)
+      - JLabel(name=lblCopyIntro, text=lblCopyIntro)
+      - JCheckBox(name=chkCopyIntro)
+      - JLabel(name=lblCopyOutro, text=lblCopyOutro)
+      - JCheckBox(name=chkCopyOutro)
+      - MigLayout: |
+           [min]                       [grow]
+           >lblNotifyOnStart           chkNotifyOnStart           [pref]
+           >lblNotifyDepsAfterInstall  chkNotifyDepsAfterInstall  [pref]
+           >lblCopyIntro               chkCopyIntro               [pref]
+           >lblCopyOutro               chkCopyOutro               [pref]
+    - JPanel(name=panUI, groupTitle=panUI):
+      - JLabel(name=lblLaF, text=lblLaF)
+      - JComboBox(name=cmbLaF, minimumSize=150x10)
+      - MigLayout: |
+           [min]        [grow]
+           >lblLaF      cmbLaF    [pref]
+    - MigLayout: |
+         [grow]
+         panCommon               [pref]
+         panUI                   [pref]
+         >btnOk+*=1,btnCancel=1  [min]
+  
Index: java/installer2/src/net/oni2/aeinstaller/gui/toolmanager/ToolManager.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/toolmanager/ToolManager.java	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/toolmanager/ToolManager.java	(revision 722)
@@ -0,0 +1,158 @@
+package net.oni2.aeinstaller.gui.toolmanager;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ResourceBundle;
+import java.util.TreeSet;
+
+import javax.swing.AbstractAction;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.KeyStroke;
+
+import net.oni2.SettingsManager;
+import net.oni2.aeinstaller.backend.oni.Installer;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.gui.downloadwindow.Downloader;
+import net.oni2.aeinstaller.gui.modtable.ModSelectionListener;
+import net.oni2.aeinstaller.gui.modtable.ModTable;
+import net.oni2.aeinstaller.gui.modtable.ModTable.ETableContentType;
+import net.oni2.aeinstaller.gui.packageinfobox.PackageInfoBox;
+
+import org.javabuilders.BuildResult;
+import org.javabuilders.swing.SwingJavaBuilder;
+
+/**
+ * @author Christian Illy
+ */
+public class ToolManager extends JDialog implements ModSelectionListener {
+	private static final long serialVersionUID = 343221630538866384L;
+
+	private ResourceBundle bundle = ResourceBundle
+			.getBundle("net.oni2.aeinstaller.localization."
+					+ getClass().getSimpleName());
+	@SuppressWarnings("unused")
+	private BuildResult result = SwingJavaBuilder.build(this, bundle);
+
+	private JSplitPane contents;
+
+	private JScrollPane scrollTools;
+	private ModTable tblTools;
+
+	private PackageInfoBox pkgInfo;
+	private JButton btnInstall;
+
+	private Icon icoInstall = null;
+	private Icon icoUninstall = null;
+
+	private Package selectedPackage = null;
+
+	/**
+	 * Open the dialog
+	 */
+	public ToolManager() {
+		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);
+
+		contents.setDividerLocation(SettingsManager.getInstance().get("win_tools_divloc", 550));
+		contents.setResizeWeight(0.4);
+
+		tblTools = new ModTable(ETableContentType.TOOLS);
+		tblTools.reloadData();
+		scrollTools.setViewportView(tblTools);
+
+		tblTools.addModSelectionListener(this);
+
+		icoInstall = new ImageIcon(getClass().getResource(
+				SwingJavaBuilder.getConfig().getResource("img.install")));
+		icoUninstall = new ImageIcon(getClass().getResource(
+				SwingJavaBuilder.getConfig().getResource("img.uninstall")));
+
+		setSize(SettingsManager.getInstance().get("win_tools_width", 950), SettingsManager.getInstance().get("win_tools_height", 600));
+		setLocationRelativeTo(null);
+	}
+
+	@SuppressWarnings("unused")
+	private void install() {
+		if (selectedPackage != null) {
+			if (selectedPackage.isInstalled()) {
+				TreeSet<Package> tools = new TreeSet<Package>();
+				tools.add(selectedPackage);
+				Installer.installTools(tools, true);
+			} else {
+				if (!selectedPackage.isLocalAvailable()) {
+					if (SettingsManager.getInstance().isOfflineMode()) {
+						JOptionPane.showMessageDialog(this,
+								bundle.getString("offlineMode.text"),
+								bundle.getString("offlineMode.title"),
+								JOptionPane.WARNING_MESSAGE);
+						return;
+					}
+
+					TreeSet<Package> toDownload = new TreeSet<Package>();
+					toDownload.add(selectedPackage);
+
+					Downloader dl = new Downloader(toDownload, null);
+					try {
+						dl.setVisible(true);
+						if (!dl.isFinished())
+							return;
+					} finally {
+						dl.dispose();
+					}
+				}
+
+				TreeSet<Package> tools = new TreeSet<Package>();
+				tools.add(selectedPackage);
+				Installer.installTools(tools, false);
+			}
+		}
+		modSelectionChanged(tblTools, selectedPackage);
+	}
+
+	@Override
+	public void modSelectionChanged(ModTable source, Package mod) {
+		selectedPackage = mod;
+
+		pkgInfo.updateInfo(mod);
+		if (mod != null) {
+			btnInstall.setEnabled(true);
+			if (mod.isInstalled()) {
+				btnInstall.setText(bundle.getString("btnInstall.un.text"));
+				btnInstall.setToolTipText(bundle
+						.getString("btnInstall.un.tooltip"));
+				btnInstall.setIcon(icoUninstall);
+			} else {
+				btnInstall.setText(bundle.getString("btnInstall.text"));
+				btnInstall.setToolTipText(bundle
+						.getString("btnInstall.tooltip"));
+				btnInstall.setIcon(icoInstall);
+			}
+		}
+	}
+	
+	@SuppressWarnings("unused")
+	private void closing() {
+		SettingsManager.getInstance().put("win_tools_divloc", contents.getDividerLocation());
+		SettingsManager.getInstance().put("win_tools_width", getWidth());
+		SettingsManager.getInstance().put("win_tools_height", getHeight());
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/gui/toolmanager/ToolManager.yml
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/gui/toolmanager/ToolManager.yml	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/gui/toolmanager/ToolManager.yml	(revision 722)
@@ -0,0 +1,27 @@
+JDialog:
+  name: frame
+  title: frame.title
+  size: 950x600
+  locationRelativeTo: null
+  defaultCloseOperation: disposeOnClose
+  iconImage: img.ae
+  modalityType: applicationModal
+  onWindowClosing: [closing]
+  content:
+    - JSplitPane(name=contents, orientation=horizontalSplit, continuousLayout=true):
+        - JPanel(name=panTools):
+            - JScrollPane(name=scrollTools, vScrollBar=always, hScrollBar=never)
+            - MigLayout: |
+                 [grow]
+                 scrollTools   [grow]
+        - JPanel(name=panInfo):
+            - PackageInfoBox(name=pkgInfo)
+            - JButton(name=btnInstall, icon=img.install, text=btnInstall.text, toolTipText=btnInstall.tooltip, enabled=false, onAction=[install])
+            - MigLayout: |
+                 [grow]
+                 pkgInfo        [grow]
+                 >btnInstall<   [min]
+    - MigLayout: |
+         [grow]
+         contents [grow]
+  
Index: java/installer2/src/net/oni2/aeinstaller/localization/CorePackagesDialog.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/localization/CorePackagesDialog.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/localization/CorePackagesDialog.properties	(revision 722)
@@ -0,0 +1,1 @@
+frame.title=AE Installer: Core packages
Index: java/installer2/src/net/oni2/aeinstaller/localization/Downloader.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/localization/Downloader.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/localization/Downloader.properties	(revision 722)
@@ -0,0 +1,15 @@
+frame.title=AE Installer: Downloading mods
+
+btnAbort.title=Abort
+lblName.title=Currently downloading:
+lblIsDep.title= (dependency)
+lblElapsed.title=Elapsed:
+lblRemaining.title=Remaining:
+lblDownloaded.title=Downloaded:
+lblTotal.title=Total:
+lblRate.title=Rate:
+
+abort.text=If you abort the download now the installation of mods will also be aborted.\nDo you want to abort now?
+abort.title=Really abort?
+
+unpacking=none - unpacking files
Index: java/installer2/src/net/oni2/aeinstaller/localization/Global.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/localization/Global.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/localization/Global.properties	(revision 722)
@@ -0,0 +1,11 @@
+offlineMode.title=Offline mode
+offlineMode.text=AEI is running in offline mode.\nNo updates or downloads of new mods are possible.\nPlease restart AEI when you have internet connection to update or download new packages.
+
+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
+
+dotNetMissing.title=.NET is not installed
+dotNetMissing.text=.NET, which is required to use this tool, is not installed on this machine.<br>Please download and install it:<br>%1
+
+offlineModeStartup.title=Offline mode
+offlineModeStartup.text=Connection to the ModDepot could not be established.\nAEI will run in offline mode.\nUpdates or installation of mods not already downloaded will not be possible.
Index: java/installer2/src/net/oni2/aeinstaller/localization/MainWin.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/localization/MainWin.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/localization/MainWin.properties	(revision 722)
@@ -0,0 +1,99 @@
+# Main Frame
+buttonExit.title=Exit
+
+menu.main=&Main
+menu.settings=&Settings
+menu.settingsTooltip=Settings
+menu.about=&About
+menu.aboutTooltip=About
+menu.exit=&Exit\tCtrl+Q
+menu.exitTooltip=Exit
+
+menu.file=&File
+menu.runOniFull=Run &Oni (fullscreen)
+menu.runOniFullTooltip=Run Oni in fullscreen mode
+menu.runOniWin=Run Oni (&window)
+menu.runOniWinTooltip=Run Oni in windowed mode
+menu.openEditionFolder=Open Edition folder
+menu.openEditionFolderTooltip=Open the Edition folder in the file explorer
+menu.loadConfig=&Load mod selection...
+menu.loadConfigTooltip=Load mod selection from a file
+menu.saveConfig=&Save current mod selection...
+menu.saveConfigTooltip=Save current mod selection to a file
+
+menu.corePackages=&Show core packages
+menu.corePackagesTooltip=View which core packages are used
+menu.reglobalize=&Rebuild Core Data
+menu.reglobalizeTooltip=Rebuild Core Data
+menu.update=&Check for updates
+menu.updateTooltip=Check for updates to already downloaded packages on the Depot
+
+menu.tools=&Tools
+menu.manageTools=&Manage Tools
+menu.manageToolsTooltip=Install/Remove Tools
+
+btnUnSelectAll.text=(Un)select all
+btnRevertSelection.text=Revert selection
+btnRevertSelection.tooltip=Select mods which are currently installed
+btnInstall.text=&Install
+btnInstall.tooltip=Install selected mods
+lblModTypes.text=Mod type: 
+lblShowOnly.text=Show only:
+radAll.text=All
+radOnline.text=Online
+radLocal.text=Downloaded
+lblShowFilter.text=Filter:
+lblSelectedMods.text=Mods selected for installation:
+lblDownloadSize.text=Size of files to download: 
+
+updateDepot.title=Updating Mod Depot cache
+
+rebuildCore.title=Rebuild Edition core data
+rebuildCore.text=Rebuilding the core data of the Edition will take some time.\nAlso this normally should never be needed.\nDo you really want to continue? 
+
+installDependencies.title=Unmet dependencies
+installDependencies.text=There are unmet dependencies for some mods.\n\nFollowing mods are already downloaded and will be selected:\n%s\n\n\
+These mods will have to be additionally downloaded:\n%s\n\n\
+Size of files to be downloaded: %s.\n\nContinue?
+installDependencies.none=<None>
+installIncompatibilities.title=Incompatible mods
+installIncompatibilities.text=The following mods are incompatible:\n\n%s\n\nInstallation will be aborted.
+installDone.title=Installation done
+installDone.text=You can now play AE Oni.
+installDoneDeps.text=In addition to the manually selected mods the following\nwere installed because they were depended upon:\n%s\n\nYou can now play AE Oni.
+
+updatesAvailable.title=Updates available
+updatesAvailable.text=The following mods and tools have newer versions on the Depot.<br>Check the packages you want to be updated.
+updatesAvailableSize.text=Size of files to download is %s.<br>Update now?
+updatesNotAvailable.title=No updates available
+updatesNotAvailable.text=There are currently no updates to the mods and tools you have downloaded.
+checkOnStartup.text=Check for updates at startup
+
+noOniSplit.title=OniSplit not available
+noOniSplit.text=The Edition is not yet initialized and OniSplit is missing (offline mode?).\nCan not resume from here, exiting!
+askInitialize.title=Initialize Edition
+askInitialize.text=The Edition is not yet initialized.\nIf you do not initialize now the installer will exit.\nInitialize Edition core now?
+
+initializingEdition.title=Initializing Edition core
+installing.title=Installing mods
+checkCorePackages.title=Checking for core packages
+coreToolsInstall.title=Installing core tools
+corePackagesUpdated.title=Updated core packages
+corePackagesUpdated.text=The following core packages have been updated:%s
+
+doUpdate.title=Updating packages
+
+jreNotFound.text=This tool requires a JRE but it was not found.
+jreNotFound.title=No JRE found
+
+dotNetNotFound.text=This tool requires a .NET runtime but it was not found.
+dotNetNotFound.title=No .NET runtime found
+
+wineNotFound.text=Wine is required to run a Windows executable on Linux but it was not found.
+wineNotFound.title=Wine not found
+
+oniExeNotFound.text=Oni's executable was not found.
+oniExeNotFound.title=Oni not found
+
+exeNotFound.text=Executable was not found.
+exeNotFound.title=Exe not found
Index: java/installer2/src/net/oni2/aeinstaller/localization/ModTable.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/localization/ModTable.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/localization/ModTable.properties	(revision 722)
@@ -0,0 +1,29 @@
+mod.name=Name
+mod.package_number=Pkg#
+mod.install=Install
+mod.creator=Creator
+mod.state=State
+mod.added=Added
+mod.date=Last change
+
+filterTo.ALL=All
+filterTo.NAME=Mod name
+filterTo.CREATOR=Creator
+filterTo.DESCRIPTION=Description
+
+state.installed=Is <b>I</b>nstalled
+state.updatable=<b>U</b>pdate available
+state.downloaded=Is <b>D</b>ownloaded
+yes=Yes
+no=No
+
+openModFolder.text=Open mod folder
+openDepotPage.text=Open Depot page in browser
+downloadPackage.text=(Re)Download the package
+deletePackage.text=Delete package locally
+
+deletePackageLocalOnly.title=Local only package
+deletePackageLocalOnly.text=This package seems not to be on the Depot.\nFor security reasons deleting this package through the AEI is forbidden.
+
+deletePackageConfirm.title=Delete package folder
+deletePackageConfirm.text=Do you really want to delete the downloaded package?
Index: java/installer2/src/net/oni2/aeinstaller/localization/PackageInfoBox.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/localization/PackageInfoBox.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/localization/PackageInfoBox.properties	(revision 722)
@@ -0,0 +1,9 @@
+lblTitle.text=Name:
+lblCreator.text=Creator:
+lblTypes.text=Types:
+lblPlatform.text=Platform:
+lblPackageNumber.text=Package number:
+lblVersionNumber.text=Version number:
+lblFiles.text=Number of files:
+lblDescription.text=Description:
+lblDownloadSize.text=Download size:
Index: java/installer2/src/net/oni2/aeinstaller/localization/SettingsDialog.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/localization/SettingsDialog.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/localization/SettingsDialog.properties	(revision 722)
@@ -0,0 +1,22 @@
+frame.title=AE Installer: Settings
+btnOk=Save
+btnCancel=Cancel
+
+panUI=Look and feel
+lblLaF=GUI theme:
+
+panCommon=Common settings
+lblNotifyOnStart=Notify about updates on startup:
+lblNotifyDepsAfterInstall=Only notify about dependencies after installation:
+lblNotifyDepsAfterInstall.tooltip=<html>\
+Normally you are notified about unmet dependencies <b>before</b><br>\
+the installation continues so you can choose not to download<br>\
+these packages. If you select this option the installation<br>\
+process is not interrupted until it is finished and you are<br>\
+informed on what additional mods were installed to meet<br>\
+dependencies <b>afterwards</b>.</html>
+lblCopyIntro=Enable starting video on installation:
+lblCopyOutro=Enable ending video on installation:
+
+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: java/installer2/src/net/oni2/aeinstaller/localization/ToolManager.properties
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/localization/ToolManager.properties	(revision 722)
+++ java/installer2/src/net/oni2/aeinstaller/localization/ToolManager.properties	(revision 722)
@@ -0,0 +1,6 @@
+frame.title=AE Installer: Tool Manager
+
+btnInstall.text=Install
+btnInstall.tooltip=Install this tool
+btnInstall.un.text=Uninstall
+btnInstall.un.tooltip=Uninstall this tool
