Index: AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 633)
+++ AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 634)
@@ -1,2 +1,2 @@
 appname=AE Installer 2
-appversion=0.92a
+appversion=0.92b
Index: AE/installer2/src/net/oni2/aeinstaller/AEInstaller2.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/AEInstaller2.java	(revision 633)
+++ AE/installer2/src/net/oni2/aeinstaller/AEInstaller2.java	(revision 634)
@@ -74,8 +74,8 @@
 		if (!debug) {
 			try {
-				System.setOut(new PrintStream(new File(Paths.getPrefsPath(),
-						"aei_output.log")));
-				System.setErr(new PrintStream(new File(Paths.getPrefsPath(),
-						"aei_error.log")));
+				PrintStream ps = new PrintStream(new File(Paths.getPrefsPath(),
+						"aei_output.log"));
+				System.setOut(ps);
+				System.setErr(ps);
 			} catch (FileNotFoundException e1) {
 				e1.printStackTrace();
@@ -123,4 +123,5 @@
 		JFrame.setDefaultLookAndFeelDecorated(true);
 
+		System.out.println(basicBundle.getString("appname") + " " + basicBundle.getString("appversion"));
 		System.out.println("JarPath:   " + Paths.getInstallerPath());
 		System.out.println("PrefsPath: " + Paths.getPrefsPath());
@@ -143,4 +144,5 @@
 				+ SizeFormatter.format(Paths.getInstallerPath()
 						.getUsableSpace(), 3));
+		System.out.println();
 
 		if (!OniSplit.isDotNETInstalled()) {
@@ -161,6 +163,9 @@
 					dlUrl = "http://www.go-mono.com/mono-downloads/download.html";
 			}
-			hll.setText(startupBundle.getString("dotNetMissing.text").replaceAll(
-					"%1", String.format("<a href=\"%s\">%s</a>", dlUrl, dlUrl)));
+			hll.setText(startupBundle
+					.getString("dotNetMissing.text")
+					.replaceAll(
+							"%1",
+							String.format("<a href=\"%s\">%s</a>", dlUrl, dlUrl)));
 			JOptionPane.showMessageDialog(null, hll,
 					startupBundle.getString("dotNetMissing.title"),
Index: AE/installer2/src/net/oni2/aeinstaller/DepotPackageCheck.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/DepotPackageCheck.java	(revision 633)
+++ AE/installer2/src/net/oni2/aeinstaller/DepotPackageCheck.java	(revision 634)
@@ -4,5 +4,4 @@
 import java.util.HashSet;
 
-import net.oni2.aeinstaller.backend.depot.DepotCacheUpdateProgressListener;
 import net.oni2.aeinstaller.backend.depot.DepotConfig;
 import net.oni2.aeinstaller.backend.depot.DepotManager;
@@ -21,15 +20,5 @@
 	public static void main(String[] args) {
 		System.out.println("Reading Depot data:");
-		DepotManager.getInstance().updateInformation(false,
-				new DepotCacheUpdateProgressListener() {
-
-					@Override
-					public void cacheUpdateProgress(String stepName,
-							int current, int total) {
-						System.out.format("%50s", "");
-						System.out.format("\rStep %2d / %2d - %s", current,
-								total, stepName);
-					}
-				});
+		DepotManager.getInstance().updateInformation(false);
 		System.out.println("\nReading done");
 		System.out.println();
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotCacheUpdateProgressListener.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotCacheUpdateProgressListener.java	(revision 633)
+++ 	(revision )
@@ -1,16 +1,0 @@
-package net.oni2.aeinstaller.backend.depot;
-
-/**
- * @author Christian Illy
- */
-public interface DepotCacheUpdateProgressListener {
-	/**
-	 * @param stepName
-	 *            Current step name (e.g. "Taxonomy Vocabulary")
-	 * @param current
-	 *            Current progress in terms of requests
-	 * @param total
-	 *            Total requests to do
-	 */
-	public void cacheUpdateProgress(String stepName, int current, int total);
-}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java	(revision 633)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/depot/DepotManager.java	(revision 634)
@@ -1,14 +1,20 @@
 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.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 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.Settings;
 import net.oni2.aeinstaller.backend.Settings.Platform;
@@ -21,5 +27,5 @@
 import net.oni2.aeinstaller.backend.depot.model.TaxonomyVocabulary;
 import net.oni2.aeinstaller.backend.mods.ECompatiblePlatform;
-import net.oni2.aeinstaller.backend.network.DrupalJSONQuery;
+import net.oni2.aeinstaller.backend.network.FileDownloader;
 
 import org.apache.http.HttpResponse;
@@ -64,9 +70,6 @@
 	 * @param forceRefreshAll
 	 *            Force refreshing all data, even if it seems to be cached
-	 * @param listener
-	 *            Listener for update status
-	 */
-	public void updateInformation(boolean forceRefreshAll,
-			DepotCacheUpdateProgressListener listener) {
+	 */
+	public void updateInformation(boolean forceRefreshAll) {
 		taxonomyTerms.clear();
 		taxonomyVocabulary.clear();
@@ -77,52 +80,44 @@
 
 		try {
-			JSONArray ja;
-			JSONObject jo;
-
-			ja = DrupalJSONQuery
-					.executeQuery("http://mods.oni2.net/jsoncache/vocabulary.json");
-			for (int i = 0; i < ja.length(); i++) {
-				jo = ja.getJSONObject(i);
-				TaxonomyVocabulary tv = new TaxonomyVocabulary(jo);
-				taxonomyVocabulary.put(tv.getVid(), tv);
+			java.io.File zipName = new java.io.File(Paths.getDownloadPath(),
+					"jsoncache.zip");
+			FileDownloader fd = new FileDownloader(DepotConfig.getDepotUrl()
+					+ "jsoncache/jsoncache.zip", zipName);
+			fd.start();
+			while (fd.getState() != net.oni2.aeinstaller.backend.network.FileDownloader.EState.FINISHED) {
+				Thread.sleep(50);
 			}
 
-			ja = DrupalJSONQuery
-					.executeQuery("http://mods.oni2.net/jsoncache/terms.json");
-			for (int i = 0; i < ja.length(); i++) {
-				jo = ja.getJSONObject(i);
-				TaxonomyTerm tt = new TaxonomyTerm(jo);
-				taxonomyTerms.put(tt.getTid(), tt);
-			}
-
-			ja = DrupalJSONQuery
-					.executeQuery("http://mods.oni2.net/jsoncache/nodes.json");
-			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);
-			}
-
-			ja = DrupalJSONQuery
-					.executeQuery("http://mods.oni2.net/jsoncache/files.json");
-			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);
+			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();
 			}
 
@@ -135,7 +130,63 @@
 		} catch (JSONException e) {
 			e.printStackTrace();
-		} catch (Exception e) {
-			System.err.println(e.getMessage());
-			e.printStackTrace();
+		} catch (IOException e1) {
+			e1.printStackTrace();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		}
+	}
+
+	private void initFiles(JSONArray ja) throws JSONException {
+		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 {
+		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 {
+		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 {
+		JSONObject jo;
+
+		for (int i = 0; i < ja.length(); i++) {
+			jo = ja.getJSONObject(i);
+			TaxonomyVocabulary tv = new TaxonomyVocabulary(jo);
+			taxonomyVocabulary.put(tv.getVid(), tv);
 		}
 	}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownload.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownload.java	(revision 633)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/download/ModDownload.java	(revision 634)
@@ -8,9 +8,9 @@
 import net.oni2.aeinstaller.backend.Paths;
 import net.oni2.aeinstaller.backend.mods.Mod;
+import net.oni2.aeinstaller.backend.mods.unpack.UnpackListener;
+import net.oni2.aeinstaller.backend.mods.unpack.Unpacker;
 import net.oni2.aeinstaller.backend.network.FileDownloadListener;
 import net.oni2.aeinstaller.backend.network.FileDownloader;
 import net.oni2.aeinstaller.backend.network.FileDownloader.EState;
-import net.oni2.aeinstaller.backend.unpack.UnpackListener;
-import net.oni2.aeinstaller.backend.unpack.Unpacker;
 
 /**
@@ -75,5 +75,5 @@
 		try {
 			downloader = new FileDownloader(mod.getFile().getUri_full(),
-					zipFile.getPath());
+					zipFile);
 			downloader.addListener(this);
 			unpacker = new Unpacker(zipFile, targetFolder, this);
@@ -166,5 +166,5 @@
 	@Override
 	public void statusUpdate(Unpacker source,
-			net.oni2.aeinstaller.backend.unpack.Unpacker.EState state) {
+			net.oni2.aeinstaller.backend.mods.unpack.Unpacker.EState state) {
 		switch (state) {
 			case INIT:
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/unpack/UnpackListener.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/unpack/UnpackListener.java	(revision 634)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/unpack/UnpackListener.java	(revision 634)
@@ -0,0 +1,18 @@
+package net.oni2.aeinstaller.backend.mods.unpack;
+
+import net.oni2.aeinstaller.backend.mods.unpack.Unpacker.EState;
+
+/**
+ * @author Christian Illy
+ */
+public interface UnpackListener {
+	/**
+	 * Called for progress changes within the unpacker
+	 * 
+	 * @param source
+	 *            Source of event
+	 * @param state
+	 *            Current state
+	 */
+	public void statusUpdate(Unpacker source, EState state);
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/unpack/Unpacker.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/unpack/Unpacker.java	(revision 634)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/unpack/Unpacker.java	(revision 634)
@@ -0,0 +1,189 @@
+package net.oni2.aeinstaller.backend.mods.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)) {
+									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: AE/installer2/src/net/oni2/aeinstaller/backend/network/DrupalJSONQuery.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/network/DrupalJSONQuery.java	(revision 633)
+++ 	(revision )
@@ -1,89 +1,0 @@
-package net.oni2.aeinstaller.backend.network;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.util.EntityUtils;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * @author Christian Illy
- */
-public class DrupalJSONQuery {
-
-	/**
-	 * Get the JSON array found at the given url
-	 * 
-	 * @param url
-	 *            URL to look at for the JSON data
-	 * @return JSON array of data
-	 * @throws Exception
-	 *             On HTTP status code <200 / >299
-	 */
-	public static JSONArray executeQuery(String url) throws Exception {
-		BufferedReader input = null;
-		HttpRequestBase httpQuery = null;
-
-		try {
-			DefaultHttpClient httpclient = new DefaultHttpClient();
-			httpQuery = new HttpGet(url);
-
-			HttpResponse response = httpclient.execute(httpQuery);
-
-			int code = response.getStatusLine().getStatusCode();
-			if ((code > 299) || (code < 200)) {
-				throw new Exception(String.format(
-						"Error fetching content (HTTP status code %d).", code));
-			}
-
-			HttpEntity entity = response.getEntity();
-
-			input = new BufferedReader(new InputStreamReader(
-					entity.getContent()));
-			StringBuffer json = new StringBuffer();
-
-			char data[] = new char[1024];
-			int dataRead;
-			while ((dataRead = input.read(data, 0, 1024)) != -1) {
-				json.append(data, 0, dataRead);
-			}
-
-			EntityUtils.consume(entity);
-
-			JSONArray jA = null;
-			if (json.charAt(0) == '{') {
-				jA = new JSONArray();
-				jA.put(new JSONObject(json.toString()));
-			} else
-				jA = new JSONArray(json.toString());
-
-			return jA;
-		} catch (JSONException e) {
-			e.printStackTrace();
-		} catch (UnsupportedEncodingException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		} finally {
-			if (httpQuery != null)
-				httpQuery.releaseConnection();
-			if (input != null) {
-				try {
-					input.close();
-				} catch (IOException e) {
-					e.printStackTrace();
-				}
-			}
-		}
-		return null;
-	}
-}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java	(revision 633)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/network/FileDownloader.java	(revision 634)
@@ -61,7 +61,7 @@
 	 *             If url could not be opened
 	 */
-	public FileDownloader(String url, String target) throws IOException {
+	public FileDownloader(String url, File target) throws IOException {
 		this.url = new URL(url);
-		this.target = new File(target);
+		this.target = target;
 
 		URLConnection connection = this.url.openConnection();
@@ -130,4 +130,11 @@
 				target.delete();
 		}
+	}
+
+	/**
+	 * @return current state
+	 */
+	public EState getState() {
+		return state;
 	}
 
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 633)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 634)
@@ -10,4 +10,5 @@
 import java.io.IOException;
 import java.net.URL;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -42,5 +43,4 @@
 import net.oni2.aeinstaller.backend.Settings.Platform;
 import net.oni2.aeinstaller.backend.SizeFormatter;
-import net.oni2.aeinstaller.backend.depot.DepotCacheUpdateProgressListener;
 import net.oni2.aeinstaller.backend.depot.DepotManager;
 import net.oni2.aeinstaller.backend.mods.Mod;
@@ -165,19 +165,14 @@
 	private void execDepotUpdate(final BackgroundEvent evt) {
 		if (!Settings.getInstance().isOfflineMode()) {
+			long start = new Date().getTime();
+
 			try {
-				DepotManager.getInstance().updateInformation(false,
-						new DepotCacheUpdateProgressListener() {
-
-							@Override
-							public void cacheUpdateProgress(String stepName,
-									int current, int total) {
-								evt.setProgressEnd(total);
-								evt.setProgressValue(current);
-								evt.setProgressMessage(stepName);
-							}
-						});
+				DepotManager.getInstance().updateInformation(false);
 			} catch (Exception e) {
 				e.printStackTrace();
 			}
+
+			System.out.println("Took: " + (new Date().getTime() - start)
+					+ " msec");
 		}
 
