package jadx.cli;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import jadx.api.IJadxArgs;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
public class JadxCLIArgs implements IJadxArgs {
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
protected List<String> files;
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
protected String outDirName;
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
protected boolean skipResources = false;
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
protected boolean skipSources = false;
@Parameter(names = {"-e", "--export-gradle"}, description = "save as android gradle project")
protected boolean exportAsGradleProject = false;
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@Parameter(names = "--no-replace-consts", converter = InvertedBooleanConverter.class,
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)")
protected boolean escapeUnicode = false;
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
protected boolean deobfuscationOn = false;
@Parameter(names = {"--deobf-min"}, description = "min length of name")
protected int deobfuscationMinLength = 2;
@Parameter(names = {"--deobf-max"}, description = "max length of name")
protected int deobfuscationMaxLength = 64;
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
protected boolean deobfuscationForceSave = false;
@Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias")
protected boolean deobfuscationUseSourceNameAsAlias = false;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
protected boolean cfgOutput = false;
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
protected boolean rawCfgOutput = false;
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
protected boolean verbose = false;
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
protected boolean printHelp = false;
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public boolean processArgs(String[] args) {
return parse(args) && process();
}
private boolean parse(String[] args) {
try {
new JCommander(this, args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
return false;
}
}
private boolean process() {
if (isPrintHelp()) {
printUsage();
return false;
}
try {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive");
}
if (files != null) {
for (String fileName : files) {
File file = new File(fileName);
if (file.exists()) {
input.add(file);
} else {
throw new JadxException("File not found: " + file);
}
}
}
if (input.size() > 1) {
throw new JadxException("Only one input file is supported");
}
if (outDirName != null) {
outputDir = new File(outDirName);
}
if (isVerbose()) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
// remove INFO ThresholdFilter
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
if (appender != null) {
appender.clearAllFilters();
}
}
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
return false;
}
return true;
}
public void printUsage() {
JCommander jc = new JCommander(this);
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<String, ParameterDescription>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
int len = p.getNames().length();
if (len > maxNamesLen) {
maxNamesLen = len;
}
}
Field[] fields = JadxCLIArgs.class.getDeclaredFields();
for (Field f : fields) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
continue;
}
StringBuilder opt = new StringBuilder();
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");
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++) {
str.append(' ');
}
}
public static class InvertedBooleanConverter implements IStringConverter<Boolean> {
@Override
public Boolean convert(String value) {
return "false".equals(value);
}
}
public List<File> getInput() {
return input;
}
@Override
public File getOutDir() {
return outputDir;
}
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
}
public boolean isPrintHelp() {
return printHelp;
}
@Override
public boolean isSkipResources() {
return skipResources;
}
@Override
public boolean isSkipSources() {
return skipSources;
}
@Override
public int getThreadsCount() {
return threadsCount;
}
@Override
public boolean isCFGOutput() {
return cfgOutput;
}
@Override
public boolean isRawCFGOutput() {
return rawCfgOutput;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
}
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@Override
public boolean isVerbose() {
return verbose;
}
@Override
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@Override
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
@Override
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
@Override
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
@Override
public boolean useSourceNameAsClassAlias() {
return deobfuscationUseSourceNameAsAlias;
}
@Override
public boolean escapeUnicode() {
return escapeUnicode;
}
@Override
public boolean isReplaceConsts() {
return replaceConsts;
}
@Override
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
}