/* ** 2011 April 5 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. */ package info.ata4.bspsrc.cli; import info.ata4.bsplib.app.SourceApp; import info.ata4.bsplib.app.SourceAppDB; import info.ata4.bspsrc.*; import info.ata4.bspsrc.modules.geom.BrushMode; import info.ata4.bspsrc.util.SourceFormat; import info.ata4.log.LogUtils; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.cli.*; import org.apache.commons.io.FileUtils; /** * Helper class for CLI parsing and handling. * * @author Nico Bergemann <barracuda415 at yahoo.de> */ public class BspSourceCli { private static final Logger L = LogUtils.getLogger(); private Options optsMain = new Options(); private Options optsEntity = new Options(); private Options optsWorld = new Options(); private Options optsTexture = new Options(); private Options optsOther = new Options(); private MultiOptions optsAll = new MultiOptions(); /** * @param args the command line arguments */ public static void main(String args[]) { LogUtils.configure(); try { BspSourceCli cli = new BspSourceCli(); BspSourceConfig cfg = cli.getConfig(args); BspSource bspsrc = new BspSource(cfg); bspsrc.run(); } catch (Throwable t) { // "Really bad!" L.log(Level.SEVERE, "Fatal BSPSource error", t); } } /** * Prints application usage, then exits the app. */ private void printHelp() { System.out.println("BSPSource " + BspSource.VERSION); System.out.println("usage: bspsrc [options] <path> [path...]"); System.out.println(); OptionHelpFormatter clHelp = new OptionHelpFormatter(); clHelp.printHelp("Main options:", optsMain); clHelp.printHelp("Entity options:", optsEntity); clHelp.printHelp("World brush options:", optsWorld); clHelp.printHelp("Texture options:", optsTexture); clHelp.printHelp("Other options:", optsOther); System.exit(0); } /** * Prints application version, then exits the app. */ private void printVersion() { System.out.println("BSPSource " + BspSource.VERSION); System.out.println(); System.out.println("Based on VMEX v0.98g by Rof <rof@mellish.org.uk>"); System.out.println("Extended and modified by Nico Bergemann <barracuda415@yahoo.de>"); System.exit(0); } private void printAppIDs() { System.out.printf("%6s %s\n", "ID", "Name"); List<SourceApp> apps = SourceAppDB.getInstance().getAppList(); for (SourceApp app : apps) { System.out.printf("%6d %s\n", app.getAppID(), app.getName()); } System.exit(0); } /** * Parses an arguments string and applies all settings to BSPSource. * * @param args arguments string */ @SuppressWarnings("static-access") public BspSourceConfig getConfig(String[] args) { BspSourceConfig config = new BspSourceConfig(); // basic options Option helpOpt, versionOpt, debugOpt, outputOpt, recursiveOpt, fileListOpt; optsMain.addOption(helpOpt = new Option("h", "help", false, "Print this help.")); optsMain.addOption(versionOpt = new Option("v", "Print version info.")); optsMain.addOption(debugOpt = new Option("d", "Enable debug mode. Increases verbosity and adds additional data to the VMF file.")); optsMain.addOption(recursiveOpt = new Option("r", "Decompile all files found in the given directory.")); optsMain.addOption(outputOpt = OptionBuilder .hasArg() .withArgName("file") .withDescription("Override output path for VMF file(s). Treated as directory if multiple BSP files are provided. \ndefault: <mappath>/<mapname>_d.vmf") .withType(String.class) .create('o')); optsMain.addOption(fileListOpt = OptionBuilder .hasArg() .withArgName("file") .withDescription("Use a text files with paths as input BSP file list.") .withType(String.class) .create('l')); // entity options Option nbentsOpt, npentsOpt, npropsOpt, noverlOpt, ncubemOpt, ndetailsOpt, nareapOpt, nocclOpt, nladderOpt, nrotfixOpt; optsEntity.addOption(npentsOpt = new Option("no_point_ents", "Don't write any point entities.")); optsEntity.addOption(nbentsOpt = new Option("no_brush_ents", "Don't write any brush entities.")); optsEntity.addOption(npropsOpt = new Option("no_sprp", "Don't write prop_static entities.")); optsEntity.addOption(noverlOpt = new Option("no_overlays", "Don't write info_overlay entities.")); optsEntity.addOption(ncubemOpt = new Option("no_cubemaps", "Don't write env_cubemap entities.")); optsEntity.addOption(ndetailsOpt = new Option("no_details", "Don't write func_detail entities.")); optsEntity.addOption(nareapOpt = new Option("no_areaportals", "Don't write func_areaportal(_window) entities.")); optsEntity.addOption(nocclOpt = new Option("no_occluders", "Don't write func_occluder entities.")); optsEntity.addOption(nladderOpt = new Option("no_ladders", "Don't write func_ladder entities.")); optsEntity.addOption(nrotfixOpt = new Option("no_rotfix", "Don't fix instance entity brush rotations for Hammer.")); // world brush options Option nbrushOpt, ndispOpt, bmodeOpt, thicknOpt; optsWorld.addOption(nbrushOpt = new Option("no_brushes", "Don't write any world brushes.")); optsWorld.addOption(ndispOpt = new Option("no_disps", "Don't write displacement surfaces.")); optsWorld.addOption(bmodeOpt = OptionBuilder .hasArg() .withArgName("enum") .withDescription("Brush decompiling mode:\n" + BrushMode.BRUSHPLANES.name() + " - brushes and planes\n" + BrushMode.ORIGFACE.name() + " - original faces only\n" + BrushMode.ORIGFACE_PLUS.name() + " - original + split faces\n" + BrushMode.SPLITFACE.name() + " - split faces only\n" + "default: " + config.brushMode.name()) .create("brushmode")); optsWorld.addOption(thicknOpt = OptionBuilder .hasArg() .withArgName("float") .withDescription("Thickness of brushes created from flat faces in units.\n" + "default: " + config.backfaceDepth) .create("thickness")); // texture options Option ntexfixOpt, ftexOpt, bftexOpt; optsTexture.addOption(ntexfixOpt = new Option("no_texfix", "Don't fix texture names.")); optsTexture.addOption(ftexOpt = OptionBuilder .hasArg() .withArgName("string") .withDescription("Replace all face textures with this one.") .create("facetex")); optsTexture.addOption(bftexOpt = OptionBuilder .hasArg() .withArgName("string") .withDescription("Replace all back-face textures with this one. Used in face-based decompiling modes only.") .create("bfacetex")); // other options Option nvmfOpt, nlumpfilesOpt, nprotOpt, listappidsOpt, appidOpt, nvisgrpOpt, ncamsOpt, formatOpt; optsOther.addOption(nvmfOpt = new Option("no_vmf", "Don't write any VMF files, read BSP only.")); optsOther.addOption(nlumpfilesOpt = new Option("no_lumpfiles", "Don't load lump files (.lmp) associated with the BSP file.")); optsOther.addOption(nprotOpt = new Option("no_prot", "Skip decompiling protection checking. Can increase speed when mass-decompiling unprotected maps.")); optsOther.addOption(listappidsOpt = new Option("appids", "List all available application IDs")); optsOther.addOption(nvisgrpOpt = new Option("no_visgroups", "Don't group entities from instances into visgroups.")); optsOther.addOption(ncamsOpt = new Option("no_cams", "Don't create Hammer cameras above each player spawn.")); optsOther.addOption(appidOpt = OptionBuilder .hasArg() .withArgName("string/int") .withDescription("Overrides game detection by using " + "this Steam Application ID instead.\n" + "Use -appids to list all known app-IDs.") .create("appid")); optsOther.addOption(formatOpt = OptionBuilder .hasArg() .withArgName("enum") .withDescription("Sets the VMF format used for the decompiled maps:\n" + SourceFormat.AUTO.name() + " - " + SourceFormat.AUTO + "\n" + SourceFormat.OLD.name() + " - " + SourceFormat.OLD + "\n" + SourceFormat.NEW.name() + " - " + SourceFormat.NEW + "\n" + "default: " + config.sourceFormat.name()) .create("format")); // all options optsAll.addOptions(optsMain); optsAll.addOptions(optsEntity); optsAll.addOptions(optsWorld); optsAll.addOptions(optsTexture); optsAll.addOptions(optsOther); if (args.length == 0) { printHelp(); System.exit(0); } CommandLineParser clParser = new PosixParser(); CommandLine cl = null; File outputFile = null; boolean recursive = false; Set<BspFileEntry> files = config.getFileSet(); try { // parse the command line arguments cl = clParser.parse(optsAll, args); // help if(cl.hasOption(helpOpt.getOpt())) { printHelp(); } // version if (cl.hasOption(versionOpt.getOpt())) { printVersion(); } // list app-ids if (cl.hasOption(listappidsOpt.getOpt())) { printAppIDs(); } // main options config.setDebug(cl.hasOption(debugOpt.getOpt())); if (cl.hasOption(outputOpt.getOpt())) { outputFile = new File(cl.getOptionValue(outputOpt.getOpt())); } recursive = cl.hasOption(recursiveOpt.getOpt()); if (cl.hasOption(fileListOpt.getOpt())) { try { List<String> filePaths = FileUtils.readLines(new File(cl.getOptionValue(fileListOpt.getOpt()))); for (String filePath : filePaths) { BspFileEntry entry = new BspFileEntry(new File(filePath)); // override destination directory? if (outputFile != null) { entry.setVmfFile(new File(outputFile, entry.getVmfFile().getName())); } files.add(entry); } } catch (IOException ex) { throw new RuntimeException("Can't read file list", ex); } } // entity options config.writePointEntities = !cl.hasOption(npentsOpt.getOpt()); config.writeBrushEntities = !cl.hasOption(nbentsOpt.getOpt()); config.writeStaticProps = !cl.hasOption(npropsOpt.getOpt()); config.writeOverlays = !cl.hasOption(noverlOpt.getOpt()); config.writeDisp = !cl.hasOption(ndispOpt.getOpt()); config.writeAreaportals = !cl.hasOption(nareapOpt.getOpt()); config.writeOccluders = !cl.hasOption(nocclOpt.getOpt()); config.writeCubemaps = !cl.hasOption(ncubemOpt.getOpt()); config.writeDetails = !cl.hasOption(ndetailsOpt.getOpt()); config.writeLadders = !cl.hasOption(nladderOpt.getOpt()); // world options config.writeWorldBrushes = !cl.hasOption(nbrushOpt.getOpt()); if (cl.hasOption(bmodeOpt.getOpt())) { String modeStr = cl.getOptionValue(bmodeOpt.getOpt()); try { config.brushMode = BrushMode.valueOf(modeStr); } catch (IllegalArgumentException ex) { // try again as ordinal enum value try { int mode = Integer.valueOf(modeStr.toUpperCase()); config.brushMode = BrushMode.fromOrdinal(mode); } catch (IllegalArgumentException ex2) { throw new RuntimeException("Invalid brush mode"); } } } if (cl.hasOption(formatOpt.getOpt())) { String formatStr = cl.getOptionValue(formatOpt.getOpt()); try { config.sourceFormat = SourceFormat.valueOf(formatStr); } catch (IllegalArgumentException ex) { // try again as ordinal enum value try { int format = Integer.valueOf(formatStr.toUpperCase()); config.sourceFormat = SourceFormat.fromOrdinal(format); } catch (IllegalArgumentException ex2) { throw new RuntimeException("Invalid source format"); } } } if (cl.hasOption(thicknOpt.getOpt())) { float thickness = Float.valueOf(cl.getOptionValue(thicknOpt.getOpt())); config.backfaceDepth = thickness; } // texture options config.fixCubemapTextures = !cl.hasOption(ntexfixOpt.getOpt()); if (cl.hasOption(ftexOpt.getOpt())) { config.faceTexture = cl.getOptionValue(ftexOpt.getOpt()); } if (cl.hasOption(bftexOpt.getOpt())) { config.backfaceTexture = cl.getOptionValue(bftexOpt.getOpt()); } // other options config.loadLumpFiles = !cl.hasOption(nlumpfilesOpt.getOpt()); config.skipProt = cl.hasOption(nprotOpt.getOpt()); config.fixEntityRot = !cl.hasOption(nrotfixOpt.getOpt()); config.nullOutput = cl.hasOption(nvmfOpt.getOpt()); config.writeVisgroups = !cl.hasOption(nvisgrpOpt.getOpt()); config.writeCameras = !cl.hasOption(ncamsOpt.getOpt()); if (cl.hasOption(appidOpt.getOpt())) { String appidStr = cl.getOptionValue(appidOpt.getOpt()).toUpperCase(); try { int appid = Integer.valueOf(appidStr); config.defaultApp = SourceAppDB.getInstance().fromID(appid); } catch (IllegalArgumentException ex2) { throw new RuntimeException("Invalid default App-ID"); } } } catch (Exception ex) { L.log(Level.SEVERE, "Internal CLI error", ex); System.exit(0); } // get non-recognized arguments, these are the BSP input files String[] argsLeft = cl.getArgs(); if (argsLeft.length == 1 && outputFile != null) { // set VMF file for one BSP BspFileEntry entry = new BspFileEntry(new File(argsLeft[0])); entry.setVmfFile(outputFile); files.add(entry); } else { for (String arg : argsLeft) { File file = new File(arg); if (file.isDirectory()) { Collection<File> subFiles = FileUtils.listFiles(file, new String[]{"bsp"}, recursive); for (File subFile : subFiles) { BspFileEntry entry = new BspFileEntry(subFile); // override destination directory? if (outputFile != null) { entry.setVmfFile(new File(outputFile, entry.getVmfFile().getName())); } files.add(entry); } } else { BspFileEntry entry = new BspFileEntry(file); // override destination directory? if (outputFile != null) { entry.setVmfFile(new File(outputFile, entry.getVmfFile().getName())); } files.add(entry); } } } if (files.isEmpty()) { L.severe("No BSP file(s) specified"); System.exit(1); } return config; } }