diff --git a/jadx-cli/build.gradle b/jadx-cli/build.gradle index ccb6863967c5e6ea632e76db2e647b873dcbb865..e77c09f9d81ac2d9254e3b9af112b24f4724e0c0 100644 --- a/jadx-cli/build.gradle +++ b/jadx-cli/build.gradle @@ -5,7 +5,7 @@ applicationName = 'jadx' dependencies { compile(project(':jadx-core')) - compile 'com.beust:jcommander:1.47' + compile 'com.beust:jcommander:1.72' compile 'ch.qos.logback:logback-classic:1.2.3' } diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java index 7193906367e24174109591f8c242fea7dba83922..56e1b096c6a3b100411fd3d9bf7467cfaa354a6c 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.JadxDecompiler; +import jadx.core.utils.exceptions.JadxArgsValidateException; public class JadxCLI { private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class); @@ -22,7 +23,12 @@ public class JadxCLI { static void processAndSave(JadxCLIArgs inputArgs) { JadxDecompiler jadx = new JadxDecompiler(inputArgs.toJadxArgs()); - jadx.load(); + try { + jadx.load(); + } catch (JadxArgsValidateException e) { + LOG.error("Incorrect arguments: {}", e.getMessage()); + System.exit(1); + } jadx.save(); if (jadx.getErrorsCount() != 0) { jadx.printErrorsReport(); diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 38278afba2e8f677eceacf115c93d1bf5ff6c067..09dbddf83f72a24dd3c7c3ae5a3ca66286bf8789 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -10,7 +10,6 @@ import java.util.stream.Collectors; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; -import com.beust.jcommander.IStringConverter; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterDescription; @@ -25,7 +24,7 @@ import jadx.core.utils.files.FileUtils; public class JadxCLIArgs { - @Parameter(description = " (.dex, .apk, .jar or .class)") + @Parameter(description = " (.apk, .dex, .jar or .class)") protected List files = new ArrayList<>(1); @Parameter(names = {"-d", "--output-dir"}, description = "output directory") @@ -37,9 +36,6 @@ public class JadxCLIArgs { @Parameter(names = {"-dr", "--output-dir-res"}, description = "output directory for resources") protected String outDirRes; - @Parameter(names = {"-j", "--threads-count"}, description = "processing threads count") - protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT; - @Parameter(names = {"-r", "--no-res"}, description = "do not decode resources") protected boolean skipResources = false; @@ -49,15 +45,16 @@ public class JadxCLIArgs { @Parameter(names = {"-e", "--export-gradle"}, description = "save as android gradle project") protected boolean exportAsGradleProject = false; + @Parameter(names = {"-j", "--threads-count"}, description = "processing threads count") + protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT; + @Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)") protected boolean showInconsistentCode = false; - @Parameter(names = {"--no-imports"}, converter = InvertedBooleanConverter.class, - description = "disable use of imports, always write entire package name") + @Parameter(names = {"--no-imports"}, description = "disable use of imports, always write entire package name") protected boolean useImports = true; - @Parameter(names = "--no-replace-consts", converter = InvertedBooleanConverter.class, - description = "don't replace constant value with matching constant field") + @Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field") protected boolean replaceConsts = true; @Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)") @@ -90,6 +87,9 @@ public class JadxCLIArgs { @Parameter(names = {"-v", "--verbose"}, description = "verbose output") protected boolean verbose = false; + @Parameter(names = {"--version"}, description = "print jadx version") + protected boolean printVersion = false; + @Parameter(names = {"-h", "--help"}, description = "print this help", help = true) protected boolean printHelp = false; @@ -99,7 +99,7 @@ public class JadxCLIArgs { private boolean parse(String[] args) { try { - new JCommander(this, args); + makeJCommander().parse(args); return true; } catch (ParameterException e) { System.err.println("Arguments parse error: " + e.getMessage()); @@ -108,16 +108,24 @@ public class JadxCLIArgs { } } + private JCommander makeJCommander() { + return JCommander.newBuilder().addObject(this).build(); + } + private boolean process() { - if (isPrintHelp()) { + if (printHelp) { printUsage(); return false; } + if (printVersion) { + System.out.println(JadxDecompiler.getVersion()); + return false; + } try { if (threadsCount <= 0) { throw new JadxException("Threads count must be positive, got: " + threadsCount); } - if (isVerbose()) { + if (verbose) { ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); // remove INFO ThresholdFilter @@ -135,7 +143,7 @@ public class JadxCLIArgs { } public void printUsage() { - JCommander jc = new JCommander(this); + JCommander jc = makeJCommander(); // print usage in not sorted fields order (by default its sorted by description) PrintStream out = System.out; out.println(); @@ -162,13 +170,13 @@ public class JadxCLIArgs { continue; } StringBuilder opt = new StringBuilder(); - opt.append(' ').append(p.getNames()); + opt.append(" ").append(p.getNames()); addSpaces(opt, maxNamesLen - opt.length() + 2); opt.append("- ").append(p.getDescription()); out.println(opt); } out.println("Example:"); - out.println(" jadx -d out classes.dex"); + out.println(" jadx -d out classes.dex"); } private static void addSpaces(StringBuilder str, int count) { @@ -202,13 +210,6 @@ public class JadxCLIArgs { return args; } - public static class InvertedBooleanConverter implements IStringConverter { - @Override - public Boolean convert(String value) { - return "false".equals(value); - } - } - public List getFiles() { return files; } @@ -225,10 +226,6 @@ public class JadxCLIArgs { return outDirRes; } - public boolean isPrintHelp() { - return printHelp; - } - public boolean isSkipResources() { return skipResources; } @@ -241,14 +238,6 @@ public class JadxCLIArgs { return threadsCount; } - public boolean isCFGOutput() { - return cfgOutput; - } - - public boolean isRawCFGOutput() { - return rawCfgOutput; - } - public boolean isFallbackMode() { return fallbackMode; } @@ -261,10 +250,6 @@ public class JadxCLIArgs { return useImports; } - public boolean isVerbose() { - return verbose; - } - public boolean isDeobfuscationOn() { return deobfuscationOn; } @@ -289,6 +274,18 @@ public class JadxCLIArgs { return escapeUnicode; } + public boolean isEscapeUnicode() { + return escapeUnicode; + } + + public boolean isCfgOutput() { + return cfgOutput; + } + + public boolean isRawCfgOutput() { + return rawCfgOutput; + } + public boolean isReplaceConsts() { return replaceConsts; } diff --git a/jadx-cli/src/main/resources/logback.xml b/jadx-cli/src/main/resources/logback.xml index 670c71320b322ba30a743ad2ba3fd555e6133532..6a1e61641cbd6e8d06309122b52659289ccee8c1 100644 --- a/jadx-cli/src/main/resources/logback.xml +++ b/jadx-cli/src/main/resources/logback.xml @@ -5,7 +5,7 @@ INFO - %d{HH:mm:ss} %-5level - %msg%n + %-5level - %msg%n diff --git a/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java b/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java index 6704d05984b919b0e4146625c0fcf3016754101d..5afff66e4a618268a57b7f505235cb3d8b1fafee 100644 --- a/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java +++ b/jadx-cli/src/test/java/jadx/cli/JadxCLIArgsTest.java @@ -1,22 +1,40 @@ package jadx.cli; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; public class JadxCLIArgsTest { + private static final Logger LOG = LoggerFactory.getLogger(JadxCLIArgsTest.class); + @Test public void testInvertedBooleanOption() { assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false)); assertThat(parse("").isReplaceConsts(), is(true)); } + @Test + public void testEscapeUnicodeOption() { + assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true)); + assertThat(parse("").isEscapeUnicode(), is(false)); + } + + @Test + public void testSrcOption() { + assertThat(parse("--no-src").isSkipSources(), is(true)); + assertThat(parse("-s").isSkipSources(), is(true)); + assertThat(parse("").isSkipSources(), is(false)); + } + private JadxCLIArgs parse(String... args) { JadxCLIArgs jadxArgs = new JadxCLIArgs(); boolean res = jadxArgs.processArgs(args); assertThat(res, is(true)); + LOG.info("Jadx args: {}", jadxArgs.toJadxArgs()); return jadxArgs; } } diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index cda8778098a340236105254c6e4b0eda32eba3eb..bd6f12548ece31b0391f0e98e16d6a6fb1b1e312 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -211,4 +211,31 @@ public class JadxArgs { public void setExportAsGradleProject(boolean exportAsGradleProject) { this.exportAsGradleProject = exportAsGradleProject; } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("JadxArgs{"); + sb.append("inputFiles=").append(inputFiles); + sb.append(", outDir=").append(outDir); + sb.append(", outDirSrc=").append(outDirSrc); + sb.append(", outDirRes=").append(outDirRes); + sb.append(", threadsCount=").append(threadsCount); + sb.append(", cfgOutput=").append(cfgOutput); + sb.append(", rawCFGOutput=").append(rawCFGOutput); + sb.append(", fallbackMode=").append(fallbackMode); + sb.append(", showInconsistentCode=").append(showInconsistentCode); + sb.append(", useImports=").append(useImports); + sb.append(", isSkipResources=").append(isSkipResources); + sb.append(", isSkipSources=").append(isSkipSources); + sb.append(", isDeobfuscationOn=").append(isDeobfuscationOn); + sb.append(", isDeobfuscationForceSave=").append(isDeobfuscationForceSave); + sb.append(", useSourceNameAsClassAlias=").append(useSourceNameAsClassAlias); + sb.append(", deobfuscationMinLength=").append(deobfuscationMinLength); + sb.append(", deobfuscationMaxLength=").append(deobfuscationMaxLength); + sb.append(", escapeUnicode=").append(escapeUnicode); + sb.append(", replaceConsts=").append(replaceConsts); + sb.append(", exportAsGradleProject=").append(exportAsGradleProject); + sb.append('}'); + return sb.toString(); + } } diff --git a/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java b/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java index 761aa09d127d86998eff74909308db9a5fe81eb5..4bb856d298d2864688660abc7bb3bfaf2f5da911 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java @@ -1,25 +1,44 @@ package jadx.api; import java.io.File; +import java.util.List; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.exceptions.JadxArgsValidateException; public class JadxArgsValidator { private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class); public static void validate(JadxArgs args) { - if (args.getInputFiles().isEmpty()) { - throw new JadxRuntimeException("Please specify input file"); + checkInputFiles(args); + validateOutDirs(args); + + if (LOG.isDebugEnabled()) { + LOG.debug("Effective jadx args: {}", args); + } + } + + private static void checkInputFiles(JadxArgs args) { + List inputFiles = args.getInputFiles(); + if (inputFiles.isEmpty()) { + throw new JadxArgsValidateException("Please specify input file"); + } + if (inputFiles.size() > 1) { + for (File inputFile : inputFiles) { + String fileName = inputFile.getName(); + if (fileName.startsWith("--")) { + throw new JadxArgsValidateException("Unknown argument: " + fileName); + } + } + throw new JadxArgsValidateException("Only one input file supported"); } - for (File file : args.getInputFiles()) { + for (File file : inputFiles) { checkFile(file); } - validateOutDirs(args); } private static void validateOutDirs(JadxArgs args) { @@ -71,16 +90,16 @@ public class JadxArgsValidator { private static void checkFile(File file) { if (!file.exists()) { - throw new JadxRuntimeException("File not found " + file.getAbsolutePath()); + throw new JadxArgsValidateException("File not found " + file.getAbsolutePath()); } if (file.isDirectory()) { - throw new JadxRuntimeException("Expected file but found directory instead: " + file.getAbsolutePath()); + throw new JadxArgsValidateException("Expected file but found directory instead: " + file.getAbsolutePath()); } } private static void checkDir(File dir) { if (dir != null && dir.exists() && !dir.isDirectory()) { - throw new JadxRuntimeException("Output directory exists as file " + dir); + throw new JadxArgsValidateException("Output directory exists as file " + dir); } } diff --git a/jadx-core/src/main/java/jadx/core/utils/exceptions/JadxArgsValidateException.java b/jadx-core/src/main/java/jadx/core/utils/exceptions/JadxArgsValidateException.java new file mode 100644 index 0000000000000000000000000000000000000000..391d3927cd3d52bc3992f8005e39dcf29deb7923 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/exceptions/JadxArgsValidateException.java @@ -0,0 +1,14 @@ +package jadx.core.utils.exceptions; + +public class JadxArgsValidateException extends RuntimeException { + + private static final long serialVersionUID = -7457621776087311909L; + + public JadxArgsValidateException(String message) { + super(message); + } + + public JadxArgsValidateException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 5c28e56f206688f5f454c80d104d446daf1ee34a..bebb12a105ba3a154094a8a1b13c627851b7350a 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -270,7 +270,7 @@ public class JadxSettingsWindow extends JDialog { }); JCheckBox cfg = new JCheckBox(); - cfg.setSelected(settings.isCFGOutput()); + cfg.setSelected(settings.isCfgOutput()); cfg.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { settings.setCfgOutput(e.getStateChange() == ItemEvent.SELECTED); @@ -279,7 +279,7 @@ public class JadxSettingsWindow extends JDialog { }); JCheckBox rawCfg = new JCheckBox(); - rawCfg.setSelected(settings.isRawCFGOutput()); + rawCfg.setSelected(settings.isRawCfgOutput()); rawCfg.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { settings.setRawCfgOutput(e.getStateChange() == ItemEvent.SELECTED);