drafted out commands

This commit is contained in:
Leyla Becker 2025-08-17 10:46:16 -05:00
parent 0f138f61a8
commit 8ffdd5556b
3 changed files with 585 additions and 410 deletions

View file

@ -67,7 +67,7 @@
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>2.2.0</version>
<version>4.7.7</version>
</dependency>
</dependencies>

View file

@ -22,9 +22,6 @@ public class FTLModManager {
public static final String APP_NAME = "Slipstream Mod Manager";
public static final ComparableVersion APP_VERSION = new ComparableVersion("1.9.1");
public static final String APP_URL = "TODO";
public static final String APP_AUTHOR = "jan-leila";
public static void main(String[] args) {
// Redirect any libraries' java.util.Logging messages.
@ -43,7 +40,7 @@ public class FTLModManager {
encoder.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
encoder.start();
FileAppender<ILoggingEvent> fileAppender = new FileAppender<ILoggingEvent>();
FileAppender<ILoggingEvent> fileAppender = new FileAppender<>();
fileAppender.setContext(lc);
fileAppender.setName("LogFile");
fileAppender.setFile(new File("./modman-log.txt").getAbsolutePath());
@ -59,17 +56,8 @@ public class FTLModManager {
log.debug("OS: {} {}", System.getProperty("os.name"), System.getProperty("os.version"));
log.debug("VM: {}, {}, {}", System.getProperty("java.vm.name"), System.getProperty("java.version"), System.getProperty("os.arch"));
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException( Thread t, Throwable e ) {
log.error("Uncaught exception in thread: {}", t.toString(), e);
}
});
Thread.setDefaultUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception in thread: {}", t.toString(), e));
if (args.length == 0 ) {
System.out.println("Project called with no arguments.");
return;
}
SlipstreamCLI.main(args);
}
}

View file

