Skip to content

Commit

Permalink
feat: plugin options, add verify checksum option for dex input (#1385)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Feb 21, 2022
1 parent 0933539 commit 54683e3
Show file tree
Hide file tree
Showing 24 changed files with 435 additions and 25 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,17 @@ options:
-q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version
-h, --help - print this help

Plugin options (-P<name>=<value>):
1) dex-input (Load .dex and .apk files)
-Pdex-input.verify-checksum - Verify dex file checksum before load, values: [yes, no], default: yes

Examples:
jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid, printable" classes.dex
jadx --log-level ERROR app.apk
jadx -Pdex-input.verify-checksum=no app.apk
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
Expand Down
57 changes: 54 additions & 3 deletions jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
import com.beust.jcommander.Parameterized;

import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;

public class JCommanderWrapper<T> {
private final JCommander jc;
Expand Down Expand Up @@ -70,24 +75,25 @@ public void printUsage() {
maxNamesLen = len;
}
}
maxNamesLen += 3;

JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
for (Field f : getFields(args.getClass())) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
if (p == null || p.getParameter().hidden()) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
String description = p.getDescription();
addSpaces(opt, maxNamesLen - opt.length() + 3);
addSpaces(opt, maxNamesLen - opt.length());
if (description.contains("\n")) {
String[] lines = description.split("\n");
opt.append("- ").append(lines[0]);
for (int i = 1; i < lines.length; i++) {
opt.append('\n');
addSpaces(opt, maxNamesLen + 5);
addSpaces(opt, maxNamesLen + 2);
opt.append(lines[i]);
}
} else {
Expand All @@ -99,11 +105,14 @@ public void printUsage() {
}
out.println(opt);
}
out.println(appendPluginOptions(maxNamesLen));
out.println();
out.println("Examples:");
out.println(" jadx -d out classes.dex");
out.println(" jadx --rename-flags \"none\" classes.dex");
out.println(" jadx --rename-flags \"valid, printable\" classes.dex");
out.println(" jadx --log-level ERROR app.apk");
out.println(" jadx -Pdex-input.verify-checksum=no app.apk");
}

