Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/management/Installer.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/management/Installer.java	(revision 785)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/management/Installer.java	(revision 804)
@@ -25,4 +25,5 @@
 import net.oni2.aeinstaller.backend.oni.PersistDat;
 import net.oni2.aeinstaller.backend.oni.XMLTools;
+import net.oni2.aeinstaller.backend.oni.management.tools.ToolInstallationList;
 import net.oni2.aeinstaller.backend.packages.EBSLInstallType;
 import net.oni2.aeinstaller.backend.packages.Package;
@@ -100,7 +101,11 @@
 		log.println("AEI2 version: "
 				+ SwingJavaBuilder.getConfig().getResource("appversion"));
+
+		ToolInstallationList til = ToolInstallationList.getInstance();
 		log.println("Installed tools:");
 		for (Package t : PackageManager.getInstance().getInstalledTools()) {
-			log.println(String.format(" - %s (%s)", t.getName(), t.getVersion()));
+			log.println(String.format(" - %s (%s)", t.getName(), t.getVersion())
+					+ (til.isModified(t.getPackageNumber()) ? " (! LOCALLY MODIFIED !)"
+							: ""));
 		}
 		log.println("Installing mods:");
@@ -236,5 +241,5 @@
 		log.println();
 		Date end = new Date();
-		log.println("Initialization ended at " + sdf.format(end));
+		log.println("Installation ended at " + sdf.format(end));
 		log.println("Process took "
 				+ ((end.getTime() - start.getTime()) / 1000) + " seconds");
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/management/ToolsManager.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/management/ToolsManager.java	(revision 785)
+++ 	(revision )
@@ -1,142 +1,0 @@
-package net.oni2.aeinstaller.backend.oni.management;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.TreeSet;
-
-import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
-import net.oni2.aeinstaller.backend.Paths;
-import net.oni2.aeinstaller.backend.packages.Package;
-import net.oni2.platformtools.PlatformInformation;
-import net.oni2.platformtools.PlatformInformation.Platform;
-
-import org.apache.commons.io.FileUtils;
-
-import com.thoughtworks.xstream.XStream;
-import com.thoughtworks.xstream.io.xml.StaxDriver;
-
-/**
- * @author Christian Illy
- */
-public class ToolsManager {
-	/**
-	 * @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);
-						if (f.canExecute())
-							CaseInsensitiveFile.getCaseInsensitiveFile(
-									targetFolder, f.getName()).setExecutable(
-									true);
-					}
-				}
-			} catch (IOException e) {
-				e.printStackTrace();
-			}
-		}
-		if (remove)
-			if (targetFolder.list().length == 0)
-				targetFolder.delete();
-	}
-
-}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolFileIterator.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolFileIterator.java	(revision 804)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolFileIterator.java	(revision 804)
@@ -0,0 +1,62 @@
+package net.oni2.aeinstaller.backend.oni.management.tools;
+
+import java.io.File;
+
+import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.platformtools.PlatformInformation;
+import net.oni2.platformtools.PlatformInformation.Platform;
+
+/**
+ * @author Christian Illy
+ */
+public class ToolFileIterator {
+
+	/**
+	 * Iterate over tool package files with source and respective target file
+	 * 
+	 * @param tool
+	 *            Tool to iterate over
+	 * @param handler
+	 *            Handler to handle found files
+	 */
+	public static void iteratePlatformToolFiles(Package tool,
+			ToolFileIteratorEntry handler) {
+		File plain = CaseInsensitiveFile.getCaseInsensitiveFile(
+				tool.getLocalPath(), "plain");
+		if (plain.exists()) {
+			if (tool.hasSeparatePlatformDirs()) {
+				File plainCommon = CaseInsensitiveFile.getCaseInsensitiveFile(
+						plain, "common");
+				File plainMac = CaseInsensitiveFile.getCaseInsensitiveFile(
+						plain, "mac_only");
+				File plainWin = CaseInsensitiveFile.getCaseInsensitiveFile(
+						plain, "win_only");
+				if (plainCommon.exists())
+					iterateFiles(plainCommon, Paths.getEditionBasePath(),
+							handler);
+				if (PlatformInformation.getPlatform() == Platform.MACOS
+						&& plainMac.exists())
+					iterateFiles(plainMac, Paths.getEditionBasePath(), handler);
+				else if (plainWin.exists())
+					iterateFiles(plainWin, Paths.getEditionBasePath(), handler);
+			} else {
+				iterateFiles(plain, Paths.getEditionBasePath(), handler);
+			}
+		}
+	}
+
+	private static void iterateFiles(File srcFolder, File targetFolder,
+			ToolFileIteratorEntry handler) {
+		for (File f : srcFolder.listFiles()) {
+			if (f.isDirectory())
+				iterateFiles(f, CaseInsensitiveFile.getCaseInsensitiveFile(
+						targetFolder, f.getName()), handler);
+			else {
+				handler.toolFile(f, new File(targetFolder, f.getName()));
+			}
+		}
+	}
+
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolFileIteratorEntry.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolFileIteratorEntry.java	(revision 804)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolFileIteratorEntry.java	(revision 804)
@@ -0,0 +1,18 @@
+package net.oni2.aeinstaller.backend.oni.management.tools;
+
+import java.io.File;
+
+/**
+ * @author Christian Illy
+ */
+public interface ToolFileIteratorEntry {
+	/**
+	 * Handle a file in a tool package
+	 * 
+	 * @param source
+	 *            Source of file (within local package repository)
+	 * @param target
+	 *            Target of file if installed
+	 */
+	public void toolFile(File source, File target);
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolInstallationList.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolInstallationList.java	(revision 804)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolInstallationList.java	(revision 804)
@@ -0,0 +1,163 @@
+package net.oni2.aeinstaller.backend.oni.management.tools;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.TreeMap;
+
+import net.oni2.aeinstaller.backend.Paths;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.StaxDriver;
+
+/**
+ * @author Christian Illy
+ */
+public class ToolInstallationList {
+
+	private static ToolInstallationList instance = loadCurrentList();
+
+	private TreeMap<Integer, HashSet<String>> tools = new TreeMap<Integer, HashSet<String>>();
+	private transient HashSet<Integer> modifiedPackages = new HashSet<Integer>();
+
+	/**
+	 * Add tool to entry list
+	 * 
+	 * @param packageId
+	 *            Id of package
+	 * @param files
+	 *            Files of package
+	 */
+	public void addTool(int packageId, HashSet<String> files) {
+		tools.put(packageId, files);
+	}
+
+	/**
+	 * Remove tool from entry list
+	 * 
+	 * @param packageId
+	 *            Id of package
+	 */
+	public void removeTool(int packageId) {
+		tools.remove(packageId);
+	}
+
+	/**
+	 * Check if given package id is installed as tool
+	 * 
+	 * @param packageId
+	 *            Id of tool package
+	 * @return Is installed?
+	 */
+	public boolean isInstalled(int packageId) {
+		return tools.containsKey(packageId);
+	}
+
+	/**
+	 * Retrieve installed files for package
+	 * 
+	 * @param packageId
+	 *            Id of tool package
+	 * @return File hashset
+	 */
+	public HashSet<String> getFiles(int packageId) {
+		return tools.get(packageId);
+	}
+
+	/**
+	 * @return Tool list
+	 */
+	public TreeMap<Integer, HashSet<String>> getItems() {
+		return tools;
+	}
+
+	/**
+	 * Mark a package as locally modified
+	 * 
+	 * @param packageId
+	 *            Id of tool package
+	 * @param modified
+	 *            Is modified?
+	 */
+	public void markModified(int packageId, boolean modified) {
+		if (modified)
+			modifiedPackages.add(packageId);
+		else
+			modifiedPackages.remove(packageId);
+	}
+
+	/**
+	 * @return List of all modified tools
+	 */
+	public HashSet<Integer> getModifiedTools() {
+		return modifiedPackages;
+	}
+
+	/**
+	 * Check if a specific tool is modified
+	 * 
+	 * @param packageId
+	 *            Id of tool package
+	 * @return Is tool modified?
+	 */
+	public boolean isModified(int packageId) {
+		return modifiedPackages.contains(packageId);
+	}
+
+	/**
+	 * @return Get singleton instance
+	 */
+	public static ToolInstallationList getInstance() {
+		return instance;
+	}
+
+	private static File getFile() {
+		return new File(Paths.getInstallerPath(), "installed_tools.xml");
+	}
+
+	private static XStream getStream() {
+		XStream xs = new XStream(new StaxDriver());
+		xs.alias("TIL", ToolInstallationList.class);
+		return xs;
+	}
+
+	private static ToolInstallationList loadCurrentList() {
+		ToolInstallationList til = new ToolInstallationList();
+		try {
+			if (getFile().exists()) {
+				FileInputStream fis = new FileInputStream(getFile());
+				XStream xs = getStream();
+				Object obj = xs.fromXML(fis);
+				if (obj instanceof ToolInstallationList) {
+					til = (ToolInstallationList) obj;
+					til.modifiedPackages = new HashSet<Integer>();
+				}
+				fis.close();
+			}
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return til;
+	}
+
+	/**
+	 * Write this tool installation list to the installed_tools.xml file
+	 */
+	public void saveList() {
+		try {
+			FileOutputStream fos = new FileOutputStream(getFile());
+			XStream xs = getStream();
+			xs.toXML(this, fos);
+			fos.close();
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+}
Index: java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolsManager.java
===================================================================
--- java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolsManager.java	(revision 804)
+++ java/installer2/src/net/oni2/aeinstaller/backend/oni/management/tools/ToolsManager.java	(revision 804)
@@ -0,0 +1,111 @@
+package net.oni2.aeinstaller.backend.oni.management.tools;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.TreeSet;
+
+import net.oni2.aeinstaller.backend.CaseInsensitiveFile;
+import net.oni2.aeinstaller.backend.FileChecksum;
+import net.oni2.aeinstaller.backend.Paths;
+import net.oni2.aeinstaller.backend.packages.Package;
+import net.oni2.aeinstaller.backend.packages.PackageManager;
+
+import org.apache.commons.io.FileUtils;
+
+/**
+ * @author Christian Illy
+ */
+public class ToolsManager {
+
+	/**
+	 * Verify integrity of installed tools
+	 */
+	public static void verifyToolsIntegrity() {
+		final ToolInstallationList til = ToolInstallationList.getInstance();
+		for (final Package m : PackageManager.getInstance().getInstalledTools()) {
+			ToolFileIterator.iteratePlatformToolFiles(m,
+					new ToolFileIteratorEntry() {
+						@Override
+						public void toolFile(File source, File target) {
+							byte[] chkSrc = FileChecksum
+									.calculateFileMD5(source);
+							if (!target.exists()) {
+								til.markModified(m.getPackageNumber(), true);
+							} else {
+								byte[] chkTrg = FileChecksum
+										.calculateFileMD5(target);
+								if (!Arrays.equals(chkSrc, chkTrg))
+									til.markModified(m.getPackageNumber(), true);
+							}
+						}
+					});
+		}
+	}
+
+	/**
+	 * @param tools
+	 *            Tools to (un)install
+	 * @param uninstall
+	 *            Uninstall tools or install?
+	 */
+	public static void installTools(TreeSet<Package> tools, boolean uninstall) {
+		ToolInstallationList til = ToolInstallationList.getInstance();
+		for (Package m : tools) {
+			if (!uninstall) { // Install:
+				final HashSet<String> files = new HashSet<String>();
+				ToolFileIterator.iteratePlatformToolFiles(m,
+						new ToolFileIteratorEntry() {
+							@Override
+							public void toolFile(File source, File target) {
+								copyToolsFiles(source, target, files);
+							}
+						});
+				til.addTool(m.getPackageNumber(), files);
+			} else { // Uninstall:
+				if (til.isInstalled(m.getPackageNumber())) {
+					removeTool(til.getFiles(m.getPackageNumber()));
+					til.removeTool(m.getPackageNumber());
+				}
+			}
+		}
+		til.saveList();
+	}
+
+	private static void copyToolsFiles(File src, File target,
+			HashSet<String> files) {
+		try {
+			File targetFile = CaseInsensitiveFile.getCaseInsensitiveFile(
+					target.getParentFile(), target.getName());
+
+			// Case mismatch?
+			if (!targetFile.getName().equals(src.getName()))
+				targetFile.delete();
+
+			files.add(target.getPath().replace(
+					Paths.getEditionBasePath().getPath(), ""));
+
+			FileUtils.copyFile(src, target);
+			if (src.canExecute())
+				target.setExecutable(true);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	private static void removeTool(HashSet<String> files) {
+		for (String p : files) {
+			File targetFile = new File(Paths.getEditionBasePath().getPath() + p);
+			if (targetFile.getPath().contains(
+					Paths.getEditionBasePath().getPath())) {
+				File targetFolder = targetFile.getParentFile();
+
+				if (targetFile.exists())
+					targetFile.delete();
+				if (targetFolder.list().length == 0)
+					targetFolder.delete();
+			}
+		}
+	}
+}
