feat: plugin options, add verify checksum option for dex input (#1385)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ import com.beust.jcommander.ParameterException;
|
||||
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;
|
||||
@@ -70,24 +75,25 @@ public class JCommanderWrapper<T> {
|
||||
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 {
|
||||
@@ -99,11 +105,14 @@ public class JCommanderWrapper<T> {
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,4 +154,46 @@ public class JCommanderWrapper<T> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import jadx.api.JadxArgs;
|
||||
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;
|
||||
|
||||
@@ -21,7 +22,7 @@ public class JadxCLI {
|
||||
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();
|
||||
@@ -38,11 +39,16 @@ public class JadxCLI {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -57,8 +63,34 @@ public class JadxCLI {
|
||||
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) -> {
|
||||
|
||||
@@ -2,12 +2,15 @@ package jadx.cli;
|
||||
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
@@ -212,7 +218,6 @@ public class JadxCLIArgs {
|
||||
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();
|
||||
@@ -260,6 +265,7 @@ public class JadxCLIArgs {
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
args.setCommentsLevel(commentsLevel);
|
||||
args.setUseDxInput(useDx);
|
||||
args.setPluginOptions(pluginOptions);
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -411,6 +417,14 @@ public class JadxCLIArgs {
|
||||
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;
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ import java.io.File;
|
||||
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;
|
||||
@@ -100,6 +102,8 @@ public class JadxArgs {
|
||||
*/
|
||||
private boolean skipFilesSave = false;
|
||||
|
||||
private Map<String, String> pluginOptions = new HashMap<>();
|
||||
|
||||
public JadxArgs() {
|
||||
// use default options
|
||||
}
|
||||
@@ -474,6 +478,14 @@ public class JadxArgs {
|
||||
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
|
||||
@@ -507,6 +519,7 @@ public class JadxArgs {
|
||||
+ ", codeCache=" + codeCache
|
||||
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
|
||||
+ ", useDxInput=" + useDxInput
|
||||
+ ", pluginOptions=" + pluginOptions
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import jadx.api.plugins.JadxPlugin;
|
||||
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;
|
||||
@@ -168,6 +169,18 @@ public final class JadxDecompiler implements Closeable {
|
||||
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) {
|
||||
|
||||
@@ -582,6 +582,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
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) {
|
||||
|
||||
@@ -62,6 +62,11 @@ import jadx.api.CommentsLevel;
|
||||
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;
|
||||
@@ -69,6 +74,7 @@ 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;
|
||||
@@ -117,8 +123,11 @@ public class JadxSettingsWindow extends JDialog {
|
||||
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 -> {
|
||||
@@ -550,6 +559,39 @@ public class JadxSettingsWindow extends JDialog {
|
||||
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()) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package jadx.gui.utils.ui;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
public class DocumentUpdateListener implements DocumentListener {
|
||||
|
||||
private final Consumer<DocumentEvent> listener;
|
||||
|
||||
public DocumentUpdateListener(Consumer<DocumentEvent> listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent event) {
|
||||
this.listener.accept(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent event) {
|
||||
this.listener.accept(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent event) {
|
||||
this.listener.accept(event);
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,7 @@ preferences.title=Einstellungen
|
||||
preferences.deobfuscation=Deobfuskierung
|
||||
preferences.appearance=Aussehen
|
||||
preferences.decompile=Dekompilierung
|
||||
#preferences.plugins=Plugins
|
||||
preferences.project=Projekt
|
||||
preferences.other=Andere
|
||||
preferences.language=Sprache
|
||||
|
||||
@@ -119,6 +119,7 @@ preferences.title=Preferences
|
||||
preferences.deobfuscation=Deobfuscation
|
||||
preferences.appearance=Appearance
|
||||
preferences.decompile=Decompilation
|
||||
preferences.plugins=Plugins
|
||||
preferences.project=Project
|
||||
preferences.other=Other
|
||||
preferences.language=Language
|
||||
|
||||
@@ -119,6 +119,7 @@ preferences.title=Preferencias
|
||||
preferences.deobfuscation=Desofuscación
|
||||
#preferences.appearance=Appearance
|
||||
preferences.decompile=Descompilación
|
||||
#preferences.plugins=Plugins
|
||||
#preferences.project=
|
||||
preferences.other=Otros
|
||||
preferences.language=Idioma
|
||||
|
||||
@@ -119,6 +119,7 @@ preferences.title=설정
|
||||
preferences.deobfuscation=난독화 해제
|
||||
preferences.appearance=외관
|
||||
preferences.decompile=디컴파일
|
||||
#preferences.plugins=Plugins
|
||||
preferences.project=프로젝트
|
||||
preferences.other=기타
|
||||
preferences.language=언어
|
||||
|
||||
@@ -119,6 +119,7 @@ preferences.title=首选项
|
||||
preferences.deobfuscation=反混淆
|
||||
preferences.appearance=界面
|
||||
preferences.decompile=反编译
|
||||
#preferences.plugins=Plugins
|
||||
preferences.project=项目
|
||||
preferences.other=其他
|
||||
preferences.language=语言
|
||||
|
||||
@@ -119,6 +119,7 @@ preferences.title=選項
|
||||
preferences.deobfuscation=去模糊化
|
||||
preferences.appearance=外觀
|
||||
preferences.decompile=反編譯
|
||||
#preferences.plugins=Plugins
|
||||
preferences.project=專案
|
||||
preferences.other=其他
|
||||
preferences.language=語言
|
||||
|
||||
+19
-9
@@ -23,19 +23,27 @@ import jadx.plugins.input.dex.utils.DexCheckSum;
|
||||
public class DexFileLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DexFileLoader.class);
|
||||
|
||||
// sharing between all instances (can be used in other plugins) // TODO:
|
||||
private static int dexUniqId = 1;
|
||||
|
||||
public static List<DexReader> collectDexFiles(List<Path> pathsList) {
|
||||
private final DexInputOptions options;
|
||||
|
||||
public DexFileLoader(DexInputOptions options) {
|
||||
this.options = options;
|
||||
resetDexUniqId();
|
||||
}
|
||||
|
||||
public List<DexReader> collectDexFiles(List<Path> pathsList) {
|
||||
return pathsList.stream()
|
||||
.map(Path::toFile)
|
||||
.map(DexFileLoader::loadDexFromFile)
|
||||
.map(this::loadDexFromFile)
|
||||
.filter(list -> !list.isEmpty())
|
||||
.flatMap(Collection::stream)
|
||||
.peek(dr -> LOG.debug("Loading dex: {}", dr))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static List<DexReader> loadDexFromFile(File file) {
|
||||
private List<DexReader> loadDexFromFile(File file) {
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
return checkFileMagic(file, inputStream, file.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
@@ -44,7 +52,7 @@ public class DexFileLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<DexReader> checkFileMagic(File file, InputStream inputStream, String inputFileName) throws IOException {
|
||||
private List<DexReader> checkFileMagic(File file, InputStream inputStream, String inputFileName) throws IOException {
|
||||
try (InputStream in = inputStream.markSupported() ? inputStream : new BufferedInputStream(inputStream)) {
|
||||
byte[] magic = new byte[DexConsts.MAX_MAGIC_SIZE];
|
||||
in.mark(magic.length);
|
||||
@@ -54,7 +62,9 @@ public class DexFileLoader {
|
||||
if (isStartWithBytes(magic, DexConsts.DEX_FILE_MAGIC)) {
|
||||
in.reset();
|
||||
byte[] content = readAllBytes(in);
|
||||
DexCheckSum.verify(content);
|
||||
if (options.isVerifyChecksum()) {
|
||||
DexCheckSum.verify(content);
|
||||
}
|
||||
DexReader dexReader = new DexReader(getNextUniqId(), inputFileName, content);
|
||||
return Collections.singletonList(dexReader);
|
||||
}
|
||||
@@ -65,7 +75,7 @@ public class DexFileLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private static List<DexReader> collectDexFromZip(File file) {
|
||||
private List<DexReader> collectDexFromZip(File file) {
|
||||
List<DexReader> result = new ArrayList<>();
|
||||
try {
|
||||
ZipSecurity.readZipEntries(file, (entry, in) -> {
|
||||
@@ -107,15 +117,15 @@ public class DexFileLoader {
|
||||
return buf.toByteArray();
|
||||
}
|
||||
|
||||
private static int getNextUniqId() {
|
||||
private static synchronized int getNextUniqId() {
|
||||
dexUniqId++;
|
||||
if (dexUniqId >= 0xFFFF) {
|
||||
resetDexUniqId();
|
||||
dexUniqId = 1;
|
||||
}
|
||||
return dexUniqId;
|
||||
}
|
||||
|
||||
public static void resetDexUniqId() {
|
||||
private static synchronized void resetDexUniqId() {
|
||||
dexUniqId = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package jadx.plugins.input.dex;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.api.plugins.options.impl.JadxOptionDescription;
|
||||
|
||||
public class DexInputOptions {
|
||||
|
||||
private static final String VERIFY_CHECKSUM_OPT = DexInputPlugin.PLUGIN_ID + ".verify-checksum";
|
||||
|
||||
private boolean verifyChecksum = true;
|
||||
|
||||
public void apply(Map<String, String> options) {
|
||||
verifyChecksum = getBooleanOption(options, VERIFY_CHECKSUM_OPT, true);
|
||||
}
|
||||
|
||||
public List<OptionDescription> buildOptionsDescriptions() {
|
||||
List<OptionDescription> list = new ArrayList<>(1);
|
||||
list.add(new JadxOptionDescription(
|
||||
VERIFY_CHECKSUM_OPT,
|
||||
"Verify dex file checksum before load",
|
||||
"yes",
|
||||
Arrays.asList("yes", "no")));
|
||||
return list;
|
||||
}
|
||||
|
||||
private boolean getBooleanOption(Map<String, String> options, String key, boolean defValue) {
|
||||
String val = options.get(key);
|
||||
if (val == null) {
|
||||
return defValue;
|
||||
}
|
||||
String valLower = val.toLowerCase(Locale.ROOT);
|
||||
if (valLower.equals("yes") || valLower.equals("true")) {
|
||||
return true;
|
||||
}
|
||||
if (valLower.equals("no") || valLower.equals("false")) {
|
||||
return false;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown value '" + val + "' for option '" + key + "'"
|
||||
+ ", expect: 'yes' or 'no'");
|
||||
}
|
||||
|
||||
public boolean isVerifyChecksum() {
|
||||
return verifyChecksum;
|
||||
}
|
||||
}
|
||||
+23
-8
@@ -3,33 +3,48 @@ package jadx.plugins.input.dex;
|
||||
import java.io.Closeable;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.input.data.impl.EmptyLoadResult;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
|
||||
public class DexInputPlugin implements JadxInputPlugin {
|
||||
public class DexInputPlugin implements JadxInputPlugin, JadxPluginOptions {
|
||||
public static final String PLUGIN_ID = "dex-input";
|
||||
|
||||
public DexInputPlugin() {
|
||||
DexFileLoader.resetDexUniqId();
|
||||
}
|
||||
private final DexInputOptions options = new DexInputOptions();
|
||||
private final DexFileLoader loader = new DexFileLoader(options);
|
||||
|
||||
@Override
|
||||
public JadxPluginInfo getPluginInfo() {
|
||||
return new JadxPluginInfo("dex-input", "DexInput", "Load .dex and .apk files");
|
||||
return new JadxPluginInfo(PLUGIN_ID, "DexInput", "Load .dex and .apk files");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILoadResult loadFiles(List<Path> input) {
|
||||
return loadDexFiles(input, null);
|
||||
return loadFiles(input, null);
|
||||
}
|
||||
|
||||
public static ILoadResult loadDexFiles(List<Path> inputFiles, Closeable closeable) {
|
||||
List<DexReader> dexReaders = DexFileLoader.collectDexFiles(inputFiles);
|
||||
public ILoadResult loadFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
|
||||
List<DexReader> dexReaders = loader.collectDexFiles(inputFiles);
|
||||
if (dexReaders.isEmpty()) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
}
|
||||
return new DexLoadResult(dexReaders, closeable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOptions(Map<String, String> options) {
|
||||
this.options.apply(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OptionDescription> getOptionsDescriptions() {
|
||||
return this.options.buildOptionsDescriptions();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -11,6 +11,8 @@ import jadx.plugins.input.dex.DexInputPlugin;
|
||||
|
||||
public class JavaConvertPlugin implements JadxInputPlugin {
|
||||
|
||||
private final DexInputPlugin dexInput = new DexInputPlugin();
|
||||
|
||||
@Override
|
||||
public JadxPluginInfo getPluginInfo() {
|
||||
return new JadxPluginInfo(
|
||||
@@ -27,6 +29,6 @@ public class JavaConvertPlugin implements JadxInputPlugin {
|
||||
result.close();
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
}
|
||||
return DexInputPlugin.loadDexFiles(result.getConverted(), result);
|
||||
return dexInput.loadFiles(result.getConverted(), result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
|
||||
public class JadxPluginManager {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxPluginManager.class);
|
||||
@@ -56,9 +58,34 @@ public class JadxPluginManager {
|
||||
if (!allPlugins.add(pluginData)) {
|
||||
throw new IllegalArgumentException("Duplicate plugin id: " + pluginData + ", class " + plugin.getClass());
|
||||
}
|
||||
if (plugin instanceof JadxPluginOptions) {
|
||||
verifyOptions(((JadxPluginOptions) plugin), pluginData.getPluginId());
|
||||
}
|
||||
return pluginData;
|
||||
}
|
||||
|
||||
private void verifyOptions(JadxPluginOptions plugin, String pluginId) {
|
||||
List<OptionDescription> descriptions = plugin.getOptionsDescriptions();
|
||||
if (descriptions == null) {
|
||||
throw new IllegalArgumentException("Null option descriptions in plugin id: " + pluginId);
|
||||
}
|
||||
String prefix = pluginId + '.';
|
||||
descriptions.forEach(descObj -> {
|
||||
String optName = descObj.name();
|
||||
if (optName == null || !optName.startsWith(prefix)) {
|
||||
throw new IllegalArgumentException("Plugin option name should start with plugin id: '" + prefix + "', option: " + optName);
|
||||
}
|
||||
String desc = descObj.description();
|
||||
if (desc == null || desc.isEmpty()) {
|
||||
throw new IllegalArgumentException("Plugin option description not set, plugin: " + pluginId);
|
||||
}
|
||||
List<String> values = descObj.values();
|
||||
if (values == null) {
|
||||
throw new IllegalArgumentException("Plugin option values is null, option: " + optName + ", plugin: " + pluginId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean unload(String pluginId) {
|
||||
boolean result = allPlugins.removeIf(pd -> {
|
||||
String id = pd.getPluginId();
|
||||
@@ -87,6 +114,13 @@ public class JadxPluginManager {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<JadxPluginOptions> getPluginsWithOptions() {
|
||||
return resolvedPlugins.stream()
|
||||
.filter(JadxPluginOptions.class::isInstance)
|
||||
.map(JadxPluginOptions.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private synchronized void resolve() {
|
||||
Map<String, List<PluginData>> provides = allPlugins.stream()
|
||||
.collect(Collectors.groupingBy(p -> p.getInfo().getProvides()));
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package jadx.api.plugins.options;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
|
||||
public interface JadxPluginOptions extends JadxPlugin {
|
||||
|
||||
void setOptions(Map<String, String> options);
|
||||
|
||||
List<OptionDescription> getOptionsDescriptions();
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package jadx.api.plugins.options;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface OptionDescription {
|
||||
|
||||
String name();
|
||||
|
||||
String description();
|
||||
|
||||
/**
|
||||
* Possible values.
|
||||
* Empty if not a limited set
|
||||
*/
|
||||
List<String> values();
|
||||
|
||||
/**
|
||||
* Default value.
|
||||
* Null if required
|
||||
*/
|
||||
@Nullable
|
||||
String defaultValue();
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package jadx.api.plugins.options.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
|
||||
public class JadxOptionDescription implements OptionDescription {
|
||||
|
||||
private final String name;
|
||||
private final String desc;
|
||||
private final String defaultValue;
|
||||
private final List<String> values;
|
||||
|
||||
public JadxOptionDescription(String name, String desc, @Nullable String defaultValue, List<String> values) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
this.defaultValue = defaultValue;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String defaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> values() {
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OptionDescription{" + desc + ", values=" + values + '}';
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -11,6 +11,8 @@ import jadx.plugins.input.dex.DexInputPlugin;
|
||||
|
||||
public class SmaliInputPlugin implements JadxInputPlugin {
|
||||
|
||||
private final DexInputPlugin dexInput = new DexInputPlugin();
|
||||
|
||||
@Override
|
||||
public JadxPluginInfo getPluginInfo() {
|
||||
return new JadxPluginInfo("smali-input", "SmaliInput", "Load .smali files");
|
||||
@@ -22,6 +24,6 @@ public class SmaliInputPlugin implements JadxInputPlugin {
|
||||
if (!convert.execute(input)) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
}
|
||||
return DexInputPlugin.loadDexFiles(convert.getDexFiles(), convert);
|
||||
return dexInput.loadFiles(convert.getDexFiles(), convert);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user