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 {
}
/**
* 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
* be created when adding, removing or replacing files.
*/
@Override
public RepackResult repack() throws IOException {
long bytesChanged = 0;

View file

@ -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;

View file

@ -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;

View file

@ -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<ReportMessage> tmpMessages = new ArrayList<ReportMessage>();
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<AbstractPack> srcPacks = new ArrayList<AbstractPack>( 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<String> innerPaths = srcP.list();
for ( AbstractPack srcPack : srcPacks ) {
List<String> 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 );
}

View file

@ -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.
*/

View file

@ -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<File> modFiles = new ArrayList<File>();
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<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.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<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.
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<String,AbstractPack> topFolderMap = new HashMap<String,AbstractPack>();
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<String> moddedItems = new ArrayList<String>();
List<String> knownPaths = new ArrayList<String>();
knownPaths.addAll( dataP.list() );
knownPaths.addAll( resP.list() );
for ( AbstractPack pack : packContainer.getPacks() ) {
knownPaths.addAll( pack.list() );
}
List<String> knownPathsLower = new ArrayList<String>( knownPaths.size() );
for ( String innerPath : knownPaths ) {
knownPathsLower.add( innerPath.toLowerCase() );
}
// Group1: parentPath, Group2: topFolder, Group3: fileName
Pattern pathPtn = Pattern.compile( "^(([^/]+)/(?:.*/)?)([^/]+)$" );
List<String> 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 ) {}
}
}
}
}

View file

@ -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<AbstractPack> srcPacks = new ArrayList<AbstractPack>( 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<String> innerPaths = srcP.list();
List<String> 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 ) {}
}
}
}
}

View file

@ -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 );

View file

@ -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<String> 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<String> 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 ) {}
}
}