@ -13,11 +13,7 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.IVersionProvider;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -53,330 +49,351 @@ public class SlipstreamCLI {
System.exit(1);
};
SlipstreamCommand slipstreamCmd = new SlipstreamCommand();
CommandLine commandLine = new CommandLine( slipstreamCmd );
try {
commandLine.parse( args );
}
catch ( ParameterException e ) {
//For multiple subcommands, e.getCommandLine() returns the one that failed.
CommandLine commandLine = new CommandLine(new BaseCommand())
.addSubcommand("start", new StartCommand())
.addSubcommand("patch", new PatchCommand())
.addSubcommand("list", new ListCommand())
.addSubcommand("clean", new CleanCommand());
System.err.println( "Error parsing commandline: "+ e.getMessage() );
System.exit( 1 );
commandLine.execute(args);
// try {
// commandLine.parse(args);
// }
// catch (ParameterException e) {
// //For multiple subcommands, e.getCommandLine() returns the one that failed.
//
// System.err.println("Error parsing commandline: "+ e.getMessage());
// System.exit(1);
// }
//
// ActionFlow actionFlow = getCommandAction(commandLine);
//
// switch (actionFlow) {
// case VERSION -> {
// commandLine.usage(System.out);
// System.exit(0);
// }
// case HELP -> {
// commandLine.printVersionHelp(System.out);
// System.exit(0);
// }
// }
// if (slipstreamCmd.validate) {
// boolean success = validate(slipstreamCmd.modFileNames);
// System.exit(success ? 0 : 1);
// }
//
// File configFile = new File("modman.cfg");
// SlipstreamConfig appConfig = new SlipstreamConfig(configFile);
//
// if (slipstreamCmd.listMods) {
// listMods(appConfig);
// System.exit(0);
// }
//
// File datsDir = null;
// if (slipstreamCmd.extractDatsDir != null ||
// slipstreamCmd.patch ||
// slipstreamCmd.runftl) {
// datsDir = getDatsDir(appConfig);
// }
//
// if (slipstreamCmd.extractDatsDir != null) {
// extractDatsDir(slipstreamCmd, datsDir);
// System.exit(0);
// }
//
// if (slipstreamCmd.patch) {
// boolean success = patch(slipstreamCmd, datsDir);
// if (!success) {
// System.exit(1);
// }
// }
//
// if (slipstreamCmd.runftl) {
// boolean success = runFtl(appConfig, datsDir);
// System.exit(success ? 0: 1);
// }
//
// System.exit(0);
}
if ( commandLine.isUsageHelpRequested() ) {
commandLine.usage( System.out );
System.exit( 0 );
}
if ( commandLine.isVersionHelpRequested() ) {
commandLine.printVersionHelp( System.out );
System.exit( 0 );
}
// private static ActionFlow getCommandAction(CommandLine commandLine) {
// if (commandLine.isUsageHelpRequested()) {
// return ActionFlow.HELP;
// }
// if (commandLine.isVersionHelpRequested()) {
// return ActionFlow.VERSION;
// }
//
// return null;
// }
if ( slipstreamCmd.validate ) {
boolean success = validate(slipstreamCmd.modFileNames);
System.exit( success ? 0 : 1);
}
// private static void extractDatsDir(RunCommand slipstreamCmd, File datsDir) {
// log.info("Extracting dats...");
//
// File extractDir = slipstreamCmd.extractDatsDir;
//
// 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()) {
// boolean success = extractDir.mkdirs();
// if (success) {
// log.error("Error extracting dats");
// System.exit(1);
// }
// };
//
// dstPack = new FolderPack(extractDir);
//
// for (AbstractPack srcPack : srcPacks) {
// List<String> innerPaths = srcPack.list();
//
// for (String innerPath : innerPaths) {
// if (dstPack.contains(innerPath)) {
// log.info("While extracting resources, this file was overwritten: "+ innerPath);
// dstPack.remove(innerPath);
// }
// is = srcPack.getInputStream(innerPath);
// dstPack.add(innerPath, is);
// }
// srcPack.close();
// }
// }
// catch (IOException e) {
// log.error("Error extracting dats", e);
// System.exit(1);
// }
// finally {
// try {if (is != null) is.close();}
// catch (IOException ignored) {}
//
// try {if (dstPack != null) dstPack.close();}
// catch (IOException ignored) {}
//
// for (AbstractPack pack : srcPacks) {
// try {pack.close();}
// catch (IOException ignored) {}
// }
// }
// }
//
// private static boolean patch(RunCommand slipstreamCmd, File datsDir) {
// log.info("Patching...");
//
// DelayedDeleteHook deleteHook = new DelayedDeleteHook();
// Runtime.getRuntime().addShutdownHook(deleteHook);
//
// List<File> modFiles = new ArrayList<File>();
// if (slipstreamCmd.modFileNames != null) {
// for (String modFileName : slipstreamCmd.modFileNames) {
// File modFile = new File(modsDir, modFileName);
//
// if (modFile.isDirectory()) {
// log.info("Zipping dir: {}/", modFile.getName());
// try {
// modFile = createTempMod(modFile);
// deleteHook.addDoomedFile(modFile);
// }
// catch (IOException e) {
// log.error("Error zipping dir: {}/", modFile.getName(), e);
// return false;
// }
// }
//
// modFiles.add(modFile);
// }
// }
//
// boolean globalPanic = slipstreamCmd.globalPanic;
//
// SilentPatchObserver patchObserver = new SilentPatchObserver();
// ModPatchThread patchThread = new ModPatchThread(modFiles, datsDir, backupDir, globalPanic, patchObserver);
// Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
// deleteHook.addWatchedThread(patchThread);
//
// patchThread.start();
// while (patchThread.isAlive()) {
// try {patchThread.join();}
// catch (InterruptedException ignored) {}
// }
//
// return patchObserver.hasSucceeded();
// }
File configFile = new File( "modman.cfg" );
SlipstreamConfig appConfig = new SlipstreamConfig( configFile );
// private static boolean validate(String[] modFileNames) {
// DelayedDeleteHook deleteHook = new DelayedDeleteHook();
// Runtime.getRuntime().addShutdownHook(deleteHook);
//
// log.info("Validating...");
//
// StringBuilder resultBuf = new StringBuilder();
// ReportFormatter formatter = new ReportFormatter();
// boolean anyInvalid = false;
//
// for (String modFileName : modFileNames) {
// File modFile = new File(modsDir, modFileName);
//
// if (modFile.isDirectory()) {
// log.info("Zipping dir: {}/", modFile.getName());
// try {
// modFile = createTempMod(modFile);
// deleteHook.addDoomedFile(modFile);
// }
// catch (IOException e) {
// log.error("Error zipping dir: {}/", 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) anyInvalid = true;
// }
// if (resultBuf.isEmpty()) {
// resultBuf.append("No mods were checked.");
// }
//
// System.out.println();
// System.out.println(resultBuf);
// return !anyInvalid;
// }
//
// private static void listMods(SlipstreamConfig appConfig) {
// log.info("Listing mods...");
//
// boolean allowZip = appConfig.getProperty(SlipstreamConfig.ALLOW_ZIP, "false").equals("true");
// File[] modFiles = modsDir.listFiles(new ModAndDirFileFilter(allowZip, true));
// List<String> dirList = new ArrayList<>();
// List<String> fileList = new ArrayList<>();
// assert modFiles != null;
// 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);
// }
//
// private static boolean runFtl(SlipstreamConfig appConfig, File datsDir) {
// log.info("Running FTL...");
//
// File exeFile = null;
// String[] exeArgs = null;
//
// // Try to run via Steam.
// if ("true".equals(appConfig.getProperty(SlipstreamConfig.RUN_STEAM_FTL, "false"))) {
//
// String steamPath = appConfig.getProperty(SlipstreamConfig.STEAM_EXE_PATH);
// if (!steamPath.isEmpty()) {
// exeFile = new File(steamPath);
//
// if (exeFile.exists()) {
// exeArgs = new String[] {"-applaunch", FTLUtilities.STEAM_APPID_FTL};
// }
// else {
// log.warn(String.format("%s does not exist: %s", SlipstreamConfig.STEAM_EXE_PATH, exeFile.getAbsolutePath()));
// exeFile = null;
// }
// }
//
// if (exeFile == null) {
// log.warn("Steam executable could not be found, so FTL will be launched directly");
// }
//
// }
// // Try to run directly.
// if (exeFile == null) {
// exeFile = FTLUtilities.findGameExe(datsDir);
//
// if (exeFile != null) {
// exeArgs = new String[0];
// } else {
// log.warn("FTL executable could not be found");
// }
// }
//
// if (exeFile != null) {
// try {
// FTLUtilities.launchExe(exeFile, exeArgs);
// }
// catch (Exception e) {
// log.error("Error launching FTL", e);
// return false;
// }
// }
// else {
// log.error("No executables were found to launch FTL");
// return false;
// }
//
// return true;
// }
if ( slipstreamCmd.listMods ) {
listMods(appConfig);
System.exit( 0 );
}
File datsDir = null;
if ( slipstreamCmd.extractDatsDir != null ||
slipstreamCmd.patch ||
slipstreamCmd.runftl ) {
datsDir = getDatsDir( appConfig );
}
if ( slipstreamCmd.extractDatsDir != null ) {
extractDatsDir(slipstreamCmd, datsDir);
System.exit( 0 );
}
if ( slipstreamCmd.patch ) {
boolean success = patch(slipstreamCmd, datsDir);
if (!success) {
System.exit( 1 );
}
}
if ( slipstreamCmd.runftl ) {
boolean success = runFtl(appConfig, datsDir);
System.exit(success ? 0 : 1 );
}
System.exit( 0 );
}
private static void extractDatsDir(SlipstreamCommand slipstreamCmd, File datsDir) {
log.info( "Extracting dats..." );
File extractDir = slipstreamCmd.extractDatsDir;
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() ) {
boolean success = extractDir.mkdirs();
if (success) {
log.error( "Error extracting dats");
System.exit( 1 );
}
};
dstPack = new FolderPack( extractDir );
for ( AbstractPack srcPack : srcPacks ) {
List<String> innerPaths = srcPack.list();
for ( String innerPath : innerPaths ) {
if ( dstPack.contains( innerPath ) ) {
log.info( "While extracting resources, this file was overwritten: "+ innerPath );
dstPack.remove( innerPath );
}
is = srcPack.getInputStream( innerPath );
dstPack.add( innerPath, is );
}
srcPack.close();
}
}
catch ( IOException e ) {
log.error( "Error extracting dats", e );
System.exit( 1 );
}
finally {
try {if ( is != null ) is.close();}
catch ( IOException ignored) {}
try {if ( dstPack != null ) dstPack.close();}
catch ( IOException ignored) {}
for ( AbstractPack pack : srcPacks ) {
try {pack.close();}
catch ( IOException ignored) {}
}
}
}
private static boolean patch(SlipstreamCommand slipstreamCmd, File datsDir) {
log.info( "Patching..." );
DelayedDeleteHook deleteHook = new DelayedDeleteHook();
Runtime.getRuntime().addShutdownHook( deleteHook );
List<File> modFiles = new ArrayList<File>();
if ( slipstreamCmd.modFileNames != null ) {
for ( String modFileName : slipstreamCmd.modFileNames ) {
File modFile = new File( modsDir, modFileName );
if ( modFile.isDirectory() ) {
log.info("Zipping dir: {}/", modFile.getName());
try {
modFile = createTempMod( modFile );
deleteHook.addDoomedFile( modFile );
}
catch ( IOException e ) {
log.error("Error zipping dir: {}/", modFile.getName(), e);
return false;
}
}
modFiles.add( modFile );
}
}
boolean globalPanic = slipstreamCmd.globalPanic;
SilentPatchObserver patchObserver = new SilentPatchObserver();
ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, globalPanic, patchObserver );
Thread.setDefaultUncaughtExceptionHandler( exceptionHandler );
deleteHook.addWatchedThread( patchThread );
patchThread.start();
while ( patchThread.isAlive() ) {
try {patchThread.join();}
catch ( InterruptedException ignored) {}
}
return patchObserver.hasSucceeded();
}
private static boolean validate(String[] modFileNames) {
DelayedDeleteHook deleteHook = new DelayedDeleteHook();
Runtime.getRuntime().addShutdownHook( deleteHook );
log.info( "Validating..." );
StringBuilder resultBuf = new StringBuilder();
ReportFormatter formatter = new ReportFormatter();
boolean anyInvalid = false;
for ( String modFileName : modFileNames ) {
File modFile = new File( modsDir, modFileName );
if ( modFile.isDirectory() ) {
log.info("Zipping dir: {}/", modFile.getName());
try {
modFile = createTempMod( modFile );
deleteHook.addDoomedFile( modFile );
}
catch ( IOException e ) {
log.error("Error zipping dir: {}/", 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) anyInvalid = true;
}
if (resultBuf.isEmpty()) {
resultBuf.append( "No mods were checked." );
}
System.out.println();
System.out.println(resultBuf);
return !anyInvalid;
}
private static void listMods(SlipstreamConfig appConfig) {
log.info( "Listing mods..." );
boolean allowZip = appConfig.getProperty( SlipstreamConfig.ALLOW_ZIP, "false" ).equals( "true" );
File[] modFiles = modsDir.listFiles( new ModAndDirFileFilter( allowZip, true ) );
List<String> dirList = new ArrayList<>();
List<String> fileList = new ArrayList<>();
assert modFiles != null;
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 );
}
private static boolean runFtl(SlipstreamConfig appConfig, File datsDir) {
log.info( "Running FTL..." );
File exeFile = null;
String[] exeArgs = null;
// Try to run via Steam.
if ( "true".equals( appConfig.getProperty( SlipstreamConfig.RUN_STEAM_FTL, "false" ) ) ) {
String steamPath = appConfig.getProperty( SlipstreamConfig.STEAM_EXE_PATH );
if (!steamPath.isEmpty()) {
exeFile = new File( steamPath );
if ( exeFile.exists() ) {
exeArgs = new String[] {"-applaunch", FTLUtilities.STEAM_APPID_FTL};
}
else {
log.warn( String.format( "%s does not exist: %s", SlipstreamConfig.STEAM_EXE_PATH, exeFile.getAbsolutePath() ) );
exeFile = null;
}
}
if ( exeFile == null ) {
log.warn( "Steam executable could not be found, so FTL will be launched directly" );
}
}
// Try to run directly.
if ( exeFile == null ) {
exeFile = FTLUtilities.findGameExe( datsDir );
if ( exeFile != null ) {
exeArgs = new String[0];
} else {
log.warn( "FTL executable could not be found" );
}
}
if ( exeFile != null ) {
try {
FTLUtilities.launchExe( exeFile, exeArgs );
}
catch ( Exception e ) {
log.error( "Error launching FTL", e );
return false;
}
}
else {
log.error( "No executables were found to launch FTL" );
return false;
}
return true;
}
/**
* Checks the validity of the config's dats path and returns it.
* Or exits if the path is invalid.
*/
private static File getDatsDir( SlipstreamConfig appConfig ) {
File datsDir = null;
String datsPath = appConfig.getProperty( SlipstreamConfig.FTL_DATS_PATH, "" );
if (!datsPath.isEmpty()) {
log.info( "Using FTL dats path from config: "+ datsPath );
datsDir = new File( datsPath );
if (!FTLUtilities.isDatsDirValid(datsDir)) {
log.error( "The config's "+ SlipstreamConfig.FTL_DATS_PATH +" does not exist, or it is invalid" );
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;
}
// /**
// * Checks the validity of the config's dats path and returns it.
// * Or exits if the path is invalid.
// */
// private static File getDatsDir(SlipstreamConfig appConfig) {
// File datsDir = null;
// String datsPath = appConfig.getProperty(SlipstreamConfig.FTL_DATS_PATH, "");
//
// if (!datsPath.isEmpty()) {
// log.info("Using FTL dats path from config: "+ datsPath);
// datsDir = new File(datsPath);
// if (!FTLUtilities.isDatsDirValid(datsDir)) {
// log.error("The config's "+ SlipstreamConfig.FTL_DATS_PATH +" does not exist, or it is invalid");
// 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;
// }
/**
@ -421,43 +438,221 @@ public class SlipstreamCLI {
}
}
@Command(
name = "list",
description = "list all available mods"
)
public static class ListCommand implements Runnable {
@Override
public void run() {
System.out.println("list command");
}
}
@Command(
name = "modman",
name = "patch",
description = "create a patched binary using the available mods"
)
public static class PatchCommand implements Runnable {
@Parameters(index = "0", description = "the location unpatched binary is located at")
File binary;
@Parameters(description = "list of mods that the binary will be patched with before it starts. (defaults to all mods if non are provided)", mapFallbackValue = Option.NULL_VALUE)
String mods;
@Option(names = "--dry", description = "skip the patching step but still run the rest of the application")
boolean dry;
@Override
public void run() {
System.out.println("patch command");
}
}
@Command(
name = "clean",
description = "remove all patched binaries"
)
public static class CleanCommand implements Runnable {
@Override
public void run() {
System.out.println("clean command");
}
}
@Command(
name = "install",
description = "install the slipstream mod manager"
)
public static class InstallCommand implements Runnable {
@Override
public void run() {
System.out.println("install command");
}
}
@Command(
name = "uninstall",
description = "uninstall the slipstream mod manager"
)
public static class UninstallCommand implements Runnable {
@Override
public void run() {
System.out.println("uninstall command");
}
}
@Command(
name = "start",
abbreviateSynopsis = true,
sortOptions = false,
description = "Perform actions against an FTL installation and/or a list of named mods.",
footer = "%nIf a named mod is a directory, a temporary zip will be created.",
description = "Creates and starts a patched version of FTL.",
versionProvider = SlipstreamVersionProvider.class
)
public static class SlipstreamCommand {
@Option(names = "--extract-dats", paramLabel = "DIR", description = "extract FTL resources into a dir")
File extractDatsDir;
@Option(names = "--global-panic", description = "patch as if advanced find tags had panic='true'")
boolean globalPanic;
@Option(names = "--list-mods", description = "list available mod names")
boolean listMods;
@Option(names = "--runftl", description = "run the game (standalone or with 'patch')")
boolean runftl;
@Option(names = "--patch", description = "revert to vanilla and add named mods (if any)")
boolean patch;
@Option(names = "--validate", description = "check named mods for problems")
boolean validate;
@Option(names = {"-h", "--help"}, usageHelp = true, description = "display this help and exit")
boolean helpRequested;
public static class StartCommand implements Runnable {
@Option(names = "--version", versionHelp = true, description = "output version information and exit")
boolean versionRequested;
boolean isVersionCommand;
@Option(names = { "--game-folder", "--binary", "--data-folder" }, description = "the location of the game files", converter = GameDirectoryConverter.class, mapFallbackValue = Option.NULL_VALUE)
GameDirectory gameDirectory;
@Parameters(description = "list of mods that the binary will be patched with before it starts. (defaults to all mods if non are provided)", mapFallbackValue = Option.NULL_VALUE)
String[] mods;
@Option(names = "--dry", description = "skip the patching step but still run the rest of the application")
boolean dry;
@Override
public void run() {
System.out.println("start command: " + gameDirectory.root.getAbsolutePath());
}
}
static class GameDirectoryConverter implements ITypeConverter<GameDirectory> {
private GameDirectory getGameDirectory(String fileName) {
if (fileName == null ) {
// TODO: get this from env variables
// TODO: if env variable not set check some common places and prompt the user if they want to use that
return null;
}
return GameDirectory.createGameDirectory(new File(fileName));
}
@Override
public GameDirectory convert(String fileName) {
GameDirectory gameDirectory = getGameDirectory(fileName);
if (gameDirectory == null) {
System.out.println("Game not found. Please specify the --game-folder argument, or set the FTL_GAME_FOLDER environment variable.");
System.exit(1);
}
return gameDirectory;
}
}
static class GameDirectory {
private static final String GAME_FOLDER_NAME = "FTL Faster Than Light";
private static final String GAME_DATA_FOLDER_NAME = "data";
private static final String MODS_FOLDER_NAME = "mods";
private static final String BACKUP_FOLDER_NAME = "backup";
private static final String LAUNCH_SCRIPT_FILE_NAME = "FTL";
private static final String X86_BINARY_FILE_NAME = "FTL.x86";
private static boolean isValidGameDirectory(File gameDirectory) {
if (
!(
gameDirectory.exists()
&& gameDirectory.isDirectory()
&& gameDirectory.getName().equals(GAME_FOLDER_NAME)
)
) {
return false;
}
File gameDataFolder = new File(gameDirectory, GAME_DATA_FOLDER_NAME);
if (
!(
gameDataFolder.exists()
&& gameDataFolder.isDirectory()
)
) {
return false;
}
File gameLaunchScript = new File(gameDataFolder, LAUNCH_SCRIPT_FILE_NAME);
if (
!(
gameLaunchScript.exists()
&& gameLaunchScript.isFile()
)
) {
return false;
}
// TODO: deal with FTL.amd64
File gameBinary = new File(gameDataFolder, X86_BINARY_FILE_NAME);
return gameBinary.exists() && gameBinary.isFile();
}
static GameDirectory createGameDirectory(File file) {
if (file.isFile()) {
String filename = file.getName();
if (filename.equals(X86_BINARY_FILE_NAME)) {
File root = file.getParentFile().getParentFile();
if (isValidGameDirectory(root)) {
return new GameDirectory((root));
}
}
}
if (file.isDirectory()){
if (isValidGameDirectory(file)) {
return new GameDirectory(file);
}
String directoryName = file.getName();
if (directoryName.equals(GAME_DATA_FOLDER_NAME)) {
File root = file.getParentFile();
if (isValidGameDirectory(root)) {
return new GameDirectory((root));
}
}
}
return null;
}
public final File root;
public final File dataDir;
public final File modsDir;
public final File backupDir;
public final File launchScript;
public final File binary;
GameDirectory(File root) {
this.root = root;
this.dataDir = new File(root, "data");
this.modsDir = new File(this.dataDir, "mods");
this.backupDir = new File(this.dataDir, "backup");
this.launchScript = new File(this.dataDir, "FTL");
// TODO: this should probably change when we need to use FTL.amd64 instead
this.binary = new File(this.dataDir, X86_BINARY_FILE_NAME);
}
}
@Command(name = "slipstream")
public static class BaseCommand implements Runnable {
@Option(names = {"-h", "--help"}, usageHelp = true, description = "display this help and exit")
boolean isHelpCommand;
@Spec
Model.CommandSpec spec;
@Override
public void run() {
// if the command was invoked without subcommand, show the usage help
spec.commandLine().usage(System.err);
}
@Parameters(paramLabel = "MODFILE", description = "names of files or directories in the mods/ dir")
String[] modFileNames;
}
public static class SlipstreamVersionProvider implements IVersionProvider {
@ -512,15 +707,7 @@ public class SlipstreamCLI {
}
private static class ModAndDirFileFilter implements FileFilter {
private final boolean allowZip;
private final boolean allowDirs;
public ModAndDirFileFilter( boolean allowZip, boolean allowDirs ) {
this.allowZip = allowZip;
this.allowDirs = allowDirs;
}
private record ModAndDirFileFilter(boolean allowZip, boolean allowDirs) implements FileFilter {
@Override
public boolean accept(File f) {