From 8ffdd5556bd94eaa061cef4979b85e8599c39739 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 17 Aug 2025 10:46:16 -0500 Subject: [PATCH] drafted out commands --- pom.xml | 2 +- .../net/vhati/modmanager/FTLModManager.java | 48 +- .../vhati/modmanager/cli/SlipstreamCLI.java | 945 +++++++++++------- 3 files changed, 585 insertions(+), 410 deletions(-) diff --git a/pom.xml b/pom.xml index 53264db..9cbb927 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ info.picocli picocli - 2.2.0 + 4.7.7 diff --git a/src/main/java/net/vhati/modmanager/FTLModManager.java b/src/main/java/net/vhati/modmanager/FTLModManager.java index 3ea6dd5..edc78a1 100644 --- a/src/main/java/net/vhati/modmanager/FTLModManager.java +++ b/src/main/java/net/vhati/modmanager/FTLModManager.java @@ -18,15 +18,12 @@ import net.vhati.modmanager.core.ComparableVersion; public class FTLModManager { - private static final Logger log = LoggerFactory.getLogger( FTLModManager.class ); + private static final Logger log = LoggerFactory.getLogger(FTLModManager.class); 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 final ComparableVersion APP_VERSION = new ComparableVersion("1.9.1"); - - public static void main( String[] args ) { + public static void main(String[] args) { // Redirect any libraries' java.util.Logging messages. SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); @@ -38,38 +35,29 @@ public class FTLModManager { LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory(); PatternLayoutEncoder encoder = new PatternLayoutEncoder(); - encoder.setContext( lc ); + encoder.setContext(lc); encoder.setCharset(StandardCharsets.UTF_8); - encoder.setPattern( "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" ); + encoder.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"); encoder.start(); - FileAppender fileAppender = new FileAppender(); - fileAppender.setContext( lc ); - fileAppender.setName( "LogFile" ); - fileAppender.setFile( new File( "./modman-log.txt" ).getAbsolutePath() ); - fileAppender.setAppend( false ); - fileAppender.setEncoder( encoder ); + FileAppender fileAppender = new FileAppender<>(); + fileAppender.setContext(lc); + fileAppender.setName("LogFile"); + fileAppender.setFile(new File("./modman-log.txt").getAbsolutePath()); + fileAppender.setAppend(false); + fileAppender.setEncoder(encoder); fileAppender.start(); - lc.getLogger( Logger.ROOT_LOGGER_NAME ).addAppender( fileAppender ); + lc.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(fileAppender); // Log a welcome message. - log.debug( "Started: {}", new Date() ); - log.debug( "{} v{}", APP_NAME, APP_VERSION ); - 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" ) ); + log.debug("Started: {}", new Date()); + log.debug("{} v{}", APP_NAME, APP_VERSION); + 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 ); + SlipstreamCLI.main(args); } } diff --git a/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java index 358df41..330051d 100644 --- a/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java +++ b/src/main/java/net/vhati/modmanager/cli/SlipstreamCLI.java @@ -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; @@ -39,344 +35,365 @@ import net.vhati.modmanager.core.SlipstreamConfig; public class SlipstreamCLI { - private static final Logger log = LoggerFactory.getLogger( SlipstreamCLI.class ); + private static final Logger log = LoggerFactory.getLogger(SlipstreamCLI.class); - private static final File backupDir = new File( "./backup/" ); - private static final File modsDir = new File( "./mods/" ); + private static final File backupDir = new File("./backup/"); + private static final File modsDir = new File("./mods/"); private static Thread.UncaughtExceptionHandler exceptionHandler = null; - public static void main( String[] args ) { + public static void main(String[] args) { exceptionHandler = (t, e) -> { log.error("Uncaught exception in thread: {}", t.toString(), e); - System.exit( 1 ); + 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); - if ( commandLine.isUsageHelpRequested() ) { - commandLine.usage( System.out ); - System.exit( 0 ); - } - if ( commandLine.isVersionHelpRequested() ) { - commandLine.printVersionHelp( System.out ); - System.exit( 0 ); - } +// 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 (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); } - private static void extractDatsDir(SlipstreamCommand slipstreamCmd, File datsDir) { - log.info( "Extracting dats..." ); +// private static ActionFlow getCommandAction(CommandLine commandLine) { +// if (commandLine.isUsageHelpRequested()) { +// return ActionFlow.HELP; +// } +// if (commandLine.isVersionHelpRequested()) { +// return ActionFlow.VERSION; +// } +// +// return null; +// } - File extractDir = slipstreamCmd.extractDatsDir; +// private static void extractDatsDir(RunCommand slipstreamCmd, File datsDir) { +// log.info("Extracting dats..."); +// +// File extractDir = slipstreamCmd.extractDatsDir; +// +// FolderPack dstPack = null; +// List srcPacks = new ArrayList(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 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 modFiles = new ArrayList(); +// 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(); +// } - FolderPack dstPack = null; - List srcPacks = new ArrayList( 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" ); +// 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 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) 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 dirList = new ArrayList<>(); +// List 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 ( 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 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 modFiles = new ArrayList(); - 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 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) 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 dirList = new ArrayList<>(); - List 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; +// } /** @@ -384,8 +401,8 @@ public class SlipstreamCLI { * 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" ); + private static File createTempMod(File dir) throws IOException { + File tempFile = File.createTempFile(dir.getName() +"_temp-", ".zip"); try (FileOutputStream fos = new FileOutputStream(tempFile)) { ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos)); @@ -397,12 +414,12 @@ public class SlipstreamCLI { return tempFile; } - private static void addDirToArchive( ZipOutputStream zos, File dir, String pathPrefix ) throws IOException { - if ( pathPrefix == null ) pathPrefix = ""; + private static void addDirToArchive(ZipOutputStream zos, File dir, String pathPrefix) throws IOException { + if (pathPrefix == null) pathPrefix = ""; - for ( File f : Objects.requireNonNull(dir.listFiles())) { - if ( f.isDirectory() ) { - addDirToArchive( zos, f, pathPrefix + f.getName() +"/" ); + for (File f : Objects.requireNonNull(dir.listFiles())) { + if (f.isDirectory()) { + addDirToArchive(zos, f, pathPrefix + f.getName() +"/"); continue; } @@ -421,50 +438,228 @@ 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", - 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.", - versionProvider = SlipstreamVersionProvider.class + name = "patch", + description = "create a patched binary using the available mods" ) - public static class SlipstreamCommand { - @Option(names = "--extract-dats", paramLabel = "DIR", description = "extract FTL resources into a dir") - File extractDatsDir; + public static class PatchCommand implements Runnable { + @Parameters(index = "0", description = "the location unpatched binary is located at") + File binary; - @Option(names = "--global-panic", description = "patch as if advanced find tags had panic='true'") - boolean globalPanic; + @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 = "--list-mods", description = "list available mod names") - boolean listMods; + @Option(names = "--dry", description = "skip the patching step but still run the rest of the application") + boolean dry; - @Option(names = "--runftl", description = "run the game (standalone or with 'patch')") - boolean runftl; + @Override + public void run() { + System.out.println("patch command"); + } + } - @Option(names = "--patch", description = "revert to vanilla and add named mods (if any)") - boolean patch; + @Command( + name = "clean", + description = "remove all patched binaries" + ) + public static class CleanCommand implements Runnable { + @Override + public void run() { + System.out.println("clean command"); + } + } - @Option(names = "--validate", description = "check named mods for problems") - boolean validate; + @Command( + name = "install", + description = "install the slipstream mod manager" + ) + public static class InstallCommand implements Runnable { + @Override + public void run() { + System.out.println("install command"); + } + } - @Option(names = {"-h", "--help"}, usageHelp = true, description = "display this help and exit") - boolean helpRequested; + @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 = "Creates and starts a patched version of FTL.", + versionProvider = SlipstreamVersionProvider.class + ) + 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 { + + 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 { @Override public String[] getVersion() { return new String[] { - String.format( "%s %s", FTLModManager.APP_NAME, FTLModManager.APP_VERSION ), + String.format("%s %s", FTLModManager.APP_NAME, FTLModManager.APP_VERSION), "Copyright (C) 2013,2014,2017,2018 David Millis", "", "This program is free software; you can redistribute it and/or modify", @@ -490,19 +685,19 @@ public class SlipstreamCLI { private boolean succeeded = false; @Override - public void patchingProgress( final int value, final int max ) { + public void patchingProgress(final int value, final int max) { } @Override - public void patchingStatus( String message ) { + public void patchingStatus(String message) { } @Override - public void patchingMod( File modFile ) { + public void patchingMod(File modFile) { } @Override - public synchronized void patchingEnded( boolean outcome, Exception e ) { + public synchronized void patchingEnded(boolean outcome, Exception e) { succeeded = outcome; done = true; } @@ -512,26 +707,18 @@ 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 ) { - if ( f.isDirectory() ) return allowDirs; + public boolean accept(File f) { + if (f.isDirectory()) return allowDirs; - if ( f.getName().endsWith(".ftl") ) return true; + if (f.getName().endsWith(".ftl")) return true; - if ( allowZip ) { - return f.getName().endsWith(".zip"); + if (allowZip) { + return f.getName().endsWith(".zip"); + } + return false; } - return false; } - } }