mirror of
https://github.com/skylot/jadx.git
synced 2024-11-23 12:50:02 +00:00
refactor: make input plugin api similar to pass plugins
This commit is contained in:
parent
0c4d46ead5
commit
a89dbc1152
@ -18,7 +18,7 @@ import com.beust.jcommander.ParameterException;
|
||||
import com.beust.jcommander.Parameterized;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.impl.plugins.SimplePluginContext;
|
||||
import jadx.api.impl.plugins.PluginsContext;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
@ -73,7 +73,7 @@ public class JCommanderWrapper<T> {
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
// print usage in not sorted fields order (by default its sorted by description)
|
||||
// print usage in not sorted fields order (by default sorted by description)
|
||||
PrintStream out = System.out;
|
||||
out.println();
|
||||
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
||||
@ -176,16 +176,11 @@ public class JCommanderWrapper<T> {
|
||||
int k = 1;
|
||||
// load and init all options plugins to print all options
|
||||
try (JadxDecompiler decompiler = new JadxDecompiler(argsObj.toJadxArgs())) {
|
||||
Map<String, String> pluginOptions = decompiler.getArgs().getPluginOptions();
|
||||
SimplePluginContext context = new SimplePluginContext(decompiler);
|
||||
for (JadxPlugin plugin : decompiler.getPluginManager().getAllPlugins()) {
|
||||
if (plugin instanceof JadxPluginOptions) {
|
||||
JadxPluginOptions optionsPlugin = (JadxPluginOptions) plugin;
|
||||
optionsPlugin.setOptions(pluginOptions);
|
||||
optionsPlugin.init(context);
|
||||
if (appendPlugin(optionsPlugin, sb, maxNamesLen, k)) {
|
||||
k++;
|
||||
}
|
||||
PluginsContext context = new PluginsContext(decompiler);
|
||||
decompiler.getPluginManager().initAll(context);
|
||||
for (Map.Entry<JadxPlugin, JadxPluginOptions> entry : context.getOptionsMap().entrySet()) {
|
||||
if (appendPlugin(entry.getKey(), entry.getValue(), sb, maxNamesLen, k)) {
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -195,8 +190,8 @@ public class JCommanderWrapper<T> {
|
||||
return "\nPlugin options (-P<name>=<value>):" + sb;
|
||||
}
|
||||
|
||||
private boolean appendPlugin(JadxPluginOptions plugin, StringBuilder out, int maxNamesLen, int k) {
|
||||
List<OptionDescription> descs = plugin.getOptionsDescriptions();
|
||||
private boolean appendPlugin(JadxPlugin plugin, JadxPluginOptions options, StringBuilder out, int maxNamesLen, int k) {
|
||||
List<OptionDescription> descs = options.getOptionsDescriptions();
|
||||
if (descs.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -12,9 +12,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.impl.plugins.PluginsContext;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.core.clsp.ClsSet;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
@ -38,10 +40,12 @@ public class ConvertToClsSet {
|
||||
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
|
||||
Path output = inputPaths.remove(0);
|
||||
|
||||
PluginsContext pluginsContext = new PluginsContext(new JadxDecompiler());
|
||||
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||
pluginManager.load();
|
||||
List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||
pluginManager.initResolved(pluginsContext);
|
||||
List<ICodeLoader> loadedInputs = new ArrayList<>();
|
||||
for (JadxCodeInput inputPlugin : pluginsContext.getCodeInputs()) {
|
||||
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ public class JadxArgsValidator {
|
||||
|
||||
private static void checkInputFiles(JadxDecompiler jadx, JadxArgs args) {
|
||||
List<File> inputFiles = args.getInputFiles();
|
||||
if (inputFiles.isEmpty() && jadx.getCustomLoads().isEmpty()) {
|
||||
if (inputFiles.isEmpty() && jadx.getCustomCodeLoaders().isEmpty()) {
|
||||
throw new JadxArgsValidateException("Please specify input file");
|
||||
}
|
||||
for (File inputFile : inputFiles) {
|
||||
|
@ -23,7 +23,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.impl.plugins.SimplePluginContext;
|
||||
import jadx.api.impl.plugins.PluginsContext;
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
@ -31,10 +31,8 @@ import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.metadata.annotations.VarRef;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
import jadx.api.plugins.pass.types.JadxAfterLoadPass;
|
||||
import jadx.api.plugins.pass.types.JadxPassType;
|
||||
@ -88,7 +86,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
|
||||
private final JadxArgs args;
|
||||
private final JadxPluginManager pluginManager = new JadxPluginManager();
|
||||
private final List<ILoadResult> loadedInputs = new ArrayList<>();
|
||||
private final List<ICodeLoader> loadedInputs = new ArrayList<>();
|
||||
|
||||
private RootNode root;
|
||||
private List<JavaClass> classes;
|
||||
@ -99,9 +97,9 @@ public final class JadxDecompiler implements Closeable {
|
||||
|
||||
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
|
||||
|
||||
private final List<ILoadResult> customLoads = new ArrayList<>();
|
||||
private final PluginsContext pluginsContext = new PluginsContext(this);
|
||||
private final List<ICodeLoader> customCodeLoaders = new ArrayList<>();
|
||||
private final Map<JadxPassType, List<JadxPass>> customPasses = new HashMap<>();
|
||||
private @Nullable JadxGuiContext guiContext;
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new JadxArgs());
|
||||
@ -135,13 +133,13 @@ public final class JadxDecompiler implements Closeable {
|
||||
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
|
||||
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||
long start = System.currentTimeMillis();
|
||||
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
|
||||
ILoadResult loadResult = inputPlugin.loadFiles(inputFiles);
|
||||
if (loadResult != null && !loadResult.isEmpty()) {
|
||||
loadedInputs.add(loadResult);
|
||||
for (JadxCodeInput codeLoader : pluginsContext.getCodeInputs()) {
|
||||
ICodeLoader loader = codeLoader.loadFiles(inputFiles);
|
||||
if (loader != null && !loader.isEmpty()) {
|
||||
loadedInputs.add(loader);
|
||||
}
|
||||
}
|
||||
loadedInputs.addAll(customLoads);
|
||||
loadedInputs.addAll(customCodeLoaders);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Loaded using {} inputs plugin in {} ms", loadedInputs.size(), System.currentTimeMillis() - start);
|
||||
}
|
||||
@ -180,39 +178,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
|
||||
p -> p.getPluginInfo().getPluginId()));
|
||||
}
|
||||
applyPluginOptions();
|
||||
initPlugins();
|
||||
}
|
||||
|
||||
private void applyPluginOptions() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initPlugins() {
|
||||
customPasses.clear();
|
||||
|
||||
List<JadxPlugin> plugins = pluginManager.getResolvedPlugins();
|
||||
SimplePluginContext context = new SimplePluginContext(this);
|
||||
context.setGuiContext(guiContext);
|
||||
for (JadxPlugin passPlugin : plugins) {
|
||||
try {
|
||||
passPlugin.init(context);
|
||||
} catch (Exception e) {
|
||||
String pluginId = passPlugin.getPluginInfo().getPluginId();
|
||||
throw new JadxRuntimeException("Failed to pass plugin: " + pluginId, e);
|
||||
}
|
||||
}
|
||||
pluginManager.initResolved(pluginsContext);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
List<String> passes = customPasses.values().stream().flatMap(Collection::stream)
|
||||
.map(p -> p.getInfo().getName()).collect(Collectors.toList());
|
||||
@ -684,20 +650,20 @@ public final class JadxDecompiler implements Closeable {
|
||||
return decompileScheduler;
|
||||
}
|
||||
|
||||
public void addCustomLoad(ILoadResult customLoad) {
|
||||
customLoads.add(customLoad);
|
||||
public void addCustomCodeLoader(ICodeLoader customCodeLoader) {
|
||||
customCodeLoaders.add(customCodeLoader);
|
||||
}
|
||||
|
||||
public List<ILoadResult> getCustomLoads() {
|
||||
return customLoads;
|
||||
public List<ICodeLoader> getCustomCodeLoaders() {
|
||||
return customCodeLoaders;
|
||||
}
|
||||
|
||||
public void addCustomPass(JadxPass pass) {
|
||||
customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass);
|
||||
}
|
||||
|
||||
public void setJadxGuiContext(JadxGuiContext guiContext) {
|
||||
this.guiContext = guiContext;
|
||||
public PluginsContext getPluginsContext() {
|
||||
return pluginsContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,86 @@
|
||||
package jadx.api.impl.plugins;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class PluginsContext implements JadxPluginContext {
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
private final List<JadxCodeInput> codeInputs = new ArrayList<>();
|
||||
private final Map<JadxPlugin, JadxPluginOptions> optionsMap = new IdentityHashMap<>();
|
||||
private @Nullable JadxGuiContext guiContext;
|
||||
|
||||
private @Nullable JadxPlugin currentPlugin;
|
||||
|
||||
public PluginsContext(JadxDecompiler decompiler) {
|
||||
this.decompiler = decompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JadxArgs getArgs() {
|
||||
return decompiler.getArgs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JadxDecompiler getDecompiler() {
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPass(JadxPass pass) {
|
||||
decompiler.addCustomPass(pass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCodeInput(JadxCodeInput codeInput) {
|
||||
codeInputs.add(codeInput);
|
||||
}
|
||||
|
||||
public List<JadxCodeInput> getCodeInputs() {
|
||||
return codeInputs;
|
||||
}
|
||||
|
||||
public void setCurrentPlugin(JadxPlugin currentPlugin) {
|
||||
this.currentPlugin = currentPlugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOptions(JadxPluginOptions options) {
|
||||
Objects.requireNonNull(currentPlugin);
|
||||
try {
|
||||
options.setOptions(decompiler.getArgs().getPluginOptions());
|
||||
optionsMap.put(currentPlugin, options);
|
||||
} catch (Exception e) {
|
||||
String pluginId = currentPlugin.getPluginInfo().getPluginId();
|
||||
throw new JadxRuntimeException("Failed to apply options for plugin: " + pluginId, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<JadxPlugin, JadxPluginOptions> getOptionsMap() {
|
||||
return optionsMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JadxGuiContext getGuiContext() {
|
||||
return guiContext;
|
||||
}
|
||||
|
||||
public void setGuiContext(JadxGuiContext guiContext) {
|
||||
this.guiContext = guiContext;
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package jadx.api.impl.plugins;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
import jadx.api.plugins.pass.JadxPassContext;
|
||||
|
||||
public class SimplePassContext implements JadxPassContext {
|
||||
|
||||
private final JadxDecompiler jadxDecompiler;
|
||||
|
||||
public SimplePassContext(JadxDecompiler jadxDecompiler) {
|
||||
this.jadxDecompiler = jadxDecompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPass(JadxPass pass) {
|
||||
jadxDecompiler.addCustomPass(pass);
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package jadx.api.impl.plugins;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.api.plugins.pass.JadxPassContext;
|
||||
|
||||
public class SimplePluginContext implements JadxPluginContext {
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
private final JadxPassContext passContext;
|
||||
private @Nullable JadxGuiContext guiContext;
|
||||
|
||||
public SimplePluginContext(JadxDecompiler decompiler) {
|
||||
this.decompiler = decompiler;
|
||||
this.passContext = new SimplePassContext(decompiler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JadxArgs getArgs() {
|
||||
return decompiler.getArgs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JadxDecompiler getDecompiler() {
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JadxPassContext getPassContext() {
|
||||
return passContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JadxGuiContext getGuiContext() {
|
||||
return guiContext;
|
||||
}
|
||||
|
||||
public void setGuiContext(JadxGuiContext guiContext) {
|
||||
this.guiContext = guiContext;
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
package jadx.api.plugins;
|
||||
|
||||
public interface JadxPlugin {
|
||||
|
||||
JadxPluginInfo getPluginInfo();
|
||||
|
||||
default void init(JadxPluginContext context) {
|
||||
// default to no-op
|
||||
}
|
||||
void init(JadxPluginContext context);
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.gui.JadxGuiContext;
|
||||
import jadx.api.plugins.pass.JadxPassContext;
|
||||
import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
|
||||
public interface JadxPluginContext {
|
||||
|
||||
@ -13,7 +15,11 @@ public interface JadxPluginContext {
|
||||
|
||||
JadxDecompiler getDecompiler();
|
||||
|
||||
JadxPassContext getPassContext();
|
||||
void addPass(JadxPass pass);
|
||||
|
||||
void addCodeInput(JadxCodeInput codeInput);
|
||||
|
||||
void registerOptions(JadxPluginOptions options);
|
||||
|
||||
@Nullable
|
||||
JadxGuiContext getGuiContext();
|
||||
|
@ -15,9 +15,10 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.JadxInputPlugin;
|
||||
import jadx.api.impl.plugins.PluginsContext;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class JadxPluginManager {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxPluginManager.class);
|
||||
@ -59,34 +60,9 @@ 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();
|
||||
@ -111,22 +87,6 @@ public class JadxPluginManager {
|
||||
return Collections.unmodifiableList(resolvedPlugins);
|
||||
}
|
||||
|
||||
public List<JadxInputPlugin> getInputPlugins() {
|
||||
return getPluginsWithType(JadxInputPlugin.class);
|
||||
}
|
||||
|
||||
public List<JadxPluginOptions> getPluginsWithOptions() {
|
||||
return getPluginsWithType(JadxPluginOptions.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends JadxPlugin> List<T> getPluginsWithType(Class<T> type) {
|
||||
return resolvedPlugins.stream()
|
||||
.filter(p -> type.isAssignableFrom(p.getClass()))
|
||||
.map(p -> (T) p)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private synchronized void resolve() {
|
||||
Map<String, List<PluginData>> provides = allPlugins.stream()
|
||||
.collect(Collectors.groupingBy(p -> p.getInfo().getProvides()));
|
||||
@ -151,6 +111,52 @@ public class JadxPluginManager {
|
||||
resolvedPlugins = result.stream().map(PluginData::getPlugin).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void initAll(PluginsContext context) {
|
||||
init(context, getAllPlugins());
|
||||
}
|
||||
|
||||
public void initResolved(PluginsContext context) {
|
||||
init(context, resolvedPlugins);
|
||||
}
|
||||
|
||||
private void init(PluginsContext context, List<JadxPlugin> plugins) {
|
||||
for (JadxPlugin plugin : plugins) {
|
||||
try {
|
||||
context.setCurrentPlugin(plugin);
|
||||
plugin.init(context);
|
||||
} catch (Exception e) {
|
||||
String pluginId = plugin.getPluginInfo().getPluginId();
|
||||
throw new JadxRuntimeException("Failed to init plugin: " + pluginId, e);
|
||||
}
|
||||
}
|
||||
for (Map.Entry<JadxPlugin, JadxPluginOptions> entry : context.getOptionsMap().entrySet()) {
|
||||
verifyOptions(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyOptions(JadxPlugin plugin, JadxPluginOptions options) {
|
||||
String pluginId = plugin.getPluginInfo().getPluginId();
|
||||
List<OptionDescription> descriptions = options.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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static final class PluginData implements Comparable<PluginData> {
|
||||
private final JadxPlugin plugin;
|
||||
private final JadxPluginInfo info;
|
||||
|
@ -0,0 +1,13 @@
|
||||
package jadx.api.plugins.input;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
|
||||
public interface ICodeLoader extends Closeable {
|
||||
|
||||
void visitClasses(Consumer<IClassData> consumer);
|
||||
|
||||
boolean isEmpty();
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package jadx.api.plugins.input;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public interface JadxCodeInput {
|
||||
ICodeLoader loadFiles(List<Path> input);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package jadx.api.plugins.input;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
|
||||
public interface JadxInputPlugin extends JadxPlugin {
|
||||
ILoadResult loadFiles(List<Path> input);
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package jadx.api.plugins.input.data;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface ILoadResult extends Closeable {
|
||||
void visitClasses(Consumer<IClassData> consumer);
|
||||
|
||||
void visitResources(Consumer<IResourceData> consumer);
|
||||
|
||||
boolean isEmpty();
|
||||
}
|
@ -3,13 +3,12 @@ package jadx.api.plugins.input.data.impl;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.input.data.IResourceData;
|
||||
|
||||
public class EmptyLoadResult implements ILoadResult {
|
||||
public class EmptyCodeLoader implements ICodeLoader {
|
||||
|
||||
public static final EmptyLoadResult INSTANCE = new EmptyLoadResult();
|
||||
public static final EmptyCodeLoader INSTANCE = new EmptyCodeLoader();
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
@ -20,10 +19,6 @@ public class EmptyLoadResult implements ILoadResult {
|
||||
public void visitClasses(Consumer<IClassData> consumer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitResources(Consumer<IResourceData> consumer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
@ -3,9 +3,7 @@ package jadx.api.plugins.options;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
|
||||
public interface JadxPluginOptions extends JadxPlugin {
|
||||
public interface JadxPluginOptions {
|
||||
|
||||
void setOptions(Map<String, String> options);
|
||||
|
||||
|
@ -4,9 +4,21 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class BaseOptionsParser {
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
|
||||
public boolean getBooleanOption(Map<String, String> options, String key, boolean defValue) {
|
||||
public abstract class BaseOptionsParser implements JadxPluginOptions {
|
||||
|
||||
protected Map<String, String> options;
|
||||
|
||||
@Override
|
||||
public void setOptions(Map<String, String> options) {
|
||||
this.options = options;
|
||||
parseOptions();
|
||||
}
|
||||
|
||||
public abstract void parseOptions();
|
||||
|
||||
public boolean getBooleanOption(String key, boolean defValue) {
|
||||
String val = options.get(key);
|
||||
if (val == null) {
|
||||
return defValue;
|
||||
@ -22,7 +34,7 @@ public class BaseOptionsParser {
|
||||
+ ", expect: 'yes' or 'no'");
|
||||
}
|
||||
|
||||
public <T> T getOption(Map<String, String> options, String key, Function<String, T> parse, T defValue) {
|
||||
public <T> T getOption(String key, Function<String, T> parse, T defValue) {
|
||||
String val = options.get(key);
|
||||
if (val == null) {
|
||||
return defValue;
|
||||
|
@ -1,6 +0,0 @@
|
||||
package jadx.api.plugins.pass;
|
||||
|
||||
public interface JadxPassContext {
|
||||
|
||||
void addPass(JadxPass pass);
|
||||
}
|
@ -23,8 +23,8 @@ import jadx.api.ResourcesLoader;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.passes.DecompilePassWrapper;
|
||||
import jadx.api.impl.passes.PreparePassWrapper;
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.pass.JadxPass;
|
||||
import jadx.api.plugins.pass.types.JadxDecompilePass;
|
||||
import jadx.api.plugins.pass.types.JadxPassType;
|
||||
@ -115,9 +115,9 @@ public class RootNode {
|
||||
}
|
||||
}
|
||||
|
||||
public void loadClasses(List<ILoadResult> loadedInputs) {
|
||||
for (ILoadResult loadedInput : loadedInputs) {
|
||||
loadedInput.visitClasses(cls -> {
|
||||
public void loadClasses(List<ICodeLoader> loadedInputs) {
|
||||
for (ICodeLoader codeLoader : loadedInputs) {
|
||||
codeLoader.visitClasses(cls -> {
|
||||
try {
|
||||
addClassNode(new ClassNode(RootNode.this, cls));
|
||||
} catch (Exception e) {
|
||||
|
@ -46,7 +46,7 @@ public class JadxDecompilerTest {
|
||||
public void testDirectDexInput() throws IOException {
|
||||
try (JadxDecompiler jadx = new JadxDecompiler();
|
||||
InputStream in = new FileInputStream(getFileFromSampleDir("hello.dex"))) {
|
||||
jadx.addCustomLoad(new DexInputPlugin().loadDexFromInputStream(in, "input"));
|
||||
jadx.addCustomCodeLoader(new DexInputPlugin().loadDexFromInputStream(in, "input"));
|
||||
jadx.load();
|
||||
for (JavaClass cls : jadx.getClasses()) {
|
||||
System.out.println(cls.getCode());
|
||||
|
@ -19,14 +19,13 @@ import jadx.api.JavaNode;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
import jadx.api.impl.plugins.PluginsContext;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginManager;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.ProcessState;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.plugins.context.PluginsContext;
|
||||
import jadx.gui.plugins.context.GuiPluginsContext;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@ -47,7 +46,7 @@ public class JadxWrapper {
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
private volatile @Nullable JadxDecompiler decompiler;
|
||||
private PluginsContext pluginsContext;
|
||||
private GuiPluginsContext guiPluginsContext;
|
||||
|
||||
public JadxWrapper(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
@ -62,8 +61,8 @@ public class JadxWrapper {
|
||||
project.fillJadxArgs(jadxArgs);
|
||||
|
||||
this.decompiler = new JadxDecompiler(jadxArgs);
|
||||
this.pluginsContext = new PluginsContext(mainWindow);
|
||||
this.decompiler.setJadxGuiContext(pluginsContext);
|
||||
this.guiPluginsContext = new GuiPluginsContext(mainWindow);
|
||||
this.decompiler.getPluginsContext().setGuiContext(guiPluginsContext);
|
||||
this.decompiler.load();
|
||||
initCodeCache();
|
||||
}
|
||||
@ -89,9 +88,9 @@ public class JadxWrapper {
|
||||
decompiler.close();
|
||||
decompiler = null;
|
||||
}
|
||||
if (pluginsContext != null) {
|
||||
pluginsContext.reset();
|
||||
pluginsContext = null;
|
||||
if (guiPluginsContext != null) {
|
||||
guiPluginsContext.reset();
|
||||
guiPluginsContext = null;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -197,13 +196,14 @@ public class JadxWrapper {
|
||||
getSettings().sync();
|
||||
}
|
||||
|
||||
public List<JadxPlugin> getAllPlugins() {
|
||||
public PluginsContext getPluginsContext() {
|
||||
if (decompiler != null) {
|
||||
return decompiler.getPluginManager().getAllPlugins();
|
||||
return decompiler.getPluginsContext();
|
||||
}
|
||||
try (JadxDecompiler tmpDecompiler = new JadxDecompiler()) {
|
||||
tmpDecompiler.load();
|
||||
return tmpDecompiler.getPluginsContext();
|
||||
}
|
||||
JadxPluginManager pluginManager = new JadxPluginManager();
|
||||
pluginManager.load();
|
||||
return pluginManager.getAllPlugins();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,12 +10,12 @@ import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.ActionHandler;
|
||||
|
||||
public class PluginsContext implements JadxGuiContext {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PluginsContext.class);
|
||||
public class GuiPluginsContext implements JadxGuiContext {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GuiPluginsContext.class);
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
|
||||
public PluginsContext(MainWindow mainWindow) {
|
||||
public GuiPluginsContext(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@ -61,8 +62,8 @@ import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
|
||||
import jadx.api.args.GeneratedRenamesMappingFileMode;
|
||||
import jadx.api.args.ResourceNameSource;
|
||||
import jadx.api.impl.plugins.PluginsContext;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@ -602,14 +603,18 @@ public class JadxSettingsWindow extends JDialog {
|
||||
|
||||
private SettingsGroup makePluginOptionsGroup() {
|
||||
SettingsGroup pluginsGroup = new SettingsGroup(NLS.str("preferences.plugins"));
|
||||
for (JadxPlugin plugin : mainWindow.getWrapper().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();
|
||||
PluginsContext pluginsContext = mainWindow.getWrapper().getPluginsContext();
|
||||
for (Map.Entry<JadxPlugin, JadxPluginOptions> entry : pluginsContext.getOptionsMap().entrySet()) {
|
||||
JadxPlugin plugin = entry.getKey();
|
||||
JadxPluginOptions options = entry.getValue();
|
||||
String pluginId = plugin.getPluginInfo().getPluginId();
|
||||
for (OptionDescription opt : options.getOptionsDescriptions()) {
|
||||
String title;
|
||||
if (pluginId.equals("jadx-script")) {
|
||||
title = '[' + opt.name().replace("jadx-script.", "script:") + "] " + opt.description();
|
||||
} else {
|
||||
title = '[' + pluginId + "] " + opt.description();
|
||||
}
|
||||
if (opt.values().isEmpty() || opt.getType() == OptionDescription.OptionType.BOOLEAN) {
|
||||
try {
|
||||
pluginsGroup.addRow(title, getPluginOptionEditor(opt));
|
||||
|
@ -3,7 +3,6 @@ package jadx.plugins.input.dex;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.api.plugins.options.impl.BaseOptionsParser;
|
||||
@ -15,11 +14,12 @@ public class DexInputOptions extends BaseOptionsParser {
|
||||
|
||||
private boolean verifyChecksum = true;
|
||||
|
||||
public void apply(Map<String, String> options) {
|
||||
verifyChecksum = getBooleanOption(options, VERIFY_CHECKSUM_OPT, true);
|
||||
@Override
|
||||
public void parseOptions() {
|
||||
verifyChecksum = getBooleanOption(VERIFY_CHECKSUM_OPT, true);
|
||||
}
|
||||
|
||||
public List<OptionDescription> buildOptionsDescriptions() {
|
||||
public List<OptionDescription> getOptionsDescriptions() {
|
||||
return Collections.singletonList(
|
||||
new JadxOptionDescription(
|
||||
VERIFY_CHECKSUM_OPT,
|
||||
|
@ -5,19 +5,17 @@ import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
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;
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
|
||||
import jadx.api.plugins.utils.CommonFileUtils;
|
||||
|
||||
public class DexInputPlugin implements JadxInputPlugin, JadxPluginOptions {
|
||||
public class DexInputPlugin implements JadxPlugin {
|
||||
public static final String PLUGIN_ID = "dex-input";
|
||||
|
||||
private final DexInputOptions options = new DexInputOptions();
|
||||
@ -29,39 +27,34 @@ public class DexInputPlugin implements JadxInputPlugin, JadxPluginOptions {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILoadResult loadFiles(List<Path> input) {
|
||||
public void init(JadxPluginContext context) {
|
||||
context.registerOptions(options);
|
||||
context.addCodeInput(this::loadFiles);
|
||||
}
|
||||
|
||||
public ICodeLoader loadFiles(List<Path> input) {
|
||||
return loadFiles(input, null);
|
||||
}
|
||||
|
||||
public ILoadResult loadFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
|
||||
public ICodeLoader loadFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
|
||||
List<DexReader> dexReaders = loader.collectDexFiles(inputFiles);
|
||||
if (dexReaders.isEmpty()) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
return EmptyCodeLoader.INSTANCE;
|
||||
}
|
||||
return new DexLoadResult(dexReaders, closeable);
|
||||
}
|
||||
|
||||
public ILoadResult loadDex(byte[] content, @Nullable String fileName) {
|
||||
public ICodeLoader loadDex(byte[] content, @Nullable String fileName) {
|
||||
String fileLabel = fileName == null ? "input.dex" : fileName;
|
||||
DexReader dexReader = loader.loadDexReader(fileLabel, content);
|
||||
return new DexLoadResult(Collections.singletonList(dexReader), null);
|
||||
}
|
||||
|
||||
public ILoadResult loadDexFromInputStream(InputStream in, @Nullable String fileLabel) {
|
||||
public ICodeLoader loadDexFromInputStream(InputStream in, @Nullable String fileLabel) {
|
||||
try {
|
||||
return loadDex(CommonFileUtils.loadBytes(in), fileLabel);
|
||||
} catch (Exception e) {
|
||||
throw new DexException("Failed to read input stream", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOptions(Map<String, String> options) {
|
||||
this.options.apply(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OptionDescription> getOptionsDescriptions() {
|
||||
return this.options.buildOptionsDescriptions();
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,10 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.input.data.IResourceData;
|
||||
|
||||
public class DexLoadResult implements ILoadResult {
|
||||
public class DexLoadResult implements ICodeLoader {
|
||||
private final List<DexReader> dexReaders;
|
||||
@Nullable
|
||||
private final Closeable closeable;
|
||||
@ -28,10 +27,6 @@ public class DexLoadResult implements ILoadResult {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitResources(Consumer<IResourceData> consumer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (closeable != null) {
|
||||
|
@ -9,10 +9,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.AccessFlagsScope;
|
||||
import jadx.api.plugins.input.data.ICodeReader;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.plugins.input.dex.utils.SmaliTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -38,7 +38,7 @@ class DexInputPluginTest {
|
||||
System.out.println("Input file: " + sample.toAbsolutePath());
|
||||
long start = System.currentTimeMillis();
|
||||
List<Path> files = Collections.singletonList(sample);
|
||||
try (ILoadResult result = new DexInputPlugin().loadFiles(files)) {
|
||||
try (ICodeLoader result = new DexInputPlugin().loadFiles(files)) {
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
result.visitClasses(cls -> {
|
||||
System.out.println();
|
||||
|
@ -3,7 +3,6 @@ package jadx.plugins.input.javaconvert;
|
||||
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.BaseOptionsParser;
|
||||
@ -21,12 +20,14 @@ public class JavaConvertOptions extends BaseOptionsParser {
|
||||
private Mode mode = Mode.BOTH;
|
||||
private boolean d8Desugar = false;
|
||||
|
||||
public void apply(Map<String, String> options) {
|
||||
mode = getOption(options, MODE_OPT, name -> Mode.valueOf(name.toUpperCase(Locale.ROOT)), Mode.BOTH);
|
||||
d8Desugar = getBooleanOption(options, D8_DESUGAR_OPT, false);
|
||||
@Override
|
||||
public void parseOptions() {
|
||||
mode = getOption(MODE_OPT, name -> Mode.valueOf(name.toUpperCase(Locale.ROOT)), Mode.BOTH);
|
||||
d8Desugar = getBooleanOption(D8_DESUGAR_OPT, false);
|
||||
}
|
||||
|
||||
public List<OptionDescription> buildOptionsDescriptions() {
|
||||
@Override
|
||||
public List<OptionDescription> getOptionsDescriptions() {
|
||||
return Arrays.asList(
|
||||
new JadxOptionDescription(
|
||||
MODE_OPT,
|
||||
|
@ -2,17 +2,16 @@ package jadx.plugins.input.javaconvert;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
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;
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
|
||||
import jadx.plugins.input.dex.DexInputPlugin;
|
||||
|
||||
public class JavaConvertPlugin implements JadxInputPlugin, JadxPluginOptions {
|
||||
public class JavaConvertPlugin implements JadxPlugin, JadxCodeInput {
|
||||
|
||||
public static final String PLUGIN_ID = "java-convert";
|
||||
|
||||
@ -30,22 +29,18 @@ public class JavaConvertPlugin implements JadxInputPlugin, JadxPluginOptions {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILoadResult loadFiles(List<Path> input) {
|
||||
public void init(JadxPluginContext context) {
|
||||
context.registerOptions(options);
|
||||
context.addCodeInput(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeLoader loadFiles(List<Path> input) {
|
||||
ConvertResult result = loader.process(input);
|
||||
if (result.isEmpty()) {
|
||||
result.close();
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
return EmptyCodeLoader.INSTANCE;
|
||||
}
|
||||
return dexInput.loadFiles(result.getConverted(), result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOptions(Map<String, String> options) {
|
||||
this.options.apply(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OptionDescription> getOptionsDescriptions() {
|
||||
return this.options.buildOptionsDescriptions();
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,14 @@ import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
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.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
|
||||
import jadx.plugins.input.java.utils.JavaClassParseException;
|
||||
|
||||
public class JavaInputPlugin implements JadxInputPlugin {
|
||||
public class JavaInputPlugin implements JadxPlugin {
|
||||
|
||||
public static final JadxPluginInfo PLUGIN_INFO = new JadxPluginInfo(
|
||||
"java-input",
|
||||
@ -28,26 +29,26 @@ public class JavaInputPlugin implements JadxInputPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILoadResult loadFiles(List<Path> inputFiles) {
|
||||
public void init(JadxPluginContext context) {
|
||||
context.addCodeInput(JavaInputPlugin::loadClassFiles);
|
||||
}
|
||||
|
||||
public static ICodeLoader loadClassFiles(List<Path> inputFiles) {
|
||||
return loadClassFiles(inputFiles, null);
|
||||
}
|
||||
|
||||
public static ILoadResult loadClassFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
|
||||
public static ICodeLoader loadClassFiles(List<Path> inputFiles, @Nullable Closeable closeable) {
|
||||
List<JavaClassReader> readers = new JavaInputLoader().collectFiles(inputFiles);
|
||||
if (readers.isEmpty()) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
return EmptyCodeLoader.INSTANCE;
|
||||
}
|
||||
return new JavaLoadResult(readers, closeable);
|
||||
}
|
||||
|
||||
public static ILoadResult loadClassFiles(List<Path> inputFiles) {
|
||||
return loadClassFiles(inputFiles, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for provide several inputs by using load methods from {@link JavaInputLoader} class.
|
||||
*/
|
||||
public static ILoadResult load(Function<JavaInputLoader, List<JavaClassReader>> loader) {
|
||||
public static ICodeLoader load(Function<JavaInputLoader, List<JavaClassReader>> loader) {
|
||||
return wrapClassReaders(loader.apply(new JavaInputLoader()));
|
||||
}
|
||||
|
||||
@ -56,7 +57,7 @@ public class JavaInputPlugin implements JadxInputPlugin {
|
||||
* Should be used only once per JadxDecompiler instance.
|
||||
* For load several times use {@link JavaInputPlugin#load(Function)} method.
|
||||
*/
|
||||
public static ILoadResult loadFromInputStream(InputStream in, String fileName) {
|
||||
public static ICodeLoader loadFromInputStream(InputStream in, String fileName) {
|
||||
try {
|
||||
return wrapClassReaders(new JavaInputLoader().loadInputStream(in, fileName));
|
||||
} catch (Exception e) {
|
||||
@ -69,14 +70,14 @@ public class JavaInputPlugin implements JadxInputPlugin {
|
||||
* Should be used only once per JadxDecompiler instance.
|
||||
* For load several times use {@link JavaInputPlugin#load(Function)} method.
|
||||
*/
|
||||
public static ILoadResult loadSingleClass(byte[] content, String fileName) {
|
||||
public static ICodeLoader loadSingleClass(byte[] content, String fileName) {
|
||||
JavaClassReader reader = new JavaInputLoader().loadClass(content, fileName);
|
||||
return new JavaLoadResult(Collections.singletonList(reader));
|
||||
}
|
||||
|
||||
public static ILoadResult wrapClassReaders(List<JavaClassReader> readers) {
|
||||
public static ICodeLoader wrapClassReaders(List<JavaClassReader> readers) {
|
||||
if (readers.isEmpty()) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
return EmptyCodeLoader.INSTANCE;
|
||||
}
|
||||
return new JavaLoadResult(readers);
|
||||
}
|
||||
|
@ -9,11 +9,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.data.IClassData;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.input.data.IResourceData;
|
||||
|
||||
public class JavaLoadResult implements ILoadResult {
|
||||
public class JavaLoadResult implements ICodeLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaLoadResult.class);
|
||||
|
||||
private final List<JavaClassReader> readers;
|
||||
@ -40,10 +39,6 @@ public class JavaLoadResult implements ILoadResult {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitResources(Consumer<IResourceData> consumer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return readers.isEmpty();
|
||||
|
@ -16,7 +16,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.api.plugins.input.ICodeLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
@ -40,7 +40,7 @@ class CustomLoadTest {
|
||||
List<Path> files = Stream.of("HelloWorld.class", "HelloWorld$HelloInner.class")
|
||||
.map(this::getSample)
|
||||
.collect(Collectors.toList());
|
||||
ILoadResult loadResult = JavaInputPlugin.loadClassFiles(files);
|
||||
ICodeLoader loadResult = JavaInputPlugin.loadClassFiles(files);
|
||||
loadDecompiler(loadResult);
|
||||
assertThat(jadx.getClassesWithInners())
|
||||
.hasSize(2)
|
||||
@ -52,7 +52,7 @@ class CustomLoadTest {
|
||||
void loadFromInputStream() throws IOException {
|
||||
String fileName = "HelloWorld$HelloInner.class";
|
||||
try (InputStream in = Files.newInputStream(getSample(fileName))) {
|
||||
ILoadResult loadResult = JavaInputPlugin.loadFromInputStream(in, fileName);
|
||||
ICodeLoader loadResult = JavaInputPlugin.loadFromInputStream(in, fileName);
|
||||
loadDecompiler(loadResult);
|
||||
assertThat(jadx.getClassesWithInners())
|
||||
.hasSize(1)
|
||||
@ -66,7 +66,7 @@ class CustomLoadTest {
|
||||
void loadSingleClass() throws IOException {
|
||||
String fileName = "HelloWorld.class";
|
||||
byte[] content = Files.readAllBytes(getSample(fileName));
|
||||
ILoadResult loadResult = JavaInputPlugin.loadSingleClass(content, fileName);
|
||||
ICodeLoader loadResult = JavaInputPlugin.loadSingleClass(content, fileName);
|
||||
loadDecompiler(loadResult);
|
||||
assertThat(jadx.getClassesWithInners())
|
||||
.hasSize(1)
|
||||
@ -77,7 +77,7 @@ class CustomLoadTest {
|
||||
|
||||
@Test
|
||||
void load() {
|
||||
ILoadResult loadResult = JavaInputPlugin.load(loader -> {
|
||||
ICodeLoader loadResult = JavaInputPlugin.load(loader -> {
|
||||
List<JavaClassReader> inputs = new ArrayList<>(2);
|
||||
try {
|
||||
String hello = "HelloWorld.class";
|
||||
@ -110,9 +110,9 @@ class CustomLoadTest {
|
||||
jadx.getClassesWithInners().forEach(cls -> System.out.println(cls.getCode()));
|
||||
}
|
||||
|
||||
public void loadDecompiler(ILoadResult load) {
|
||||
public void loadDecompiler(ICodeLoader codeLoader) {
|
||||
try {
|
||||
jadx.addCustomLoad(load);
|
||||
jadx.addCustomCodeLoader(codeLoader);
|
||||
jadx.load();
|
||||
} catch (Exception e) {
|
||||
fail(e);
|
||||
|
@ -3,13 +3,15 @@ package jadx.plugins.input.raung;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
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.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
|
||||
import jadx.plugins.input.java.JavaInputPlugin;
|
||||
|
||||
public class RaungInputPlugin implements JadxInputPlugin {
|
||||
public class RaungInputPlugin implements JadxPlugin, JadxCodeInput {
|
||||
|
||||
@Override
|
||||
public JadxPluginInfo getPluginInfo() {
|
||||
@ -20,10 +22,15 @@ public class RaungInputPlugin implements JadxInputPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILoadResult loadFiles(List<Path> input) {
|
||||
public void init(JadxPluginContext context) {
|
||||
context.addCodeInput(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeLoader loadFiles(List<Path> input) {
|
||||
RaungConvert convert = new RaungConvert();
|
||||
if (!convert.execute(input)) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
return EmptyCodeLoader.INSTANCE;
|
||||
}
|
||||
return JavaInputPlugin.loadClassFiles(convert.getFiles(), convert);
|
||||
}
|
||||
|
@ -8,12 +8,10 @@ import net.fabricmc.mappingio.tree.MappingTree;
|
||||
import net.fabricmc.mappingio.tree.MemoryMappingTree;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.args.UserRenamesMappingsMode;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
import jadx.api.plugins.JadxPluginInfo;
|
||||
import jadx.api.plugins.pass.JadxPassContext;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.plugins.mappings.load.CodeMappingsVisitor;
|
||||
import jadx.plugins.mappings.load.MappingsVisitor;
|
||||
@ -27,12 +25,10 @@ public class RenameMappingsPlugin implements JadxPlugin {
|
||||
|
||||
@Override
|
||||
public void init(JadxPluginContext context) {
|
||||
JadxArgs args = ((JadxDecompiler) context.getDecompiler()).getArgs();
|
||||
MappingTree mappingTree = openMapping(args);
|
||||
MappingTree mappingTree = openMapping(context.getArgs());
|
||||
if (mappingTree != null) {
|
||||
JadxPassContext passContext = context.getPassContext();
|
||||
passContext.addPass(new MappingsVisitor(mappingTree));
|
||||
passContext.addPass(new CodeMappingsVisitor(mappingTree));
|
||||
context.addPass(new MappingsVisitor(mappingTree));
|
||||
context.addPass(new CodeMappingsVisitor(mappingTree));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,26 +1,20 @@
|
||||
package jadx.plugins.script
|
||||
|
||||
import jadx.api.plugins.JadxPlugin
|
||||
import jadx.api.plugins.JadxPluginContext
|
||||
import jadx.api.plugins.JadxPluginInfo
|
||||
import jadx.api.plugins.options.JadxPluginOptions
|
||||
import jadx.api.plugins.options.OptionDescription
|
||||
import jadx.plugins.script.passes.JadxScriptAfterLoadPass
|
||||
import jadx.plugins.script.runner.ScriptEval
|
||||
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
|
||||
|
||||
class JadxScriptPlugin : JadxPluginOptions {
|
||||
var scriptOptions: JadxScriptAllOptions = JadxScriptAllOptions(emptyMap())
|
||||
class JadxScriptPlugin : JadxPlugin {
|
||||
private val scriptOptions = JadxScriptAllOptions()
|
||||
|
||||
override fun getPluginInfo() = JadxPluginInfo("jadx-script", "Jadx Script", "Scripting support for jadx")
|
||||
|
||||
override fun setOptions(options: Map<String, String>) {
|
||||
scriptOptions = JadxScriptAllOptions(options)
|
||||
}
|
||||
|
||||
override fun init(init: JadxPluginContext) {
|
||||
init.registerOptions(scriptOptions)
|
||||
val scriptStates = ScriptEval().process(init, scriptOptions) ?: return
|
||||
init.passContext.addPass(JadxScriptAfterLoadPass(scriptStates))
|
||||
init.addPass(JadxScriptAfterLoadPass(scriptStates))
|
||||
}
|
||||
|
||||
override fun getOptionsDescriptions(): List<OptionDescription> = scriptOptions.descriptions
|
||||
}
|
||||
|
@ -1,14 +1,21 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.plugins.options.JadxPluginOptions
|
||||
import jadx.api.plugins.options.OptionDescription
|
||||
import jadx.api.plugins.options.OptionDescription.OptionType
|
||||
import jadx.api.plugins.options.impl.JadxOptionDescription
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
data class JadxScriptAllOptions(
|
||||
val values: Map<String, String>,
|
||||
class JadxScriptAllOptions : JadxPluginOptions {
|
||||
lateinit var values: Map<String, String>
|
||||
val descriptions: MutableList<OptionDescription> = mutableListOf()
|
||||
)
|
||||
|
||||
override fun setOptions(options: Map<String, String>) {
|
||||
values = options
|
||||
}
|
||||
|
||||
override fun getOptionsDescriptions(): MutableList<OptionDescription> = descriptions
|
||||
}
|
||||
|
||||
class ScriptOption<T>(
|
||||
val name: String,
|
||||
|
@ -69,7 +69,7 @@ class JadxScriptInstance(
|
||||
}
|
||||
|
||||
fun addPass(pass: JadxPass) {
|
||||
scriptData.pluginContext.passContext.addPass(pass)
|
||||
scriptData.pluginContext.addPass(pass)
|
||||
}
|
||||
|
||||
val internalDecompiler: JadxDecompiler
|
||||
|
@ -3,13 +3,15 @@ package jadx.plugins.input.smali;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.JadxPluginContext;
|
||||
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.input.ICodeLoader;
|
||||
import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
|
||||
import jadx.plugins.input.dex.DexInputPlugin;
|
||||
|
||||
public class SmaliInputPlugin implements JadxInputPlugin {
|
||||
public class SmaliInputPlugin implements JadxPlugin, JadxCodeInput {
|
||||
|
||||
private final DexInputPlugin dexInput = new DexInputPlugin();
|
||||
|
||||
@ -19,10 +21,15 @@ public class SmaliInputPlugin implements JadxInputPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILoadResult loadFiles(List<Path> input) {
|
||||
public void init(JadxPluginContext context) {
|
||||
context.addCodeInput(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeLoader loadFiles(List<Path> input) {
|
||||
SmaliConvert convert = new SmaliConvert();
|
||||
if (!convert.execute(input)) {
|
||||
return EmptyLoadResult.INSTANCE;
|
||||
return EmptyCodeLoader.INSTANCE;
|
||||
}
|
||||
return dexInput.loadFiles(convert.getDexFiles(), convert);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user