Added support for extracting/sandboxing FTL 1.6.1's resources (patching is WIP)

This commit is contained in:
Vhati 2017-12-05 15:27:21 -05:00
parent 34a71e0aa5
commit f0df7faab8
10 changed files with 344 additions and 198 deletions

View file

@ -75,6 +75,15 @@ public abstract class AbstractPack {
public void close() throws IOException { 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;
}
/** /**

View file

@ -466,6 +466,7 @@ public class FTLPack extends AbstractPack {
* Repacks the dat file. This will remove gaps, which could * Repacks the dat file. This will remove gaps, which could
* be created when adding, removing or replacing files. * be created when adding, removing or replacing files.
*/ */
@Override
public RepackResult repack() throws IOException { public RepackResult repack() throws IOException {
long bytesChanged = 0; long bytesChanged = 0;

View file

@ -2,6 +2,7 @@ package net.vhati.ftldat;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.IOException; import java.io.IOException;

View file

@ -733,6 +733,7 @@ public class PkgPack extends AbstractPack {
* All innerPaths will be rewritten to the paths region, sorted by * All innerPaths will be rewritten to the paths region, sorted by
* dataOffset. * dataOffset.
*/ */
@Override
public RepackResult repack() throws IOException { public RepackResult repack() throws IOException {
long bytesChanged = 0; long bytesChanged = 0;

View file

@ -4,6 +4,7 @@ import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -17,13 +18,13 @@ import java.util.zip.ZipOutputStream;
import net.vhati.ftldat.AbstractPack; import net.vhati.ftldat.AbstractPack;
import net.vhati.ftldat.FolderPack; import net.vhati.ftldat.FolderPack;
import net.vhati.ftldat.PkgPack;
import net.vhati.ftldat.FTLPack; import net.vhati.ftldat.FTLPack;
import net.vhati.modmanager.FTLModManager; import net.vhati.modmanager.FTLModManager;
import net.vhati.modmanager.core.DelayedDeleteHook; import net.vhati.modmanager.core.DelayedDeleteHook;
import net.vhati.modmanager.core.FTLUtilities; import net.vhati.modmanager.core.FTLUtilities;
import net.vhati.modmanager.core.ModPatchObserver; import net.vhati.modmanager.core.ModPatchObserver;
import net.vhati.modmanager.core.ModPatchThread; import net.vhati.modmanager.core.ModPatchThread;
import net.vhati.modmanager.core.ModPatchThread.BackedUpDat;
import net.vhati.modmanager.core.ModUtilities; import net.vhati.modmanager.core.ModUtilities;
import net.vhati.modmanager.core.Report; import net.vhati.modmanager.core.Report;
import net.vhati.modmanager.core.Report.ReportFormatter; import net.vhati.modmanager.core.Report.ReportFormatter;
@ -124,7 +125,7 @@ public class SlipstreamCLI {
boolean anyInvalid = false; boolean anyInvalid = false;
for ( String modFileName : cmdline.getArgs() ) { for ( String modFileName : cmdline.getArgs() ) {
File modFile = new File(modsDir, modFileName); File modFile = new File( modsDir, modFileName );
if ( modFile.isDirectory() ) { if ( modFile.isDirectory() ) {
log.info( String.format( "Zipping dir: %s/", modFile.getName() ) ); log.info( String.format( "Zipping dir: %s/", modFile.getName() ) );
@ -133,7 +134,7 @@ public class SlipstreamCLI {
deleteHook.addDoomedFile( modFile ); deleteHook.addDoomedFile( modFile );
} }
catch ( IOException e ) { 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<ReportMessage> tmpMessages = new ArrayList<ReportMessage>(); List<ReportMessage> tmpMessages = new ArrayList<ReportMessage>();
tmpMessages.add( new ReportMessage( ReportMessage.SECTION, modFileName ) ); tmpMessages.add( new ReportMessage( ReportMessage.SECTION, modFileName ) );
@ -200,46 +201,61 @@ public class SlipstreamCLI {
String extractPath = cmdline.getOptionValue( "extract-dats" ); String extractPath = cmdline.getOptionValue( "extract-dats" );
File extractDir = new File( extractPath ); File extractDir = new File( extractPath );
File dataDatFile = new File( datsDir, "data.dat" ); FolderPack dstPack = null;
File resDatFile = new File( datsDir, "resource.dat" ); List<AbstractPack> srcPacks = new ArrayList<AbstractPack>( 2 );
File[] datFiles = new File[] {dataDatFile, resDatFile};
AbstractPack srcP = null;
AbstractPack dstP = null;
InputStream is = null; InputStream is = null;
try { 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(); if ( !extractDir.exists() ) extractDir.mkdirs();
dstP = new FolderPack( extractDir ); dstPack = new FolderPack( extractDir );
for ( File datFile : datFiles ) { for ( AbstractPack srcPack : srcPacks ) {
srcP = new FTLPack( datFile, "r" ); List<String> innerPaths = srcPack.list();
List<String> innerPaths = srcP.list();
for ( String innerPath : innerPaths ) { for ( String innerPath : innerPaths ) {
if ( dstP.contains( innerPath ) ) { if ( dstPack.contains( innerPath ) ) {
log.info( "While extracting resources, this file was overwritten: "+ innerPath ); log.info( "While extracting resources, this file was overwritten: "+ innerPath );
dstP.remove( innerPath ); dstPack.remove( innerPath );
} }
is = srcP.getInputStream( innerPath ); is = srcPack.getInputStream( innerPath );
dstP.add( innerPath, is ); dstPack.add( innerPath, is );
} }
srcP.close(); srcPack.close();
} }
} }
catch ( IOException e ) { catch ( IOException e ) {
log.error( "Error extracting dats.", e ); log.error( "Error extracting dats", e );
System.exit( 1 ); System.exit( 1 );
} }
finally { finally {
try {if ( is != null ) is.close();} try {if ( is != null ) is.close();}
catch ( IOException ex ) {} catch ( IOException ex ) {}
try {if ( srcP != null ) srcP.close();} try {if ( dstPack != null ) dstPack.close();}
catch ( IOException ex ) {} catch ( IOException ex ) {}
try {if ( dstP != null ) dstP.close();} for ( AbstractPack pack : srcPacks ) {
catch ( IOException ex ) {} try {pack.close();}
catch ( IOException ex ) {}
}
} }
System.exit( 0 ); System.exit( 0 );
@ -259,7 +275,7 @@ public class SlipstreamCLI {
deleteHook.addDoomedFile( modFile ); deleteHook.addDoomedFile( modFile );
} }
catch ( IOException e ) { 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 ); System.exit( 1 );
} }
} }
@ -267,17 +283,10 @@ public class SlipstreamCLI {
modFiles.add( modFile ); 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" ); boolean globalPanic = cmdline.hasOption( "global-panic" );
SilentPatchObserver patchObserver = new SilentPatchObserver(); 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 ); patchThread.setDefaultUncaughtExceptionHandler( exceptionHandler );
deleteHook.addWatchedThread( patchThread ); deleteHook.addWatchedThread( patchThread );
@ -301,7 +310,7 @@ public class SlipstreamCLI {
exeArgs = new String[] {"-applaunch", FTLUtilities.STEAM_APPID_FTL}; exeArgs = new String[] {"-applaunch", FTLUtilities.STEAM_APPID_FTL};
if ( exeFile == null ) { 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 ) { if ( exeFile == null ) {
@ -309,7 +318,7 @@ public class SlipstreamCLI {
exeArgs = new String[0]; exeArgs = new String[0];
if ( exeFile == null ) { 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 { try {
FTLUtilities.launchExe( exeFile, exeArgs ); FTLUtilities.launchExe( exeFile, exeArgs );
} catch ( Exception e ) { } catch ( Exception e ) {
log.error( "Error launching FTL.", e ); log.error( "Error launching FTL", e );
System.exit( 1 ); System.exit( 1 );
} }
} }
else { else {
log.error( "No executables were found to launch FTL." ); log.error( "No executables were found to launch FTL" );
System.exit( 1 ); System.exit( 1 );
} }
@ -352,13 +361,13 @@ public class SlipstreamCLI {
InputStream in = null; InputStream in = null;
try { try {
if ( configFile.exists() ) { if ( configFile.exists() ) {
log.trace( "Loading properties from config file." ); log.trace( "Loading properties from config file" );
in = new FileInputStream( configFile ); in = new FileInputStream( configFile );
config.load( new InputStreamReader( in, "UTF-8" ) ); config.load( new InputStreamReader( in, "UTF-8" ) );
} }
} }
catch ( IOException e ) { catch ( IOException e ) {
log.error( "Error loading config.", e ); log.error( "Error loading config", e );
} }
finally { finally {
try {if ( in != null ) in.close();} try {if ( in != null ) in.close();}
@ -381,14 +390,14 @@ public class SlipstreamCLI {
log.info( "Using FTL dats path from config: "+ datsPath ); log.info( "Using FTL dats path from config: "+ datsPath );
datsDir = new File( datsPath ); datsDir = new File( datsPath );
if ( FTLUtilities.isDatsDirValid( datsDir ) == false ) { 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; datsDir = null;
} }
} else { } else {
log.error( "No FTL dats path previously set." ); log.error( "No FTL dats path previously set" );
} }
if ( datsDir == null ) { 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 ); System.exit( 1 );
} }

View file

@ -19,22 +19,34 @@ public class FTLUtilities {
/** /**
* Confirms the FTL resources dir exists and contains the dat files. * 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! * Note: Do d.getCanonicalFile() to resolve any symlinks first!
*/ */
public static boolean isDatsDirValid( File d ) { public static boolean isDatsDirValid( File d ) {
if ( !d.exists() || !d.isDirectory() ) return false; 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; if ( new File( d, "ftl.dat" ).exists() ) return true;
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. * 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() { public static File findDatsDir() {
String steamPath = "Steam/steamapps/common/FTL Faster Than Light/resources"; String steamPath = "Steam/steamapps/common/FTL Faster Than Light";
String gogPath = "GOG.com/Faster Than Light/resources"; String gogPath = "GOG.com/Faster Than Light";
String humblePath = "FTL/resources"; String humblePath = "FTL";
String programFiles86 = System.getenv( "ProgramFiles(x86)" ); String programFiles86 = System.getenv( "ProgramFiles(x86)" );
String programFiles = System.getenv( "ProgramFiles" ); 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 ), steamPath ) );
candidates.add( new File( new File( programFiles86 ), gogPath ) ); candidates.add( new File( new File( programFiles86 ), gogPath ) );
candidates.add( new File( new File( programFiles86 ), humblePath ) ); 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 ) { if ( programFiles != null ) {
candidates.add( new File( new File( programFiles ), steamPath ) ); candidates.add( new File( new File( programFiles ), steamPath ) );
candidates.add( new File( new File( programFiles ), gogPath ) ); candidates.add( new File( new File( programFiles ), gogPath ) );
candidates.add( new File( new File( programFiles ), humblePath ) ); 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. // Linux - Steam.
if ( xdgDataHome != null ) { 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" ) );
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 ) { 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" ) );
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 (x86)/"+ humblePath ) );
candidates.add( new File( winePrefix +"/drive_c/Program Files/"+ gogPath ) ); 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/"+ 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. // OSX - Steam.
if ( home != null ) { if ( home != null ) {
@ -113,28 +144,30 @@ public class FTLUtilities {
String message = ""; String message = "";
message += "You will now be prompted to locate FTL manually.\n"; message += "You will now be prompted to locate FTL manually.\n";
message += "Select '(FTL dir)/resources/data.dat'.\n"; message += "Look in {FTL dir} to select 'ftl.dat' or 'data.dat'.\n";
message += "Or 'FTL.app', if you're on OSX."; 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 ); JOptionPane.showMessageDialog( parentComponent, message, "Find FTL", JOptionPane.INFORMATION_MESSAGE );
final JFileChooser fc = new JFileChooser(); 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.setFileHidingEnabled( false );
fc.addChoosableFileFilter(new FileFilter() { fc.addChoosableFileFilter(new FileFilter() {
@Override @Override
public String getDescription() { public String getDescription() {
return "FTL Data File - (FTL dir)/resources/data.dat"; return "FTL Data File - ftl.dat|data.dat";
} }
@Override @Override
public boolean accept( File f ) { 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 ) { if ( fc.showOpenDialog( parentComponent ) == JFileChooser.APPROVE_OPTION ) {
File f = fc.getSelectedFile(); File f = fc.getSelectedFile();
if ( f.getName().equals( "data.dat" ) ) { if ( f.getName().equals( "ftl.dat" ) || f.getName().equals( "data.dat" ) ) {
result = f.getParentFile(); result = f.getParentFile();
} }
else if ( f.getName().endsWith( ".app" ) && f.isDirectory() ) { else if ( f.getName().endsWith( ".app" ) && f.isDirectory() ) {
@ -152,29 +185,60 @@ public class FTLUtilities {
return null; return null;
} }
/** /**
* Returns the executable that will launch FTL, or null. * Returns the executable that will launch FTL, or null.
* *
* On Windows, FTLGame.exe is one dir above "resources/". * FTL 1.01-1.5.13:
* On Linux, FTL is a script, one dir above "resources/". * 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). * On OSX, FTL.app is the grandparent dir itself (a bundle).
*/ */
public static File findGameExe( File datsDir ) { public static File findGameExe( File datsDir ) {
File result = null; File result = null;
if ( System.getProperty( "os.name" ).startsWith( "Windows" ) ) { if ( System.getProperty( "os.name" ).startsWith( "Windows" ) ) {
File ftlDir = datsDir.getParentFile();
if ( ftlDir != null ) { for ( File candidateDir : new File[] {datsDir, datsDir.getParentFile()} ) {
File exeFile = new File( ftlDir, "FTLGame.exe" ); if ( candidateDir == null ) continue;
if ( exeFile.exists() ) result = exeFile;
File exeFile = new File( candidateDir, "FTLGame.exe" );
if ( exeFile.exists() ) {
result = exeFile;
break;
}
} }
} }
else if ( System.getProperty( "os.name" ).equals( "Linux" ) ) { else if ( System.getProperty( "os.name" ).equals( "Linux" ) ) {
File ftlDir = datsDir.getParentFile();
if ( ftlDir != null ) { for ( File candidateDir : new File[] {datsDir, datsDir.getParentFile()} ) {
File exeFile = new File( ftlDir, "FTL" ); if ( candidateDir == null ) continue;
if ( exeFile.exists() ) result = exeFile;
File exeFile = new File( candidateDir, "FTL" );
if ( exeFile.exists() ) {
result = exeFile;
break;
}
} }
} }
else if ( System.getProperty( "os.name" ).contains( "OS X" ) ) { else if ( System.getProperty( "os.name" ).contains( "OS X" ) ) {
@ -189,16 +253,16 @@ public class FTLUtilities {
} }
} }
} }
return result; return result;
} }
/** /**
* Returns the executable that will launch Steam, or null. * Returns the executable that will launch Steam, or null.
* *
* On Windows, Steam.exe. * On Windows, "Steam.exe".
* On Linux, steam is a script. ( http://moritzmolch.com/815 ) * On Linux, "steam" is a script. ( http://moritzmolch.com/815 )
* On OSX, Steam.app is the grandparent dir itself (a bundle). * On OSX, "Steam.app" is a bundle.
* *
* The args to launch FTL are: ["-applaunch", STEAM_APPID_FTL] * The args to launch FTL are: ["-applaunch", STEAM_APPID_FTL]
*/ */
@ -237,7 +301,6 @@ public class FTLUtilities {
return result; return result;
} }
/** /**
* Launches an executable. * Launches an executable.
* *
@ -245,6 +308,8 @@ public class FTLUtilities {
* On Linux, a binary or script. * On Linux, a binary or script.
* On OSX, an *.app bundle dir. * On OSX, an *.app bundle dir.
* *
* OSX bundles are executed with: "open -a bundle.app".
*
* @param exeFile see findGameExe() or findSteamExe() * @param exeFile see findGameExe() or findSteamExe()
* @param exeArgs arguments for the executable * @param exeArgs arguments for the executable
* @return a Process object, or null * @return a Process object, or null
@ -278,7 +343,6 @@ public class FTLUtilities {
return result; return result;
} }
/** /**
* Returns the directory for user profiles and saved games, or null. * Returns the directory for user profiles and saved games, or null.
*/ */

View file

@ -15,7 +15,10 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import net.vhati.ftldat.AbstractPack; import net.vhati.ftldat.AbstractPack;
import net.vhati.ftldat.AbstractPack.RepackResult;
import net.vhati.ftldat.FTLPack; import net.vhati.ftldat.FTLPack;
import net.vhati.ftldat.PkgPack;
import net.vhati.ftldat.PackContainer;
import net.vhati.ftldat.PackUtilities; import net.vhati.ftldat.PackUtilities;
import net.vhati.modmanager.core.ModPatchObserver; import net.vhati.modmanager.core.ModPatchObserver;
import net.vhati.modmanager.core.ModUtilities; import net.vhati.modmanager.core.ModUtilities;
@ -36,8 +39,8 @@ public class ModPatchThread extends Thread {
private Thread shutdownHook = null; private Thread shutdownHook = null;
private List<File> modFiles = new ArrayList<File>(); private List<File> modFiles = new ArrayList<File>();
private BackedUpDat dataDat = null; private File datsDir = null;
private BackedUpDat resDat = null; private File backupDir = null;
private boolean globalPanic = false; private boolean globalPanic = false;
private ModPatchObserver observer = null; private ModPatchObserver observer = null;
@ -48,10 +51,11 @@ public class ModPatchThread extends Thread {
private final int progRepackMax = 5; private final int progRepackMax = 5;
private int progMilestone = 0; private int progMilestone = 0;
public ModPatchThread( List<File> modFiles, BackedUpDat dataDat, BackedUpDat resDat, boolean globalPanic, ModPatchObserver observer ) {
public ModPatchThread( List<File> modFiles, File datsDir, File backupDir, boolean globalPanic, ModPatchObserver observer ) {
this.modFiles.addAll( modFiles ); this.modFiles.addAll( modFiles );
this.dataDat = dataDat; this.datsDir = datsDir;
this.resDat = resDat; this.backupDir = backupDir;
this.globalPanic = globalPanic; this.globalPanic = globalPanic;
this.observer = observer; this.observer = observer;
} }
@ -103,10 +107,7 @@ public class ModPatchThread extends Thread {
observer.patchingProgress( 0, progMax ); observer.patchingProgress( 0, progMax );
BackedUpDat[] allDats = new BackedUpDat[] {dataDat, resDat}; PackContainer packContainer = null;
FTLPack dataP = null;
FTLPack resP = null;
try { try {
int backupsCreated = 0; int backupsCreated = 0;
@ -114,20 +115,34 @@ public class ModPatchThread extends Thread {
int modsInstalled = 0; int modsInstalled = 0;
int datsRepacked = 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<BackedUpDat> backedUpDats = new ArrayList<BackedUpDat>( 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. // Don't let dats be read-only.
for ( BackedUpDat dat : allDats ) { for ( BackedUpDat bud : backedUpDats ) {
if ( dat.datFile.exists() ) dat.datFile.setWritable( true ); if ( bud.datFile.exists() ) bud.datFile.setWritable( true );
} }
// Create backup dats, if necessary. // Create backup dats, if necessary.
for ( BackedUpDat dat : allDats ) { for ( BackedUpDat bud : backedUpDats ) {
if ( !dat.bakFile.exists() ) { if ( !bud.bakFile.exists() ) {
log.info( String.format( "Backing up \"%s\".", dat.datFile.getName() ) ); log.info( String.format( "Backing up \"%s\".", bud.datFile.getName() ) );
observer.patchingStatus( String.format( "Backing up \"%s\".", dat.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++; backupsCreated++;
observer.patchingProgress( progMilestone + progBackupMax/allDats.length*backupsCreated, progMax ); observer.patchingProgress( progMilestone + progBackupMax/backedUpDats.size()*backupsCreated, progMax );
if ( !keepRunning ) return false; if ( !keepRunning ) return false;
} }
@ -136,17 +151,17 @@ public class ModPatchThread extends Thread {
observer.patchingProgress( progMilestone, progMax ); observer.patchingProgress( progMilestone, progMax );
observer.patchingStatus( null ); observer.patchingStatus( null );
if ( backupsCreated != allDats.length ) { if ( backupsCreated != backedUpDats.size() ) {
// Clobber current dat files with their respective backups. // Clobber current dat files with their respective backups.
// But don't bother if we made those backups just now. // But don't bother if we made those backups just now.
for ( BackedUpDat dat : allDats ) { for ( BackedUpDat bud : backedUpDats ) {
log.info( String.format( "Restoring vanilla \"%s\"...", dat.datFile.getName() ) ); log.info( String.format( "Restoring vanilla \"%s\"...", bud.datFile.getName() ) );
observer.patchingStatus( String.format( "Restoring vanilla \"%s\"...", dat.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++; datsClobbered++;
observer.patchingProgress( progMilestone + progClobberMax/allDats.length*datsClobbered, progMax ); observer.patchingProgress( progMilestone + progClobberMax/backedUpDats.size()*datsClobbered, progMax );
if ( !keepRunning ) return false; if ( !keepRunning ) return false;
} }
@ -161,30 +176,48 @@ public class ModPatchThread extends Thread {
return true; return true;
} }
dataP = new FTLPack( dataDat.datFile, "r+" ); packContainer = new PackContainer();
resP = new FTLPack( resDat.datFile, "r+" ); if ( ftlDatFile.exists() ) { // FTL 1.6.1.
AbstractPack ftlPack = new PkgPack( ftlDatFile, "r+" );
Map<String,AbstractPack> topFolderMap = new HashMap<String,AbstractPack>(); packContainer.setPackFor( "audio/", ftlPack );
topFolderMap.put( "data", dataP ); packContainer.setPackFor( "data/", ftlPack );
topFolderMap.put( "audio", resP ); packContainer.setPackFor( "fonts/", ftlPack );
topFolderMap.put( "fonts", resP ); packContainer.setPackFor( "img/", ftlPack );
topFolderMap.put( "img", resP ); packContainer.setPackFor( null, ftlPack );
topFolderMap.put( "mod-appendix", null ); // 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. // Track modified innerPaths in case they're clobbered.
List<String> moddedItems = new ArrayList<String>(); List<String> moddedItems = new ArrayList<String>();
List<String> knownPaths = new ArrayList<String>(); List<String> knownPaths = new ArrayList<String>();
knownPaths.addAll( dataP.list() ); for ( AbstractPack pack : packContainer.getPacks() ) {
knownPaths.addAll( resP.list() ); knownPaths.addAll( pack.list() );
}
List<String> knownPathsLower = new ArrayList<String>( knownPaths.size() ); List<String> knownPathsLower = new ArrayList<String>( knownPaths.size() );
for ( String innerPath : knownPaths ) { for ( String innerPath : knownPaths ) {
knownPathsLower.add( innerPath.toLowerCase() ); knownPathsLower.add( innerPath.toLowerCase() );
} }
// Group1: parentPath, Group2: topFolder, Group3: fileName List<String> knownRoots = packContainer.getRoots();
Pattern pathPtn = Pattern.compile( "^(([^/]+)/(?:.*/)?)([^/]+)$" );
// Group1: parentPath/, Group2: root/, Group3: fileName.
Pattern pathPtn = Pattern.compile( "^(?:(([^/]+/)(?:.*/)?))?([^/]+)$" );
for ( File modFile : modFiles ) { for ( File modFile : modFiles ) {
if ( !keepRunning ) return false; if ( !keepRunning ) return false;
@ -216,12 +249,12 @@ public class ModPatchThread extends Thread {
} }
String parentPath = m.group( 1 ); String parentPath = m.group( 1 );
String topFolder = m.group( 2 ); String root = m.group( 2 );
String fileName = m.group( 3 ); String fileName = m.group( 3 );
AbstractPack ftlP = topFolderMap.get( topFolder ); AbstractPack pack = packContainer.getPackFor( innerPath );
if ( ftlP == null ) { if ( pack == null ) {
if ( !topFolderMap.containsKey( topFolder ) ) if ( !knownRoots.contains( root ) )
log.warn( String.format( "Unexpected innerPath: %s", innerPath ) ); log.warn( String.format( "Unexpected innerPath: %s", innerPath ) );
zis.closeEntry(); zis.closeEntry();
continue; continue;
@ -237,24 +270,24 @@ public class ModPatchThread extends Thread {
innerPath = parentPath + fileName.replaceAll( "[.](?:xml[.]append|append[.]xml)$", ".xml" ); innerPath = parentPath + fileName.replaceAll( "[.](?:xml[.]append|append[.]xml)$", ".xml" );
innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); 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 ) ); log.warn( String.format( "Non-existent innerPath wasn't appended: %s", innerPath ) );
} }
else { else {
InputStream mainStream = null; InputStream mainStream = null;
try { try {
mainStream = ftlP.getInputStream(innerPath); mainStream = pack.getInputStream(innerPath);
InputStream mergedStream = ModUtilities.patchXMLFile( mainStream, zis, "windows-1252", globalPanic, ftlP.getName()+":"+innerPath, modFile.getName()+":"+parentPath+fileName ); InputStream mergedStream = ModUtilities.patchXMLFile( mainStream, zis, "windows-1252", globalPanic, pack.getName()+":"+innerPath, modFile.getName()+":"+parentPath+fileName );
mainStream.close(); mainStream.close();
ftlP.remove( innerPath ); pack.remove( innerPath );
ftlP.add( innerPath, mergedStream ); pack.add( innerPath, mergedStream );
} }
finally { finally {
try {if ( mainStream != null ) mainStream.close();} try {if ( mainStream != null ) mainStream.close();}
catch ( IOException e ) {} catch ( IOException e ) {}
} }
if ( !moddedItems.contains(innerPath) ) { if ( !moddedItems.contains( innerPath ) ) {
moddedItems.add( innerPath ); moddedItems.add( innerPath );
} }
} }
@ -263,25 +296,25 @@ public class ModPatchThread extends Thread {
innerPath = parentPath + fileName.replaceAll( "[.](?:xml[.]rawappend|rawappend[.]xml)$", ".xml" ); innerPath = parentPath + fileName.replaceAll( "[.](?:xml[.]rawappend|rawappend[.]xml)$", ".xml" );
innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); 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 ) ); log.warn( String.format( "Non-existent innerPath wasn't raw appended: %s", innerPath ) );
} }
else { else {
log.warn( String.format( "Appending xml as raw text: %s", innerPath ) ); log.warn( String.format( "Appending xml as raw text: %s", innerPath ) );
InputStream mainStream = null; InputStream mainStream = null;
try { try {
mainStream = ftlP.getInputStream(innerPath); mainStream = pack.getInputStream( innerPath );
InputStream mergedStream = ModUtilities.appendXMLFile( mainStream, zis, "windows-1252", ftlP.getName()+":"+innerPath, modFile.getName()+":"+parentPath+fileName ); InputStream mergedStream = ModUtilities.appendXMLFile( mainStream, zis, "windows-1252", pack.getName()+":"+innerPath, modFile.getName()+":"+parentPath+fileName );
mainStream.close(); mainStream.close();
ftlP.remove( innerPath ); pack.remove( innerPath );
ftlP.add( innerPath, mergedStream ); pack.add( innerPath, mergedStream );
} }
finally { finally {
try {if ( mainStream != null ) mainStream.close();} try {if ( mainStream != null ) mainStream.close();}
catch ( IOException e ) {} catch ( IOException e ) {}
} }
if ( !moddedItems.contains(innerPath) ) { if ( !moddedItems.contains( innerPath ) ) {
moddedItems.add( 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)" ); 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 ); moddedItems.add( innerPath );
} else { } else {
log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) ); log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) );
} }
if ( ftlP.contains( innerPath ) ) if ( pack.contains( innerPath ) )
ftlP.remove( innerPath ); pack.remove( innerPath );
ftlP.add( innerPath, fixedStream ); pack.add( innerPath, fixedStream );
} }
else if ( fileName.endsWith( ".xml" ) ) { else if ( fileName.endsWith( ".xml" ) ) {
innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); innerPath = checkCase( innerPath, knownPaths, knownPathsLower );
InputStream fixedStream = ModUtilities.rebuildXMLFile( zis, "windows-1252", modFile.getName()+":"+parentPath+fileName ); InputStream fixedStream = ModUtilities.rebuildXMLFile( zis, "windows-1252", modFile.getName()+":"+parentPath+fileName );
if ( !moddedItems.contains(innerPath) ) { if ( !moddedItems.contains( innerPath ) ) {
moddedItems.add( innerPath ); moddedItems.add( innerPath );
} else { } else {
log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) ); log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) );
} }
if ( ftlP.contains( innerPath ) ) if ( pack.contains( innerPath ) )
ftlP.remove( innerPath ); pack.remove( innerPath );
ftlP.add( innerPath, fixedStream ); pack.add( innerPath, fixedStream );
} }
else if ( fileName.endsWith( ".txt" ) ) { else if ( fileName.endsWith( ".txt" ) ) {
innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); 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)" ); 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 ); moddedItems.add( innerPath );
} else { } else {
log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) ); log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) );
} }
if ( ftlP.contains( innerPath ) ) if ( pack.contains( innerPath ) )
ftlP.remove( innerPath ); pack.remove( innerPath );
ftlP.add( innerPath, fixedStream ); pack.add( innerPath, fixedStream );
} }
else { else {
innerPath = checkCase( innerPath, knownPaths, knownPathsLower ); innerPath = checkCase( innerPath, knownPaths, knownPathsLower );
if ( !moddedItems.contains(innerPath) ) { if ( !moddedItems.contains( innerPath ) ) {
moddedItems.add( innerPath ); moddedItems.add( innerPath );
} else { } else {
log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) ); log.warn( String.format( "Clobbering earlier mods: %s", innerPath ) );
} }
if ( ftlP.contains( innerPath ) ) if ( pack.contains( innerPath ) )
ftlP.remove( innerPath ); pack.remove( innerPath );
ftlP.add( innerPath, zis ); pack.add( innerPath, zis );
} }
zis.closeEntry(); zis.closeEntry();
@ -378,16 +411,17 @@ public class ModPatchThread extends Thread {
observer.patchingProgress( progMilestone, progMax ); observer.patchingProgress( progMilestone, progMax );
// Prune 'removed' files from dats. // Prune 'removed' files from dats.
for ( AbstractPack ftlP : new AbstractPack[]{dataP, resP} ) { for ( AbstractPack pack : packContainer.getPacks() ) {
if ( ftlP instanceof FTLPack ) { observer.patchingStatus( String.format( "Repacking \"%s\"...", pack.getName() ) );
observer.patchingStatus( String.format( "Repacking \"%s\"...", ftlP.getName() ) );
long bytesChanged = ((FTLPack)ftlP).repack().bytesChanged; AbstractPack.RepackResult repackResult = pack.repack();
log.info( String.format( "Repacked \"%s\" (%d bytes affected)", ftlP.getName(), bytesChanged ) ); if ( repackResult != null ) {
long bytesChanged = repackResult.bytesChanged;
datsRepacked++; log.info( String.format( "Repacked \"%s\" (%d bytes affected)", pack.getName(), bytesChanged ) );
observer.patchingProgress( progMilestone + progRepackMax/allDats.length*datsRepacked, progMax );
} }
datsRepacked++;
observer.patchingProgress( progMilestone + progRepackMax/backedUpDats.size()*datsRepacked, progMax );
} }
progMilestone += progRepackMax; progMilestone += progRepackMax;
observer.patchingProgress( progMilestone, progMax ); observer.patchingProgress( progMilestone, progMax );
@ -396,11 +430,12 @@ public class ModPatchThread extends Thread {
return true; return true;
} }
finally { finally {
try {if ( dataP != null ) dataP.close();} if ( packContainer != null ) {
catch( Exception e ) {} for ( AbstractPack pack : packContainer.getPacks() ) {
try {pack.close();}
try {if ( resP != null ) resP.close();} catch( Exception e ) {}
catch( Exception e ) {} }
}
} }
} }

View file

@ -2,8 +2,10 @@ package net.vhati.modmanager.ui;
import java.awt.Frame; import java.awt.Frame;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream; import java.io.InputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
@ -11,6 +13,7 @@ import javax.swing.SwingUtilities;
import net.vhati.ftldat.AbstractPack; import net.vhati.ftldat.AbstractPack;
import net.vhati.ftldat.FolderPack; import net.vhati.ftldat.FolderPack;
import net.vhati.ftldat.FTLPack; import net.vhati.ftldat.FTLPack;
import net.vhati.ftldat.PkgPack;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -22,24 +25,24 @@ public class DatExtractDialog extends ProgressDialog {
private boolean started = false; private boolean started = false;
private File extractDir; private File extractDir = null;
private File[] datFiles; private File datsDir = null;
private DatExtractThread workerThread = null; private DatExtractThread workerThread = null;
public DatExtractDialog( Frame owner, File extractDir, File[] datFiles ) { public DatExtractDialog( Frame owner, File extractDir, File datsDir ) {
super( owner, false ); super( owner, false );
this.setTitle( "Extracting..." ); this.setTitle( "Extracting..." );
this.extractDir = extractDir; this.extractDir = extractDir;
this.datFiles = datFiles; this.datsDir = datsDir;
this.setSize( 400, 160 ); this.setSize( 400, 160 );
this.setMinimumSize( this.getPreferredSize() ); this.setMinimumSize( this.getPreferredSize() );
this.setLocationRelativeTo( owner ); 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 class DatExtractThread extends Thread {
private File extractDir; private File extractDir = null;
private File[] datFiles; private File datsDir = null;
public DatExtractThread( File extractDir, File[] datFiles ) {
public DatExtractThread( File extractDir, File datsDir ) {
this.extractDir = extractDir; this.extractDir = extractDir;
this.datFiles = datFiles; this.datsDir = datsDir;
} }
@Override @Override
public void run() { public void run() {
AbstractPack srcP = null; AbstractPack dstPack = null;
AbstractPack dstP = null; List<AbstractPack> srcPacks = new ArrayList<AbstractPack>( 2 );
InputStream is = null; InputStream is = null;
int progress = 0; int progress = 0;
try { 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(); if ( !extractDir.exists() ) extractDir.mkdirs();
dstP = new FolderPack( extractDir ); dstPack = new FolderPack( extractDir );
for ( File datFile : datFiles ) { for ( AbstractPack srcPack : srcPacks ) {
srcP = new FTLPack( datFile, "r" );
progress = 0; progress = 0;
List<String> innerPaths = srcP.list(); List<String> innerPaths = srcPack.list();
setProgressLater( progress, innerPaths.size() ); setProgressLater( progress, innerPaths.size() );
for ( String innerPath : innerPaths ) { for ( String innerPath : innerPaths ) {
setStatusTextLater( innerPath ); setStatusTextLater( innerPath );
if ( dstP.contains( innerPath ) ) { if ( dstPack.contains( innerPath ) ) {
log.info( "While extracting resources, this file was overwritten: "+ innerPath ); log.info( "While extracting resources, this file was overwritten: "+ innerPath );
dstP.remove( innerPath ); dstPack.remove( innerPath );
} }
is = srcP.getInputStream( innerPath ); is = srcPack.getInputStream( innerPath );
dstP.add( innerPath, is ); dstPack.add( innerPath, is );
setProgressLater( progress++ ); setProgressLater( progress++ );
} }
srcP.close(); srcPack.close();
} }
setTaskOutcomeLater( true, null ); setTaskOutcomeLater( true, null );
} }
catch ( Exception e ) { catch ( Exception e ) {
log.error( "Error extracting dats.", e ); log.error( "Error extracting dats", e );
setTaskOutcomeLater( false, e ); setTaskOutcomeLater( false, e );
} }
finally { finally {
try {if ( is != null ) is.close();} try {if ( is != null ) is.close();}
catch ( IOException e ) {} catch ( IOException e ) {}
try {if ( srcP != null ) srcP.close();} try {if ( dstPack != null ) dstPack.close();}
catch ( IOException e ) {} catch ( IOException e ) {}
try {if ( dstP != null ) dstP.close();} for ( AbstractPack pack : srcPacks ) {
catch ( IOException e ) {} try {pack.close();}
catch ( IOException ex ) {}
}
} }
} }
} }

View file

@ -60,7 +60,6 @@ import net.vhati.modmanager.core.ModDB;
import net.vhati.modmanager.core.ModFileInfo; import net.vhati.modmanager.core.ModFileInfo;
import net.vhati.modmanager.core.ModInfo; import net.vhati.modmanager.core.ModInfo;
import net.vhati.modmanager.core.ModPatchThread; import net.vhati.modmanager.core.ModPatchThread;
import net.vhati.modmanager.core.ModPatchThread.BackedUpDat;
import net.vhati.modmanager.core.ModsScanObserver; import net.vhati.modmanager.core.ModsScanObserver;
import net.vhati.modmanager.core.ModsScanThread; import net.vhati.modmanager.core.ModsScanThread;
import net.vhati.modmanager.core.ModUtilities; 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" ) ); 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 ); ModPatchDialog patchDlg = new ModPatchDialog( this, true );
String neverRunFtl = appConfig.getProperty( "never_run_ftl", "false" ); String neverRunFtl = appConfig.getProperty( "never_run_ftl", "false" );
@ -705,7 +697,7 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse
log.info( "" ); log.info( "" );
log.info( "Patching..." ); log.info( "Patching..." );
log.info( "" ); log.info( "" );
ModPatchThread patchThread = new ModPatchThread( modFiles, dataDat, resDat, false, patchDlg ); ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, false, patchDlg );
patchThread.setDefaultUncaughtExceptionHandler( this ); patchThread.setDefaultUncaughtExceptionHandler( this );
patchThread.start(); patchThread.start();
@ -780,11 +772,8 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse
File extractDir = extractChooser.getSelectedFile(); File extractDir = extractChooser.getSelectedFile();
File datsDir = new File( appConfig.getProperty( "ftl_dats_path" ) ); 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.getWorkerThread().setDefaultUncaughtExceptionHandler( this );
extractDlg.extract(); extractDlg.extract();
extractDlg.setVisible( true ); extractDlg.setVisible( true );
@ -792,9 +781,8 @@ public class ManagerFrame extends JFrame implements ActionListener, ModsScanObse
else if ( source == sandboxMenuItem ) { else if ( source == sandboxMenuItem ) {
setStatusText( "" ); setStatusText( "" );
File datsDir = new File( appConfig.getProperty( "ftl_dats_path" ) ); 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.addWindowListener( nerfListener );
sandboxFrame.setSize( 800, 600 ); sandboxFrame.setSize( 800, 600 );
sandboxFrame.setLocationRelativeTo( null ); sandboxFrame.setLocationRelativeTo( null );

View file

@ -13,6 +13,7 @@ import java.awt.event.KeyEvent;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.io.IOException; import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
@ -54,7 +55,9 @@ import javax.swing.tree.TreePath;
import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotRedoException;
import javax.swing.undo.UndoManager; import javax.swing.undo.UndoManager;
import net.vhati.ftldat.AbstractPack;
import net.vhati.ftldat.FTLPack; import net.vhati.ftldat.FTLPack;
import net.vhati.ftldat.PkgPack;
import net.vhati.modmanager.core.ModUtilities; import net.vhati.modmanager.core.ModUtilities;
import net.vhati.modmanager.core.SloppyXMLOutputProcessor; import net.vhati.modmanager.core.SloppyXMLOutputProcessor;
import net.vhati.modmanager.core.XMLPatcher; import net.vhati.modmanager.core.XMLPatcher;
@ -72,7 +75,7 @@ public class ModXMLSandbox extends JFrame implements ActionListener {
private UndoManager undoManager = new UndoManager(); private UndoManager undoManager = new UndoManager();
private Document mainDoc = null; private Document mainDoc = null;
private File dataDatFile; private File datsDir;
private JTabbedPane areasPane; private JTabbedPane areasPane;
private JScrollPane mainScroll; private JScrollPane mainScroll;
@ -91,11 +94,11 @@ public class ModXMLSandbox extends JFrame implements ActionListener {
private JLabel statusLbl; private JLabel statusLbl;
public ModXMLSandbox( File dataDatFile ) { public ModXMLSandbox( File datsDir ) {
super( "Mod XML Sandbox" ); super( "Mod XML Sandbox" );
this.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); this.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
this.dataDatFile = dataDatFile; this.datsDir = datsDir;
Font sandboxFont = new Font( "Monospaced", Font.PLAIN, 13 ); Font sandboxFont = new Font( "Monospaced", Font.PLAIN, 13 );
@ -319,17 +322,29 @@ public class ModXMLSandbox extends JFrame implements ActionListener {
private void open() { private void open() {
messageArea.setText( "" ); messageArea.setText( "" );
FTLPack dataP = null; AbstractPack pack = null;
InputStream is = null; InputStream is = null;
try { try {
dataP = new FTLPack( dataDatFile, "r" ); File ftlDatFile = new File( datsDir, "ftl.dat" );
List<String> innerPaths = dataP.list(); 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<String> innerPaths = pack.list();
String innerPath = promptForInnerPath( innerPaths ); String innerPath = promptForInnerPath( innerPaths );
if ( innerPath == null ) return; if ( innerPath == null ) return;
is = dataP.getInputStream( innerPath ); is = pack.getInputStream( innerPath );
String mainText = ModUtilities.decodeText( is, dataDatFile.getName()+":"+innerPath ).text; String mainText = ModUtilities.decodeText( is, pack.getName()+":"+innerPath ).text;
is.close(); is.close();
mainText = mainText.replaceFirst( "<[?]xml [^>]*?[?]>", "" ); mainText = mainText.replaceFirst( "<[?]xml [^>]*?[?]>", "" );
@ -356,7 +371,7 @@ public class ModXMLSandbox extends JFrame implements ActionListener {
try {if ( is != null ) is.close();} try {if ( is != null ) is.close();}
catch ( IOException f ) {} catch ( IOException f ) {}
try {if ( dataP != null ) dataP.close();} try {if ( pack != null ) pack.close();}
catch ( IOException f ) {} catch ( IOException f ) {}
} }
} }