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 net.oni2.aeinstaller.AEInstaller2; import net.oni2.aeinstaller.backend.Paths; import net.oni2.aeinstaller.backend.Settings; import net.oni2.aeinstaller.backend.Settings.Platform; import net.oni2.aeinstaller.backend.mods.EBSLInstallType; import net.oni2.aeinstaller.backend.mods.Mod; import net.oni2.aeinstaller.backend.mods.ModManager; import org.apache.commons.io.FileUtils; 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 getInstalledMods() { File installCfg = new File(Paths.getEditionGDF(), "installed_mods.xml"); return ModManager.getInstance().loadModSelection(installCfg); } /** * @return Currently installed tools */ @SuppressWarnings("unchecked") public static TreeSet getInstalledTools() { File installCfg = new File(Paths.getInstallerPath(), "installed_tools.xml"); TreeSet res = new TreeSet(); 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) obj; fis.close(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return res; } private static void writeInstalledTools(TreeSet 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 install */ public static void installTools(TreeSet tools) { TreeSet installed = getInstalledTools(); for (Mod m : tools) { File plain = new File(m.getLocalPath(), "plain"); if (plain.exists()) { if (m.hasSeparatePlatformDirs()) { File plainCommon = new File(plain, "common"); File plainMac = new File(plain, "mac_only"); File plainWin = new File(plain, "win_only"); if (plainCommon.exists()) copyToolsFiles(plainCommon); if (Settings.getPlatform() == Platform.MACOS && plainMac.exists()) copyToolsFiles(plainMac); else if (plainWin.exists()) copyToolsFiles(plainWin); } else { copyToolsFiles(plain); } } installed.add(m.getPackageNumber()); } writeInstalledTools(installed); } /** * @param tools * Tools to uninstall */ public static void uninstallTools(TreeSet tools) { TreeSet installed = getInstalledTools(); for (Mod m : tools) { if (installed.contains(m.getPackageNumber())) { File plain = new File(m.getLocalPath(), "plain"); if (plain.exists()) { if (m.hasSeparatePlatformDirs()) { File plainCommon = new File(plain, "common"); File plainMac = new File(plain, "mac_only"); File plainWin = new File(plain, "win_only"); if (plainCommon.exists()) removeToolsFiles(plainCommon, Paths.getEditionBasePath()); if (Settings.getPlatform() == Platform.MACOS && plainMac.exists()) removeToolsFiles(plainMac, Paths.getEditionBasePath()); else if (plainWin.exists()) removeToolsFiles(plainWin, Paths.getEditionBasePath()); } else { removeToolsFiles(plain, Paths.getEditionBasePath()); } } } installed.remove(m.getPackageNumber()); } writeInstalledTools(installed); } private static void copyToolsFiles(File srcFolder) { for (File f : srcFolder.listFiles()) { try { if (f.isDirectory()) FileUtils.copyDirectoryToDirectory(f, Paths.getEditionBasePath()); else FileUtils .copyFileToDirectory(f, Paths.getEditionBasePath()); } catch (IOException e) { e.printStackTrace(); } } } private static void removeToolsFiles(File srcFolder, File target) { for (File f : srcFolder.listFiles()) { if (f.isDirectory()) removeToolsFiles(f, new File(target, f.getName())); else { File targetFile = new File(target, f.getName()); if (targetFile.exists()) targetFile.delete(); } } if (target.list().length == 0) target.delete(); } /** * Install the given set of mods * * @param mods * Mods to install * @param listener * Listener for install progress updates */ public static void install(TreeSet mods, InstallProgressListener listener) { try { createEmptyPath(Paths.getEditionGDF()); } catch (IOException e) { e.printStackTrace(); } File installCfg = new File(Paths.getEditionGDF(), "installed_mods.xml"); ModManager.getInstance().saveModSelection(installCfg, mods); HashSet unlockLevels = new HashSet(); Vector foldersOni = new Vector(); foldersOni.add(Paths.getVanillaOnisPath()); for (Mod m : mods) { for (int lev : m.getUnlockLevels()) unlockLevels.add(lev); File oni = new File(m.getLocalPath(), "oni"); if (oni.exists()) { if (m.hasSeparatePlatformDirs()) { File oniCommon = new File(oni, "common"); File oniMac = new File(oni, "mac_only"); File oniWin = new File(oni, "win_only"); if (oniCommon.exists()) foldersOni.add(oniCommon); if (Settings.getPlatform() == Platform.MACOS && oniMac.exists()) foldersOni.add(oniMac); else if (oniWin.exists()) foldersOni.add(oniWin); } else { foldersOni.add(oni); } } } combineBinaryFiles(foldersOni, listener); combineBSLFolders(mods, listener); copyVideos(); if (unlockLevels.size() > 0) { unlockLevels(unlockLevels); } } private static void combineBSLFolders(TreeSet mods, InstallProgressListener listener) { listener.installProgressUpdate(95, 100, "Installing BSL files"); HashMap> modsToInclude = new HashMap>(); modsToInclude.put(EBSLInstallType.NORMAL, new Vector()); modsToInclude.put(EBSLInstallType.ADDON, new Vector()); for (Mod m : mods.descendingSet()) { File bsl = new File(m.getLocalPath(), "bsl"); if (bsl.exists()) { if (m.hasSeparatePlatformDirs()) { File bslCommon = new File(bsl, "common"); File bslMac = new File(bsl, "mac_only"); File bslWin = new File(bsl, "win_only"); if ((Settings.getPlatform() == Platform.MACOS && bslMac .exists()) || ((Settings.getPlatform() == Platform.WIN || Settings .getPlatform() == Platform.LINUX) && bslWin .exists()) || bslCommon.exists()) { modsToInclude.get(m.getBSLInstallType()).add(m); } } else { modsToInclude.get(m.getBSLInstallType()).add(m); } } } for (Mod m : modsToInclude.get(EBSLInstallType.NORMAL)) { copyBSL(m, false); } Vector addons = modsToInclude.get(EBSLInstallType.ADDON); for (int i = addons.size() - 1; i >= 0; i--) { copyBSL(addons.get(i), true); } } private static void copyBSL(Mod sourceMod, boolean addon) { File targetBaseFolder = new File(Paths.getEditionGDF(), "IGMD"); if (!targetBaseFolder.exists()) targetBaseFolder.mkdir(); Vector sources = new Vector(); File bsl = new File(sourceMod.getLocalPath(), "bsl"); if (sourceMod.hasSeparatePlatformDirs()) { File bslCommon = new File(bsl, "common"); File bslMac = new File(bsl, "mac_only"); File bslWin = new File(bsl, "win_only"); if (Settings.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 ((Settings.getPlatform() == Platform.WIN || Settings .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(); for (File fbsl : f.listFiles()) { File targetFile = new File(targetPath, fbsl.getName()); if (addon || !targetFile.exists()) { try { FileUtils.copyFile(fbsl, targetFile); } catch (IOException e) { e.printStackTrace(); } } } } } private static void combineBinaryFiles(List srcFoldersFiles, InstallProgressListener listener) { TreeMap> levels = new TreeMap>(); for (File path : srcFoldersFiles) { 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('.')); } if (levelN != null) { if (!levels.containsKey(levelN)) levels.put(levelN, new Vector()); levels.get(levelN).add(levelF); } } } int totalSteps = 0; int stepsDone = 0; for (@SuppressWarnings("unused") String s : levels.keySet()) totalSteps++; totalSteps++; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); File logFile = new File(Paths.getInstallerPath(), "Installation.log"); PrintWriter log = null; try { log = new PrintWriter(logFile); } catch (FileNotFoundException e) { e.printStackTrace(); } Date start = new Date(); log.println("Installation of mods started at " + sdf.format(start)); log.println("Importing levels"); for (String l : levels.keySet()) { log.println("\tLevel " + l); listener.installProgressUpdate(stepsDone, totalSteps, "Installing level " + l); for (File f : levels.get(l)) { log.println("\t\t\t" + f.getPath()); } Vector res = OniSplit.packLevel(levels.get(l), new File( Paths.getEditionGDF(), sanitizeLevelName(l) + ".dat")); if (res != null && res.size() > 0) { for (String s : res) log.println("\t\t" + s); } log.println(); stepsDone++; } 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 copyVideos() { if (Settings.getInstance().get("copyintro", false)) { File src = new File(Paths.getVanillaGDF(), "intro.bik"); if (src.exists()) { try { FileUtils.copyFileToDirectory(src, Paths.getEditionGDF()); } catch (IOException e) { e.printStackTrace(); } } } if (Settings.getInstance().get("copyoutro", true)) { File src = new File(Paths.getVanillaGDF(), "outro.bik"); if (src.exists()) { try { FileUtils.copyFileToDirectory(src, Paths.getEditionGDF()); } catch (IOException e) { e.printStackTrace(); } } } } private static void unlockLevels(HashSet unlockLevels) { File dat = new File(Paths.getEditionBasePath(), "persist.dat"); if (!dat.exists()) { InputStream is = AEInstaller2.class.getResourceAsStream("/net/oni2/aeinstaller/resources/persist.dat"); try { FileUtils.copyInputStreamToFile(is, dat); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } PersistDat save = new PersistDat(dat); HashSet 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 Vector res = OniSplit.export(tempLevelFolder, f); if (res != null && res.size() > 0) { for (String s : res) log.println("\t\t\t" + s); } 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 folders = new Vector(); folders.add(f); Vector res = OniSplit.importLevel(folders, new File( Paths.getVanillaOnisPath(), levelName + ".dat")); if (res != null && res.size() > 0) { for (String s : res) log.println("\t\t" + s); } 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(); } }