diff --git a/src/main/java/net/vhati/ftldat/AbstractPack.java b/src/main/java/net/vhati/ftldat/AbstractPack.java index 3a4a27b..815cc0f 100644 --- a/src/main/java/net/vhati/ftldat/AbstractPack.java +++ b/src/main/java/net/vhati/ftldat/AbstractPack.java @@ -75,6 +75,15 @@ public abstract class AbstractPack { public void close() throws IOException { } + /** + * Tidies up the dat before closing (possibly mandatory). + * + * @returns a result, or null if nothing happened + */ + public RepackResult repack() throws IOException { + return null; + } + /** diff --git a/src/main/java/net/vhati/ftldat/FTLPack.java b/src/main/java/net/vhati/ftldat/FTLPack.java index c4de60f..c9cd425 100644 --- a/src/main/java/net/vhati/ftldat/FTLPack.java +++ b/src/main/java/net/vhati/ftldat/FTLPack.java @@ -466,6 +466,7 @@ public class FTLPack extends AbstractPack { * Repacks the dat file. This will remove gaps, which could * be created when adding, removing or replacing files. */ + @Override public RepackResult repack() throws IOException { long bytesChanged = 0; diff --git a/src/main/java/net/vhati/ftldat/PackUtilities.java b/src/main/java/net/vhati/ftldat/PackUtilities.java index 95733c5..e6c6eb1 100644 --- a/src/main/java/net/vhati/ftldat/PackUtilities.java +++ b/src/main/java/net/vhati/ftldat/PackUtilities.java @@ -2,6 +2,7 @@ package net.vhati.ftldat; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; diff --git a/src/main/java/net/vhati/ftldat/PkgPack.java b/src/main/java/net/vhati/ftldat/PkgPack.java index 5e95e48..b6901fa 100644 --- a/src/main/java/net/vhati/ftldat/PkgPack.java +++ b/src/main/java/net/vhati/ftldat/PkgPack.java @@ -733,6 +733,7 @@ public class PkgPack extends AbstractPack { * All innerPaths will be rewritten to the paths region, sorted by * dataOffset. */ + @Override public RepackResult repack() throws IOException { long bytesChanged = 0; diff --git a/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java index 426b555..e31ec07 100644 --- a/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java +++ b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java @@ -4,6 +4,7 @@ import java.io.BufferedOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.InputStreamReader; @@ -17,13 +18,13 @@ import java.util.zip.ZipOutputStream; import net.vhati.ftldat.AbstractPack; import net.vhati.ftldat.FolderPack; +import net.vhati.ftldat.PkgPack; import net.vhati.ftldat.FTLPack; import net.vhati.modmanager.FTLModManager; import net.vhati.modmanager.core.DelayedDeleteHook; import net.vhati.modmanager.core.FTLUtilities; import net.vhati.modmanager.core.ModPatchObserver; import net.vhati.modmanager.core.ModPatchThread; -import net.vhati.modmanager.core.ModPatchThread.BackedUpDat; import net.vhati.modmanager.core.ModUtilities; import net.vhati.modmanager.core.Report; import net.vhati.modmanager.core.Report.ReportFormatter; @@ -124,7 +125,7 @@ public class SlipstreamCLI { boolean anyInvalid = false; for ( String modFileName : cmdline.getArgs() ) { - File modFile = new File(modsDir, modFileName); + File modFile = new File( modsDir, modFileName ); if ( modFile.isDirectory() ) { log.info( String.format( "Zipping dir: %s/", modFile.getName() ) ); @@ -133,7 +134,7 @@ public class SlipstreamCLI { deleteHook.addDoomedFile( modFile ); } catch ( IOException e ) { - log.error( String.format( "Error zipping \"%s/\".", modFile.getName() ), e ); + log.error( String.format( "Error zipping dir: %s/", modFile.getName() ), e ); List tmpMessages = new ArrayList(); tmpMessages.add( new ReportMessage( ReportMessage.SECTION, modFileName ) ); @@ -200,46 +201,61 @@ public class SlipstreamCLI { String extractPath = cmdline.getOptionValue( "extract-dats" ); File extractDir = new File( extractPath ); - File dataDatFile = new File( datsDir, "data.dat" ); - File resDatFile = new File( datsDir, "resource.dat" ); - File[] datFiles = new File[] {dataDatFile, resDatFile}; - - AbstractPack srcP = null; - AbstractPack dstP = null; + FolderPack dstPack = null; + List srcPacks = new ArrayList( 2 ); InputStream is = null; try { + File ftlDatFile = new File( datsDir, "ftl.dat" ); + File dataDatFile = new File( datsDir, "data.dat" ); + File resourceDatFile = new File( datsDir, "resource.dat" ); + + if ( ftlDatFile.exists() ) { // FTL 1.6.1. + AbstractPack ftlPack = new PkgPack( ftlDatFile, "r" ); + srcPacks.add( ftlPack ); + } + else if ( dataDatFile.exists() && resourceDatFile.exists() ) { // FTL 1.01-1.5.13. + AbstractPack dataPack = new FTLPack( dataDatFile, "r" ); + AbstractPack resourcePack = new FTLPack( resourceDatFile, "r" ); + srcPacks.add( dataPack ); + srcPacks.add( resourcePack ); + } + else { + throw new FileNotFoundException( String.format( "Could not find either \"%s\" or both \"%s\" and \"%s\"", ftlDatFile.getName(), dataDatFile.getName(), resourceDatFile.getName() ) ); + } + if ( !extractDir.exists() ) extractDir.mkdirs(); - dstP = new FolderPack( extractDir ); + dstPack = new FolderPack( extractDir ); - for ( File datFile : datFiles ) { - srcP = new FTLPack( datFile, "r" ); - List innerPaths = srcP.list(); + for ( AbstractPack srcPack : srcPacks ) { + List innerPaths = srcPack.list(); for ( String innerPath : innerPaths ) { - if ( dstP.contains( innerPath ) ) { + if ( dstPack.contains( innerPath ) ) { log.info( "While extracting resources, this file was overwritten: "+ innerPath ); - dstP.remove( innerPath ); + dstPack.remove( innerPath ); } - is = srcP.getInputStream( innerPath ); - dstP.add( innerPath, is ); + is = srcPack.getInputStream( innerPath ); + dstPack.add( innerPath, is ); } - srcP.close(); + srcPack.close(); } } catch ( IOException e ) { - log.error( "Error extracting dats.", e ); + log.error( "Error extracting dats", e ); System.exit( 1 ); } finally { try {if ( is != null ) is.close();} catch ( IOException ex ) {} - try {if ( srcP != null ) srcP.close();} + try {if ( dstPack != null ) dstPack.close();} catch ( IOException ex ) {} - try {if ( dstP != null ) dstP.close();} - catch ( IOException ex ) {} + for ( AbstractPack pack : srcPacks ) { + try {pack.close();} + catch ( IOException ex ) {} + } } System.exit( 0 ); @@ -259,7 +275,7 @@ public class SlipstreamCLI { deleteHook.addDoomedFile( modFile ); } catch ( IOException e ) { - log.error( String.format( "Error zipping \"%s/\".", modFile.getName() ), e ); + log.error( String.format( "Error zipping dir: %s/", modFile.getName() ), e ); System.exit( 1 ); } } @@ -267,17 +283,10 @@ public class SlipstreamCLI { modFiles.add( modFile ); } - BackedUpDat dataDat = new BackedUpDat(); - dataDat.datFile = new File( datsDir, "data.dat" ); - dataDat.bakFile = new File( backupDir, "data.dat.bak" ); - BackedUpDat resDat = new BackedUpDat(); - resDat.datFile = new File( datsDir, "resource.dat" ); - resDat.bakFile = new File( backupDir, "resource.dat.bak" ); - boolean globalPanic = cmdline.hasOption( "global-panic" ); SilentPatchObserver patchObserver = new SilentPatchObserver(); - ModPatchThread patchThread = new ModPatchThread( modFiles, dataDat, resDat, globalPanic, patchObserver ); + ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, globalPanic, patchObserver ); patchThread.setDefaultUncaughtExceptionHandler( exceptionHandler ); deleteHook.addWatchedThread( patchThread ); @@ -301,7 +310,7 @@ public class SlipstreamCLI { exeArgs = new String[] {"-applaunch", FTLUtilities.STEAM_APPID_FTL}; if ( exeFile == null ) { - log.warn( "Steam executable could not be found. FTL will be launched directly." ); + log.warn( "Steam executable could not be found; FTL will be launched directly" ); } } if ( exeFile == null ) { @@ -309,7 +318,7 @@ public class SlipstreamCLI { exeArgs = new String[0]; if ( exeFile == null ) { - log.warn( "FTL executable could not be found." ); + log.warn( "FTL executable could not be found" ); } } @@ -317,12 +326,12 @@ public class SlipstreamCLI { try { FTLUtilities.launchExe( exeFile, exeArgs ); } catch ( Exception e ) { - log.error( "Error launching FTL.", e ); + log.error( "Error launching FTL", e ); System.exit( 1 ); } } else { - log.error( "No executables were found to launch FTL." ); + log.error( "No executables were found to launch FTL" ); System.exit( 1 ); } @@ -352,13 +361,13 @@ public class SlipstreamCLI { InputStream in = null; try { if ( configFile.exists() ) { - log.trace( "Loading properties from config file." ); + log.trace( "Loading properties from config file" ); in = new FileInputStream( configFile ); config.load( new InputStreamReader( in, "UTF-8" ) ); } } catch ( IOException e ) { - log.error( "Error loading config.", e ); + log.error( "Error loading config", e ); } finally { try {if ( in != null ) in.close();} @@ -381,14 +390,14 @@ public class SlipstreamCLI { log.info( "Using FTL dats path from config: "+ datsPath ); datsDir = new File( datsPath ); if ( FTLUtilities.isDatsDirValid( datsDir ) == false ) { - log.error( "The config's ftl_dats_path does not exist." ); + log.error( "The config's ftl_dats_path does not exist" ); datsDir = null; } } else { - log.error( "No FTL dats path previously set." ); + log.error( "No FTL dats path previously set" ); } if ( datsDir == null ) { - log.error( "Run the GUI once, or edit the config file, and try again." ); + log.error( "Run the GUI once, or edit the config file, and try again" ); System.exit( 1 ); } diff --git a/src/main/java/net/vhati/modmanager/core/FTLUtilities.java b/src/main/java/net/vhati/modmanager/core/FTLUtilities.java index 42c18c9..4de93b2 100644 --- a/src/main/java/net/vhati/modmanager/core/FTLUtilities.java +++ b/src/main/java/net/vhati/modmanager/core/FTLUtilities.java @@ -19,22 +19,34 @@ public class FTLUtilities { /** * Confirms the FTL resources dir exists and contains the dat files. * + * This checks for either "ftl.dat" or both "data.dat" and "resource.dat". + * * Note: Do d.getCanonicalFile() to resolve any symlinks first! */ public static boolean isDatsDirValid( File d ) { if ( !d.exists() || !d.isDirectory() ) return false; - if ( !new File( d, "data.dat" ).exists() ) return false; - if ( !new File( d, "resource.dat" ).exists() ) return false; - return true; + + if ( new File( d, "ftl.dat" ).exists() ) return true; + + if ( new File( d, "data.dat" ).exists() && new File( d, "resource.dat" ).exists() ) { + return true; + } + + return false; } /** * Returns the FTL resources dir, or null. + * + * Windows: Steam, GOG, HumbleBundle + * Linux (Wine): GOG, HumbleBundle + * Linux: Steam, HumbleBundle + * OSX: Steam, HumbleBundle */ public static File findDatsDir() { - String steamPath = "Steam/steamapps/common/FTL Faster Than Light/resources"; - String gogPath = "GOG.com/Faster Than Light/resources"; - String humblePath = "FTL/resources"; + String steamPath = "Steam/steamapps/common/FTL Faster Than Light"; + String gogPath = "GOG.com/Faster Than Light"; + String humblePath = "FTL"; String programFiles86 = System.getenv( "ProgramFiles(x86)" ); String programFiles = System.getenv( "ProgramFiles" ); @@ -55,18 +67,32 @@ public class FTLUtilities { candidates.add( new File( new File( programFiles86 ), steamPath ) ); candidates.add( new File( new File( programFiles86 ), gogPath ) ); candidates.add( new File( new File( programFiles86 ), humblePath ) ); + + candidates.add( new File( new File( programFiles86 ), steamPath +"/resources" ) ); + candidates.add( new File( new File( programFiles86 ), gogPath +"/resources" ) ); + candidates.add( new File( new File( programFiles86 ), humblePath +"/resources" ) ); } if ( programFiles != null ) { candidates.add( new File( new File( programFiles ), steamPath ) ); candidates.add( new File( new File( programFiles ), gogPath ) ); candidates.add( new File( new File( programFiles ), humblePath ) ); + + candidates.add( new File( new File( programFiles ), steamPath +"/resources" ) ); + candidates.add( new File( new File( programFiles ), gogPath +"/resources" ) ); + candidates.add( new File( new File( programFiles ), humblePath +"/resources" ) ); } // Linux - Steam. if ( xdgDataHome != null ) { + candidates.add( new File( xdgDataHome +"/Steam/steamapps/common/FTL Faster Than Light/data" ) ); + candidates.add( new File( xdgDataHome +"/Steam/SteamApps/common/FTL Faster Than Light/data" ) ); + candidates.add( new File( xdgDataHome +"/Steam/steamapps/common/FTL Faster Than Light/data/resources" ) ); candidates.add( new File( xdgDataHome +"/Steam/SteamApps/common/FTL Faster Than Light/data/resources" ) ); } if ( home != null ) { + candidates.add( new File( home +"/.steam/steam/steamapps/common/FTL Faster Than Light/data" ) ); + candidates.add( new File( home +"/.steam/steam/SteamApps/common/FTL Faster Than Light/data" ) ); + candidates.add( new File( home +"/.steam/steam/steamapps/common/FTL Faster Than Light/data/resources" ) ); candidates.add( new File( home +"/.steam/steam/SteamApps/common/FTL Faster Than Light/data/resources" ) ); } @@ -76,6 +102,11 @@ public class FTLUtilities { candidates.add( new File( winePrefix +"/drive_c/Program Files (x86)/"+ humblePath ) ); candidates.add( new File( winePrefix +"/drive_c/Program Files/"+ gogPath ) ); candidates.add( new File( winePrefix +"/drive_c/Program Files/"+ humblePath ) ); + + candidates.add( new File( winePrefix +"/drive_c/Program Files (x86)/"+ gogPath +"/resources" ) ); + candidates.add( new File( winePrefix +"/drive_c/Program Files (x86)/"+ humblePath +"/resources" ) ); + candidates.add( new File( winePrefix +"/drive_c/Program Files/"+ gogPath +"/resources" ) ); + candidates.add( new File( winePrefix +"/drive_c/Program Files/"+ humblePath +"/resources" ) ); } // OSX - Steam. if ( home != null ) { @@ -113,28 +144,30 @@ public class FTLUtilities { String message = ""; message += "You will now be prompted to locate FTL manually.\n"; - message += "Select '(FTL dir)/resources/data.dat'.\n"; - message += "Or 'FTL.app', if you're on OSX."; + message += "Look in {FTL dir} to select 'ftl.dat' or 'data.dat'.\n"; + message += "\n"; + message += "It may be buried under a subdirectory called 'resources/'.\n"; + message += "Or select 'FTL.app', if you're on OSX."; JOptionPane.showMessageDialog( parentComponent, message, "Find FTL", JOptionPane.INFORMATION_MESSAGE ); final JFileChooser fc = new JFileChooser(); - fc.setDialogTitle( "Find data.dat or FTL.app" ); + fc.setDialogTitle( "Find ftl.dat or data.dat or FTL.app" ); fc.setFileHidingEnabled( false ); fc.addChoosableFileFilter(new FileFilter() { @Override public String getDescription() { - return "FTL Data File - (FTL dir)/resources/data.dat"; + return "FTL Data File - ftl.dat|data.dat"; } @Override public boolean accept( File f ) { - return f.isDirectory() || f.getName().equals( "data.dat" ) || f.getName().equals( "FTL.app" ); + return f.isDirectory() || f.getName().equals( "ftl.dat" ) || f.getName().equals( "data.dat" ) || f.getName().equals( "FTL.app" ); } }); - fc.setMultiSelectionEnabled(false); + fc.setMultiSelectionEnabled( false ); if ( fc.showOpenDialog( parentComponent ) == JFileChooser.APPROVE_OPTION ) { File f = fc.getSelectedFile(); - if ( f.getName().equals( "data.dat" ) ) { + if ( f.getName().equals( "ftl.dat" ) || f.getName().equals( "data.dat" ) ) { result = f.getParentFile(); } else if ( f.getName().endsWith( ".app" ) && f.isDirectory() ) { @@ -152,29 +185,60 @@ public class FTLUtilities { return null; } - /** * Returns the executable that will launch FTL, or null. * - * On Windows, FTLGame.exe is one dir above "resources/". - * On Linux, FTL is a script, one dir above "resources/". + * FTL 1.01-1.5.13: + * Windows + * {FTL dir}/resources/*.dat + * {FTL dir}/FTLGame.exe + * Linux + * {FTL dir}/data/resources/*.dat + * {FTL dir}/data/FTL + * OSX + * {FTL dir}/Contents/Resources/*.dat + * {FTL dir} + * + * FTL 1.6.1: + * Windows + * {FTL dir}/*.dat + * {FTL dir}/FTLGame.exe + * Linux + * {FTL dir}/data/*.dat + * {FTL dir}/data/FTL + * OSX + * {FTL dir}/Contents/Resources/*.dat + * {FTL dir} + * + * On Windows, FTLGame.exe is a binary. + * On Linux, FTL is a script. * On OSX, FTL.app is the grandparent dir itself (a bundle). */ public static File findGameExe( File datsDir ) { File result = null; if ( System.getProperty( "os.name" ).startsWith( "Windows" ) ) { - File ftlDir = datsDir.getParentFile(); - if ( ftlDir != null ) { - File exeFile = new File( ftlDir, "FTLGame.exe" ); - if ( exeFile.exists() ) result = exeFile; + + for ( File candidateDir : new File[] {datsDir, datsDir.getParentFile()} ) { + if ( candidateDir == null ) continue; + + File exeFile = new File( candidateDir, "FTLGame.exe" ); + if ( exeFile.exists() ) { + result = exeFile; + break; + } } } else if ( System.getProperty( "os.name" ).equals( "Linux" ) ) { - File ftlDir = datsDir.getParentFile(); - if ( ftlDir != null ) { - File exeFile = new File( ftlDir, "FTL" ); - if ( exeFile.exists() ) result = exeFile; + + for ( File candidateDir : new File[] {datsDir, datsDir.getParentFile()} ) { + if ( candidateDir == null ) continue; + + File exeFile = new File( candidateDir, "FTL" ); + if ( exeFile.exists() ) { + result = exeFile; + break; + } } } else if ( System.getProperty( "os.name" ).contains( "OS X" ) ) { @@ -189,16 +253,16 @@ public class FTLUtilities { } } } + return result; } - /** * Returns the executable that will launch Steam, or null. * - * On Windows, Steam.exe. - * On Linux, steam is a script. ( http://moritzmolch.com/815 ) - * On OSX, Steam.app is the grandparent dir itself (a bundle). + * On Windows, "Steam.exe". + * On Linux, "steam" is a script. ( http://moritzmolch.com/815 ) + * On OSX, "Steam.app" is a bundle. * * The args to launch FTL are: ["-applaunch", STEAM_APPID_FTL] */ @@ -237,7 +301,6 @@ public class FTLUtilities { return result; } - /** * Launches an executable. * @@ -245,6 +308,8 @@ public class FTLUtilities { * On Linux, a binary or script. * On OSX, an *.app bundle dir. * + * OSX bundles are executed with: "open -a bundle.app". + * * @param exeFile see findGameExe() or findSteamExe() * @param exeArgs arguments for the executable * @return a Process object, or null @@ -278,7 +343,6 @@ public class FTLUtilities { return result; } - /** * Returns the directory for user profiles and saved games, or null. */ diff --git a/src/main/java/net/vhati/modmanager/core/ModPatchThread.java b/src/main/java/net/vhati/modmanager/core/ModPatchThread.java index 6bc5139..1778c64 100644 --- a/src/main/java/net/vhati/modmanager/core/ModPatchThread.java +++ b/src/main/java/net/vhati/modmanager/core/ModPatchThread.java @@ -15,7 +15,10 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import net.vhati.ftldat.AbstractPack; +import net.vhati.ftldat.AbstractPack.RepackResult; import net.vhati.ftldat.FTLPack; +import net.vhati.ftldat.PkgPack; +import net.vhati.ftldat.PackContainer; import net.vhati.ftldat.PackUtilities; import net.vhati.modmanager.core.ModPatchObserver; import net.vhati.modmanager.core.ModUtilities; @@ -36,8 +39,8 @@ public class ModPatchThread extends Thread { private Thread shutdownHook = null; private List modFiles = new ArrayList(); - private BackedUpDat dataDat = null; - private BackedUpDat resDat = null; + private File datsDir = null; + private File backupDir = null; private boolean globalPanic = false; private ModPatchObserver observer = null; @@ -48,10 +51,11 @@ public class ModPatchThread extends Thread { private final int progRepackMax = 5; private int progMilestone = 0; - public ModPatchThread( List modFiles, BackedUpDat dataDat, BackedUpDat resDat, boolean globalPanic, ModPatchObserver observer ) { + + public ModPatchThread( List modFiles, File datsDir, File backupDir, boolean globalPanic, ModPatchObserver observer ) { this.modFiles.addAll( modFiles ); - this.dataDat = dataDat; - this.resDat = resDat; + this.datsDir = datsDir; + this.backupDir = backupDir; this.globalPanic = globalPanic; this.observer = observer; } @@ -103,10 +107,7 @@ public class ModPatchThread extends Thread { observer.patchingProgress( 0, progMax ); - BackedUpDat[] allDats = new BackedUpDat[] {dataDat, resDat}; - - FTLPack dataP = null; - FTLPack resP = null; + PackContainer packContainer = null; try { int backupsCreated = 0; @@ -114,20 +115,34 @@ public class ModPatchThread extends Thread { int modsInstalled = 0; int datsRepacked = 0; + File ftlDatFile = new File( datsDir, "ftl.dat" ); + File dataDatFile = new File( datsDir, "data.dat" ); + File resourceDatFile = new File( datsDir, "resource.dat" ); + + List backedUpDats = new ArrayList( 2 ); + for ( File datFile : new File[] {ftlDatFile, dataDatFile, resourceDatFile} ) { + if ( !datFile.exists() ) continue; + + BackedUpDat bud = new BackedUpDat(); + bud.datFile = datFile; + bud.bakFile = new File( backupDir, datFile.getName() +".bak" ); + backedUpDats.add( bud ); + } + // Don't let dats be read-only. - for ( BackedUpDat dat : allDats ) { - if ( dat.datFile.exists() ) dat.datFile.setWritable( true ); + for ( BackedUpDat bud : backedUpDats ) { + if ( bud.datFile.exists() ) bud.datFile.setWritable( true ); } // Create backup dats, if necessary. - for ( BackedUpDat dat : allDats ) { - if ( !dat.bakFile.exists() ) { - log.info( String.format( "Backing up \"%s\".", dat.datFile.getName() ) ); - observer.patchingStatus( String.format( "Backing up \"%s\".", dat.datFile.getName() ) ); + for ( BackedUpDat bud : backedUpDats ) { + if ( !bud.bakFile.exists() ) { + log.info( String.format( "Backing up \"%s\".", bud.datFile.getName() ) ); + observer.patchingStatus( String.format( "Backing up \"%s\".", bud.datFile.getName() ) ); - PackUtilities.copyFile( dat.datFile, dat.bakFile ); + PackUtilities.copyFile( bud.datFile, bud.bakFile ); backupsCreated++; - observer.patchingProgress( progMilestone + progBackupMax/allDats.length*backupsCreated, progMax ); + observer.patchingProgress( progMilestone + progBackupMax/backedUpDats.size()*backupsCreated, progMax ); if ( !keepRunning ) return false; } @@ -136,17 +151,17 @@ public class ModPatchThread extends Thread { observer.patchingProgress( progMilestone, progMax ); observer.patchingStatus( null ); - if ( backupsCreated != allDats.length ) { + if ( backupsCreated != backedUpDats.size() ) { // Clobber current dat files with their respective backups. // But don't bother if we made those backups just now. - for ( BackedUpDat dat : allDats ) { - log.info( String.format( "Restoring vanilla \"%s\"...", dat.datFile.getName() ) ); - observer.patchingStatus( String.format( "Restoring vanilla \"%s\"...", dat.datFile.getName() ) ); + for ( BackedUpDat bud : backedUpDats ) { + log.info( String.format( "Restoring vanilla \"%s\"...", bud.datFile.getName() ) ); + observer.patchingStatus( String.format( "Restoring vanilla \"%s\"...", bud.datFile.getName() ) ); - PackUtilities.copyFile( dat.bakFile, dat.datFile ); + PackUtilities.copyFile( bud.bakFile, bud.datFile ); datsClobbered++; - observer.patchingProgress( progMilestone + progClobberMax/allDats.length*datsClobbered, progMax ); + observer.patchingProgress( progMilestone + progClobberMax/backedUpDats.size()*datsClobbered, progMax ); if ( !keepRunning ) return false; } @@ -161,30 +176,48 @@ public class ModPatchThread extends Thread { return true; } - dataP = new FTLPack( dataDat.datFile, "r+" ); - resP = new FTLPack( resDat.datFile, "r+" ); + packContainer = new PackContainer(); + if ( ftlDatFile.exists() ) { // FTL 1.6.1. + AbstractPack ftlPack = new PkgPack( ftlDatFile, "r+" ); - Map topFolderMap = new HashMap(); - topFolderMap.put( "data", dataP ); - topFolderMap.put( "audio", resP ); - topFolderMap.put( "fonts", resP ); - topFolderMap.put( "img", resP ); - topFolderMap.put( "mod-appendix", null ); + packContainer.setPackFor( "audio/", ftlPack ); + packContainer.setPackFor( "data/", ftlPack ); + packContainer.setPackFor( "fonts/", ftlPack ); + packContainer.setPackFor( "img/", ftlPack ); + packContainer.setPackFor( null, ftlPack ); + // Supposedly "exe_icon.png" has been observed at top-level? + } + else if ( dataDatFile.exists() && resourceDatFile.exists() ) { // FTL 1.01-1.5.13. + AbstractPack dataPack = new FTLPack( dataDatFile, "r+" ); + AbstractPack resourcePack = new FTLPack( resourceDatFile, "r+" ); + + packContainer.setPackFor( "audio/", resourcePack ); + packContainer.setPackFor( "data/", dataPack ); + packContainer.setPackFor( "fonts/", resourcePack ); + packContainer.setPackFor( "img/", resourcePack ); + } + else { + throw new IOException( String.format( "Could not find either \"%s\" or both \"%s\" and \"%s\"", ftlDatFile.getName(), dataDatFile.getName(), resourceDatFile.getName() ) ); + } + packContainer.setPackFor( "mod-appendix/", null ); // Track modified innerPaths in case they're clobbered. List moddedItems = new ArrayList(); List knownPaths = new ArrayList(); - knownPaths.addAll( dataP.list() ); - knownPaths.addAll( resP.list() ); + for ( AbstractPack pack : packContainer.getPacks() ) { + knownPaths.addAll( pack.list() ); + } List knownPathsLower = new ArrayList( knownPaths.size() ); for ( String innerPath : knownPaths ) { knownPathsLower.add( innerPath.toLowerCase() ); } - // Group1: parentPath, Group2: topFolder, Group3: fileName - Pattern pathPtn = Pattern.compile( "^(([^/]+)/(?:.*/)?)([^/]+)$" ); + List knownRoots = packContainer.getRoots(); + + // Group1: parentPath/, Group2: root/, Group3: fileName. + Pattern pathPtn = Pattern.compile( "^(?:(([^/]+/)(?:.*/)?))?([^/]+)$" ); for ( File modFile : modFiles ) { if ( !keepRunning ) return false; @@ -216,12 +249,12 @@ public class ModPatchThread extends Thread { } String parentPath = m.group( 1 ); - String topFolder = m.group( 2 ); + String root = m.group( 2 ); String fileName = m.group( 3 ); - AbstractPack ftlP = topFolderMap.get( topFolder ); - if ( ftlP == null ) { - if ( !topFolderMap.containsKey( topFolder ) ) + AbstractPack pack = packContainer.getPackFor( innerPath ); + if ( pack == null ) { + if ( !knownRoots.contains( root ) ) log.warn( String.format( "Unexpected innerPath: %s", innerPath ) ); zis.closeEntry(); continue; @@ -237,24 +270,24 @@ public class ModPatchThread extends Thread { innerPath = parentPath + fileName.replaceAll( "[.](?:xml[.]append|append[.]xml)$", ".xml" ); innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); - if ( !ftlP.contains( innerPath ) ) { + if ( !pack.contains( innerPath ) ) { log.warn( String.format( "Non-existent innerPath wasn't appended: %s", innerPath ) ); } else { InputStream mainStream = null; try { - mainStream = ftlP.getInputStream(innerPath); - InputStream mergedStream = ModUtilities.patchXMLFile( mainStream, zis, "windows-1252", globalPanic, ftlP.getName()+":"+innerPath, modFile.getName()+":"+parentPath+fileName ); + mainStream = pack.getInputStream(innerPath); + InputStream mergedStream = ModUtilities.patchXMLFile( mainStream, zis, "windows-1252", globalPanic, pack.getName()+":"+innerPath, modFile.getName()+":"+parentPath+fileName ); mainStream.close(); - ftlP.remove( innerPath ); - ftlP.add( innerPath, mergedStream ); + pack.remove( innerPath ); + pack.add( innerPath, mergedStream ); } finally { try {if ( mainStream != null ) mainStream.close();} catch ( IOException e ) {} } - if ( !moddedItems.contains(innerPath) ) { + if ( !moddedItems.contains( innerPath ) ) { moddedItems.add( innerPath ); } } @@ -263,25 +296,25 @@ public class ModPatchThread extends Thread { innerPath = parentPath + fileName.replaceAll( "[.](?:xml[.]rawappend|rawappend[.]xml)$", ".xml" ); innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); - if ( !ftlP.contains( innerPath ) ) { + if ( !pack.contains( innerPath ) ) { log.warn( String.format( "Non-existent innerPath wasn't raw appended: %s", innerPath ) ); } else { log.warn( String.format( "Appending xml as raw text: %s", innerPath ) ); InputStream mainStream = null; try { - mainStream = ftlP.getInputStream(innerPath); - InputStream mergedStream = ModUtilities.appendXMLFile( mainStream, zis, "windows-1252", ftlP.getName()+":"+innerPath, modFile.getName()+":"+parentPath+fileName ); + mainStream = pack.getInputStream( innerPath ); + InputStream mergedStream = ModUtilities.appendXMLFile( mainStream, zis, "windows-1252", pack.getName()+":"+innerPath, modFile.getName()+":"+parentPath+fileName ); mainStream.close(); - ftlP.remove( innerPath ); - ftlP.add( innerPath, mergedStream ); + pack.remove( innerPath ); + pack.add( innerPath, mergedStream ); } finally { try {if ( mainStream != null ) mainStream.close();} catch ( IOException e ) {} } - if ( !moddedItems.contains(innerPath) ) { + if ( !moddedItems.contains( innerPath ) ) { moddedItems.add( innerPath ); } } @@ -299,30 +332,30 @@ public class ModPatchThread extends Thread { InputStream fixedStream = ModUtilities.encodeText( fixedText, "windows-1252", modFile.getName()+":"+parentPath+fileName+" (with new EOL)" ); - if ( !moddedItems.contains(innerPath) ) { + if ( !moddedItems.contains( innerPath ) ) { moddedItems.add( innerPath ); } else { log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) ); } - if ( ftlP.contains( innerPath ) ) - ftlP.remove( innerPath ); - ftlP.add( innerPath, fixedStream ); + if ( pack.contains( innerPath ) ) + pack.remove( innerPath ); + pack.add( innerPath, fixedStream ); } else if ( fileName.endsWith( ".xml" ) ) { innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); InputStream fixedStream = ModUtilities.rebuildXMLFile( zis, "windows-1252", modFile.getName()+":"+parentPath+fileName ); - if ( !moddedItems.contains(innerPath) ) { + if ( !moddedItems.contains( innerPath ) ) { moddedItems.add( innerPath ); } else { log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) ); } - if ( ftlP.contains( innerPath ) ) - ftlP.remove( innerPath ); - ftlP.add( innerPath, fixedStream ); + if ( pack.contains( innerPath ) ) + pack.remove( innerPath ); + pack.add( innerPath, fixedStream ); } else if ( fileName.endsWith( ".txt" ) ) { innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); @@ -334,28 +367,28 @@ public class ModPatchThread extends Thread { InputStream fixedStream = ModUtilities.encodeText( fixedText, "windows-1252", modFile.getName()+":"+parentPath+fileName+" (with new EOL)" ); - if ( !moddedItems.contains(innerPath) ) { + if ( !moddedItems.contains( innerPath ) ) { moddedItems.add( innerPath ); } else { log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) ); } - if ( ftlP.contains( innerPath ) ) - ftlP.remove( innerPath ); - ftlP.add( innerPath, fixedStream ); + if ( pack.contains( innerPath ) ) + pack.remove( innerPath ); + pack.add( innerPath, fixedStream ); } else { innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); - if ( !moddedItems.contains(innerPath) ) { + if ( !moddedItems.contains( innerPath ) ) { moddedItems.add( innerPath ); } else { log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) ); } - if ( ftlP.contains( innerPath ) ) - ftlP.remove( innerPath ); - ftlP.add( innerPath, zis ); + if ( pack.contains( innerPath ) ) + pack.remove( innerPath ); + pack.add( innerPath, zis ); } zis.closeEntry(); @@ -378,16 +411,17 @@ public class ModPatchThread extends Thread { observer.patchingProgress( progMilestone, progMax ); // Prune 'removed' files from dats. - for ( AbstractPack ftlP : new AbstractPack[]{dataP, resP} ) { - if ( ftlP instanceof FTLPack ) { - observer.patchingStatus( String.format( "Repacking \"%s\"...", ftlP.getName() ) ); + for ( AbstractPack pack : packContainer.getPacks() ) { + observer.patchingStatus( String.format( "Repacking \"%s\"...", pack.getName() ) ); - long bytesChanged = ((FTLPack)ftlP).repack().bytesChanged; - log.info( String.format( "Repacked \"%s\" (%d bytes affected)", ftlP.getName(), bytesChanged ) ); - - datsRepacked++; - observer.patchingProgress( progMilestone + progRepackMax/allDats.length*datsRepacked, progMax ); + AbstractPack.RepackResult repackResult = pack.repack(); + if ( repackResult != null ) { + long bytesChanged = repackResult.bytesChanged; + log.info( String.format( "Repacked \"%s\" (%d bytes affected)", pack.getName(), bytesChanged ) ); } + + datsRepacked++; + observer.patchingProgress( progMilestone + progRepackMax/backedUpDats.size()*datsRepacked, progMax ); } progMilestone += progRepackMax; observer.patchingProgress( progMilestone, progMax ); @@ -396,11 +430,12 @@ public class ModPatchThread extends Thread { return true; } finally { - try {if ( dataP != null ) dataP.close();} - catch( Exception e ) {} - - try {if ( resP != null ) resP.close();} - catch( Exception e ) {} + if ( packContainer != null ) { + for ( AbstractPack pack : packContainer.getPacks() ) { + try {pack.close();} + catch( Exception e ) {} + } + } } } diff --git a/src/main/java/net/vhati/modmanager/ui/DatExtractDialog.java b/src/main/java/net/vhati/modmanager/ui/DatExtractDialog.java index 97760d2..30b7d17 100644 --- a/src/main/java/net/vhati/modmanager/ui/DatExtractDialog.java +++ b/src/main/java/net/vhati/modmanager/ui/DatExtractDialog.java @@ -2,8 +2,10 @@ package net.vhati.modmanager.ui; import java.awt.Frame; import java.io.File; +import java.io.FileNotFoundException; import java.io.InputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import javax.swing.JDialog; import javax.swing.SwingUtilities; @@ -11,6 +13,7 @@ import javax.swing.SwingUtilities; import net.vhati.ftldat.AbstractPack; import net.vhati.ftldat.FolderPack; import net.vhati.ftldat.FTLPack; +import net.vhati.ftldat.PkgPack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -22,24 +25,24 @@ public class DatExtractDialog extends ProgressDialog { private boolean started = false; - private File extractDir; - private File[] datFiles; + private File extractDir = null; + private File datsDir = null; private DatExtractThread workerThread = null; - public DatExtractDialog( Frame owner, File extractDir, File[] datFiles ) { + public DatExtractDialog( Frame owner, File extractDir, File datsDir ) { super( owner, false ); this.setTitle( "Extracting..." ); this.extractDir = extractDir; - this.datFiles = datFiles; + this.datsDir = datsDir; this.setSize( 400, 160 ); this.setMinimumSize( this.getPreferredSize() ); this.setLocationRelativeTo( owner ); - workerThread = new DatExtractThread( extractDir, datFiles ); + workerThread = new DatExtractThread( extractDir, datsDir ); } /** @@ -79,59 +82,79 @@ public class DatExtractDialog extends ProgressDialog { private class DatExtractThread extends Thread { - private File extractDir; - private File[] datFiles; + private File extractDir = null; + private File datsDir = null; - public DatExtractThread( File extractDir, File[] datFiles ) { + + public DatExtractThread( File extractDir, File datsDir ) { this.extractDir = extractDir; - this.datFiles = datFiles; + this.datsDir = datsDir; } @Override public void run() { - AbstractPack srcP = null; - AbstractPack dstP = null; + AbstractPack dstPack = null; + List srcPacks = new ArrayList( 2 ); InputStream is = null; int progress = 0; try { + File ftlDatFile = new File( datsDir, "ftl.dat" ); + File dataDatFile = new File( datsDir, "data.dat" ); + File resourceDatFile = new File( datsDir, "resource.dat" ); + + if ( ftlDatFile.exists() ) { // FTL 1.6.1. + AbstractPack ftlPack = new PkgPack( ftlDatFile, "r" ); + srcPacks.add( ftlPack ); + } + else if ( dataDatFile.exists() && resourceDatFile.exists() ) { // FTL 1.01-1.5.13. + AbstractPack dataPack = new FTLPack( dataDatFile, "r" ); + AbstractPack resourcePack = new FTLPack( resourceDatFile, "r" ); + srcPacks.add( dataPack ); + srcPacks.add( resourcePack ); + } + else { + throw new FileNotFoundException( String.format( "Could not find either \"%s\" or both \"%s\" and \"%s\"", ftlDatFile.getName(), dataDatFile.getName(), resourceDatFile.getName() ) ); + } + if ( !extractDir.exists() ) extractDir.mkdirs(); - dstP = new FolderPack( extractDir ); + dstPack = new FolderPack( extractDir ); - for ( File datFile : datFiles ) { - srcP = new FTLPack( datFile, "r" ); + for ( AbstractPack srcPack : srcPacks ) { progress = 0; - List innerPaths = srcP.list(); + List innerPaths = srcPack.list(); setProgressLater( progress, innerPaths.size() ); for ( String innerPath : innerPaths ) { setStatusTextLater( innerPath ); - if ( dstP.contains( innerPath ) ) { + if ( dstPack.contains( innerPath ) ) { log.info( "While extracting resources, this file was overwritten: "+ innerPath ); - dstP.remove( innerPath ); + dstPack.remove( innerPath ); } - is = srcP.getInputStream( innerPath ); - dstP.add( innerPath, is ); + is = srcPack.getInputStream( innerPath ); + dstPack.add( innerPath, is ); setProgressLater( progress++ ); } - srcP.close(); + srcPack.close(); } setTaskOutcomeLater( true, null ); } catch ( Exception e ) { - log.error( "Error extracting dats.", e ); + log.error( "Error extracting dats", e ); setTaskOutcomeLater( false, e ); } finally { try {if ( is != null ) is.close();} catch ( IOException e ) {} - try {if ( srcP != null ) srcP.close();} + try {if ( dstPack != null ) dstPack.close();} catch ( IOException e ) {} - try {if ( dstP != null ) dstP.close();} - catch ( IOException e ) {} + for ( AbstractPack pack : srcPacks ) { + try {pack.close();} + catch ( IOException ex ) {} + } } } } diff --git a/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java b/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java index 2671478..e546abd 100644 --- a/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java +++ b/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java @@ -60,7 +60,6 @@ import net.vhati.modmanager.core.ModDB; import net.vhati.modmanager.core.ModFileInfo; import net.vhati.modmanager.core.ModInfo; import net.vhati.modmanager.core.ModPatchThread; -import net.vhati.modmanager.core.ModPatchThread.BackedUpDat; import net.vhati.modmanager.core.ModsScanObserver; import net.vhati.modmanager.core.ModsScanThread; import net.vhati.modmanager.core.ModUtilities; @@ -666,13 +665,6 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse File datsDir = new File( appConfig.getProperty( "ftl_dats_path" ) ); - BackedUpDat dataDat = new BackedUpDat(); - dataDat.datFile = new File( datsDir, "data.dat" ); - dataDat.bakFile = new File( backupDir, "data.dat.bak" ); - BackedUpDat resDat = new BackedUpDat(); - resDat.datFile = new File( datsDir, "resource.dat" ); - resDat.bakFile = new File( backupDir, "resource.dat.bak" ); - ModPatchDialog patchDlg = new ModPatchDialog( this, true ); String neverRunFtl = appConfig.getProperty( "never_run_ftl", "false" ); @@ -705,7 +697,7 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse log.info( "" ); log.info( "Patching..." ); log.info( "" ); - ModPatchThread patchThread = new ModPatchThread( modFiles, dataDat, resDat, false, patchDlg ); + ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, false, patchDlg ); patchThread.setDefaultUncaughtExceptionHandler( this ); patchThread.start(); @@ -780,11 +772,8 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse File extractDir = extractChooser.getSelectedFile(); File datsDir = new File( appConfig.getProperty( "ftl_dats_path" ) ); - File dataDatFile = new File( datsDir, "data.dat" ); - File resDatFile = new File( datsDir, "resource.dat" ); - File[] datFiles = new File[] {dataDatFile, resDatFile}; - DatExtractDialog extractDlg = new DatExtractDialog( this, extractDir, datFiles ); + DatExtractDialog extractDlg = new DatExtractDialog( this, extractDir, datsDir ); extractDlg.getWorkerThread().setDefaultUncaughtExceptionHandler( this ); extractDlg.extract(); extractDlg.setVisible( true ); @@ -792,9 +781,8 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse else if ( source == sandboxMenuItem ) { setStatusText( "" ); File datsDir = new File( appConfig.getProperty( "ftl_dats_path" ) ); - File dataDatFile = new File( datsDir, "data.dat" ); - ModXMLSandbox sandboxFrame = new ModXMLSandbox( dataDatFile ); + ModXMLSandbox sandboxFrame = new ModXMLSandbox( datsDir ); sandboxFrame.addWindowListener( nerfListener ); sandboxFrame.setSize( 800, 600 ); sandboxFrame.setLocationRelativeTo( null ); diff --git a/src/main/java/net/vhati/modmanager/ui/ModXMLSandbox.java b/src/main/java/net/vhati/modmanager/ui/ModXMLSandbox.java index 6807c30..bc5724d 100644 --- a/src/main/java/net/vhati/modmanager/ui/ModXMLSandbox.java +++ b/src/main/java/net/vhati/modmanager/ui/ModXMLSandbox.java @@ -13,6 +13,7 @@ import java.awt.event.KeyEvent; import java.io.File; import java.io.InputStream; import java.io.IOException; +import java.io.FileNotFoundException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Enumeration; @@ -54,7 +55,9 @@ import javax.swing.tree.TreePath; import javax.swing.undo.CannotRedoException; import javax.swing.undo.UndoManager; +import net.vhati.ftldat.AbstractPack; import net.vhati.ftldat.FTLPack; +import net.vhati.ftldat.PkgPack; import net.vhati.modmanager.core.ModUtilities; import net.vhati.modmanager.core.SloppyXMLOutputProcessor; import net.vhati.modmanager.core.XMLPatcher; @@ -72,7 +75,7 @@ public class ModXMLSandbox extends JFrame implements ActionListener { private UndoManager undoManager = new UndoManager(); private Document mainDoc = null; - private File dataDatFile; + private File datsDir; private JTabbedPane areasPane; private JScrollPane mainScroll; @@ -91,11 +94,11 @@ public class ModXMLSandbox extends JFrame implements ActionListener { private JLabel statusLbl; - public ModXMLSandbox( File dataDatFile ) { + public ModXMLSandbox( File datsDir ) { super( "Mod XML Sandbox" ); this.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); - this.dataDatFile = dataDatFile; + this.datsDir = datsDir; Font sandboxFont = new Font( "Monospaced", Font.PLAIN, 13 ); @@ -319,17 +322,29 @@ public class ModXMLSandbox extends JFrame implements ActionListener { private void open() { messageArea.setText( "" ); - FTLPack dataP = null; + AbstractPack pack = null; InputStream is = null; try { - dataP = new FTLPack( dataDatFile, "r" ); - List innerPaths = dataP.list(); + File ftlDatFile = new File( datsDir, "ftl.dat" ); + File dataDatFile = new File( datsDir, "data.dat" ); + + if ( ftlDatFile.exists() ) { // FTL 1.6.1. + pack = new PkgPack( ftlDatFile, "r" ); + } + else if ( dataDatFile.exists() ) { // FTL 1.01-1.5.13. + pack = new FTLPack( dataDatFile, "r" ); + } + else { + throw new FileNotFoundException( String.format( "Could not find either \"%s\" or \"%s\"", ftlDatFile.getName(), dataDatFile.getName() ) ); + } + + List innerPaths = pack.list(); String innerPath = promptForInnerPath( innerPaths ); if ( innerPath == null ) return; - is = dataP.getInputStream( innerPath ); - String mainText = ModUtilities.decodeText( is, dataDatFile.getName()+":"+innerPath ).text; + is = pack.getInputStream( innerPath ); + String mainText = ModUtilities.decodeText( is, pack.getName()+":"+innerPath ).text; is.close(); mainText = mainText.replaceFirst( "<[?]xml [^>]*?[?]>", "" ); @@ -356,7 +371,7 @@ public class ModXMLSandbox extends JFrame implements ActionListener { try {if ( is != null ) is.close();} catch ( IOException f ) {} - try {if ( dataP != null ) dataP.close();} + try {if ( pack != null ) pack.close();} catch ( IOException f ) {} } }