/**
Expand Down Expand Up @@ -145,4 +154,46 @@ private static void addSpaces(StringBuilder str, int count) {
str.append(' ');
}
}

private String appendPluginOptions(int maxNamesLen) {
StringBuilder sb = new StringBuilder();
JadxPluginManager pluginManager = new JadxPluginManager();
pluginManager.load();
int k = 1;
for (JadxPlugin plugin : pluginManager.getAllPlugins()) {
if (plugin instanceof JadxPluginOptions) {
if (appendPlugin(((JadxPluginOptions) plugin), sb, maxNamesLen, k)) {
k++;
}
}
}
if (sb.length() == 0) {
return "";
}
return "\nPlugin options (-P<name>=<value>):" + sb;
}

private boolean appendPlugin(JadxPluginOptions plugin, StringBuilder out, int maxNamesLen, int k) {
List<OptionDescription> descs = plugin.getOptionsDescriptions();
if (descs.isEmpty()) {
return false;
}
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
out.append("\n ").append(k).append(") ");
out.append(pluginInfo.getPluginId()).append(" (").append(pluginInfo.getDescription()).append(") ");
for (OptionDescription desc : descs) {
StringBuilder opt = new StringBuilder();
opt.append(" -P").append(desc.name());
addSpaces(opt, maxNamesLen - opt.length());
opt.append("- ").append(desc.description());
if (!desc.values().isEmpty()) {
opt.append(", values: ").append(desc.values());
}
if (desc.defaultValue() != null) {
opt.append(", default: ").append(desc.defaultValue());
}
out.append("\n").append(opt);
}
return true;
}
}
36 changes: 34 additions & 2 deletions jadx-cli/src/main/java/jadx/cli/JadxCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jadx.api.JadxDecompiler;
import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.cli.LogHelper.LogLevelEnum;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;

Expand All @@ -21,7 +22,7 @@ public static void main(String[] args) {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
LOG.error("Process error:", e);
result = 1;
} finally {
FileUtils.deleteTempRootDir();
Expand All @@ -38,11 +39,16 @@ public static int execute(String[] args) {
}

private static int processAndSave(JadxCLIArgs cliArgs) {
setLogLevelsForLoadingStage(cliArgs);
JadxArgs jadxArgs = cliArgs.toJadxArgs();
jadxArgs.setCodeCache(new NoOpCodeCache());
jadxArgs.setCodeWriterProvider(SimpleCodeWriter::new);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
if (checkForErrors(jadx)) {
return 1;
}
LogHelper.setLogLevelFromArgs(cliArgs);
if (!SingleClassMode.process(jadx, cliArgs)) {
save(jadx);
}
Expand All @@ -57,8 +63,34 @@ private static int processAndSave(JadxCLIArgs cliArgs) {
return 0;
}

private static void setLogLevelsForLoadingStage(JadxCLIArgs cliArgs) {
switch (cliArgs.getLogLevel()) {
case QUIET:
LogHelper.setLogLevelFromArgs(cliArgs);
break;

case PROGRESS:
// show load errors
LogHelper.applyLogLevel(LogLevelEnum.ERROR);
break;
}
}

private static boolean checkForErrors(JadxDecompiler jadx) {
if (jadx.getRoot().getClasses().isEmpty()) {
LOG.error("Load failed! No classes for decompile!");
return true;
}
if (jadx.getErrorsCount() > 0) {
LOG.error("Load with errors! Check log for details");
// continue processing
return false;
}
return false;
}

private static void save(JadxDecompiler jadx) {
if (LogHelper.getLogLevel() == LogHelper.LogLevelEnum.QUIET) {
if (LogHelper.getLogLevel() == LogLevelEnum.QUIET) {
jadx.save();
} else {
jadx.save(500, (done, total) -> {
Expand Down
16 changes: 15 additions & 1 deletion jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.beust.jcommander.DynamicParameter;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;

Expand Down Expand Up @@ -177,6 +180,9 @@ public class JadxCLIArgs {
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;

@DynamicParameter(names = "-P", description = "Plugin options", hidden = true)
protected Map<String, String> pluginOptions = new HashMap<>();

public boolean processArgs(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
return jcw.parse(args) && process(jcw);
Expand Down Expand Up @@ -212,7 +218,6 @@ private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
LogHelper.setLogLevelFromArgs(this);
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
jcw.printUsage();
Expand Down Expand Up @@ -260,6 +265,7 @@ public JadxArgs toJadxArgs() {
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
args.setPluginOptions(pluginOptions);
return args;
}

Expand Down Expand Up @@ -411,6 +417,14 @@ public CommentsLevel getCommentsLevel() {
return commentsLevel;
}

public LogHelper.LogLevelEnum getLogLevel() {
return logLevel;
}

public Map<String, String> getPluginOptions() {
return pluginOptions;
}

static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;

Expand Down
13 changes: 13 additions & 0 deletions jadx-core/src/main/java/jadx/api/JadxArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
Expand Down Expand Up @@ -100,6 +102,8 @@ public enum UseKotlinMethodsForVarNames {
*/
private boolean skipFilesSave = false;

private Map<String, String> pluginOptions = new HashMap<>();

public JadxArgs() {
// use default options
}
Expand Down Expand Up @@ -474,6 +478,14 @@ public void setSkipFilesSave(boolean skipFilesSave) {
this.skipFilesSave = skipFilesSave;
}

public Map<String, String> getPluginOptions() {
return pluginOptions;
}

public void setPluginOptions(Map<String, String> pluginOptions) {
this.pluginOptions = pluginOptions;
}

