Added commandline interface
This commit is contained in:
parent
4a091297ed
commit
a585047427
13 changed files with 731 additions and 149 deletions
486
src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java
Normal file
486
src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java
Normal file
|
@ -0,0 +1,486 @@
|
|||
package net.vhati.modmanager.cli;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import net.vhati.ftldat.FTLDat;
|
||||
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;
|
||||
import net.vhati.modmanager.core.Report.ReportMessage;
|
||||
|
||||
import org.apache.commons.cli.BasicParser;
|
||||
import org.apache.commons.cli.OptionBuilder;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
public class SlipstreamCLI {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(SlipstreamCLI.class);
|
||||
|
||||
private static File backupDir = new File( "./backup/" );
|
||||
private static File modsDir = new File( "./mods/" );
|
||||
|
||||
|
||||
public static void main( String[] args ) {
|
||||
|
||||
BasicParser parser = new BasicParser();
|
||||
|
||||
Options options = new Options();
|
||||
options.addOption( OptionBuilder.withLongOpt( "extract-dats" )
|
||||
.withDescription( "extract FTL resources into a dir" )
|
||||
.hasArg()
|
||||
.withArgName("DIR")
|
||||
.create() );
|
||||
options.addOption( OptionBuilder.withLongOpt( "list-mods" )
|
||||
.withDescription( "list available mod names" )
|
||||
.create() );
|
||||
options.addOption( OptionBuilder.withLongOpt( "runftl" )
|
||||
.withDescription( "run the game" )
|
||||
.create() );
|
||||
options.addOption( OptionBuilder.withLongOpt( "patch" )
|
||||
.withDescription( "revert to vanilla and add named mods (if any)" )
|
||||
.create() );
|
||||
options.addOption( OptionBuilder.withLongOpt( "validate" )
|
||||
.withDescription( "check named mods for problems" )
|
||||
.create() );
|
||||
options.addOption( "h", "help", false, "display this help and exit" );
|
||||
options.addOption( OptionBuilder.withLongOpt( "version" )
|
||||
.withDescription( "output version information and exit" )
|
||||
.create() );
|
||||
CommandLine cmdline = null;
|
||||
try {
|
||||
cmdline = parser.parse( options, args, true );
|
||||
}
|
||||
catch( ParseException e ) {
|
||||
System.err.println( "Error parsing commandline: "+ e.getMessage() );
|
||||
System.exit( 1 );
|
||||
}
|
||||
|
||||
if ( cmdline.hasOption( "h" ) ) { // Exits.
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
|
||||
String helpHeader = "Perform actions against an FTL installation and/or a list of named mods."+ formatter.getNewLine();
|
||||
|
||||
String helpFooter = formatter.getNewLine();
|
||||
helpFooter += "Each MODFILE is a filename in the mods/ dir."+ formatter.getNewLine();
|
||||
helpFooter += "If a named mod is a directory, a temporary zip will be created.";
|
||||
|
||||
formatter.printHelp( "modman [OPTION] [MODFILE]...", helpHeader, options, helpFooter );
|
||||
System.exit( 0 );
|
||||
}
|
||||
if ( cmdline.hasOption( "version" ) ) { // Exits.
|
||||
System.out.println( getVersionMessage() );
|
||||
System.exit( 0 );
|
||||
}
|
||||
|
||||
DelayedDeleteHook deleteHook = new DelayedDeleteHook();
|
||||
Runtime.getRuntime().addShutdownHook( deleteHook );
|
||||
|
||||
if ( cmdline.hasOption( "validate" ) ) { // Exits (0/1).
|
||||
log.info( "Validating..." );
|
||||
|
||||
StringBuilder resultBuf = new StringBuilder();
|
||||
ReportFormatter formatter = new ReportFormatter();
|
||||
boolean anyInvalid = false;
|
||||
|
||||
for ( String modFileName : cmdline.getArgs() ) {
|
||||
File modFile = new File(modsDir, modFileName);
|
||||
|
||||
if ( modFile.isDirectory() ) {
|
||||
log.info( String.format( "Zipping dir: %s/", modFile.getName() ) );
|
||||
try {
|
||||
modFile = createTempMod( modFile );
|
||||
deleteHook.addDoomedFile( modFile );
|
||||
}
|
||||
catch ( IOException e ) {
|
||||
log.error( String.format( "Error zipping \"%s/\".", modFile.getName() ), e );
|
||||
|
||||
List<ReportMessage> tmpMessages = new ArrayList<ReportMessage>();
|
||||
tmpMessages.add( new ReportMessage( ReportMessage.SECTION, modFileName ) );
|
||||
tmpMessages.add( new ReportMessage( ReportMessage.EXCEPTION, e.getMessage() ) );
|
||||
|
||||
formatter.format( tmpMessages, resultBuf, 0 );
|
||||
resultBuf.append( "\n" );
|
||||
|
||||
anyInvalid = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Report validateReport = ModUtilities.validateModFile( modFile );
|
||||
|
||||
formatter.format( validateReport.messages, resultBuf, 0 );
|
||||
resultBuf.append( "\n" );
|
||||
|
||||
if ( validateReport.outcome == false ) anyInvalid = true;
|
||||
}
|
||||
if ( resultBuf.length() == 0 ) {
|
||||
resultBuf.append( "No mods were checked." );
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println( resultBuf.toString() );
|
||||
System.exit( anyInvalid ? 1 : 0 );
|
||||
}
|
||||
|
||||
File configFile = new File( "modman.cfg" );
|
||||
Properties config = getConfig( configFile );
|
||||
|
||||
if ( cmdline.hasOption( "list-mods" ) ) { // Exits.
|
||||
log.info( "Listing mods..." );
|
||||
|
||||
boolean allowZip = config.getProperty( "allow_zip", "false" ).equals( "true" );
|
||||
File[] modFiles = modsDir.listFiles( new ModAndDirFileFilter( allowZip, true ) );
|
||||
List<String> dirList = new ArrayList<String>();
|
||||
List<String> fileList = new ArrayList<String>();
|
||||
for ( File f : modFiles ) {
|
||||
if ( f.isDirectory() )
|
||||
dirList.add( f.getName() +"/" );
|
||||
else
|
||||
fileList.add( f.getName() );
|
||||
}
|
||||
Collections.sort( dirList );
|
||||
Collections.sort( fileList );
|
||||
for ( String s : dirList ) System.out.println( s );
|
||||
for ( String s : fileList ) System.out.println( s );
|
||||
|
||||
System.exit( 0 );
|
||||
}
|
||||
|
||||
File datsDir = null;
|
||||
if ( cmdline.hasOption( "extract-dats" ) ||
|
||||
cmdline.hasOption( "patch" ) ||
|
||||
cmdline.hasOption( "runftl" ) ) {
|
||||
datsDir = getDatsDir( config );
|
||||
}
|
||||
|
||||
if ( cmdline.hasOption( "extract-dats" ) ) { // Exits (0/1).
|
||||
log.info( "Extracting dats..." );
|
||||
|
||||
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};
|
||||
|
||||
FTLDat.AbstractPack srcP = null;
|
||||
FTLDat.AbstractPack dstP = null;
|
||||
InputStream is = null;
|
||||
try {
|
||||
if ( !extractDir.exists() ) extractDir.mkdirs();
|
||||
|
||||
dstP = new FTLDat.FolderPack( extractDir );
|
||||
|
||||
for ( File datFile : datFiles ) {
|
||||
srcP = new FTLDat.FTLPack( datFile, false );
|
||||
List<String> innerPaths = srcP.list();
|
||||
|
||||
for ( String innerPath : innerPaths ) {
|
||||
if ( dstP.contains( innerPath ) ) {
|
||||
log.info( "While extracting resources, this file was overwritten: "+ innerPath );
|
||||
dstP.remove( innerPath );
|
||||
}
|
||||
is = srcP.getInputStream( innerPath );
|
||||
dstP.add( innerPath, is );
|
||||
}
|
||||
srcP.close();
|
||||
}
|
||||
}
|
||||
catch ( IOException 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();}
|
||||
catch ( IOException ex ) {}
|
||||
|
||||
try {if ( dstP != null ) dstP.close();}
|
||||
catch ( IOException ex ) {}
|
||||
}
|
||||
|
||||
System.exit( 0 );
|
||||
}
|
||||
|
||||
if ( cmdline.hasOption( "patch" ) ) { // Exits sometimes (1 on failure).
|
||||
log.info( "Patching..." );
|
||||
|
||||
List<File> modFiles = new ArrayList<File>();
|
||||
for ( String modFileName : cmdline.getArgs() ) {
|
||||
File modFile = new File( modsDir, modFileName );
|
||||
|
||||
if ( modFile.isDirectory() ) {
|
||||
log.info( String.format( "Zipping dir: %s/", modFile.getName() ) );
|
||||
try {
|
||||
modFile = createTempMod( modFile );
|
||||
deleteHook.addDoomedFile( modFile );
|
||||
}
|
||||
catch ( IOException e ) {
|
||||
log.error( String.format( "Error zipping \"%s/\".", modFile.getName() ), e );
|
||||
System.exit( 1 );
|
||||
}
|
||||
}
|
||||
|
||||
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" );
|
||||
|
||||
SilentPatchObserver patchObserver = new SilentPatchObserver();
|
||||
ModPatchThread patchThread = new ModPatchThread( modFiles, dataDat, resDat, patchObserver );
|
||||
deleteHook.addWatchedThread( patchThread );
|
||||
|
||||
patchThread.start();
|
||||
while ( patchThread.isAlive() ) {
|
||||
try {patchThread.join();}
|
||||
catch ( InterruptedException e ) {}
|
||||
}
|
||||
|
||||
if ( !patchObserver.hasSucceeded() ) System.exit( 1 );
|
||||
}
|
||||
|
||||
if ( cmdline.hasOption( "runftl" ) ) { // Exits (0/1).
|
||||
log.info( "Running FTL..." );
|
||||
|
||||
File exeFile = FTLUtilities.findGameExe( datsDir );
|
||||
if ( exeFile != null ) {
|
||||
try {
|
||||
FTLUtilities.launchGame( exeFile );
|
||||
} catch ( Exception e ) {
|
||||
log.error( "Error launching FTL.", e );
|
||||
System.exit( 1 );
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.error( "Could not find FTL's executable." );
|
||||
System.exit( 1 );
|
||||
}
|
||||
|
||||
System.exit( 0 );
|
||||
}
|
||||
|
||||
System.exit( 0 );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads settings from a config file.
|
||||
*
|
||||
* If an error occurs, it'll be logged,
|
||||
* and default settings will be returned.
|
||||
*/
|
||||
private static Properties getConfig( File configFile ) {
|
||||
|
||||
Properties config = new Properties();
|
||||
config.setProperty( "allow_zip", "false" );
|
||||
config.setProperty( "ftl_dats_path", "" );
|
||||
config.setProperty( "never_run_ftl", "false" );
|
||||
config.setProperty( "use_default_ui", "false" );
|
||||
// "update_catalog" doesn't have a default.
|
||||
|
||||
// Read the config file.
|
||||
InputStream in = null;
|
||||
try {
|
||||
if ( configFile.exists() ) {
|
||||
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 );
|
||||
}
|
||||
finally {
|
||||
try {if ( in != null ) in.close();}
|
||||
catch ( IOException e ) {}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the validity of the config's dats path and returns it.
|
||||
* Or exits if the path is invalid.
|
||||
*/
|
||||
private static File getDatsDir( Properties config ) {
|
||||
File datsDir = null;
|
||||
String datsPath = config.getProperty( "ftl_dats_path", "" );
|
||||
|
||||
if ( datsPath.length() > 0 ) {
|
||||
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." );
|
||||
datsDir = null;
|
||||
}
|
||||
} else {
|
||||
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." );
|
||||
System.exit( 1 );
|
||||
}
|
||||
|
||||
return datsDir;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a temporary zip made from a directory.
|
||||
*
|
||||
* Empty subdirs will be omitted.
|
||||
* The archive will be not be deleted on exit (handle that elsewhere).
|
||||
*/
|
||||
private static File createTempMod( File dir ) throws IOException {
|
||||
File tempFile = File.createTempFile( dir.getName() +"_temp-", ".zip" );
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream( tempFile );
|
||||
ZipOutputStream zos = new ZipOutputStream( new BufferedOutputStream( fos ) );
|
||||
addDirToArchive( zos, dir, null );
|
||||
zos.close();
|
||||
}
|
||||
finally {
|
||||
try {if ( fos != null ) fos.close();}
|
||||
catch ( IOException e ) {}
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
private static void addDirToArchive( ZipOutputStream zos, File dir, String pathPrefix ) throws IOException {
|
||||
if ( pathPrefix == null ) pathPrefix = "";
|
||||
|
||||
for ( File f : dir.listFiles() ) {
|
||||
if ( f.isDirectory() ) {
|
||||
addDirToArchive( zos, f, pathPrefix + f.getName() +"/" );
|
||||
continue;
|
||||
}
|
||||
|
||||
FileInputStream is = null;
|
||||
try {
|
||||
is = new FileInputStream( f );
|
||||
zos.putNextEntry( new ZipEntry( pathPrefix + f.getName() ) );
|
||||
|
||||
byte[] buf = new byte[4096];
|
||||
int len;
|
||||
while ( (len = is.read(buf)) >= 0 ) {
|
||||
zos.write( buf, 0, len );
|
||||
}
|
||||
|
||||
zos.closeEntry();
|
||||
}
|
||||
finally {
|
||||
try {if ( is != null ) is.close();}
|
||||
catch ( IOException e ) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getVersionMessage() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append( String.format( "%s %s\n", FTLModManager.APP_NAME, FTLModManager.APP_VERSION ) );
|
||||
buf.append( "Copyright (C) 2013 David Millis\n" );
|
||||
buf.append( "\n" );
|
||||
buf.append( "This program is free software; you can redistribute it and/or modify\n" );
|
||||
buf.append( "it under the terms of the GNU General Public License as published by\n" );
|
||||
buf.append( "the Free Software Foundation; version 2.\n" );
|
||||
buf.append( "\n" );
|
||||
buf.append( "This program is distributed in the hope that it will be useful,\n" );
|
||||
buf.append( "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" );
|
||||
buf.append( "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" );
|
||||
buf.append( "GNU General Public License for more details.\n" );
|
||||
buf.append( "\n" );
|
||||
buf.append( "You should have received a copy of the GNU General Public License\n" );
|
||||
buf.append( "along with this program. If not, see http://www.gnu.org/licenses/.\n" );
|
||||
buf.append( "\n" );
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class SilentPatchObserver implements ModPatchObserver {
|
||||
private boolean done = false;
|
||||
private boolean succeeded = false;
|
||||
|
||||
@Override
|
||||
public void patchingProgress( final int value, final int max ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void patchingStatus( String message ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void patchingMod( File modFile ) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void patchingEnded( boolean outcome, Exception e ) {
|
||||
succeeded = outcome;
|
||||
done = true;
|
||||
}
|
||||
|
||||
public synchronized boolean isDone() { return done; }
|
||||
public synchronized boolean hasSucceeded() { return succeeded; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class ModAndDirFileFilter implements FileFilter {
|
||||
private boolean allowZip;
|
||||
private boolean allowDirs;
|
||||
|
||||
public ModAndDirFileFilter( boolean allowZip, boolean allowDirs ) {
|
||||
this.allowZip = allowZip;
|
||||
this.allowDirs = allowDirs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept( File f ) {
|
||||
if ( f.isDirectory() ) return allowDirs;
|
||||
|
||||
if ( f.getName().endsWith(".ftl") ) return true;
|
||||
|
||||
if ( allowZip ) {
|
||||
if ( f.getName().endsWith(".zip") ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue