Index: AE/installer2/setup_win/AEI.iss
===================================================================
--- AE/installer2/setup_win/AEI.iss	(revision 622)
+++ AE/installer2/setup_win/AEI.iss	(revision 623)
@@ -1,4 +1,4 @@
 #define AppId "{{B67333BB-1CF9-4EFD-A40B-E25B5CB4C8A7}}"
-#define AppVersion "0.80"
+#define AppVersion "0.81"
 #define AppLongName "Anniversary Edition of Oni"
 #define AppShortName "AEInstaller"
Index: AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 622)
+++ AE/installer2/src/net/oni2/aeinstaller/AEInstaller.properties	(revision 623)
@@ -1,4 +1,4 @@
 appname=AE Installer 2
-appversion=0.82
+appversion=0.83
 
 invalidPath.title=Wrong directory
Index: AE/installer2/src/net/oni2/aeinstaller/Images.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/Images.properties	(revision 622)
+++ AE/installer2/src/net/oni2/aeinstaller/Images.properties	(revision 623)
@@ -32,2 +32,3 @@
 img.ae=/net/oni2/aeinstaller/images/AElogo.png
 img.oni=/net/oni2/aeinstaller/images/oni.png
+img.transparent=/net/oni2/aeinstaller/images/transparent.png
Index: AE/installer2/src/net/oni2/aeinstaller/backend/AppExecution.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/AppExecution.java	(revision 623)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/AppExecution.java	(revision 623)
@@ -0,0 +1,62 @@
+package net.oni2.aeinstaller.backend;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * @author Christian Illy
+ */
+public class AppExecution {
+
+	/**
+	 * Execute a short running application and wait for termination
+	 * 
+	 * @param cmdLine
+	 *            List of command and arguments
+	 * @return List of output lines
+	 * @throws IOException
+	 *             Exc
+	 */
+	public static Vector<String> executeAndWait(List<String> cmdLine)
+			throws IOException {
+		ProcessBuilder pb = new ProcessBuilder(cmdLine);
+		pb.redirectErrorStream(true);
+		Process proc = pb.start();
+
+		InputStream is = proc.getInputStream();
+		InputStreamReader isr = new InputStreamReader(is);
+		BufferedReader br = new BufferedReader(isr);
+
+		String line;
+		Vector<String> lines = new Vector<String>();
+
+		while ((line = br.readLine()) != null) {
+			lines.add(line);
+		}
+		return lines;
+	}
+
+	/**
+	 * Execute an app without waiting
+	 * 
+	 * @param cmd
+	 *            Command and parameters
+	 * @param workingDirectory
+	 *            Working directory of app
+	 */
+	public static void execute(List<String> cmd, File workingDirectory) {
+		try {
+			ProcessBuilder pb = new ProcessBuilder(cmd);
+			pb.directory(workingDirectory);
+			pb.start();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/QuickAppExecution.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/QuickAppExecution.java	(revision 622)
+++ 	(revision )
@@ -1,43 +1,0 @@
-package net.oni2.aeinstaller.backend;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.List;
-import java.util.Vector;
-
-/**
- * @author Christian Illy
- */
-public class QuickAppExecution {
-
-	/**
-	 * Execute a short running application
-	 * 
-	 * @param cmdLine
-	 *            List of command and arguments
-	 * @return List of output lines
-	 * @throws IOException
-	 *             Exc
-	 */
-	public static Vector<String> execute(List<String> cmdLine)
-			throws IOException {
-		ProcessBuilder pb = new ProcessBuilder(cmdLine);
-		pb.redirectErrorStream(true);
-		Process proc = pb.start();
-
-		InputStream is = proc.getInputStream();
-		InputStreamReader isr = new InputStreamReader(is);
-		BufferedReader br = new BufferedReader(isr);
-
-		String line;
-		Vector<String> lines = new Vector<String>();
-
-		while ((line = br.readLine()) != null) {
-			lines.add(line);
-		}
-		return lines;
-	}
-
-}
Index: AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java	(revision 622)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/Settings.java	(revision 623)
@@ -80,5 +80,5 @@
 		Vector<String> res = null;
 		try {
-			res = QuickAppExecution.execute(cmd);
+			res = AppExecution.executeAndWait(cmd);
 		} catch (IOException e) {
 			e.printStackTrace();
@@ -134,5 +134,5 @@
 				Vector<String> res = null;
 				try {
-					res = QuickAppExecution.execute(cmd);
+					res = AppExecution.executeAndWait(cmd);
 				} catch (IOException e) {
 					e.printStackTrace();
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/Mod.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/Mod.java	(revision 622)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/Mod.java	(revision 623)
@@ -26,4 +26,5 @@
 
 	private HashSet<Type> types = new HashSet<Type>();
+	private boolean tool = false;
 	private ECompatiblePlatform platform = null;
 	private String version = "";
@@ -36,6 +37,11 @@
 	private net.oni2.aeinstaller.backend.depot.model.File file = null;
 
+	private File exeFile = null;
+	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;
@@ -52,8 +58,9 @@
 		packageNumber = nm.getPackageNumber();
 		platform = nm.getPlatform();
+		tool = nm.isTool();
 		for (TaxonomyTerm tt : nm.getTypes()) {
 			Type t = ModManager.getInstance().getTypeByName(tt.getName());
 			types.add(t);
-			if (!nm.isTool() && !isMandatoryMod() && isValidOnPlatform())
+			if (!tool && !isMandatoryMod() && isValidOnPlatform())
 				t.addEntry(this);
 		}
@@ -76,4 +83,5 @@
 		File config = new File(getLocalPath(), "Mod_Info.cfg");
 		File aeicfg = new File(getLocalPath(), "aei.cfg");
+		File plain = new File(getLocalPath(), "plain");
 		if (config.exists()) {
 			try {
@@ -131,4 +139,22 @@
 							}
 						}
+					} 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 %05d does contain a non-number UnlockLevel value: '%s'\n",
+												packageNumber, s);
+							}
+						}
+					} else if (sName.equalsIgnoreCase("ExeName")) {
+						exeFile = new File(Paths.getEditionBasePath(), sVal);
+					} else if (sName.equalsIgnoreCase("WorkingDir")) {
+						workingDir = sVal;
+					} else if (sName.equalsIgnoreCase("IconName")) {
+						iconFile = new File(Paths.getEditionBasePath(), sVal);
 					}
 				}