@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
Expand Down Expand Up @@ -507,6 +519,7 @@ public String toString() {
+ ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput
+ ", pluginOptions=" + pluginOptions
+ '}';
}
}
13 changes: 13 additions & 0 deletions jadx-core/src/main/java/jadx/api/JadxDecompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
Expand Down Expand Up @@ -168,6 +169,18 @@ private void loadPlugins(JadxArgs args) {
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
p -> p.getPluginInfo().getPluginId()));
}
Map<String, String> pluginOptions = args.getPluginOptions();
if (!pluginOptions.isEmpty()) {
LOG.debug("Applying plugin options: {}", pluginOptions);
for (JadxPluginOptions plugin : pluginManager.getPluginsWithOptions()) {
try {
plugin.setOptions(pluginOptions);
} catch (Exception e) {
String pluginId = plugin.getPluginInfo().getPluginId();
throw new JadxRuntimeException("Failed to apply options for plugin: " + pluginId, e);
}
}
}
}

public void registerPlugin(JadxPlugin plugin) {
Expand Down
4 changes: 4 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,10 @@ public void setLineNumbersMode(LineNumbersMode lineNumbersMode) {
this.lineNumbersMode = lineNumbersMode;
}

public void setPluginOptions(Map<String, String> pluginOptions) {
this.pluginOptions = pluginOptions;
}

private void upgradeSettings(int fromVersion) {
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
if (fromVersion == 0) {
Expand Down
42 changes: 42 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,19 @@
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.args.DeobfuscationMapFileMode;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.FontUtils;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.LangLocale;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.DocumentUpdateListener;

public class JadxSettingsWindow extends JDialog {
private static final long serialVersionUID = -1804570470377354148L;
Expand Down Expand Up @@ -117,8 +123,11 @@ private void initUI() {
leftPanel.add(makeAppearanceGroup());
leftPanel.add(makeOtherGroup());
leftPanel.add(makeSearchResGroup());
leftPanel.add(makePluginOptionsGroup());
leftPanel.add(Box.createVerticalGlue());

rightPanel.add(makeDecompilationGroup());
rightPanel.add(Box.createVerticalGlue());

JButton saveBtn = new JButton(NLS.str("preferences.save"));
saveBtn.addActionListener(event -> {
Expand Down Expand Up @@ -550,6 +559,39 @@ private SettingsGroup makeDecompilationGroup() {
return other;
}

private SettingsGroup makePluginOptionsGroup() {
SettingsGroup pluginsGroup = new SettingsGroup(NLS.str("preferences.plugins"));
JadxPluginManager pluginManager = mainWindow.getWrapper().getDecompiler().getPluginManager();
for (JadxPlugin plugin : pluginManager.getAllPlugins()) {
if (!(plugin instanceof JadxPluginOptions)) {
continue;
}
JadxPluginInfo pluginInfo = plugin.getPluginInfo();
JadxPluginOptions optPlugin = (JadxPluginOptions) plugin;
for (OptionDescription opt : optPlugin.getOptionsDescriptions()) {
String title = "[" + pluginInfo.getPluginId() + "] " + opt.description();
if (opt.values().isEmpty()) {
JTextField textField = new JTextField();
textField.getDocument().addDocumentListener(new DocumentUpdateListener(event -> {
settings.getPluginOptions().put(opt.name(), textField.getText());
needReload();
}));
pluginsGroup.addRow(title, textField);
} else {
String curValue = settings.getPluginOptions().get(opt.name());
JComboBox<String> combo = new JComboBox<>(opt.values().toArray(new String[0]));
combo.setSelectedItem(curValue != null ? curValue : opt.defaultValue());
combo.addActionListener(e -> {
settings.getPluginOptions().put(opt.name(), ((String) combo.getSelectedItem()));
needReload();
});
pluginsGroup.addRow(title, combo);
}
}
}
return pluginsGroup;
}

private SettingsGroup makeOtherGroup() {
JComboBox<LangLocale> languageCbx = new JComboBox<>(NLS.getLangLocales());
for (LangLocale locale : NLS.getLangLocales()) {
Expand Down
Loading

0 comments on commit 54683e3

Please sign in to comment.