diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index fbd7fe9f4a7..d13bbff6f1f 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -46,6 +46,7 @@ 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 @@ private void loadInputFiles() { List inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath); List 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); diff --git a/jadx-core/src/main/java/jadx/api/plugins/JadxPluginContext.java b/jadx-core/src/main/java/jadx/api/plugins/JadxPluginContext.java index 4e11eb36477..498b1ae1a25 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/JadxPluginContext.java +++ b/jadx-core/src/main/java/jadx/api/plugins/JadxPluginContext.java @@ -6,6 +6,7 @@ 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 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(); } diff --git a/jadx-core/src/main/java/jadx/api/plugins/JadxPluginInfoBuilder.java b/jadx-core/src/main/java/jadx/api/plugins/JadxPluginInfoBuilder.java index 10d1bb43ae9..2f036fb1b32 100644 --- a/jadx-core/src/main/java/jadx/api/plugins/JadxPluginInfoBuilder.java +++ b/jadx-core/src/main/java/jadx/api/plugins/JadxPluginInfoBuilder.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/api/plugins/data/IJadxPlugins.java b/jadx-core/src/main/java/jadx/api/plugins/data/IJadxPlugins.java new file mode 100644 index 00000000000..cbb3d5d2653 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/plugins/data/IJadxPlugins.java @@ -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 getInstance(Class

pluginCls); +} diff --git a/jadx-core/src/main/java/jadx/api/plugins/data/JadxPluginRuntimeData.java b/jadx-core/src/main/java/jadx/api/plugins/data/JadxPluginRuntimeData.java new file mode 100644 index 00000000000..3c18512fdc8 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/plugins/data/JadxPluginRuntimeData.java @@ -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 getCodeInputs(); + + @Nullable + JadxPluginOptions getOptions(); + + String getInputsHash(); + + /** + * Convenient method to simplify code loading from custom files. + */ + ICodeLoader loadCodeFiles(List files, @Nullable Closeable closeable); +} diff --git a/jadx-core/src/main/java/jadx/core/plugins/JadxPluginManager.java b/jadx-core/src/main/java/jadx/core/plugins/JadxPluginManager.java index b30b7bb3bd1..fa2d3e2bf6d 100644 --- a/jadx-core/src/main/java/jadx/core/plugins/JadxPluginManager.java +++ b/jadx-core/src/main/java/jadx/core/plugins/JadxPluginManager.java @@ -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 allPlugins = new TreeSet<>(); private final SortedSet resolvedPlugins = new TreeSet<>(); private final Map 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 void register(JadxPlugin plugin) { } 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()); diff --git a/jadx-core/src/main/java/jadx/core/plugins/JadxPluginsData.java b/jadx-core/src/main/java/jadx/core/plugins/JadxPluginsData.java new file mode 100644 index 00000000000..5f14ecdf5e9 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/plugins/JadxPluginsData.java @@ -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 getInstance(Class

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")); + } +} diff --git a/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java b/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java index 92120b2e301..cc5c7b070c9 100644 --- a/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java +++ b/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java @@ -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.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 { +public class PluginContext implements JadxPluginContext, JadxPluginRuntimeData, Comparable { 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 getCodeInputs() { return codeInputs; } @@ -89,6 +100,7 @@ public void registerInputsHashSupplier(Supplier supplier) { this.inputsHashSupplier = supplier; } + @Override public String getInputsHash() { if (inputsHashSupplier == null) { return defaultOptionsHash(); @@ -128,22 +140,38 @@ public void setGuiContext(JadxGuiContext guiContext) { 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 files, @Nullable Closeable closeable) { + return new MergeCodeLoader( + Utils.collectionMap(codeInputs, codeInput -> codeInput.loadFiles(files)), + closeable); + } + @Override public boolean equals(Object other) { if (this == other) { diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/MergeCodeLoader.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/MergeCodeLoader.java new file mode 100644 index 00000000000..678e3f4e348 --- /dev/null +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/MergeCodeLoader.java @@ -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 codeLoaders; + private final @Nullable Closeable closeable; + + public MergeCodeLoader(List codeLoaders) { + this(codeLoaders, null); + } + + public MergeCodeLoader(List codeLoaders, @Nullable Closeable closeable) { + this.codeLoaders = codeLoaders; + this.closeable = closeable; + } + + @Override + public void visitClasses(Consumer 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(); + } + } +} diff --git a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java index 5ee72aaf2af..56596e1dfa9 100644 --- a/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java +++ b/jadx-plugins/jadx-java-convert/src/main/java/jadx/plugins/input/javaconvert/JavaConvertPlugin.java @@ -6,30 +6,33 @@ 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 ICodeLoader loadFiles(List input) { result.close(); return EmptyCodeLoader.INSTANCE; } - return dexInput.loadFiles(result.getConverted(), result); + return dexInput.loadCodeFiles(result.getConverted(), result); } } diff --git a/jadx-plugins/jadx-raung-input/build.gradle.kts b/jadx-plugins/jadx-raung-input/build.gradle.kts index 80cb98ae0c8..3b2d00170c2 100644 --- a/jadx-plugins/jadx-raung-input/build.gradle.kts +++ b/jadx-plugins/jadx-raung-input/build.gradle.kts @@ -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") } diff --git a/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungInputPlugin.java b/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungInputPlugin.java index 76d8709ffe3..83fb7b64eeb 100644 --- a/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungInputPlugin.java +++ b/jadx-plugins/jadx-raung-input/src/main/java/jadx/plugins/input/raung/RaungInputPlugin.java @@ -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 JadxPluginInfo getPluginInfo() { @Override public void init(JadxPluginContext context) { - context.addCodeInput(this); - } - - @Override - public ICodeLoader loadFiles(List 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); + }); } } diff --git a/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliInputPlugin.java b/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliInputPlugin.java index 58e47f4932c..f0ca49676ce 100644 --- a/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliInputPlugin.java +++ b/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliInputPlugin.java @@ -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 JadxPluginInfo getPluginInfo() { @Override public void init(JadxPluginContext context) { - context.addCodeInput(this); - } - - @Override - public ICodeLoader loadFiles(List 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); + }); } } diff --git a/jadx-plugins/jadx-xapk-input/src/main/java/jadx/plugins/input/xapk/XapkInputPlugin.kt b/jadx-plugins/jadx-xapk-input/src/main/java/jadx/plugins/input/xapk/XapkInputPlugin.kt index 559c7289591..f4cf213b154 100644 --- a/jadx-plugins/jadx-xapk-input/src/main/java/jadx/plugins/input/xapk/XapkInputPlugin.kt +++ b/jadx-plugins/jadx-xapk-input/src/main/java/jadx/plugins/input/xapk/XapkInputPlugin.kt @@ -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) }