@@ -166,4 +192,6 @@
 			}
 		}
+		if (node == null)
+			tool = plain.exists();
 	}
 
@@ -254,4 +282,11 @@
 
 	/**
+	 * @return Is this mod actually a tool?
+	 */
+	public boolean isTool() {
+		return tool;
+	}
+
+	/**
 	 * @return Compatible platforms
 	 */
@@ -326,4 +361,39 @@
 	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 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();
 	}
 
Index: AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java	(revision 622)
+++ AE/installer2/src/net/oni2/aeinstaller/backend/mods/ModManager.java	(revision 623)
@@ -253,15 +253,26 @@
 
 	/**
-	 * Get a mod by its package number
+	 * @return Currently installed tools
+	 */
+	public TreeSet<Mod> getInstalledTools() {
+		TreeSet<Mod> res = new TreeSet<Mod>();
+		for (int n : Installer.getInstalledTools()) {
+			res.add(getModByNumber(n));
+		}
+		return res;
+	}
+
+	/**
+	 * Get a mod/tool by its package number
 	 * 
 	 * @param number
 	 *            Package number
-	 * @return Mod or null
+	 * @return Mod/tool or null
 	 */
 	public Mod getModByNumber(int number) {
-		for (Mod m : mods.values()) {
-			if (m.getPackageNumber() == number)
-				return m;
-		}
+		if (mods.containsKey(number))
+			return mods.get(number);
+		if (tools.containsKey(number))
+			return tools.get(number);
 		return null;
 	}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 622)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.java	(revision 623)
@@ -8,4 +8,5 @@
 import java.io.File;
 import java.io.IOException;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -17,4 +18,7 @@
 import java.util.Vector;
 
+import javax.swing.AbstractAction;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JComboBox;
@@ -38,4 +42,6 @@
 import javax.swing.table.TableRowSorter;
 
+import net.oni2.aeinstaller.AEInstaller2;
+import net.oni2.aeinstaller.backend.AppExecution;
 import net.oni2.aeinstaller.backend.Paths;
 import net.oni2.aeinstaller.backend.Settings;
@@ -80,4 +86,6 @@
 
 	private JMenu mainMenu;
+	private JMenu toolsMenu;
+	private TreeSet<JMenuItem> toolsMenuItems = new TreeSet<JMenuItem>();
 
 	private JSplitPane contents;
@@ -412,4 +420,37 @@
 
 	@SuppressWarnings("unused")
