feat(plugins): add API for search/use other plugins

This commit is contained in:
Skylot
2024-01-15 18:37:40 +00:00
parent fdc3fe1a8d
commit 8ed48183c7
14 changed files with 248 additions and 61 deletions
@@ -46,6 +46,7 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleTask;
import jadx.core.plugins.JadxPluginManager;
import jadx.core.plugins.PluginContext;
import jadx.core.plugins.events.JadxEventsImpl;
import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils;
@@ -147,10 +148,16 @@ 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 (JadxCodeInput codeLoader : pluginManager.getCodeInputs()) {
ICodeLoader loader = codeLoader.loadFiles(inputFiles);
if (loader != null && !loader.isEmpty()) {
loadedInputs.add(loader);
for (PluginContext plugin : pluginManager.getResolvedPluginContexts()) {
for (JadxCodeInput codeLoader : plugin.getCodeInputs()) {
try {
ICodeLoader loader = codeLoader.loadFiles(inputFiles);
if (loader != null && !loader.isEmpty()) {
loadedInputs.add(loader);
}
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load code for plugin: " + plugin, e);
}
}
}
loadedInputs.addAll(customCodeLoaders);
@@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.data.IJadxPlugins;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.input.JadxCodeInput;
@@ -31,11 +32,19 @@ public interface JadxPluginContext {
*/
void registerInputsHashSupplier(Supplier<String> supplier);
/**
* Access to jadx-gui specific methods
*/
@Nullable
JadxGuiContext getGuiContext();
/**
* Subscribe and send events
*/
IJadxEvents events();
@Nullable
JadxGuiContext getGuiContext();
/**
* Access to registered plugins and runtime data
*/
IJadxPlugins plugins();
}
@@ -11,12 +11,16 @@ public class JadxPluginInfoBuilder {
private String homepage = "";
private @Nullable String provides;
public JadxPluginInfoBuilder() {
/**
* Start building method
*/
public static JadxPluginInfoBuilder pluginId(String pluginId) {
JadxPluginInfoBuilder builder = new JadxPluginInfoBuilder();
builder.pluginId = Objects.requireNonNull(pluginId);
return builder;
}
public JadxPluginInfoBuilder pluginId(String pluginId) {
this.pluginId = Objects.requireNonNull(pluginId);
return this;
private JadxPluginInfoBuilder() {
}
public JadxPluginInfoBuilder name(String name) {
@@ -0,0 +1,12 @@
package jadx.api.plugins.data;
import jadx.api.plugins.JadxPlugin;
public interface IJadxPlugins {
JadxPluginRuntimeData getById(String pluginId);
JadxPluginRuntimeData getProviding(String provideId);
<P extends JadxPlugin> P getInstance(Class<P> pluginCls);
}
@@ -0,0 +1,38 @@
package jadx.api.plugins.data;
import java.io.Closeable;
import java.nio.file.Path;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.options.JadxPluginOptions;
/**
* Runtime plugin data.
*/
public interface JadxPluginRuntimeData {
boolean isInitialized();
String getPluginId();
JadxPlugin getPluginInstance();
JadxPluginInfo getPluginInfo();
List<JadxCodeInput> getCodeInputs();
@Nullable
JadxPluginOptions getOptions();
String getInputsHash();
/**
* Convenient method to simplify code loading from custom files.
*/
ICodeLoader loadCodeFiles(List<Path> files, @Nullable Closeable closeable);
}
@@ -25,6 +25,7 @@ public class JadxPluginManager {
private static final Logger LOG = LoggerFactory.getLogger(JadxPluginManager.class);
private final JadxDecompiler decompiler;
private final JadxPluginsData pluginsData;
private final SortedSet<PluginContext> allPlugins = new TreeSet<>();
private final SortedSet<PluginContext> resolvedPlugins = new TreeSet<>();
private final Map<String, String> provideSuggestions = new TreeMap<>();
@@ -33,6 +34,7 @@ public class JadxPluginManager {
public JadxPluginManager(JadxDecompiler decompiler) {
this.decompiler = decompiler;
this.pluginsData = new JadxPluginsData(decompiler, this);
}
/**
@@ -58,7 +60,7 @@ public class JadxPluginManager {
}
private PluginContext addPlugin(JadxPlugin plugin) {
PluginContext pluginContext = new PluginContext(decompiler, plugin);
PluginContext pluginContext = new PluginContext(decompiler, pluginsData, plugin);
LOG.debug("Loading plugin: {}", pluginContext);
if (!allPlugins.add(pluginContext)) {
throw new IllegalArgumentException("Duplicate plugin id: " + pluginContext + ", class " + plugin.getClass());
@@ -0,0 +1,47 @@
package jadx.core.plugins;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.data.IJadxPlugins;
import jadx.api.plugins.data.JadxPluginRuntimeData;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class JadxPluginsData implements IJadxPlugins {
private final JadxDecompiler decompiler;
private final JadxPluginManager pluginManager;
public JadxPluginsData(JadxDecompiler decompiler, JadxPluginManager pluginManager) {
this.decompiler = decompiler;
this.pluginManager = pluginManager;
}
@Override
public JadxPluginRuntimeData getById(String pluginId) {
return pluginManager.getResolvedPluginContexts()
.stream()
.filter(p -> p.getPluginId().equals(pluginId))
.findFirst()
.orElseThrow(() -> new JadxRuntimeException("Plugin with id '" + pluginId + "' not found"));
}
@Override
public JadxPluginRuntimeData getProviding(String provideId) {
return pluginManager.getResolvedPluginContexts()
.stream()
.filter(p -> p.getPluginInfo().getProvides().equals(provideId))
.findFirst()
.orElseThrow(() -> new JadxRuntimeException("Plugin providing '" + provideId + "' not found"));
}
@SuppressWarnings("unchecked")
@Override
public <P extends JadxPlugin> P getInstance(Class<P> pluginCls) {
return pluginManager.getResolvedPluginContexts()
.stream()
.filter(p -> p.getPluginInstance().getClass().equals(pluginCls))
.map(p -> ((P) p.getPluginInstance()))
.findFirst()
.orElseThrow(() -> new JadxRuntimeException("Plugin class '" + pluginCls + "' not found"));
}
}
@@ -1,5 +1,7 @@
package jadx.core.plugins;
import java.io.Closeable;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -13,18 +15,24 @@ import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.data.IJadxPlugins;
import jadx.api.plugins.data.JadxPluginRuntimeData;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.input.data.impl.MergeCodeLoader;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.api.plugins.options.OptionFlag;
import jadx.api.plugins.pass.JadxPass;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
public class PluginContext implements JadxPluginContext, Comparable<PluginContext> {
public class PluginContext implements JadxPluginContext, JadxPluginRuntimeData, Comparable<PluginContext> {
private final JadxDecompiler decompiler;
private final JadxPluginsData pluginsData;
private final JadxPlugin plugin;
private final JadxPluginInfo pluginInfo;
private @Nullable JadxGuiContext guiContext;
@@ -35,8 +43,9 @@ public class PluginContext implements JadxPluginContext, Comparable<PluginContex
private boolean initialized;
PluginContext(JadxDecompiler decompiler, JadxPlugin plugin) {
PluginContext(JadxDecompiler decompiler, JadxPluginsData pluginsData, JadxPlugin plugin) {
this.decompiler = decompiler;
this.pluginsData = pluginsData;
this.plugin = plugin;
this.pluginInfo = plugin.getPluginInfo();
}
@@ -46,6 +55,7 @@ public class PluginContext implements JadxPluginContext, Comparable<PluginContex
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@@ -70,6 +80,7 @@ public class PluginContext implements JadxPluginContext, Comparable<PluginContex
this.codeInputs.add(codeInput);
}
@Override
public List<JadxCodeInput> getCodeInputs() {
return codeInputs;
}
@@ -89,6 +100,7 @@ public class PluginContext implements JadxPluginContext, Comparable<PluginContex
this.inputsHashSupplier = supplier;
}
@Override
public String getInputsHash() {
if (inputsHashSupplier == null) {
return defaultOptionsHash();
@@ -128,22 +140,38 @@ public class PluginContext implements JadxPluginContext, Comparable<PluginContex
this.guiContext = guiContext;
}
public JadxPlugin getPlugin() {
@Override
public JadxPlugin getPluginInstance() {
return plugin;
}
@Override
public JadxPluginInfo getPluginInfo() {
return pluginInfo;
}
@Override
public String getPluginId() {
return pluginInfo.getPluginId();
}
public JadxPluginOptions getOptions() {
@Override
public @Nullable JadxPluginOptions getOptions() {
return options;
}
@Override
public IJadxPlugins plugins() {
return pluginsData;
}
@Override
public ICodeLoader loadCodeFiles(List<Path> files, @Nullable Closeable closeable) {
return new MergeCodeLoader(
Utils.collectionMap(codeInputs, codeInput -> codeInput.loadFiles(files)),
closeable);
}
@Override
public boolean equals(Object other) {
if (this == other) {
@@ -0,0 +1,53 @@
package jadx.api.plugins.input.data.impl;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.data.IClassData;
public class MergeCodeLoader implements ICodeLoader {
private final List<ICodeLoader> codeLoaders;
private final @Nullable Closeable closeable;
public MergeCodeLoader(List<ICodeLoader> codeLoaders) {
this(codeLoaders, null);
}
public MergeCodeLoader(List<ICodeLoader> codeLoaders, @Nullable Closeable closeable) {
this.codeLoaders = codeLoaders;
this.closeable = closeable;
}
@Override
public void visitClasses(Consumer<IClassData> consumer) {
for (ICodeLoader codeLoader : codeLoaders) {
codeLoader.visitClasses(consumer);
}
}
@Override
public boolean isEmpty() {
for (ICodeLoader codeLoader : codeLoaders) {
if (!codeLoader.isEmpty()) {
return false;
}
}
return true;
}
@Override
public void close() throws IOException {
for (ICodeLoader codeLoader : codeLoaders) {
codeLoader.close();
}
if (closeable != null) {
closeable.close();
}
}
}
@@ -6,30 +6,33 @@ import java.util.List;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.JadxPluginInfoBuilder;
import jadx.api.plugins.data.JadxPluginRuntimeData;
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 JadxPlugin, JadxCodeInput {
public static final String PLUGIN_ID = "java-convert";
private final DexInputPlugin dexInput = new DexInputPlugin();
private final JavaConvertOptions options = new JavaConvertOptions();
private final JavaConvertLoader loader = new JavaConvertLoader(options);
private JadxPluginRuntimeData dexInput;
@Override
public JadxPluginInfo getPluginInfo() {
return new JadxPluginInfo(
PLUGIN_ID,
"Java Convert",
"Convert .class, .jar and .aar files to dex",
"java-input");
return JadxPluginInfoBuilder.pluginId(PLUGIN_ID)
.name("Java Convert")
.description("Convert .class, .jar and .aar files to dex")
.provides("java-input")
.build();
}
@Override
public void init(JadxPluginContext context) {
dexInput = context.plugins().getById(DexInputPlugin.PLUGIN_ID);
context.registerOptions(options);
context.addCodeInput(this);
}
@@ -41,6 +44,6 @@ public class JavaConvertPlugin implements JadxPlugin, JadxCodeInput {
result.close();
return EmptyCodeLoader.INSTANCE;
}
return dexInput.loadFiles(result.getConverted(), result);
return dexInput.loadCodeFiles(result.getConverted(), result);
}
}
@@ -5,7 +5,5 @@ plugins {
dependencies {
api(project(":jadx-core"))
implementation(project(":jadx-plugins:jadx-java-input"))
implementation("io.github.skylot:raung-asm:0.1.0")
}
@@ -1,17 +1,12 @@
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.ICodeLoader;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.data.JadxPluginRuntimeData;
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
import jadx.plugins.input.java.JavaInputPlugin;
public class RaungInputPlugin implements JadxPlugin, JadxCodeInput {
public class RaungInputPlugin implements JadxPlugin {
@Override
public JadxPluginInfo getPluginInfo() {
@@ -20,15 +15,13 @@ public class RaungInputPlugin implements JadxPlugin, JadxCodeInput {
@Override
public void init(JadxPluginContext context) {
context.addCodeInput(this);
}
@Override
public ICodeLoader loadFiles(List<Path> input) {
RaungConvert convert = new RaungConvert();
if (!convert.execute(input)) {
return EmptyCodeLoader.INSTANCE;
}
return JavaInputPlugin.loadClassFiles(convert.getFiles(), convert);
JadxPluginRuntimeData javaInput = context.plugins().getProviding("java-input");
context.addCodeInput(inputs -> {
RaungConvert convert = new RaungConvert();
if (!convert.execute(inputs)) {
return EmptyCodeLoader.INSTANCE;
}
return javaInput.loadCodeFiles(convert.getFiles(), convert);
});
}
}
@@ -1,19 +1,13 @@
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.ICodeLoader;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.data.JadxPluginRuntimeData;
import jadx.api.plugins.input.data.impl.EmptyCodeLoader;
import jadx.plugins.input.dex.DexInputPlugin;
public class SmaliInputPlugin implements JadxPlugin, JadxCodeInput {
private final DexInputPlugin dexInput = new DexInputPlugin();
public class SmaliInputPlugin implements JadxPlugin {
@Override
public JadxPluginInfo getPluginInfo() {
@@ -22,15 +16,13 @@ public class SmaliInputPlugin implements JadxPlugin, JadxCodeInput {
@Override
public void init(JadxPluginContext context) {
context.addCodeInput(this);
}
@Override
public ICodeLoader loadFiles(List<Path> input) {
SmaliConvert convert = new SmaliConvert();
if (!convert.execute(input)) {
return EmptyCodeLoader.INSTANCE;
}
return dexInput.loadFiles(convert.getDexFiles(), convert);
JadxPluginRuntimeData dexInput = context.plugins().getById(DexInputPlugin.PLUGIN_ID);
context.addCodeInput(input -> {
SmaliConvert convert = new SmaliConvert();
if (!convert.execute(input)) {
return EmptyCodeLoader.INSTANCE;
}
return dexInput.loadCodeFiles(convert.getDexFiles(), convert);
});
}
}
@@ -8,7 +8,7 @@ import jadx.plugins.input.dex.DexInputPlugin
class XapkInputPlugin : JadxPlugin {
private val codeInput = XapkCustomCodeInput(this)
private val resourcesLoader = XapkCustomResourcesLoader()
internal var dexInputPlugin = DexInputPlugin()
internal lateinit var dexInputPlugin: DexInputPlugin
override fun getPluginInfo() = JadxPluginInfo(
"xapk-input",
@@ -17,6 +17,7 @@ class XapkInputPlugin : JadxPlugin {
)
override fun init(context: JadxPluginContext) {
dexInputPlugin = context.plugins().getInstance(DexInputPlugin::class.java)
context.addCodeInput(codeInput)
context.decompiler.addCustomResourcesLoader(resourcesLoader)
}