diff --git a/pom.xml b/pom.xml
index 808ba86..3d2fabc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,6 +46,11 @@
jdom2
2.0.5
+
+ commons-cli
+ commons-cli
+ 1.2
+
diff --git a/readme_developers.txt b/readme_developers.txt
index 8397f65..a400263 100644
--- a/readme_developers.txt
+++ b/readme_developers.txt
@@ -39,6 +39,10 @@ This project depends on the following libraries.
http://logging.apache.org/log4j/2.x/
(JavaDocs are not available.)
+- Apache Commons CLI 1.2
+ http://commons.apache.org/proper/commons-cli/
+ (For JavaDocs, scroll down.)
+
Here's a batch file that builds when double-clicked (edit the vars).
diff --git a/skel_common/readme_changelog.txt b/skel_common/readme_changelog.txt
index 1007e94..69b91f6 100644
--- a/skel_common/readme_changelog.txt
+++ b/skel_common/readme_changelog.txt
@@ -1,5 +1,8 @@
Changelog
+???:
+- Added a commandline interface
+
1.1:
- Added a button to open the mods/ folder
- Added FTL resource extraction
diff --git a/skel_unix/modman-cli.sh b/skel_unix/modman-cli.sh
new file mode 100644
index 0000000..a5ad913
--- /dev/null
+++ b/skel_unix/modman-cli.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# Get the absolute path to this script's folder.
+if echo "$0" | awk '{exit(!/^\//);}'; then
+ maindir=$(dirname "$0");
+else
+ maindir=$PWD/$(dirname "$0");
+fi
+
+cd "${maindir}"
+java -jar modman.jar "$@"
diff --git a/src/main/java/net/vhati/ftldat/FTLDat.java b/src/main/java/net/vhati/ftldat/FTLDat.java
index 14326a8..875d252 100644
--- a/src/main/java/net/vhati/ftldat/FTLDat.java
+++ b/src/main/java/net/vhati/ftldat/FTLDat.java
@@ -81,11 +81,11 @@ public class FTLDat {
}
}
finally {
- try {if (is != null) is.close();}
- catch (IOException e) {}
+ try {if ( is != null ) is.close();}
+ catch ( IOException e ) {}
- try {if (os != null) os.close();}
- catch (IOException e) {}
+ try {if ( os != null ) os.close();}
+ catch ( IOException e ) {}
}
}
@@ -368,8 +368,8 @@ public class FTLDat {
}
}
finally {
- try {if (os != null) os.close();}
- catch (IOException e) {}
+ try {if ( os != null ) os.close();}
+ catch ( IOException e ) {}
}
}
@@ -388,8 +388,8 @@ public class FTLDat {
}
}
finally {
- try {if (is != null) is.close();}
- catch (IOException e) {}
+ try {if ( is != null ) is.close();}
+ catch ( IOException e ) {}
}
}
diff --git a/src/main/java/net/vhati/modmanager/FTLModManager.java b/src/main/java/net/vhati/modmanager/FTLModManager.java
index 665e887..e8a747c 100644
--- a/src/main/java/net/vhati/modmanager/FTLModManager.java
+++ b/src/main/java/net/vhati/modmanager/FTLModManager.java
@@ -13,6 +13,7 @@ import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
+import net.vhati.modmanager.cli.SlipstreamCLI;
import net.vhati.modmanager.core.ComparableVersion;
import net.vhati.modmanager.core.FTLUtilities;
import net.vhati.modmanager.ui.ManagerFrame;
@@ -25,13 +26,14 @@ public class FTLModManager {
private static final Logger log = LogManager.getLogger(FTLModManager.class);
- private static final String APP_NAME = "Slipstream Mod Manager";
- private static final ComparableVersion APP_VERSION = new ComparableVersion( "???" );
- private static final String APP_URL = "http://www.ftlgame.com/forum/viewtopic.php?f=12&t=17102";
- private static final String APP_AUTHOR = "Vhati";
+ public static final String APP_NAME = "Slipstream Mod Manager";
+ public static final ComparableVersion APP_VERSION = new ComparableVersion( "???" );
+ public static final String APP_URL = "http://www.ftlgame.com/forum/viewtopic.php?f=12&t=17102";
+ public static final String APP_AUTHOR = "Vhati";
public static void main( String[] args ) {
+ if ( args.length > 0 ) SlipstreamCLI.main( args );
log.debug( String.format( "%s v%s", APP_NAME, APP_VERSION ) );
log.debug( String.format( "%s %s", System.getProperty("os.name"), System.getProperty("os.version") ) );
diff --git a/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java
new file mode 100644
index 0000000..6876941
--- /dev/null
+++ b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java
@@ -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 tmpMessages = new ArrayList();
+ 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 dirList = new ArrayList();
+ List fileList = new ArrayList();
+ 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 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 modFiles = new ArrayList();
+ 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;
+ }
+ }
+}
diff --git a/src/main/java/net/vhati/modmanager/core/DelayedDeleteHook.java b/src/main/java/net/vhati/modmanager/core/DelayedDeleteHook.java
new file mode 100644
index 0000000..c14e016
--- /dev/null
+++ b/src/main/java/net/vhati/modmanager/core/DelayedDeleteHook.java
@@ -0,0 +1,74 @@
+package net.vhati.modmanager.core;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * A shutdown hook that waits on threads before deleting files.
+ *
+ * This hook's waiting will keep the VM alive until the threads complete.
+ *
+ * Usage:
+ * DelayedDeleteHook deleteHook = new DelayedDeleteHook();
+ * Runtime.getRuntime().addShutdownHook( deleteHook );
+ */
+public class DelayedDeleteHook extends Thread {
+ private LinkedHashSet watchedThreads = new LinkedHashSet();
+ private LinkedHashSet doomedFiles = new LinkedHashSet();
+
+ public synchronized void addWatchedThread( Thread t ) {
+ if ( watchedThreads == null )
+ throw new IllegalStateException( "Shutdown in progress" );
+ watchedThreads.add( t );
+ }
+
+ public synchronized void addDoomedFile( File f ) {
+ if ( doomedFiles == null )
+ throw new IllegalStateException( "Shutdown in progress" );
+ doomedFiles.add( f );
+ }
+
+ @Override
+ public void run() {
+ ArrayList pendingThreads;
+ ArrayList pendingFiles;
+ boolean interrupted = false;
+
+ synchronized ( this ) {
+ pendingThreads = new ArrayList( watchedThreads );
+ pendingFiles = new ArrayList( doomedFiles );
+ watchedThreads = null;
+ doomedFiles = null;
+ }
+
+ try {
+ // Wait on each thread.
+ Iterator it = pendingThreads.iterator();
+ while ( it.hasNext() ) {
+ Thread t = it.next();
+ while ( t.isAlive() ) {
+ try {
+ t.join();
+ }
+ catch ( InterruptedException e ) {
+ interrupted = true;
+ }
+ }
+ it.remove();
+ }
+
+ Collections.reverse( pendingFiles );
+ for ( File f : pendingFiles ) {
+ f.delete();
+ }
+ }
+ finally {
+ if ( interrupted ) Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/src/main/java/net/vhati/modmanager/core/ModPatchObserver.java b/src/main/java/net/vhati/modmanager/core/ModPatchObserver.java
index ec29dcd..591f1e8 100644
--- a/src/main/java/net/vhati/modmanager/core/ModPatchObserver.java
+++ b/src/main/java/net/vhati/modmanager/core/ModPatchObserver.java
@@ -31,5 +31,5 @@ public interface ModPatchObserver {
* Patching ended.
* If anything went wrong, e may be non-null.
*/
- public void patchingEnded( boolean success, Exception e );
+ public void patchingEnded( boolean outcome, Exception e );
}
diff --git a/src/main/java/net/vhati/modmanager/core/ModPatchThread.java b/src/main/java/net/vhati/modmanager/core/ModPatchThread.java
index b136cc9..93fc99a 100644
--- a/src/main/java/net/vhati/modmanager/core/ModPatchThread.java
+++ b/src/main/java/net/vhati/modmanager/core/ModPatchThread.java
@@ -63,10 +63,9 @@ public class ModPatchThread extends Thread {
keepRunning = false;
boolean interrupted = false;
try {
- while ( true ) {
+ while ( ModPatchThread.this.isAlive() ) {
try {
ModPatchThread.this.join();
- break;
}
catch ( InterruptedException e ) {
interrupted = true;
@@ -84,7 +83,7 @@ public class ModPatchThread extends Thread {
result = patch();
}
catch ( Exception e ) {
- log.error( "Patching failed. See log for details.", e );
+ log.error( "Patching failed.", e );
exception = e;
result = false;
}
diff --git a/src/main/java/net/vhati/modmanager/scraper/ForumScraper.java b/src/main/java/net/vhati/modmanager/scraper/ForumScraper.java
index e5c8a6b..bc50b01 100644
--- a/src/main/java/net/vhati/modmanager/scraper/ForumScraper.java
+++ b/src/main/java/net/vhati/modmanager/scraper/ForumScraper.java
@@ -14,6 +14,7 @@ import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
@@ -50,6 +51,13 @@ import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
+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;
@@ -69,83 +77,137 @@ public class ForumScraper {
ignoredURLs.add( "http://www.ftlgame.com/forum/viewtopic.php?f=12&t=11083" );
ignoredURLs.add( "http://www.ftlgame.com/forum/viewtopic.php?f=4&t=2938" );
ignoredURLs.add( "http://www.moddb.com/mods/better-planets-and-backgrounds/downloads/better-asteroids" );
+ ignoredURLs.add( "http://www.ftlgame.com/forum/viewtopic.php?f=4&t=2947" );
+ ignoredURLs.add( "http://www.ftlgame.com/forum/viewtopic.php?f=12&t=11604" );
// SpaceDock is an app.
ignoredURLs.add( "http://www.ftlgame.com/forum/viewtopic.php?f=11&t=16842" );
// Beginning Scrap Advantage is bundled in GMM.
ignoredURLs.add( "http://www.ftlgame.com/forum/viewtopic.php?f=4&t=2464" );
- List