+	private void refreshToolsMenu() {
+		for (JMenuItem i : toolsMenuItems) {
+			toolsMenu.remove(i);
+		}
+		toolsMenuItems.clear();
+		for (Mod m : ModManager.getInstance().getInstalledTools()) {
+			if (m.getExeFile() != null && m.getExeFile().exists()) {
+				JMenuItem item = new JMenuItem();
+				final Vector<String> params = new Vector<String>();
+				params.add(m.getExeFile().getPath());
+				final File wd = m.getWorkingDir();
+				Icon 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);
+				}
+				item.setAction(new AbstractAction(m.getName(), ico) {
+					private static final long serialVersionUID = 1L;
+
+					@Override
+					public void actionPerformed(ActionEvent e) {
+						AppExecution.execute(params, wd);
+					}
+				});
+				toolsMenuItems.add(item);
+				toolsMenu.add(item);
+			}
+		}
+	}
+
+	@SuppressWarnings("unused")
 	private void revertSelection() {
 		model.revertSelection();
@@ -510,5 +551,19 @@
 
 		if (instReady) {
-			Installer.install(mods, new InstallProgressListener() {
+			TreeSet<Mod> actuallyMods = new TreeSet<Mod>();
+			TreeSet<Mod> actuallyTools = new TreeSet<Mod>();
+
+			for (Mod m : mods) {
+				if (m.isTool())
+					actuallyTools.add(m);
+				else
+					actuallyMods.add(m);
+			}
+
+			if (actuallyTools.size() > 0) {
+				Installer.installTools(actuallyTools);
+			}
+
+			Installer.install(actuallyMods, new InstallProgressListener() {
 				@Override
 				public void installProgressUpdate(int done, int total,
@@ -657,11 +712,5 @@
 		Vector<String> params = getBasicOniLaunchParams();
 		if (params.size() > 0) {
-			try {
-				ProcessBuilder pb = new ProcessBuilder(params);
-				pb.directory(Paths.getEditionBasePath());
-				pb.start();
-			} catch (IOException e) {
-				e.printStackTrace();
-			}
+			AppExecution.execute(params, Paths.getEditionBasePath());
 		}
 	}
@@ -672,11 +721,5 @@
 		if (params.size() > 0) {
 			params.add("-noswitch");
-			try {
-				ProcessBuilder pb = new ProcessBuilder(params);
-				pb.directory(Paths.getEditionBasePath());
-				pb.start();
-			} catch (IOException e) {
-				e.printStackTrace();
-			}
+			AppExecution.execute(params, Paths.getEditionBasePath());
 		}
 	}
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.properties
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.properties	(revision 622)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.properties	(revision 623)
@@ -24,8 +24,10 @@
 menu.reglobalize=&Rebuild Core Data
 menu.reglobalizeTooltip=Rebuild Core Data
-menu.tools=&Manage Tools
-menu.toolsTooltip=Install/Remove Tools
 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
 
 btnRevertSelection.text=Revert selection
Index: AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.yml
===================================================================
--- AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.yml	(revision 622)
+++ AE/installer2/src/net/oni2/aeinstaller/gui/MainWin.yml	(revision 623)
@@ -6,5 +6,5 @@
   locationRelativeTo: null
   defaultCloseOperation: doNothingOnClose
-  onWindowOpened: [execDepotUpdate,checkMandatoryFiles,checkInitialize,initialize,checkUpdates,doUpdate]
+  onWindowOpened: [execDepotUpdate,checkMandatoryFiles,checkInitialize,initialize,checkUpdates,doUpdate,refreshToolsMenu,focus]
   onWindowClosing: [saveLocalData,exit]
   iconImage: img.ae
@@ -19,5 +19,5 @@
     - 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=[reglobalize])
-    - Action(name=tools, text=menu.tools, toolTipText=menu.toolsTooltip, icon=img.tools, onAction=[tools])
+    - 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])
     - JMenuBar:
@@ -37,7 +37,8 @@
             - JMenuItem(action=reglobalize)
             - JSeparator()
-            - JMenuItem(action=tools)
+            - JMenuItem(action=update)
+        - JMenu(name=toolsMenu, text=menu.tools):
+            - JMenuItem(name=manageToolsItem, action=tools)
             - JSeparator()
-            - JMenuItem(action=update)
     - JToolBar(name=toolbar, floatable=false, orientation=0):
         - JButton(action=exitAction, hideActionText=true)
