未验证 提交 54683e31 编写于 作者: S Skylot

feat: plugin options, add verify checksum option for dex input (#1385)

上级 09335395
......@@ -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()) {
......
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=語言
......
......@@ -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;
}
}
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;
}
}
......@@ -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();
}
}
......@@ -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()));
......
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();
}
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();
}
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 + '}';
}
}
......@@ -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);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册