From c7a0f7a092cd193edd1ac0611c6082f71e470161 Mon Sep 17 00:00:00 2001 From: Skylot <118523+skylot@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:39:29 +0000 Subject: [PATCH] feat: make `jadx-script-kotlin` plugin external --- buildSrc/build.gradle.kts | 2 +- .../src/main/kotlin/jadx-kotlin.gradle.kts | 5 + jadx-cli/build.gradle.kts | 1 - .../main/java/jadx/api/JadxDecompiler.java | 5 + .../jadx/core/plugins/JadxPluginManager.java | 2 +- .../java/jadx/core/plugins/PluginContext.java | 21 +- jadx-gui/build.gradle.kts | 13 +- .../src/main/java/jadx/gui/JadxWrapper.java | 50 ++-- .../main/java/jadx/gui/logs/LogAppender.java | 4 +- .../main/java/jadx/gui/logs/LogOptions.java | 4 +- .../src/main/java/jadx/gui/logs/LogPanel.java | 3 +- .../context/CommonGuiPluginsContext.java | 21 ++ .../gui/plugins/context/GuiPluginContext.java | 9 + .../plugins/context/ITreeInputCategory.java | 25 ++ .../gui/plugins/script/ScriptCodeArea.java | 90 ------- .../script/ScriptCompleteProvider.java | 157 ----------- .../plugins/script/ScriptCompletionData.java | 93 ------- .../script/ScriptCompletionRenderer.java | 26 -- .../plugins/script/ScriptContentPanel.java | 255 ------------------ .../plugins/script/ScriptErrorService.java | 124 --------- .../java/jadx/gui/settings/JadxProject.java | 6 +- .../gui/settings/TabStateViewAdapter.java | 55 ++-- .../gui/settings/data/ITabStatePersist.java | 18 ++ .../java/jadx/gui/treemodel/JInputScript.java | 107 -------- .../jadx/gui/treemodel/JInputScripts.java | 51 ---- .../main/java/jadx/gui/treemodel/JInputs.java | 25 +- .../main/java/jadx/gui/treemodel/JRoot.java | 11 +- .../src/main/java/jadx/gui/ui/MainWindow.java | 2 +- .../main/java/jadx/gui/utils/IconsCache.java | 6 +- .../src/main/java/jadx/gui/utils/UiUtils.java | 2 +- .../gui/utils/plugins/TreeInputsHelper.java | 92 +++++++ jadx-gui/src/test/java/jadx/gui/TestI18n.java | 14 + .../jadx/plugins/tools/JadxPluginsTools.java | 8 +- .../jadx-kotlin-metadata/build.gradle.kts | 2 +- jadx-plugins/jadx-script-kotlin/README.md | 18 ++ .../jadx-script-kotlin/build.gradle.kts | 87 ++++++ .../examples}/deobf/deobf-resources.jadx.kts | 0 .../examples}/deobf/deobf.jadx.kts | 0 .../examples}/deobf/deobf2.jadx.kts | 0 .../examples}/deobf/deobf_by_code.jadx.kts | 0 .../deobf/deobf_from_tostring.jadx.kts | 0 .../deobf/deobf_method_param.jadx.kts | 0 .../examples}/gui/bookmark.jadx.kts | 0 .../examples}/gui/caret_mouse.jadx.kts | 0 .../examples}/gui/custom_frida.jadx.kts | 0 .../examples}/gui/log_events.jadx.kts | 0 .../examples}/gui/menu_entry.jadx.kts | 0 .../examples}/hello.jadx.kts | 0 .../examples}/options.jadx.kts | 0 .../examples}/replace.jadx.kts | 0 .../examples}/replace_method_call.jadx.kts | 0 .../examples}/stages.jadx.kts | 0 .../script/kotlin/JadxScriptKotlinPlugin.kt | 37 +++ .../plugins/script/kotlin}/ScriptCache.kt | 52 ++-- .../jadx/plugins/script/kotlin/ScriptEval.kt | 212 +++++++++++++++ .../plugins/script}/kotlin/ScriptServices.kt | 49 ++-- .../plugins/script/kotlin/gui/JInputScript.kt | 92 +++++++ .../script/kotlin/gui/JInputScripts.kt | 51 ++++ .../kotlin/gui/JadxScriptInputCategory.kt | 44 +++ .../script/kotlin/gui/JadxScriptOptionsUI.kt | 30 +++ .../plugins/script/kotlin/gui}/KtLintUtils.kt | 32 +-- .../script/kotlin/gui/ScriptCodeArea.kt | 76 ++++++ .../kotlin/gui/ScriptCompleteProvider.kt | 137 ++++++++++ .../script/kotlin/gui/ScriptCompletionData.kt | 58 ++++ .../kotlin/gui/ScriptCompletionRenderer.kt | 26 ++ .../script/kotlin/gui/ScriptContentPanel.kt | 251 +++++++++++++++++ .../script/kotlin/gui/ScriptErrorService.kt | 108 ++++++++ .../kotlin}/passes/JadxScriptAfterLoadPass.kt | 4 +- .../kotlin/runtime/JadxScriptTemplate.kt | 29 ++ .../script/kotlin}/runtime/ScriptRuntime.kt | 27 +- .../plugins/script/kotlin/runtime}/Utils.kt | 0 .../script/kotlin}/runtime/data/Debug.kt | 4 +- .../script/kotlin}/runtime/data/Decompile.kt | 12 +- .../script/kotlin}/runtime/data/Gui.kt | 4 +- .../script/kotlin}/runtime/data/Options.kt | 4 +- .../script/kotlin}/runtime/data/Rename.kt | 4 +- .../script/kotlin}/runtime/data/Replace.kt | 4 +- .../script/kotlin}/runtime/data/Search.kt | 4 +- .../script/kotlin}/runtime/data/Stages.kt | 4 +- .../script/kotlin}/runtime/data/Wrappers.kt | 4 +- ...otlin.runtime.JadxScriptTemplate.classname | 0 .../services/jadx.api.plugins.JadxPlugin | 1 + .../src/test/kotlin/JadxScriptPluginTest.kt | 0 .../src/test/kotlin/ScriptServicesTest.kt | 24 +- .../src/test/resources/samples/hello.smali | 0 .../test/resources/samples/simple.jadx.kts | 0 .../test/resources/samples/test-deps.jadx.kts | 0 .../src/test/resources/samples/test.jadx.kts | 0 jadx-plugins/jadx-script/README.md | 29 -- .../jadx-script/examples/build.gradle.kts | 30 --- .../jadx-script/examples/context/Stubs.kt | 28 -- .../jadx-script-ide/build.gradle.kts | 17 -- .../jadx-script-plugin/build.gradle.kts | 17 -- .../plugins/script/JadxScriptOptionsUI.kt | 30 --- .../jadx/plugins/script/JadxScriptPlugin.kt | 21 -- .../kotlin/jadx/plugins/script/ScriptEval.kt | 102 ------- .../services/jadx.api.plugins.JadxPlugin | 1 - .../test/resources/samples/test-deps.jadx.kts | 11 - .../jadx-script-runtime/build.gradle.kts | 25 -- .../script/runtime/JadxScriptTemplate.kt | 91 ------- settings.gradle.kts | 6 +- 101 files changed, 1717 insertions(+), 1489 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/context/ITreeInputCategory.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCodeArea.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompleteProvider.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompletionData.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompletionRenderer.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptErrorService.java create mode 100644 jadx-gui/src/main/java/jadx/gui/settings/data/ITabStatePersist.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java delete mode 100644 jadx-gui/src/main/java/jadx/gui/treemodel/JInputScripts.java create mode 100644 jadx-gui/src/main/java/jadx/gui/utils/plugins/TreeInputsHelper.java create mode 100644 jadx-plugins/jadx-script-kotlin/README.md create mode 100644 jadx-plugins/jadx-script-kotlin/build.gradle.kts rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/deobf/deobf-resources.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/deobf/deobf.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/deobf/deobf2.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/deobf/deobf_by_code.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/deobf/deobf_from_tostring.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/deobf/deobf_method_param.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/gui/bookmark.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/gui/caret_mouse.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/gui/custom_frida.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/gui/log_events.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/gui/menu_entry.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/hello.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/options.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/replace.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/replace_method_call.jadx.kts (100%) rename jadx-plugins/{jadx-script/examples/scripts => jadx-script-kotlin/examples}/stages.jadx.kts (100%) create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/JadxScriptKotlinPlugin.kt rename jadx-plugins/{jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/ScriptCache.kt (70%) create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptEval.kt rename jadx-plugins/{jadx-script/jadx-script-ide/src/main => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script}/kotlin/ScriptServices.kt (57%) create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JInputScript.kt create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JInputScripts.kt create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JadxScriptInputCategory.kt create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JadxScriptOptionsUI.kt rename {jadx-gui/src/main/java/jadx/gui/plugins/script => jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui}/KtLintUtils.kt (85%) create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCodeArea.kt create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompleteProvider.kt create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompletionData.kt create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompletionRenderer.kt create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptContentPanel.kt create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptErrorService.kt rename jadx-plugins/{jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/passes/JadxScriptAfterLoadPass.kt (87%) create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/JadxScriptTemplate.kt rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/ScriptRuntime.kt (73%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime}/Utils.kt (100%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/data/Debug.kt (88%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/data/Decompile.kt (63%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/data/Gui.kt (93%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/data/Options.kt (96%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/data/Rename.kt (91%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/data/Replace.kt (90%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/data/Search.kt (76%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/data/Stages.kt (94%) rename jadx-plugins/{jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script => jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin}/runtime/data/Wrappers.kt (95%) create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/resources/META-INF/kotlin/script/templates/jadx.plugins.script.kotlin.runtime.JadxScriptTemplate.classname create mode 100644 jadx-plugins/jadx-script-kotlin/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin rename jadx-plugins/{jadx-script/jadx-script-plugin => jadx-script-kotlin}/src/test/kotlin/JadxScriptPluginTest.kt (100%) rename jadx-plugins/{jadx-script/jadx-script-ide => jadx-script-kotlin}/src/test/kotlin/ScriptServicesTest.kt (53%) rename jadx-plugins/{jadx-script/jadx-script-plugin => jadx-script-kotlin}/src/test/resources/samples/hello.smali (100%) rename jadx-plugins/{jadx-script/jadx-script-ide => jadx-script-kotlin}/src/test/resources/samples/simple.jadx.kts (100%) rename jadx-plugins/{jadx-script/jadx-script-ide => jadx-script-kotlin}/src/test/resources/samples/test-deps.jadx.kts (100%) rename jadx-plugins/{jadx-script/jadx-script-plugin => jadx-script-kotlin}/src/test/resources/samples/test.jadx.kts (100%) delete mode 100644 jadx-plugins/jadx-script/README.md delete mode 100644 jadx-plugins/jadx-script/examples/build.gradle.kts delete mode 100644 jadx-plugins/jadx-script/examples/context/Stubs.kt delete mode 100644 jadx-plugins/jadx-script/jadx-script-ide/build.gradle.kts delete mode 100644 jadx-plugins/jadx-script/jadx-script-plugin/build.gradle.kts delete mode 100644 jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptOptionsUI.kt delete mode 100644 jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptPlugin.kt delete mode 100644 jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptEval.kt delete mode 100644 jadx-plugins/jadx-script/jadx-script-plugin/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin delete mode 100644 jadx-plugins/jadx-script/jadx-script-plugin/src/test/resources/samples/test-deps.jadx.kts delete mode 100644 jadx-plugins/jadx-script/jadx-script-runtime/build.gradle.kts delete mode 100644 jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/JadxScriptTemplate.kt diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 42baaec93..ed67e57a2 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.0") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.10") implementation("org.openrewrite:plugin:6.19.1") } diff --git a/buildSrc/src/main/kotlin/jadx-kotlin.gradle.kts b/buildSrc/src/main/kotlin/jadx-kotlin.gradle.kts index f3fef6f09..0db45a6bb 100644 --- a/buildSrc/src/main/kotlin/jadx-kotlin.gradle.kts +++ b/buildSrc/src/main/kotlin/jadx-kotlin.gradle.kts @@ -5,6 +5,11 @@ plugins { id("org.jetbrains.kotlin.jvm") } +dependencies { + implementation(kotlin("stdlib")) + implementation(kotlin("reflect")) // don't work from plugin classloader +} + kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) diff --git a/jadx-cli/build.gradle.kts b/jadx-cli/build.gradle.kts index 58d60d115..3354a5152 100644 --- a/jadx-cli/build.gradle.kts +++ b/jadx-cli/build.gradle.kts @@ -19,7 +19,6 @@ dependencies { runtimeOnly(project(":jadx-plugins:jadx-rename-mappings")) runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata")) runtimeOnly(project(":jadx-plugins:jadx-kotlin-source-debug-extension")) - runtimeOnly(project(":jadx-plugins:jadx-script:jadx-script-plugin")) runtimeOnly(project(":jadx-plugins:jadx-xapk-input")) runtimeOnly(project(":jadx-plugins:jadx-aab-input")) runtimeOnly(project(":jadx-plugins:jadx-apkm-input")) diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index c172a77a1..6c0dd8e4f 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -138,11 +138,16 @@ public final class JadxDecompiler implements Closeable { loadFinished(); } + /** + * Reload passes and plugins without processing classes and inputs + */ public void reloadPasses() { LOG.info("reloading (passes only) ..."); customPasses.clear(); root.resetPasses(); events.reset(); + unloadPlugins(); + loadPlugins(); root.mergePasses(customPasses); root.restartVisitors(); 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 d0d8fd438..78874e2f2 100644 --- a/jadx-core/src/main/java/jadx/core/plugins/JadxPluginManager.java +++ b/jadx-core/src/main/java/jadx/core/plugins/JadxPluginManager.java @@ -148,7 +148,7 @@ public class JadxPluginManager { } context.init(); } catch (Exception e) { - LOG.warn("Failed to init plugin: {}", context.getPluginId(), e); + LOG.error("Failed to init plugin: {}", context.getPluginId(), e); } } for (PluginContext context : pluginContexts) { 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 9fb4de31c..db9a14146 100644 --- a/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java +++ b/jadx-core/src/main/java/jadx/core/plugins/PluginContext.java @@ -39,6 +39,7 @@ public class PluginContext implements JadxPluginContext, JadxPluginRuntimeData, private final JadxPluginsData pluginsData; private final JadxPlugin plugin; private final JadxPluginInfo pluginInfo; + private final ClassLoader pluginClassLoader; private AppContext appContext; @@ -53,16 +54,30 @@ public class PluginContext implements JadxPluginContext, JadxPluginRuntimeData, this.pluginsData = pluginsData; this.plugin = plugin; this.pluginInfo = plugin.getPluginInfo(); + this.pluginClassLoader = plugin.getClass().getClassLoader(); } public void init() { - plugin.init(this); - initialized = true; + classLoaderWrap(() -> { + plugin.init(this); + initialized = true; + }); } public void unload() { if (initialized) { - plugin.unload(); + classLoaderWrap(plugin::unload); + } + } + + public void classLoaderWrap(Runnable task) { + Thread thread = Thread.currentThread(); + ClassLoader prevClassLoader = thread.getContextClassLoader(); + thread.setContextClassLoader(pluginClassLoader); + try { + task.run(); + } finally { + thread.setContextClassLoader(prevClassLoader); } } diff --git a/jadx-gui/build.gradle.kts b/jadx-gui/build.gradle.kts index c64b5d28d..2677881f6 100644 --- a/jadx-gui/build.gradle.kts +++ b/jadx-gui/build.gradle.kts @@ -16,21 +16,12 @@ dependencies { // import mappings implementation(project(":jadx-plugins:jadx-rename-mappings")) - // jadx-script autocomplete support - implementation(project(":jadx-plugins:jadx-script:jadx-script-ide")) - implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime")) - implementation(kotlin("scripting-common")) - implementation("com.fifesoft:autocomplete:3.3.2") - - // use KtLint for format and check jadx scripts - implementation("com.pinterest.ktlint:ktlint-rule-engine:1.8.0") - implementation("com.pinterest.ktlint:ktlint-ruleset-standard:1.8.0") - implementation("org.jcommander:jcommander:2.0") implementation("ch.qos.logback:logback-classic:1.5.21") implementation("io.github.oshai:kotlin-logging-jvm:7.0.13") - implementation("com.fifesoft:rsyntaxtextarea:3.6.0") + implementation("com.fifesoft:rsyntaxtextarea:3.6.1") + implementation("com.fifesoft:autocomplete:3.3.2") implementation("org.drjekyll:fontchooser:3.1.0") implementation("hu.kazocsaba:image-viewer:1.2.3") implementation("com.twelvemonkeys.imageio:imageio-webp:3.12.0") // WebP support for image viewer diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index e6a79a7b3..4c5009a32 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -21,6 +21,9 @@ import jadx.api.JavaPackage; import jadx.api.ResourceFile; import jadx.api.impl.InMemoryCodeCache; import jadx.api.metadata.ICodeNodeRef; +import jadx.api.plugins.pass.JadxPassInfo; +import jadx.api.plugins.pass.impl.SimpleJadxPassInfo; +import jadx.api.plugins.pass.types.JadxPreparePass; import jadx.api.usage.impl.EmptyUsageInfoCache; import jadx.api.usage.impl.InMemoryUsageInfoCache; import jadx.cli.JadxAppCommon; @@ -30,6 +33,7 @@ import jadx.core.dex.nodes.ProcessState; import jadx.core.dex.nodes.RootNode; import jadx.core.plugins.AppContext; import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.gui.cache.code.CodeCacheMode; import jadx.gui.cache.code.CodeStringCache; import jadx.gui.cache.code.disk.BufferCodeCache; import jadx.gui.cache.code.disk.DiskCodeCache; @@ -73,10 +77,9 @@ public class JadxWrapper { decompiler = new JadxDecompiler(jadxArgs); guiPluginsContext = initGuiPluginsContext(decompiler, mainWindow); initUsageCache(jadxArgs); + registerCodeCache(decompiler); decompiler.setEventsImpl(mainWindow.events()); - decompiler.load(); - initCodeCache(); } } catch (Exception e) { LOG.error("Jadx decompiler wrapper init error", e); @@ -114,22 +117,39 @@ public class JadxWrapper { } } - private void initCodeCache() { - switch (getSettings().getCodeCacheMode()) { - case MEMORY: - getArgs().setCodeCache(new InMemoryCodeCache()); - break; - case DISK_WITH_CACHE: - getArgs().setCodeCache(new CodeStringCache(buildBufferedDiskCache())); - break; - case DISK: - getArgs().setCodeCache(buildBufferedDiskCache()); - break; + /** + * Disk cache require loaded classes to operate, but cache should be set before 'after load' event + * to allow plugins decompile classes with cache enabled. + * To resolve this, register last 'prepare' pass for cache initialization. + */ + private void registerCodeCache(JadxDecompiler jadxDecompiler) { + CodeCacheMode codeCacheMode = getSettings().getCodeCacheMode(); + if (codeCacheMode == CodeCacheMode.MEMORY) { + jadxDecompiler.getArgs().setCodeCache(new InMemoryCodeCache()); + return; } + jadxDecompiler.addCustomPass(new JadxPreparePass() { + @Override + public JadxPassInfo getInfo() { + return new SimpleJadxPassInfo("CacheInit"); + } + + @Override + public void init(RootNode root) { + switch (getSettings().getCodeCacheMode()) { + case DISK_WITH_CACHE: + root.getArgs().setCodeCache(new CodeStringCache(buildBufferedDiskCache(root))); + break; + case DISK: + root.getArgs().setCodeCache(buildBufferedDiskCache(root)); + break; + } + } + }); } - private BufferCodeCache buildBufferedDiskCache() { - DiskCodeCache diskCache = new DiskCodeCache(getDecompiler().getRoot(), getProject().getCacheDir()); + private BufferCodeCache buildBufferedDiskCache(RootNode root) { + DiskCodeCache diskCache = new DiskCodeCache(root, getProject().getCacheDir()); return new BufferCodeCache(diskCache); } diff --git a/jadx-gui/src/main/java/jadx/gui/logs/LogAppender.java b/jadx-gui/src/main/java/jadx/gui/logs/LogAppender.java index 3ad7076c0..333af4c4b 100644 --- a/jadx-gui/src/main/java/jadx/gui/logs/LogAppender.java +++ b/jadx-gui/src/main/java/jadx/gui/logs/LogAppender.java @@ -6,8 +6,6 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.utils.UiUtils; -import static jadx.plugins.script.runtime.ScriptRuntime.JADX_SCRIPT_LOG_PREFIX; - class LogAppender implements ILogListener { private final LogOptions options; private final RSyntaxTextArea textArea; @@ -39,7 +37,7 @@ class LogAppender implements ILogListener { return true; case ALL_SCRIPTS: - return logEvent.getLoggerName().startsWith(JADX_SCRIPT_LOG_PREFIX); + return logEvent.getLoggerName().startsWith("JadxScript:"); case CURRENT_SCRIPT: return logEvent.getLoggerName().equals(options.getFilter()); diff --git a/jadx-gui/src/main/java/jadx/gui/logs/LogOptions.java b/jadx-gui/src/main/java/jadx/gui/logs/LogOptions.java index c14736532..4259e0874 100644 --- a/jadx-gui/src/main/java/jadx/gui/logs/LogOptions.java +++ b/jadx-gui/src/main/java/jadx/gui/logs/LogOptions.java @@ -6,8 +6,6 @@ import ch.qos.logback.classic.Level; import jadx.core.utils.Utils; -import static jadx.plugins.script.runtime.ScriptRuntime.JADX_SCRIPT_LOG_PREFIX; - public class LogOptions { /** @@ -30,7 +28,7 @@ public class LogOptions { } public static LogOptions forScript(String scriptName) { - String filter = JADX_SCRIPT_LOG_PREFIX + scriptName; + String filter = "JadxScript:" + scriptName; return store(new LogOptions(LogMode.CURRENT_SCRIPT, current.getLogLevel(), filter)); } diff --git a/jadx-gui/src/main/java/jadx/gui/logs/LogPanel.java b/jadx-gui/src/main/java/jadx/gui/logs/LogPanel.java index 5ae0db8a0..4500b76a5 100644 --- a/jadx-gui/src/main/java/jadx/gui/logs/LogPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/logs/LogPanel.java @@ -19,7 +19,6 @@ import org.jetbrains.annotations.Nullable; import ch.qos.logback.classic.Level; import jadx.gui.settings.JadxSettings; -import jadx.gui.treemodel.JInputScript; import jadx.gui.treemodel.JNode; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.AbstractCodeArea; @@ -144,7 +143,7 @@ public class LogPanel extends JPanel { TabBlueprint selectedTab = mainWindow.getTabsController().getSelectedTab(); if (selectedTab != null) { JNode node = selectedTab.getNode(); - if (node instanceof JInputScript) { + if (node.getClass().getSimpleName().equals("JInputScript")) { // TODO: register custom log filters return node.getName(); } } diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java index 4f05c85e4..e7805e83d 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/CommonGuiPluginsContext.java @@ -10,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.plugins.PluginContext; +import jadx.gui.settings.data.ITabStatePersist; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.CodeArea; import jadx.gui.ui.codearea.JNodePopupBuilder; @@ -23,6 +24,8 @@ public class CommonGuiPluginsContext { private final List codePopupActionList = new ArrayList<>(); private final List treePopupMenuEntries = new ArrayList<>(); + private final List treeInputCategories = new ArrayList<>(); + private final List tabStatePersistAdapters = new ArrayList<>(); public CommonGuiPluginsContext(MainWindow mainWindow) { this.mainWindow = mainWindow; @@ -38,9 +41,19 @@ public class CommonGuiPluginsContext { return pluginsMap.get(pluginContext); } + public @Nullable GuiPluginContext getGuiPluginContextById(String pluginId) { + for (GuiPluginContext guiPluginContext : pluginsMap.values()) { + if (guiPluginContext.getPluginContext().getPluginId().equals(pluginId)) { + return guiPluginContext; + } + } + return null; + } + public void reset() { codePopupActionList.clear(); treePopupMenuEntries.clear(); + treeInputCategories.clear(); mainWindow.resetPluginsMenu(); } @@ -56,6 +69,14 @@ public class CommonGuiPluginsContext { return treePopupMenuEntries; } + public List getTreeInputCategories() { + return treeInputCategories; + } + + public List getTabStatePersistAdapters() { + return tabStatePersistAdapters; + } + public void addMenuAction(String name, Runnable action) { ActionHandler item = new ActionHandler(ev -> { try { diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java index bae8cb900..abde922d2 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/GuiPluginContext.java @@ -26,6 +26,7 @@ import jadx.api.plugins.gui.JadxGuiContext; import jadx.api.plugins.gui.JadxGuiSettings; import jadx.core.plugins.PluginContext; import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.gui.settings.data.ITabStatePersist; import jadx.gui.treemodel.JNode; import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.codearea.AbstractCodeContentPanel; @@ -82,6 +83,14 @@ public class GuiPluginContext implements JadxGuiContext { commonContext.getTreePopupMenuEntries().add(new TreePopupMenuEntry(name, addPredicate, action)); } + public void registerTreeInputCategory(ITreeInputCategory inputCategory) { + commonContext.getTreeInputCategories().add(inputCategory); + } + + public void registerTabStatePersistAdapter(ITabStatePersist tabStatePersist) { + commonContext.getTabStatePersistAdapters().add(tabStatePersist); + } + @Override public boolean registerGlobalKeyBinding(String id, String keyBinding, Runnable action) { KeyStroke keyStroke = KeyStroke.getKeyStroke(keyBinding); diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/context/ITreeInputCategory.java b/jadx-gui/src/main/java/jadx/gui/plugins/context/ITreeInputCategory.java new file mode 100644 index 000000000..d2550502d --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/context/ITreeInputCategory.java @@ -0,0 +1,25 @@ +package jadx.gui.plugins.context; + +import java.nio.file.Path; +import java.util.List; + +import org.jetbrains.annotations.ApiStatus; + +import jadx.gui.treemodel.JNode; + +/** + * Custom category for 'Inputs' tree section + */ +@ApiStatus.Experimental +public interface ITreeInputCategory { + + /** + * Check if file should be moved into this category + */ + boolean filesFilter(Path file); + + /** + * Build node for filtered files + */ + JNode buildInputNode(List files); +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCodeArea.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCodeArea.java deleted file mode 100644 index e72db5947..000000000 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCodeArea.java +++ /dev/null @@ -1,90 +0,0 @@ -package jadx.gui.plugins.script; - -import org.fife.ui.autocomplete.AutoCompletion; -import org.jetbrains.annotations.NotNull; - -import jadx.api.ICodeInfo; -import jadx.gui.jobs.IBackgroundTask; -import jadx.gui.jobs.LoadTask; -import jadx.gui.settings.JadxSettings; -import jadx.gui.treemodel.JInputScript; -import jadx.gui.ui.action.JadxAutoCompletion; -import jadx.gui.ui.codearea.AbstractCodeArea; -import jadx.gui.ui.panel.ContentPanel; -import jadx.gui.utils.shortcut.ShortcutsController; - -public class ScriptCodeArea extends AbstractCodeArea { - - private final JInputScript scriptNode; - private final AutoCompletion autoCompletion; - private final ShortcutsController shortcutsController; - - public ScriptCodeArea(ContentPanel contentPanel, JInputScript node) { - super(contentPanel, node); - scriptNode = node; - - setSyntaxEditingStyle(node.getSyntaxName()); - setCodeFoldingEnabled(true); - setCloseCurlyBraces(true); - - shortcutsController = contentPanel.getMainWindow().getShortcutsController(); - JadxSettings settings = contentPanel.getMainWindow().getSettings(); - autoCompletion = addAutoComplete(settings); - } - - private AutoCompletion addAutoComplete(JadxSettings settings) { - ScriptCompleteProvider provider = new ScriptCompleteProvider(this); - provider.setAutoActivationRules(false, "."); - JadxAutoCompletion ac = new JadxAutoCompletion(provider); - ac.setListCellRenderer(new ScriptCompletionRenderer(settings)); - ac.setAutoActivationEnabled(true); - ac.setAutoCompleteSingleChoices(true); - ac.install(this); - shortcutsController.bindImmediate(ac); - return ac; - } - - @Override - public @NotNull ICodeInfo getCodeInfo() { - return node.getCodeInfo(); - } - - @Override - public IBackgroundTask getLoadTask() { - return new LoadTask<>( - () -> node.getCodeInfo().getCodeStr(), - code -> { - setText(code); - setCaretPosition(0); - setLoaded(); - }); - } - - @Override - public void refresh() { - setText(node.getCodeInfo().getCodeStr()); - } - - public void updateCode(String newCode) { - int caretPos = getCaretPosition(); - setText(newCode); - setCaretPosition(caretPos); - scriptNode.setChanged(true); - } - - public void save() { - scriptNode.save(getText()); - scriptNode.setChanged(false); - } - - public JInputScript getScriptNode() { - return scriptNode; - } - - @Override - public void dispose() { - shortcutsController.unbindActionsForComponent(this); - autoCompletion.uninstall(); - super.dispose(); - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompleteProvider.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompleteProvider.java deleted file mode 100644 index b8204b68c..000000000 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompleteProvider.java +++ /dev/null @@ -1,157 +0,0 @@ -package jadx.gui.plugins.script; - -import java.awt.Point; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -import javax.swing.Icon; -import javax.swing.text.BadLocationException; -import javax.swing.text.JTextComponent; - -import org.fife.ui.autocomplete.Completion; -import org.fife.ui.autocomplete.CompletionProviderBase; -import org.fife.ui.autocomplete.ParameterizedCompletion; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import kotlin.script.experimental.api.ScriptDiagnostic; -import kotlin.script.experimental.api.SourceCode; -import kotlin.script.experimental.api.SourceCodeCompletionVariant; - -import jadx.core.utils.ListUtils; -import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.gui.ui.codearea.AbstractCodeArea; -import jadx.gui.utils.Icons; -import jadx.plugins.script.ide.ScriptCompletionResult; -import jadx.plugins.script.ide.ScriptServices; - -import static jadx.plugins.script.ide.ScriptServicesKt.AUTO_COMPLETE_INSERT_STR; - -public class ScriptCompleteProvider extends CompletionProviderBase { - private static final Logger LOG = LoggerFactory.getLogger(ScriptCompleteProvider.class); - - private static final Map ICONS_MAP = buildIconsMap(); - - private static Map buildIconsMap() { - Map map = new HashMap<>(); - map.put("class", Icons.CLASS); - map.put("method", Icons.METHOD); - map.put("field", Icons.FIELD); - map.put("property", Icons.PROPERTY); - map.put("parameter", Icons.PARAMETER); - map.put("package", Icons.PACKAGE); - return map; - } - - private final AbstractCodeArea codeArea; - private ScriptServices scriptServices; - - public ScriptCompleteProvider(AbstractCodeArea codeArea) { - this.codeArea = codeArea; - } - - private List getCompletions() { - try { - String code = codeArea.getText(); - int caretPos = codeArea.getCaretPosition(); - // TODO: resolve error after reusing ScriptCompiler - scriptServices = new ScriptServices(); - String scriptName = codeArea.getNode().getName(); - ScriptCompletionResult result = scriptServices.complete(scriptName, code, caretPos); - int replacePos = getReplacePos(caretPos, result); - if (!result.getReports().isEmpty()) { - LOG.debug("Script completion reports: {}", result.getReports()); - } - return convertCompletions(result.getCompletions(), code, replacePos); - } catch (Exception e) { - LOG.error("Code completion failed", e); - return Collections.emptyList(); - } - } - - private List convertCompletions(List completions, String code, int replacePos) { - if (completions.isEmpty()) { - return Collections.emptyList(); - } - if (LOG.isDebugEnabled()) { - String cmplStr = completions.stream().map(SourceCodeCompletionVariant::toString).collect(Collectors.joining("\n")); - LOG.debug("Completions:\n{}", cmplStr); - } - int count = completions.size(); - List list = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - SourceCodeCompletionVariant c = completions.get(i); - if (Objects.equals(c.getIcon(), "keyword")) { - // too many, not very useful - continue; - } - - ScriptCompletionData cmpl = new ScriptCompletionData(this, count - i); - cmpl.setData(c.getText(), code, replacePos); - if (Objects.equals(c.getIcon(), "method") && !Objects.equals(c.getText(), c.getDisplayText())) { - // add method args details for methods - cmpl.setSummary(c.getDisplayText() + " " + c.getTail()); - } else { - cmpl.setSummary(c.getTail()); - } - cmpl.setToolTip(c.getDisplayText()); - Icon icon = ICONS_MAP.get(c.getIcon()); - cmpl.setIcon(icon != null ? icon : Icons.FILE); - list.add(cmpl); - } - return list; - } - - private int getReplacePos(int caretPos, ScriptCompletionResult result) throws BadLocationException { - int lineRaw = codeArea.getLineOfOffset(caretPos); - int lineStart = codeArea.getLineStartOffset(lineRaw); - int line = lineRaw + 1; - int col = caretPos - lineStart + 1; - - List reports = result.getReports(); - ScriptDiagnostic cmplReport = ListUtils.filterOnlyOne(reports, r -> { - if (r.getSeverity() == ScriptDiagnostic.Severity.ERROR && r.getLocation() != null) { - SourceCode.Position start = r.getLocation().getStart(); - return start.getLine() == line && r.getMessage().endsWith(AUTO_COMPLETE_INSERT_STR); - } - return false; - }); - if (cmplReport == null) { - LOG.warn("Failed to find completion report in: {}", reports); - return caretPos; - } - reports.remove(cmplReport); - int reportCol = Objects.requireNonNull(cmplReport.getLocation()).getStart().getCol(); - return caretPos - (col - reportCol); - } - - @Override - public String getAlreadyEnteredText(JTextComponent comp) { - try { - int pos = codeArea.getCaretPosition(); - return codeArea.getText(0, pos); - } catch (Exception e) { - throw new JadxRuntimeException("Failed to get text before caret", e); - } - } - - @Override - public List getCompletionsAt(JTextComponent comp, Point p) { - return getCompletions(); - } - - @Override - protected List getCompletionsImpl(JTextComponent comp) { - return getCompletions(); - } - - @Override - public List getParameterizedCompletions(JTextComponent tc) { - return null; - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompletionData.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompletionData.java deleted file mode 100644 index 08bbbca9f..000000000 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompletionData.java +++ /dev/null @@ -1,93 +0,0 @@ -package jadx.gui.plugins.script; - -import javax.swing.Icon; -import javax.swing.text.JTextComponent; - -import org.fife.ui.autocomplete.Completion; -import org.fife.ui.autocomplete.CompletionProvider; - -public class ScriptCompletionData implements Completion { - - private final CompletionProvider provider; - private final int relevance; - - private String input; - private String code; - private int replacePos; - private Icon icon; - private String summary; - private String toolTip; - - public ScriptCompletionData(CompletionProvider provider, int relevance) { - this.provider = provider; - this.relevance = relevance; - } - - public void setData(String input, String code, int replacePos) { - this.input = input; - this.code = code; - this.replacePos = replacePos; - } - - @Override - public String getInputText() { - return input; - } - - @Override - public CompletionProvider getProvider() { - return provider; - } - - @Override - public String getAlreadyEntered(JTextComponent comp) { - return provider.getAlreadyEnteredText(comp); - } - - @Override - public int getRelevance() { - return relevance; - } - - @Override - public String getReplacementText() { - return code.substring(0, replacePos) + input; - } - - @Override - public Icon getIcon() { - return icon; - } - - public void setIcon(Icon icon) { - this.icon = icon; - } - - @Override - public String getSummary() { - return summary; - } - - public void setSummary(String summary) { - this.summary = summary; - } - - @Override - public String getToolTipText() { - return toolTip; - } - - public void setToolTip(String toolTip) { - this.toolTip = toolTip; - } - - @Override - public int compareTo(Completion other) { - return Integer.compare(relevance, other.getRelevance()); - } - - @Override - public String toString() { - return input; - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompletionRenderer.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompletionRenderer.java deleted file mode 100644 index b9e6e99fd..000000000 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptCompletionRenderer.java +++ /dev/null @@ -1,26 +0,0 @@ -package jadx.gui.plugins.script; - -import javax.swing.JList; - -import org.fife.ui.autocomplete.Completion; -import org.fife.ui.autocomplete.CompletionCellRenderer; - -import jadx.gui.settings.JadxSettings; - -import static jadx.gui.utils.UiUtils.escapeHtml; -import static jadx.gui.utils.UiUtils.fadeHtml; -import static jadx.gui.utils.UiUtils.wrapHtml; - -public class ScriptCompletionRenderer extends CompletionCellRenderer { - - public ScriptCompletionRenderer(JadxSettings settings) { - setDisplayFont(settings.getCodeFont()); - } - - @Override - protected void prepareForOtherCompletion(JList list, Completion c, int index, boolean selected, boolean hasFocus) { - ScriptCompletionData cmpl = (ScriptCompletionData) c; - setText(wrapHtml(escapeHtml(cmpl.getInputText()) + " " - + fadeHtml(escapeHtml(cmpl.getSummary())))); - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java deleted file mode 100644 index a08c16e12..000000000 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java +++ /dev/null @@ -1,255 +0,0 @@ -package jadx.gui.plugins.script; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.event.KeyEvent; -import java.util.Collections; -import java.util.List; - -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.KeyStroke; -import javax.swing.border.EmptyBorder; - -import org.fife.ui.rsyntaxtextarea.ErrorStrip; -import org.fife.ui.rtextarea.RTextScrollPane; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import kotlin.script.experimental.api.ScriptDiagnostic; -import kotlin.script.experimental.api.ScriptDiagnostic.Severity; - -import jadx.gui.logs.LogOptions; -import jadx.gui.settings.JadxSettings; -import jadx.gui.settings.LineNumbersMode; -import jadx.gui.treemodel.JInputScript; -import jadx.gui.ui.MainWindow; -import jadx.gui.ui.action.ActionModel; -import jadx.gui.ui.action.JadxGuiAction; -import jadx.gui.ui.codearea.AbstractCodeArea; -import jadx.gui.ui.codearea.AbstractCodeContentPanel; -import jadx.gui.ui.codearea.SearchBar; -import jadx.gui.ui.tab.TabbedPane; -import jadx.gui.utils.Icons; -import jadx.gui.utils.NLS; -import jadx.gui.utils.UiUtils; -import jadx.gui.utils.ui.NodeLabel; -import jadx.plugins.script.ide.ScriptAnalyzeResult; -import jadx.plugins.script.ide.ScriptServices; - -import static jadx.plugins.script.runtime.ScriptRuntime.JADX_SCRIPT_LOG_PREFIX; - -public class ScriptContentPanel extends AbstractCodeContentPanel { - private static final long serialVersionUID = 6575696321112417513L; - - private final ScriptCodeArea scriptArea; - private final SearchBar searchBar; - private final RTextScrollPane codeScrollPane; - private final JPanel actionPanel; - private final JLabel resultLabel; - private final ScriptErrorService errorService; - private final Logger scriptLog; - - public ScriptContentPanel(TabbedPane panel, JInputScript scriptNode) { - super(panel, scriptNode); - scriptArea = new ScriptCodeArea(this, scriptNode); - resultLabel = new NodeLabel(""); - errorService = new ScriptErrorService(scriptArea); - actionPanel = buildScriptActionsPanel(); - searchBar = new SearchBar(scriptArea); - codeScrollPane = new RTextScrollPane(scriptArea); - scriptLog = LoggerFactory.getLogger(JADX_SCRIPT_LOG_PREFIX + scriptNode.getName()); - - initUI(); - applySettings(); - scriptArea.load(); - } - - private void initUI() { - JPanel topPanel = new JPanel(new BorderLayout()); - topPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); - topPanel.add(actionPanel, BorderLayout.NORTH); - topPanel.add(searchBar, BorderLayout.SOUTH); - - JPanel codePanel = new JPanel(new BorderLayout()); - codePanel.setBorder(new EmptyBorder(0, 0, 0, 0)); - codePanel.add(codeScrollPane); - codePanel.add(new ErrorStrip(scriptArea), BorderLayout.LINE_END); - - setLayout(new BorderLayout()); - setBorder(new EmptyBorder(0, 0, 0, 0)); - add(topPanel, BorderLayout.NORTH); - add(codeScrollPane, BorderLayout.CENTER); - - KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F, UiUtils.ctrlButton()); - UiUtils.addKeyBinding(scriptArea, key, "SearchAction", searchBar::toggle); - } - - private JPanel buildScriptActionsPanel() { - JadxGuiAction runAction = new JadxGuiAction(ActionModel.SCRIPT_RUN, this::runScript); - JadxGuiAction saveAction = new JadxGuiAction(ActionModel.SCRIPT_SAVE, scriptArea::save); - - runAction.setShortcutComponent(scriptArea); - saveAction.setShortcutComponent(scriptArea); - - tabbedPane.getMainWindow().getShortcutsController().bindImmediate(runAction); - tabbedPane.getMainWindow().getShortcutsController().bindImmediate(saveAction); - - JButton save = saveAction.makeButton(); - scriptArea.getScriptNode().addChangeListener(save::setEnabled); - - JButton check = new JButton(NLS.str("script.check"), Icons.CHECK); - check.addActionListener(ev -> checkScript()); - JButton format = new JButton(NLS.str("script.format"), Icons.FORMAT); - format.addActionListener(ev -> reformatCode()); - JButton scriptLog = new JButton(NLS.str("script.log"), Icons.FORMAT); - scriptLog.addActionListener(ev -> showScriptLog()); - - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS)); - panel.setBorder(new EmptyBorder(0, 0, 0, 0)); - panel.add(runAction.makeButton()); - panel.add(Box.createRigidArea(new Dimension(10, 0))); - panel.add(save); - panel.add(Box.createRigidArea(new Dimension(10, 0))); - panel.add(check); - panel.add(Box.createRigidArea(new Dimension(10, 0))); - panel.add(format); - panel.add(Box.createRigidArea(new Dimension(30, 0))); - panel.add(resultLabel); - panel.add(Box.createHorizontalGlue()); - panel.add(scriptLog); - return panel; - } - - private void runScript() { - scriptArea.save(); - if (!checkScript()) { - return; - } - resetResultLabel(); - - TabbedPane tabbedPane = getTabbedPane(); - MainWindow mainWindow = tabbedPane.getMainWindow(); - mainWindow.getBackgroundExecutor().execute(NLS.str("script.run"), () -> { - try { - mainWindow.getWrapper().reloadPasses(); - } catch (Exception e) { - scriptLog.error("Passes reload failed", e); - } - }, taskStatus -> { - mainWindow.passesReloaded(); - }); - } - - private boolean checkScript() { - try { - resetResultLabel(); - String code = scriptArea.getText(); - String fileName = scriptArea.getNode().getName(); - - ScriptServices scriptServices = new ScriptServices(); - ScriptAnalyzeResult result = scriptServices.analyze(fileName, code); - boolean success = result.getSuccess(); - List issues = result.getIssues(); - for (ScriptDiagnostic issue : issues) { - Severity severity = issue.getSeverity(); - if (severity == Severity.ERROR || severity == Severity.FATAL) { - scriptLog.error("{}", issue.render(false, true, true, true)); - success = false; - } else if (severity == Severity.WARNING) { - scriptLog.warn("Compile issue: {}", issue); - } - } - List lintErrs = Collections.emptyList(); - if (success) { - lintErrs = getLintIssues(code); - } - - errorService.clearErrors(); - errorService.addCompilerIssues(issues); - errorService.addLintErrors(lintErrs); - if (!success) { - resultLabel.setText("Compile issues: " + issues.size()); - showScriptLog(); - } else if (!lintErrs.isEmpty()) { - resultLabel.setText("Lint issues: " + lintErrs.size()); - } else { - resultLabel.setText("OK"); - } - errorService.apply(); - return success; - } catch (Throwable e) { - scriptLog.error("Failed to check code", e); - return true; - } - } - - private List getLintIssues(String code) { - try { - List lintErrs = KtLintUtils.INSTANCE.lint(code); - for (JadxLintError error : lintErrs) { - scriptLog.warn("Lint issue: {} ({}:{})(ruleId={})", - error.getDetail(), error.getLine(), error.getCol(), error.getRuleId()); - } - return lintErrs; - } catch (Throwable e) { // can throw initialization error - scriptLog.warn("KtLint failed", e); - return Collections.emptyList(); - } - } - - private void reformatCode() { - resetResultLabel(); - try { - String code = scriptArea.getText(); - String formattedCode = KtLintUtils.INSTANCE.format(code); - if (!code.equals(formattedCode)) { - scriptArea.updateCode(formattedCode); - resultLabel.setText("Code updated"); - errorService.clearErrors(); - } - } catch (Throwable e) { // can throw initialization error - scriptLog.error("Failed to reformat code", e); - } - } - - private void resetResultLabel() { - resultLabel.setText(""); - } - - private void applySettings() { - JadxSettings settings = getSettings(); - codeScrollPane.setLineNumbersEnabled(settings.getLineNumbersMode() != LineNumbersMode.DISABLE); - codeScrollPane.getGutter().setLineNumberFont(settings.getCodeFont()); - scriptArea.loadSettings(); - } - - private void showScriptLog() { - getMainWindow().showLogViewer(LogOptions.forScript(getNode().getName())); - } - - @Override - public AbstractCodeArea getCodeArea() { - return scriptArea; - } - - @Override - public Component getChildrenComponent() { - return getCodeArea(); - } - - @Override - public void loadSettings() { - applySettings(); - updateUI(); - } - - public void dispose() { - scriptArea.dispose(); - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptErrorService.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptErrorService.java deleted file mode 100644 index 38c49f7be..000000000 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptErrorService.java +++ /dev/null @@ -1,124 +0,0 @@ -package jadx.gui.plugins.script; - -import java.util.List; - -import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; -import org.fife.ui.rsyntaxtextarea.parser.AbstractParser; -import org.fife.ui.rsyntaxtextarea.parser.DefaultParseResult; -import org.fife.ui.rsyntaxtextarea.parser.DefaultParserNotice; -import org.fife.ui.rsyntaxtextarea.parser.ParseResult; -import org.fife.ui.rsyntaxtextarea.parser.ParserNotice; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import kotlin.script.experimental.api.ScriptDiagnostic; -import kotlin.script.experimental.api.SourceCode; - -public class ScriptErrorService extends AbstractParser { - private static final Logger LOG = LoggerFactory.getLogger(ScriptErrorService.class); - - private final DefaultParseResult result; - private final ScriptCodeArea scriptArea; - - public ScriptErrorService(ScriptCodeArea scriptArea) { - this.scriptArea = scriptArea; - this.result = new DefaultParseResult(this); - } - - @Override - public ParseResult parse(RSyntaxDocument doc, String style) { - return result; - } - - public void clearErrors() { - result.clearNotices(); - scriptArea.removeParser(this); - } - - public void apply() { - scriptArea.removeParser(this); - scriptArea.addParser(this); - scriptArea.addNotify(); - scriptArea.requestFocus(); - jumpCaretToFirstError(); - } - - private void jumpCaretToFirstError() { - List parserNotices = result.getNotices(); - if (parserNotices.isEmpty()) { - return; - } - ParserNotice notice = parserNotices.get(0); - int offset = notice.getOffset(); - if (offset == -1) { - try { - offset = scriptArea.getLineStartOffset(notice.getLine()); - } catch (Exception e) { - LOG.error("Failed to jump to first error", e); - return; - } - } - scriptArea.scrollToPos(offset); - } - - public void addCompilerIssues(List issues) { - for (ScriptDiagnostic issue : issues) { - if (issue.getSeverity() == ScriptDiagnostic.Severity.DEBUG) { - continue; - } - DefaultParserNotice notice; - SourceCode.Location loc = issue.getLocation(); - if (loc == null) { - notice = new DefaultParserNotice(this, issue.getMessage(), 0); - } else { - try { - int line = loc.getStart().getLine(); - int offset = scriptArea.getLineStartOffset(line - 1) + loc.getStart().getCol(); - int len = loc.getEnd() == null ? -1 : loc.getEnd().getCol() - loc.getStart().getCol(); - notice = new DefaultParserNotice(this, issue.getMessage(), line, offset - 1, len); - notice.setLevel(convertLevel(issue.getSeverity())); - } catch (Exception e) { - LOG.error("Failed to convert script issue", e); - continue; - } - } - addNotice(notice); - } - } - - private static ParserNotice.Level convertLevel(ScriptDiagnostic.Severity severity) { - switch (severity) { - case FATAL: - case ERROR: - return ParserNotice.Level.ERROR; - case WARNING: - return ParserNotice.Level.WARNING; - case INFO: - case DEBUG: - return ParserNotice.Level.INFO; - } - return ParserNotice.Level.ERROR; - } - - public void addLintErrors(List errors) { - for (JadxLintError error : errors) { - try { - int line = error.getLine(); - int offset = scriptArea.getLineStartOffset(line - 1) + error.getCol() - 1; - String word = scriptArea.getWordByPosition(offset); - int len = word != null ? word.length() : -1; - DefaultParserNotice notice = new DefaultParserNotice(this, error.getDetail(), line, offset, len); - notice.setLevel(ParserNotice.Level.WARNING); - addNotice(notice); - } catch (Exception e) { - LOG.error("Failed to convert lint error", e); - } - } - } - - private void addNotice(DefaultParserNotice notice) { - LOG.debug("Add notice: {}:{}:{} - {}", - notice.getLine(), notice.getOffset(), notice.getLength(), notice.getMessage()); - result.addNotice(notice); - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java index 430603ef4..911906a9b 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java @@ -53,6 +53,7 @@ public class JadxProject { private static final int SEARCH_HISTORY_LIMIT = 30; private final transient MainWindow mainWindow; + private final transient TabStateViewAdapter tabStateViewAdapter = new TabStateViewAdapter(); private transient String name = "New Project"; private transient @Nullable Path projectPath; @@ -155,7 +156,7 @@ public class JadxProject { public void saveOpenTabs(List tabs) { List tabStateList = tabs.stream() - .map(TabStateViewAdapter::build) + .map(tabStateViewAdapter::build) .filter(Objects::nonNull) .collect(Collectors.toList()); if (data.setOpenTabs(tabStateList)) { @@ -164,8 +165,9 @@ public class JadxProject { } public List getOpenTabs(MainWindow mw) { + tabStateViewAdapter.setCustomAdapters(mw.getWrapper().getGuiPluginsContext().getTabStatePersistAdapters()); return data.getOpenTabs().stream() - .map(s -> TabStateViewAdapter.load(mw, s)) + .map(s -> tabStateViewAdapter.load(mw, s)) .filter(Objects::nonNull) .collect(Collectors.toList()); } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java b/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java index 972bc775e..719a7a503 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/TabStateViewAdapter.java @@ -1,15 +1,19 @@ package jadx.gui.settings; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.api.JavaClass; import jadx.gui.plugins.mappings.JInputMapping; +import jadx.gui.settings.data.ITabStatePersist; import jadx.gui.settings.data.TabViewState; import jadx.gui.settings.data.ViewPoint; import jadx.gui.treemodel.JClass; -import jadx.gui.treemodel.JInputScript; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JResource; import jadx.gui.treemodel.JSubResource; @@ -20,8 +24,9 @@ import jadx.gui.utils.UiUtils; public class TabStateViewAdapter { private static final Logger LOG = LoggerFactory.getLogger(TabStateViewAdapter.class); - @Nullable - public static TabViewState build(EditorViewState viewState) { + private final Map customAdaptersMap = new HashMap<>(); + + public @Nullable TabViewState build(EditorViewState viewState) { TabViewState tvs = new TabViewState(); tvs.setSubPath(viewState.getSubPath()); if (!saveJNode(tvs, viewState.getNode())) { @@ -40,8 +45,7 @@ public class TabStateViewAdapter { return tvs; } - @Nullable - public static EditorViewState load(MainWindow mw, TabViewState tvs) { + public @Nullable EditorViewState load(MainWindow mw, TabViewState tvs) { try { JNode node = loadJNode(mw, tvs); if (node == null) { @@ -63,8 +67,15 @@ public class TabStateViewAdapter { } } + public void setCustomAdapters(List customAdapters) { + customAdaptersMap.clear(); + for (ITabStatePersist customAdapter : customAdapters) { + customAdaptersMap.put(customAdapter.getNodeClass().getName(), customAdapter); + } + } + @Nullable - private static JNode loadJNode(MainWindow mw, TabViewState tvs) { + private JNode loadJNode(MainWindow mw, TabViewState tvs) { switch (tvs.getType()) { case "class": JavaClass javaClass = mw.getWrapper().searchJavaClassByRawName(tvs.getTabPath()); @@ -85,18 +96,21 @@ public class TabStateViewAdapter { } return null; - case "script": - return mw.getTreeRoot() - .followStaticPath("JInputs", "JInputScripts") - .searchNode(node -> node instanceof JInputScript && node.getName().equals(tvs.getTabPath())); - case "mapping": return mw.getTreeRoot().followStaticPath("JInputs").searchNode(node -> node instanceof JInputMapping); } + ITabStatePersist statePersist = customAdaptersMap.get(tvs.getType()); + if (statePersist != null) { + try { + return statePersist.load(tvs.getTabPath()); + } catch (Exception e) { + LOG.error("Failed to restore tab for custom node adapter: {}", tvs.getType(), e); + } + } return null; } - private static boolean saveJNode(TabViewState tvs, JNode node) { + private boolean saveJNode(TabViewState tvs, JNode node) { if (node instanceof JClass) { tvs.setType("class"); tvs.setTabPath(((JClass) node).getCls().getRawName()); @@ -113,15 +127,22 @@ public class TabStateViewAdapter { tvs.setTabPath(node.getName()); return true; } - if (node instanceof JInputScript) { - tvs.setType("script"); - tvs.setTabPath(node.getName()); - return true; - } if (node instanceof JInputMapping) { tvs.setType("mapping"); return true; } + + String typeName = node.getClass().getName(); + ITabStatePersist statePersist = customAdaptersMap.get(typeName); + if (statePersist != null) { + try { + tvs.setTabPath(statePersist.save(node)); + tvs.setType(statePersist.getNodeClass().getName()); + return true; + } catch (Exception e) { + LOG.error("Failed to save state for custom node: {}", typeName, e); + } + } return false; } } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/data/ITabStatePersist.java b/jadx-gui/src/main/java/jadx/gui/settings/data/ITabStatePersist.java new file mode 100644 index 000000000..0477d8b31 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/settings/data/ITabStatePersist.java @@ -0,0 +1,18 @@ +package jadx.gui.settings.data; + +import org.jetbrains.annotations.Nullable; + +import jadx.gui.treemodel.JNode; + +/** + * Adapter interface to allow save/load state of opened tabs + */ +public interface ITabStatePersist { + + Class getNodeClass(); + + String save(JNode node); + + @Nullable + JNode load(String stateStr); +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java deleted file mode 100644 index 63f125c8a..000000000 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java +++ /dev/null @@ -1,107 +0,0 @@ -package jadx.gui.treemodel; - -import java.nio.file.Path; - -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JPopupMenu; - -import org.fife.ui.rsyntaxtextarea.SyntaxConstants; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.api.ICodeInfo; -import jadx.api.impl.SimpleCodeInfo; -import jadx.core.utils.exceptions.JadxRuntimeException; -import jadx.core.utils.files.FileUtils; -import jadx.gui.plugins.script.ScriptContentPanel; -import jadx.gui.ui.MainWindow; -import jadx.gui.ui.panel.ContentPanel; -import jadx.gui.ui.tab.TabbedPane; -import jadx.gui.utils.NLS; -import jadx.gui.utils.UiUtils; -import jadx.gui.utils.ui.SimpleMenuItem; - -public class JInputScript extends JEditableNode { - private static final Logger LOG = LoggerFactory.getLogger(JInputScript.class); - - private static final ImageIcon SCRIPT_ICON = UiUtils.openSvgIcon("nodes/kotlin_script"); - - private final Path scriptPath; - private final String name; - - public JInputScript(Path scriptPath) { - this.scriptPath = scriptPath; - this.name = scriptPath.getFileName().toString().replace(".jadx.kts", ""); - } - - @Override - public boolean hasContent() { - return true; - } - - @Override - public ContentPanel getContentPanel(TabbedPane tabbedPane) { - return new ScriptContentPanel(tabbedPane, this); - } - - @Override - public @NotNull ICodeInfo getCodeInfo() { - try { - return new SimpleCodeInfo(FileUtils.readFile(scriptPath)); - } catch (Exception e) { - throw new JadxRuntimeException("Failed to read script file: " + scriptPath.toAbsolutePath(), e); - } - } - - @Override - public void save(String newContent) { - try { - FileUtils.writeFile(scriptPath, newContent); - LOG.debug("Script saved: {}", scriptPath.toAbsolutePath()); - } catch (Exception e) { - throw new JadxRuntimeException("Failed to write script file: " + scriptPath.toAbsolutePath(), e); - } - } - - @Override - public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { - JPopupMenu menu = new JPopupMenu(); - menu.add(new SimpleMenuItem(NLS.str("popup.add_scripts"), mainWindow::addFiles)); - menu.add(new SimpleMenuItem(NLS.str("popup.new_script"), mainWindow::addNewScript)); - menu.add(new SimpleMenuItem(NLS.str("popup.remove"), () -> mainWindow.removeInput(scriptPath))); - menu.add(new SimpleMenuItem(NLS.str("popup.rename"), () -> mainWindow.renameInput(scriptPath))); - return menu; - } - - @Override - public String getSyntaxName() { - return SyntaxConstants.SYNTAX_STYLE_KOTLIN; - } - - @Override - public JClass getJParent() { - return null; - } - - @Override - public Icon getIcon() { - return SCRIPT_ICON; - } - - @Override - public String getName() { - return name; - } - - @Override - public String makeString() { - return name; - } - - @Override - public String getTooltip() { - return scriptPath.normalize().toAbsolutePath().toString(); - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScripts.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScripts.java deleted file mode 100644 index 377a54463..000000000 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScripts.java +++ /dev/null @@ -1,51 +0,0 @@ -package jadx.gui.treemodel; - -import java.nio.file.Path; -import java.util.List; - -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JPopupMenu; - -import jadx.gui.ui.MainWindow; -import jadx.gui.utils.NLS; -import jadx.gui.utils.UiUtils; -import jadx.gui.utils.ui.SimpleMenuItem; - -public class JInputScripts extends JNode { - private static final ImageIcon INPUT_SCRIPTS_ICON = UiUtils.openSvgIcon("nodes/scriptsModel"); - - public JInputScripts(List scripts) { - for (Path script : scripts) { - add(new JInputScript(script)); - } - } - - @Override - public JPopupMenu onTreePopupMenu(MainWindow mainWindow) { - JPopupMenu menu = new JPopupMenu(); - menu.add(new SimpleMenuItem(NLS.str("popup.add_scripts"), mainWindow::addFiles)); - menu.add(new SimpleMenuItem(NLS.str("popup.new_script"), mainWindow::addNewScript)); - return menu; - } - - @Override - public JClass getJParent() { - return null; - } - - @Override - public Icon getIcon() { - return INPUT_SCRIPTS_ICON; - } - - @Override - public String getID() { - return "JInputScripts"; - } - - @Override - public String makeString() { - return NLS.str("tree.input_scripts"); - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputs.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputs.java index 3b96c9e9a..3c0674dcd 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputs.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputs.java @@ -1,38 +1,29 @@ package jadx.gui.treemodel; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import javax.swing.Icon; import javax.swing.ImageIcon; import jadx.core.utils.files.FileUtils; -import jadx.gui.JadxWrapper; import jadx.gui.settings.JadxProject; +import jadx.gui.ui.MainWindow; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; +import jadx.gui.utils.plugins.TreeInputsHelper; public class JInputs extends JNode { private static final ImageIcon INPUTS_ICON = UiUtils.openSvgIcon("nodes/projectStructure"); - public JInputs(JadxWrapper wrapper) { - JadxProject project = wrapper.getProject(); + public JInputs(MainWindow mainWindow) { + JadxProject project = mainWindow.getProject(); List inputs = project.getFilePaths(); List files = FileUtils.expandDirs(inputs); - List scripts = new ArrayList<>(); - Iterator it = files.iterator(); - while (it.hasNext()) { - Path file = it.next(); - if (file.getFileName().toString().endsWith(".jadx.kts")) { - scripts.add(file); - it.remove(); - } - } - - add(new JInputFiles(files)); - add(new JInputScripts(scripts)); + TreeInputsHelper inputsHelper = new TreeInputsHelper(mainWindow); + inputsHelper.processInputs(files); + add(new JInputFiles(inputsHelper.getSimpleFiles())); + inputsHelper.getCustomNodes().forEach(this::add); } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java index add6e2393..bec6656a9 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java @@ -19,6 +19,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.JadxWrapper; import jadx.gui.settings.JadxProject; import jadx.gui.treemodel.JResource.JResType; +import jadx.gui.ui.MainWindow; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; @@ -28,18 +29,20 @@ public class JRoot extends JNode { private static final ImageIcon ROOT_ICON = UiUtils.openSvgIcon("nodes/rootPackageFolder"); private final transient JadxWrapper wrapper; + private final transient MainWindow mainWindow; private transient boolean flatPackages = false; - private final List customNodes = new ArrayList<>(); + private final transient List customNodes = new ArrayList<>(); - public JRoot(JadxWrapper wrapper) { - this.wrapper = wrapper; + public JRoot(MainWindow mainWindow) { + this.mainWindow = mainWindow; + this.wrapper = mainWindow.getWrapper(); } public final void update() { removeAllChildren(); - add(new JInputs(wrapper)); + add(new JInputs(mainWindow)); add(new JSources(this, wrapper)); List resources = wrapper.getResources(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 34ff9f6bd..a6cc8ca62 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -828,7 +828,7 @@ public class MainWindow extends JFrame { } public void initTree() { - treeRoot = new JRoot(wrapper); + treeRoot = new JRoot(this); treeRoot.setFlatPackages(isFlattenPackage); treeModel.setRoot(treeRoot); addTreeCustomNodes(); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/IconsCache.java b/jadx-gui/src/main/java/jadx/gui/utils/IconsCache.java index a52a02329..5e12c018a 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/IconsCache.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/IconsCache.java @@ -3,13 +3,13 @@ package jadx.gui.utils; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import com.formdev.flatlaf.extras.FlatSVGIcon; +import javax.swing.ImageIcon; public class IconsCache { - private static final Map SVG_ICONS = new ConcurrentHashMap<>(); + private static final Map SVG_ICONS = new ConcurrentHashMap<>(); - public static FlatSVGIcon getSVGIcon(String name) { + public static ImageIcon getSVGIcon(String name) { return SVG_ICONS.computeIfAbsent(name, UiUtils::openSvgIcon); } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java index 5ff3bc831..8fd9f195b 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java @@ -88,7 +88,7 @@ public class UiUtils { private UiUtils() { } - public static FlatSVGIcon openSvgIcon(String name) { + public static ImageIcon openSvgIcon(String name) { String iconPath = "icons/" + name + ".svg"; FlatSVGIcon icon = new FlatSVGIcon(iconPath); boolean found; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/plugins/TreeInputsHelper.java b/jadx-gui/src/main/java/jadx/gui/utils/plugins/TreeInputsHelper.java new file mode 100644 index 000000000..022e736d8 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/plugins/TreeInputsHelper.java @@ -0,0 +1,92 @@ +package jadx.gui.utils.plugins; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.gui.plugins.context.ITreeInputCategory; +import jadx.gui.treemodel.JNode; +import jadx.gui.ui.MainWindow; + +public class TreeInputsHelper { + private static final Logger LOG = LoggerFactory.getLogger(TreeInputsHelper.class); + + private final List categoryData; + private List simpleFiles; + + public TreeInputsHelper(MainWindow mainWindow) { + categoryData = mainWindow.getWrapper().getGuiPluginsContext() + .getTreeInputCategories() + .stream() + .map(CategoryData::new) + .collect(Collectors.toList()); + } + + public void processInputs(List files) { + simpleFiles = new ArrayList<>(files.size()); + for (Path file : files) { + boolean added = false; + for (CategoryData data : categoryData) { + if (data.filesFilter(file)) { + added = true; + break; + } + } + if (!added) { + simpleFiles.add(file); + } + } + } + + public List getCustomNodes() { + return categoryData.stream() + .filter(CategoryData::notEmpty) + .map(CategoryData::buildInputNode) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + public List getSimpleFiles() { + return simpleFiles; + } + + private static final class CategoryData { + private final ITreeInputCategory provider; + private final List collectedFiles = new ArrayList<>(); + + private CategoryData(ITreeInputCategory provider) { + this.provider = provider; + } + + public boolean filesFilter(Path file) { + try { + if (provider.filesFilter(file)) { + collectedFiles.add(file); + return true; + } + } catch (Exception e) { + LOG.error("Failed to filter input files", e); + } + return false; + } + + public @Nullable JNode buildInputNode() { + try { + return provider.buildInputNode(collectedFiles); + } catch (Exception e) { + LOG.error("Failed to build custom input node", e); + return null; + } + } + + public boolean notEmpty() { + return !collectedFiles.isEmpty(); + } + } +} diff --git a/jadx-gui/src/test/java/jadx/gui/TestI18n.java b/jadx-gui/src/test/java/jadx/gui/TestI18n.java index 90fdaba23..f75c940e7 100644 --- a/jadx-gui/src/test/java/jadx/gui/TestI18n.java +++ b/jadx-gui/src/test/java/jadx/gui/TestI18n.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.Reader; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -116,12 +117,25 @@ public class TestI18n { fail("I18n file: " + path.getFileName() + " and " + DEFAULT_LANG_FILE + " differ in line " + line); } + /** + * Temporary solution to allow use I18N strings in plugins until proper API implemented + */ + private static final List EXCLUDED_KEYS = Arrays.asList( + // keys from `jadx-script-kotlin` + "tree.input_scripts", + "popup.new_script", + "popup.add_scripts", + "script.log", + "script.format", + "script.check"); + @Test public void keyIsUsed() throws IOException { Properties properties = new Properties(); try (Reader reader = Files.newBufferedReader(i18nPath.resolve(DEFAULT_LANG_FILE))) { properties.load(reader); } + EXCLUDED_KEYS.forEach(properties.keySet()::remove); Set keys = new HashSet<>(); for (Object key : properties.keySet()) { keys.add("\"" + key + '"'); diff --git a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsTools.java b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsTools.java index 6e360707e..f3d5d7973 100644 --- a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsTools.java +++ b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxPluginsTools.java @@ -26,6 +26,7 @@ import jadx.api.plugins.JadxPluginInfo; import jadx.core.Jadx; import jadx.core.plugins.versions.VerifyRequiredVersion; import jadx.core.utils.StringUtils; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.plugins.tools.data.JadxInstalledPlugins; import jadx.plugins.tools.data.JadxPluginMetadata; @@ -83,12 +84,13 @@ public class JadxPluginsTools { install(pluginMetadata); return pluginMetadata; } - rejectedVersions.add(" version " + pluginMetadata.getVersion() - + " not compatible, require: " + pluginMetadata.getRequiredJadxVersion()); + String pluginVersion = Utils.getOrElse(pluginMetadata.getVersion(), "unknown"); + rejectedVersions.add(" version '" + pluginVersion + "' not compatible, require: " + + pluginMetadata.getRequiredJadxVersion()); } throw new JadxRuntimeException("Can't find compatible version to install" + ", current jadx version: " + verifyRequiredVersion.getJadxVersion() - + "\nrejected versions:\n" + + "\nrejected plugin versions:\n" + String.join("\n", rejectedVersions)); } diff --git a/jadx-plugins/jadx-kotlin-metadata/build.gradle.kts b/jadx-plugins/jadx-kotlin-metadata/build.gradle.kts index 1899fbc77..c75dd57e0 100644 --- a/jadx-plugins/jadx-kotlin-metadata/build.gradle.kts +++ b/jadx-plugins/jadx-kotlin-metadata/build.gradle.kts @@ -6,7 +6,7 @@ plugins { dependencies { api(project(":jadx-core")) - implementation("org.jetbrains.kotlin:kotlin-metadata-jvm:2.3.0") + implementation("org.jetbrains.kotlin:kotlin-metadata-jvm:2.3.10") testImplementation(project.project(":jadx-core").sourceSets.getByName("test").output) testImplementation("org.apache.commons:commons-lang3:3.20.0") diff --git a/jadx-plugins/jadx-script-kotlin/README.md b/jadx-plugins/jadx-script-kotlin/README.md new file mode 100644 index 000000000..805144582 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/README.md @@ -0,0 +1,18 @@ +## JADX scripting support + +### Examples + +Check script examples in [`examples/`](https://github.com/skylot/jadx/tree/master/jadx-plugins/jadx-script-kotlin/examples/)(start with [`hello`](https://github.com/skylot/jadx/blob/master/jadx-plugins/jadx-script-kotlin/examples/hello.jadx.kts)) + +### Script usage + +#### In jadx-cli + +Just add script file as input + +#### In jadx-gui + +1. Add script file to the project (using `Add files` or `New script` by right-click menu on `Inputs/Scripts`) +2. Script will appear in `Inputs/Scripts` section +3. After script change, you can run it using `Run` button in script editor toolbar or reload whole project (`Reload` button in toolbar or `F5`). + Also, you can enable `Live reload` option in `File` menu to reload project automatically on scripts change diff --git a/jadx-plugins/jadx-script-kotlin/build.gradle.kts b/jadx-plugins/jadx-script-kotlin/build.gradle.kts new file mode 100644 index 000000000..86b95ca1e --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/build.gradle.kts @@ -0,0 +1,87 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + id("java-library") + + kotlin("jvm") +} + +version = System.getenv("JADX_SCRIPT_KOTLIN_PLUGIN_VERSION") ?: "dev" + +dependencies { + compileOnly(project(":jadx-core")) + compileOnly(project(":jadx-commons:jadx-app-commons")) + + compileOnly(project(":jadx-gui")) + + implementation(kotlin("scripting-common")) + implementation(kotlin("scripting-jvm")) + implementation(kotlin("scripting-jvm-host")) + implementation(kotlin("scripting-ide-services")) + implementation(kotlin("scripting-compiler-embeddable")) + implementation(kotlin("compiler-embeddable")) + + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") + + // allow to use maven dependencies in scripts + implementation(kotlin("scripting-dependencies")) + implementation(kotlin("scripting-dependencies-maven")) + + // autocomplete support in editor + compileOnly("com.fifesoft:autocomplete:3.3.2") + compileOnly("com.fifesoft:rsyntaxtextarea:3.6.0") + + // use KtLint for format and check jadx scripts + implementation("com.pinterest.ktlint:ktlint-rule-engine:1.8.0") + implementation("com.pinterest.ktlint:ktlint-ruleset-standard:1.8.0") + + compileOnly("io.github.oshai:kotlin-logging-jvm:7.0.13") + compileOnly("org.slf4j:slf4j-api:2.0.17") + + // register jadx script for IDE support (don't work now) + // kotlinScriptDef(project(":jadx-plugins:jadx-script-kotlin")) + + testImplementation(project(":jadx-core")) + testRuntimeOnly(project(":jadx-plugins:jadx-dex-input")) + testRuntimeOnly(project(":jadx-plugins:jadx-smali-input")) + + testImplementation("ch.qos.logback:logback-classic:1.5.22") + testImplementation("org.assertj:assertj-core:3.27.6") + + testImplementation("org.junit.jupiter:junit-jupiter:5.13.3") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +repositories { + mavenLocal() + mavenCentral() + google() +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } +} + +tasks { + register("dist") { + group = "jadx-plugin" + dependsOn(jar) + + from(jar) + from(project.configurations.runtimeClasspath) + + archiveBaseName = project.name + destinationDirectory = layout.buildDirectory.dir("dist") + } + + withType(Test::class) { + useJUnitPlatform() + } +} diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf/deobf-resources.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/deobf/deobf-resources.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/deobf/deobf-resources.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/deobf/deobf-resources.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf/deobf.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/deobf/deobf.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/deobf/deobf.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/deobf/deobf.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf/deobf2.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/deobf/deobf2.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/deobf/deobf2.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/deobf/deobf2.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf/deobf_by_code.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/deobf/deobf_by_code.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/deobf/deobf_by_code.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/deobf/deobf_by_code.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf/deobf_from_tostring.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/deobf/deobf_from_tostring.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/deobf/deobf_from_tostring.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/deobf/deobf_from_tostring.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/deobf/deobf_method_param.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/deobf/deobf_method_param.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/deobf/deobf_method_param.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/deobf/deobf_method_param.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/gui/bookmark.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/gui/bookmark.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/gui/bookmark.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/gui/bookmark.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/gui/caret_mouse.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/gui/caret_mouse.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/gui/caret_mouse.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/gui/caret_mouse.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/gui/custom_frida.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/gui/custom_frida.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/gui/custom_frida.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/gui/custom_frida.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/gui/log_events.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/gui/log_events.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/gui/log_events.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/gui/log_events.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/gui/menu_entry.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/gui/menu_entry.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/gui/menu_entry.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/gui/menu_entry.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/hello.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/hello.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/hello.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/hello.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/options.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/options.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/options.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/options.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/replace.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/replace.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/replace.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/replace.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/replace_method_call.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/replace_method_call.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/replace_method_call.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/replace_method_call.jadx.kts diff --git a/jadx-plugins/jadx-script/examples/scripts/stages.jadx.kts b/jadx-plugins/jadx-script-kotlin/examples/stages.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/examples/scripts/stages.jadx.kts rename to jadx-plugins/jadx-script-kotlin/examples/stages.jadx.kts diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/JadxScriptKotlinPlugin.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/JadxScriptKotlinPlugin.kt new file mode 100644 index 000000000..eac9298eb --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/JadxScriptKotlinPlugin.kt @@ -0,0 +1,37 @@ +package jadx.plugins.script.kotlin + +import jadx.api.plugins.JadxPlugin +import jadx.api.plugins.JadxPluginContext +import jadx.api.plugins.JadxPluginInfo +import jadx.api.plugins.JadxPluginInfoBuilder +import jadx.plugins.script.kotlin.gui.JadxScriptInputCategory +import jadx.plugins.script.kotlin.gui.JadxScriptOptionsUI +import jadx.plugins.script.kotlin.passes.JadxScriptAfterLoadPass +import jadx.plugins.script.kotlin.runtime.data.JadxScriptAllOptions + +class JadxScriptKotlinPlugin : JadxPlugin { + companion object { + const val PLUGIN_ID = "jadx-script-kotlin" + } + + override fun getPluginInfo(): JadxPluginInfo = JadxPluginInfoBuilder.pluginId(PLUGIN_ID) + .name("Jadx Script (Kotlin)") + .description("Scripting support for jadx using Kotlin script") + .homepage("https://github.com/jadx-decompiler/jadx-script-kotlin") + .requiredJadxVersion("1.5.4, r2596") + .provides("jadx-script") // conflict with bundled plugin from older jadx versions + .build() + + override fun init(context: JadxPluginContext) { + val scriptOptions = JadxScriptAllOptions() + context.registerOptions(scriptOptions) + val scripts = ScriptEval().process(context, scriptOptions) + if (scripts.isNotEmpty()) { + context.addPass(JadxScriptAfterLoadPass(scripts)) + context.guiContext?.let { guiContext -> + JadxScriptOptionsUI.setup(guiContext, scriptOptions) + JadxScriptInputCategory.register(context, guiContext) + } + } + } +} diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptCache.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptCache.kt similarity index 70% rename from jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptCache.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptCache.kt index ac108f680..27502403a 100644 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptCache.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptCache.kt @@ -1,9 +1,11 @@ -package jadx.plugins.script +package jadx.plugins.script.kotlin +import io.github.oshai.kotlinlogging.KotlinLogging import jadx.api.plugins.JadxPluginContext import jadx.core.utils.files.FileUtils -import java.io.File +import java.nio.file.Path import java.security.MessageDigest +import kotlin.io.path.exists import kotlin.script.experimental.api.CompiledScript import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.SourceCode @@ -12,6 +14,8 @@ import kotlin.script.experimental.jvm.impl.KJvmCompiledScript import kotlin.script.experimental.jvmhost.loadScriptFromJar import kotlin.script.experimental.jvmhost.saveToJar +private val log = KotlinLogging.logger {} + class ScriptCache { private val enableCache = System.getProperty("JADX_SCRIPT_CACHE_ENABLE", "true").equals("true", ignoreCase = true) @@ -19,7 +23,9 @@ class ScriptCache { if (!enableCache) { return CompiledJvmScriptsCache.NoCache } - return JadxScriptsCache(getCacheDir(context)) + val cacheDir = getCacheDir(context) + log.debug { "script cache created in : $cacheDir" } + return JadxScriptsCache(cacheDir) } /** @@ -27,21 +33,22 @@ class ScriptCache { * but remove all previous cache versions for the script with the same path and name. * This should reduce old cache entries count */ - class JadxScriptsCache(private val baseCacheDir: File) : CompiledJvmScriptsCache { + class JadxScriptsCache(private val baseCacheDir: Path) : CompiledJvmScriptsCache { override fun get( script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration, ): CompiledScript? { val cacheDir = hashDir(baseCacheDir, script) val file = hashFile(cacheDir, script, scriptCompilationConfiguration) - if (!file.exists()) { - return null - } - return file.loadScriptFromJar() ?: run { - // invalidate cache if the script cannot be loaded - FileUtils.deleteDir(cacheDir) - null + if (file.exists()) { + file.toFile().loadScriptFromJar().let { + log.debug { "loaded script from cache: $file" } + return it + } } + log.debug { "script not found in cache: $file" } + FileUtils.deleteDirIfExists(cacheDir) + return null } override fun store( @@ -55,34 +62,35 @@ class ScriptCache { val cacheDir = hashDir(baseCacheDir, script) val file = hashFile(cacheDir, script, scriptCompilationConfiguration) - cacheDir.deleteRecursively() - cacheDir.mkdirs() - jvmScript.saveToJar(file) + FileUtils.deleteDirIfExists(cacheDir) + FileUtils.makeDirs(cacheDir) + jvmScript.saveToJar(file.toFile()) + log.debug { "script cached: $file" } } } - private fun getCacheDir(context: JadxPluginContext): File { - val cacheBaseDir = context.files().pluginCacheDir.resolve("compiled").toFile() + private fun getCacheDir(context: JadxPluginContext): Path { + val cacheBaseDir = context.files().pluginCacheDir.resolve("compiled") FileUtils.makeDirs(cacheBaseDir) return cacheBaseDir } companion object { - private fun hashDir(baseCacheDir: File, script: SourceCode): File { + private fun hashDir(baseCacheDir: Path, script: SourceCode): Path { if (script.name == null && script.locationId == null) { - return File(baseCacheDir, "tmp") + return baseCacheDir.resolve("tmp") } val digest = MessageDigest.getInstance("MD5") digest.add(script.name) digest.add(script.locationId) - return File(baseCacheDir, digest.digest().toHexString()) + return baseCacheDir.resolve(digest.digest().toHexString()) } private fun hashFile( - cacheDir: File, + cacheDir: Path, script: SourceCode, scriptCompilationConfiguration: ScriptCompilationConfiguration, - ): File { + ): Path { val digest = MessageDigest.getInstance("MD5") digest.add(script.text) scriptCompilationConfiguration.notTransientData.entries @@ -91,7 +99,7 @@ class ScriptCache { digest.add(it.key.name) digest.add(it.value.toString()) } - return File(cacheDir, digest.digest().toHexString() + ".jar") + return cacheDir.resolve(digest.digest().toHexString() + ".jar") } private fun MessageDigest.add(str: String?) { diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptEval.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptEval.kt new file mode 100644 index 000000000..6f37da1a4 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptEval.kt @@ -0,0 +1,212 @@ +package jadx.plugins.script.kotlin + +import io.github.oshai.kotlinlogging.KotlinLogging +import jadx.api.plugins.JadxPluginContext +import jadx.plugins.script.kotlin.runtime.JadxScriptData +import jadx.plugins.script.kotlin.runtime.JadxScriptTemplate +import jadx.plugins.script.kotlin.runtime.data.JadxScriptAllOptions +import kotlinx.coroutines.runBlocking +import org.jetbrains.kotlin.scripting.resolve.skipExtensionsResolutionForImplicitsExceptInnermost +import java.io.File +import kotlin.script.experimental.api.EvaluationResult +import kotlin.script.experimental.api.KotlinType +import kotlin.script.experimental.api.ResultValue +import kotlin.script.experimental.api.ResultWithDiagnostics +import kotlin.script.experimental.api.ScriptAcceptedLocation +import kotlin.script.experimental.api.ScriptCollectedData +import kotlin.script.experimental.api.ScriptCompilationConfiguration +import kotlin.script.experimental.api.ScriptConfigurationRefinementContext +import kotlin.script.experimental.api.ScriptDiagnostic.Severity +import kotlin.script.experimental.api.ScriptEvaluationConfiguration +import kotlin.script.experimental.api.acceptedLocations +import kotlin.script.experimental.api.asSuccess +import kotlin.script.experimental.api.collectedAnnotations +import kotlin.script.experimental.api.compilationConfiguration +import kotlin.script.experimental.api.compilerOptions +import kotlin.script.experimental.api.constructorArgs +import kotlin.script.experimental.api.defaultIdentifier +import kotlin.script.experimental.api.defaultImports +import kotlin.script.experimental.api.displayName +import kotlin.script.experimental.api.fileExtension +import kotlin.script.experimental.api.filePathPattern +import kotlin.script.experimental.api.hostConfiguration +import kotlin.script.experimental.api.ide +import kotlin.script.experimental.api.implicitReceivers +import kotlin.script.experimental.api.isStandalone +import kotlin.script.experimental.api.onSuccess +import kotlin.script.experimental.api.refineConfiguration +import kotlin.script.experimental.api.with +import kotlin.script.experimental.dependencies.CompoundDependenciesResolver +import kotlin.script.experimental.dependencies.DependsOn +import kotlin.script.experimental.dependencies.FileSystemDependenciesResolver +import kotlin.script.experimental.dependencies.Repository +import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver +import kotlin.script.experimental.dependencies.resolveFromScriptSourceAnnotations +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.host.getScriptingClass +import kotlin.script.experimental.host.toScriptSource +import kotlin.script.experimental.host.with +import kotlin.script.experimental.jvm.JvmGetScriptingClass +import kotlin.script.experimental.jvm.baseClassLoader +import kotlin.script.experimental.jvm.compilationCache +import kotlin.script.experimental.jvm.dependenciesFromCurrentContext +import kotlin.script.experimental.jvm.jvm +import kotlin.script.experimental.jvm.updateClasspath +import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost +import kotlin.system.measureTimeMillis +import kotlin.time.DurationUnit +import kotlin.time.toDuration + +private val log = KotlinLogging.logger {} + +object DefCompileConf : ScriptCompilationConfiguration(ScriptEval.buildDefaultCompileConf()) + +class ScriptEval { + companion object { + fun buildDefaultCompileConf(): ScriptCompilationConfiguration { + val scriptEval = ScriptEval() + val hostConf = scriptEval.buildHostConf(null) + return scriptEval.buildCompileConf(hostConf) + } + } + + fun process(context: JadxPluginContext, scriptOptions: JadxScriptAllOptions): List { + val jadx = context.decompiler + val scripts = jadx.args.inputFiles.filter { f -> f.name.endsWith(".jadx.kts") } + if (scripts.isEmpty()) { + return emptyList() + } + val scriptDataList = mutableListOf() + for (scriptFile in scripts) { + val scriptData = JadxScriptData(jadx, context, scriptOptions, scriptFile) + scriptDataList.add(scriptData) + eval(context, scriptData) + } + return scriptDataList + } + + private fun eval( + context: JadxPluginContext, + scriptData: JadxScriptData, + ) { + scriptData.log.debug { "Loading script: ${scriptData.scriptFile.absolutePath}" } + val hostConf = buildHostConf(context) + val compileConf = buildCompileConf(hostConf) + val evalConf = buildEvalConf(scriptData, compileConf) + val scriptingHost = BasicJvmScriptingHost(hostConf) + val execTime = measureTimeMillis { + val result = scriptingHost.eval(scriptData.scriptFile.toScriptSource(), compileConf, evalConf) + processEvalResult(result, scriptData) + } + scriptData.log.debug { "Script '${scriptData.scriptName}' executed in ${execTime.toDuration(DurationUnit.MILLISECONDS)}" } + } + + private fun processEvalResult(res: ResultWithDiagnostics, scriptData: JadxScriptData) { + val log = scriptData.log + for (r in res.reports) { + val msg = r.render(withSeverity = false) + when (r.severity) { + Severity.FATAL, Severity.ERROR -> log.error(r.exception) { "Script execution error: $msg" } + Severity.WARNING -> log.warn { "Script execution issue: $msg" } + Severity.INFO -> log.info { "Script report: $msg" } + Severity.DEBUG -> log.debug { "Script debug: $msg" } + } + } + when (res) { + is ResultWithDiagnostics.Success -> { + when (val retVal = res.value.returnValue) { + is ResultValue.Error -> log.error(retVal.error) { "Script execution error:" } + is ResultValue.Value -> log.info { "Script execution result: $retVal" } + is ResultValue.Unit -> {} + ResultValue.NotEvaluated -> {} + } + } + + is ResultWithDiagnostics.Failure -> { + scriptData.error = true + log.error { "Script execution failed: ${scriptData.scriptName}" } + } + } + } + + fun buildHostConf(context: JadxPluginContext?) = ScriptingHostConfiguration { + jvm { + getScriptingClass(JvmGetScriptingClass()) + baseClassLoader.put(JadxScriptTemplate::class.java.classLoader) + context?.let { + compilationCache(ScriptCache().build(context)) + } + } + } + + fun buildCompileConf(scriptingHostConf: ScriptingHostConfiguration) = ScriptCompilationConfiguration { + hostConfiguration.put(scriptingHostConf) + + displayName.put("Jadx script") + defaultIdentifier.put("JadxScript") + + fileExtension.put("jadx.kts") + filePathPattern.put(".*\\.jadx\\.kts") + + val receiversTypes = listOf(KotlinType(JadxScriptTemplate::class)) + implicitReceivers(receiversTypes) + skipExtensionsResolutionForImplicitsExceptInnermost(receiversTypes) + + jvm { + dependenciesFromCurrentContext( + wholeClasspath = true, + ) + } + + addBaseClass() + defaultImports(DependsOn::class, Repository::class) + + refineConfiguration { + onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations) + } + + ide { + acceptedLocations(ScriptAcceptedLocation.Everywhere) + } + + isStandalone(true) + + // forcing compiler to not use modules while building script classpath + // because shadow jar remove all modules-info.class (https://github.com/GradleUp/shadow/issues/710) + compilerOptions.append("-Xjdk-release=1.8") + } + + inline fun ScriptCompilationConfiguration.Builder.addBaseClass() { + val kClass = T::class + defaultImports.append(kClass.java.name) + hostConfiguration.update { + it.with { + this[jvm.baseClassLoader] = kClass.java.classLoader + } + } + } + + fun buildEvalConf(scriptData: JadxScriptData, compileConf: ScriptCompilationConfiguration): ScriptEvaluationConfiguration { + return ScriptEvaluationConfiguration { + hostConfiguration.put(compileConf[hostConfiguration]!!) + compilationConfiguration.put(compileConf) + constructorArgs(JadxScriptTemplate(scriptData)) + } + } + + private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver()) + + fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics { + val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations) + ?.takeIf { it.isNotEmpty() } + ?: return context.compilationConfiguration.asSuccess() + return runBlocking { + resolver.resolveFromScriptSourceAnnotations(annotations) + }.onSuccess { files: List -> + log.debug { "add script dependency: $files" } + context.compilationConfiguration.with { + updateClasspath(files) + }.asSuccess() + } + } +} diff --git a/jadx-plugins/jadx-script/jadx-script-ide/src/main/kotlin/ScriptServices.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptServices.kt similarity index 57% rename from jadx-plugins/jadx-script/jadx-script-ide/src/main/kotlin/ScriptServices.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptServices.kt index aef3ef8ae..c70ce5f82 100644 --- a/jadx-plugins/jadx-script/jadx-script-ide/src/main/kotlin/ScriptServices.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/ScriptServices.kt @@ -1,27 +1,27 @@ -package jadx.plugins.script.ide +package jadx.plugins.script.kotlin -import jadx.plugins.script.ScriptEval +import jadx.api.plugins.JadxPluginContext import kotlinx.coroutines.runBlocking +import org.jetbrains.kotlin.scripting.compiler.plugin.services.FirReplHistoryProviderImpl +import org.jetbrains.kotlin.scripting.compiler.plugin.services.firReplHistoryProvider +import org.jetbrains.kotlin.scripting.compiler.plugin.services.isReplSnippetSource import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices import kotlin.script.experimental.api.ReplAnalyzerResult import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.ScriptDiagnostic -import kotlin.script.experimental.api.SourceCode import kotlin.script.experimental.api.SourceCodeCompletionVariant import kotlin.script.experimental.api.analysisDiagnostics -import kotlin.script.experimental.api.hostConfiguration import kotlin.script.experimental.api.renderedResultType +import kotlin.script.experimental.api.repl import kotlin.script.experimental.api.valueOrNull import kotlin.script.experimental.host.toScriptSource -import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration +import kotlin.script.experimental.host.with import kotlin.script.experimental.jvm.util.isError import kotlin.script.experimental.jvm.util.toSourceCodePosition -const val AUTO_COMPLETE_INSERT_STR = "ABCDEF" // defined at KJvmReplCompleter.INSERTED_STRING - data class ScriptCompletionResult( val completions: List, - val reports: List, + val reports: MutableList, ) data class ScriptAnalyzeResult( @@ -30,12 +30,28 @@ data class ScriptAnalyzeResult( val renderType: String?, ) -class ScriptServices { - private val compileConf = ScriptEval().buildCompileConf() - private val replCompiler = KJvmReplCompilerWithIdeServices( - compileConf[ScriptCompilationConfiguration.hostConfiguration] - ?: defaultJvmScriptingHostConfiguration, - ) +class ScriptServices(pluginContext: JadxPluginContext? = null) { + companion object { + const val AUTO_COMPLETE_INSERT_STR = "ABCDEF" // defined at KJvmReplCompleter.INSERTED_STRING + } + + private val compileConf: ScriptCompilationConfiguration + private val replCompiler: KJvmReplCompilerWithIdeServices + + init { + val scriptEval = ScriptEval() + val hostConf = scriptEval.buildHostConf(pluginContext) + hostConf.with { + repl { + firReplHistoryProvider(FirReplHistoryProviderImpl()) + isReplSnippetSource { sourceFile, _ -> + sourceFile?.name?.endsWith(".jadx.kts", ignoreCase = true) ?: false + } + } + } + compileConf = scriptEval.buildCompileConf(hostConf) + replCompiler = KJvmReplCompilerWithIdeServices(hostConf) + } fun complete(scriptName: String, code: String, cursor: Int): ScriptCompletionResult { val snippet = code.toScriptSource(scriptName) @@ -44,15 +60,14 @@ class ScriptServices { } return ScriptCompletionResult( completions = result.valueOrNull()?.toList() ?: emptyList(), - reports = result.reports, + reports = result.reports.toMutableList(), ) } fun analyze(scriptName: String, code: String): ScriptAnalyzeResult { val sourceCode = code.toScriptSource(scriptName) val result = runBlocking { - val cursor = SourceCode.Position(0, 0) // not used - replCompiler.analyze(sourceCode, cursor, compileConf) + replCompiler.analyze(sourceCode, 0.toSourceCodePosition(sourceCode), compileConf) } val analyzerResult = result.valueOrNull() val issues = mutableListOf() diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JInputScript.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JInputScript.kt new file mode 100644 index 000000000..3d2785e98 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JInputScript.kt @@ -0,0 +1,92 @@ +package jadx.plugins.script.kotlin.gui + +import io.github.oshai.kotlinlogging.KotlinLogging +import jadx.api.ICodeInfo +import jadx.api.impl.SimpleCodeInfo +import jadx.api.plugins.JadxPluginContext +import jadx.core.utils.exceptions.JadxRuntimeException +import jadx.core.utils.files.FileUtils +import jadx.gui.treemodel.JClass +import jadx.gui.treemodel.JEditableNode +import jadx.gui.ui.MainWindow +import jadx.gui.ui.panel.ContentPanel +import jadx.gui.ui.tab.TabbedPane +import jadx.gui.utils.NLS +import jadx.gui.utils.UiUtils +import jadx.gui.utils.ui.SimpleMenuItem +import org.fife.ui.rsyntaxtextarea.SyntaxConstants +import java.nio.file.Path +import javax.swing.Icon +import javax.swing.ImageIcon +import javax.swing.JPopupMenu + +private val log = KotlinLogging.logger {} + +class JInputScript( + val pluginContext: JadxPluginContext, + private val scriptPath: Path, +) : JEditableNode() { + companion object { + private val SCRIPT_ICON: ImageIcon = UiUtils.openSvgIcon("nodes/kotlin_script") + } + + private val name: String = scriptPath.fileName.toString().replace(".jadx.kts", "") + + override fun hasContent(): Boolean { + return true + } + + override fun getContentPanel(tabbedPane: TabbedPane): ContentPanel { + return ScriptContentPanel(pluginContext, tabbedPane, this) + } + + override fun getCodeInfo(): ICodeInfo { + try { + return SimpleCodeInfo(FileUtils.readFile(scriptPath)) + } catch (e: Exception) { + throw JadxRuntimeException("Failed to read script file: " + scriptPath.toAbsolutePath(), e) + } + } + + override fun save(newContent: String?) { + try { + FileUtils.writeFile(scriptPath, newContent) + log.debug { "Script saved: ${scriptPath.toAbsolutePath()}" } + } catch (e: Exception) { + throw JadxRuntimeException("Failed to write script file: " + scriptPath.toAbsolutePath(), e) + } + } + + override fun onTreePopupMenu(mainWindow: MainWindow): JPopupMenu { + val menu = JPopupMenu() + menu.add(SimpleMenuItem(NLS.str("popup.add_scripts")) { mainWindow.addFiles() }) + menu.add(SimpleMenuItem(NLS.str("popup.new_script")) { mainWindow.addNewScript() }) + menu.add(SimpleMenuItem(NLS.str("popup.remove")) { mainWindow.removeInput(scriptPath) }) + menu.add(SimpleMenuItem(NLS.str("popup.rename")) { mainWindow.renameInput(scriptPath) }) + return menu + } + + override fun getSyntaxName(): String { + return SyntaxConstants.SYNTAX_STYLE_KOTLIN + } + + override fun getJParent(): JClass? { + return null + } + + override fun getIcon(): Icon { + return SCRIPT_ICON + } + + override fun getName(): String { + return name + } + + override fun makeString(): String { + return name + } + + override fun getTooltip(): String { + return scriptPath.normalize().toAbsolutePath().toString() + } +} diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JInputScripts.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JInputScripts.kt new file mode 100644 index 000000000..9b4ce2b8a --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JInputScripts.kt @@ -0,0 +1,51 @@ +package jadx.plugins.script.kotlin.gui + +import jadx.api.plugins.JadxPluginContext +import jadx.gui.treemodel.JClass +import jadx.gui.treemodel.JNode +import jadx.gui.ui.MainWindow +import jadx.gui.utils.NLS +import jadx.gui.utils.UiUtils +import jadx.gui.utils.ui.SimpleMenuItem +import java.nio.file.Path +import javax.swing.Icon +import javax.swing.ImageIcon +import javax.swing.JPopupMenu + +class JInputScripts( + pluginContext: JadxPluginContext, + scripts: List, +) : JNode() { + companion object { + private val INPUT_SCRIPTS_ICON: ImageIcon = UiUtils.openSvgIcon("nodes/scriptsModel") + } + + init { + for (script in scripts) { + add(JInputScript(pluginContext, script)) + } + } + + override fun onTreePopupMenu(mainWindow: MainWindow): JPopupMenu { + val menu = JPopupMenu() + menu.add(SimpleMenuItem(NLS.str("popup.add_scripts")) { mainWindow.addFiles() }) + menu.add(SimpleMenuItem(NLS.str("popup.new_script")) { mainWindow.addNewScript() }) + return menu + } + + override fun getJParent(): JClass? { + return null + } + + override fun getIcon(): Icon { + return INPUT_SCRIPTS_ICON + } + + override fun getID(): String { + return "JInputScripts" + } + + override fun makeString(): String { + return NLS.str("tree.input_scripts") + } +} diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JadxScriptInputCategory.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JadxScriptInputCategory.kt new file mode 100644 index 000000000..cd98aa12d --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JadxScriptInputCategory.kt @@ -0,0 +1,44 @@ +package jadx.plugins.script.kotlin.gui + +import jadx.api.plugins.JadxPluginContext +import jadx.api.plugins.gui.JadxGuiContext +import jadx.gui.plugins.context.GuiPluginContext +import jadx.gui.plugins.context.ITreeInputCategory +import jadx.gui.settings.data.ITabStatePersist +import jadx.gui.treemodel.JNode +import java.nio.file.Path + +object JadxScriptInputCategory { + fun register(pluginContext: JadxPluginContext, guiContext: JadxGuiContext) { + val internalContext = guiContext as GuiPluginContext + val inputCategory = InputScriptsBuilder(pluginContext) + internalContext.registerTreeInputCategory(inputCategory) + internalContext.registerTabStatePersistAdapter(InputScriptTabStatePersist(inputCategory)) + } +} + +class InputScriptsBuilder(private val pluginContext: JadxPluginContext) : ITreeInputCategory { + var scriptsRootNode: JInputScripts? = null + + override fun filesFilter(file: Path): Boolean { + return file.fileName.toString().endsWith(".jadx.kts", ignoreCase = true) + } + + override fun buildInputNode(files: List): JNode { + val scriptsNode = JInputScripts(pluginContext, files) + scriptsRootNode = scriptsNode + return scriptsNode + } +} + +class InputScriptTabStatePersist(private val scriptsBuilder: InputScriptsBuilder) : ITabStatePersist { + override fun getNodeClass() = JInputScript::class.java + + override fun save(node: JNode): String { + return node.name + } + + override fun load(nodeName: String): JNode? { + return scriptsBuilder.scriptsRootNode?.searchNode { it.name.equals(nodeName) } + } +} diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JadxScriptOptionsUI.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JadxScriptOptionsUI.kt new file mode 100644 index 000000000..604756eb8 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/JadxScriptOptionsUI.kt @@ -0,0 +1,30 @@ +package jadx.plugins.script.kotlin.gui + +import jadx.api.plugins.gui.ISettingsGroup +import jadx.api.plugins.gui.JadxGuiContext +import jadx.plugins.script.kotlin.runtime.data.JadxScriptAllOptions +import javax.swing.JPanel + +object JadxScriptOptionsUI { + fun setup(guiContext: JadxGuiContext, scriptOptions: JadxScriptAllOptions) { + guiContext.settings().setCustomSettingsGroup(ScriptOptionsRootGroup(guiContext, scriptOptions)) + } +} + +private class ScriptOptionsRootGroup( + private val guiContext: JadxGuiContext, + private val scriptOptions: JadxScriptAllOptions, +) : ISettingsGroup { + + override fun getTitle() = "Scripts" + + override fun buildComponent() = JPanel() // empty panel for root node + + override fun getSubGroups(): List { + val settings = guiContext.settings() + return scriptOptions.descriptions + .groupBy { it.script } + .map { (script, options) -> settings.buildSettingsGroupForOptions(script, options) } + .toList() + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/KtLintUtils.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/KtLintUtils.kt similarity index 85% rename from jadx-gui/src/main/java/jadx/gui/plugins/script/KtLintUtils.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/KtLintUtils.kt index 5aa80d1ca..b526ab38b 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/KtLintUtils.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/KtLintUtils.kt @@ -1,4 +1,4 @@ -package jadx.gui.plugins.script +package jadx.plugins.script.kotlin.gui import com.pinterest.ktlint.rule.engine.api.Code import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride @@ -9,13 +9,15 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPERTY import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import org.ec4j.core.model.PropertyType -import org.slf4j.Logger -import org.slf4j.LoggerFactory + +data class JadxLintError( + val line: Int, + val col: Int, + val ruleId: String, + val detail: String, +) object KtLintUtils { - - val LOG: Logger = LoggerFactory.getLogger(KtLintUtils::class.java) - private val ktLint by lazy { KtLintRuleEngine( ruleProviders = StandardRuleSetProvider().getRuleProviders(), @@ -36,18 +38,18 @@ object KtLintUtils { } fun lint(content: String): List { - val code = Code.fromSnippet(content, script = true) val errors = mutableListOf() + val code = Code.fromSnippet(content, script = true) ktLint.lint(code) { lintError -> - errors.add(JadxLintError(lintError.line, lintError.col, lintError.ruleId.value, lintError.detail)) + errors.add( + JadxLintError( + line = lintError.line, + col = lintError.col, + ruleId = lintError.ruleId.value, + detail = lintError.detail, + ), + ) } return errors } } - -data class JadxLintError( - val line: Int, - val col: Int, - val ruleId: String, - val detail: String, -) diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCodeArea.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCodeArea.kt new file mode 100644 index 000000000..f657c2f7c --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCodeArea.kt @@ -0,0 +1,76 @@ +package jadx.plugins.script.kotlin.gui + +import jadx.api.ICodeInfo +import jadx.gui.jobs.IBackgroundTask +import jadx.gui.jobs.LoadTask +import jadx.gui.settings.JadxSettings +import jadx.gui.ui.action.JadxAutoCompletion +import jadx.gui.ui.codearea.AbstractCodeArea +import jadx.gui.ui.panel.ContentPanel +import jadx.gui.utils.shortcut.ShortcutsController +import org.fife.ui.autocomplete.AutoCompletion + +class ScriptCodeArea(contentPanel: ContentPanel, val scriptNode: JInputScript) : + AbstractCodeArea(contentPanel, scriptNode) { + private val autoCompletion: AutoCompletion + private val shortcutsController: ShortcutsController + + init { + setSyntaxEditingStyle(scriptNode.syntaxName) + isCodeFoldingEnabled = true + closeCurlyBraces = true + + shortcutsController = contentPanel.mainWindow.shortcutsController + val settings = contentPanel.mainWindow.settings + autoCompletion = addAutoComplete(settings) + } + + private fun addAutoComplete(settings: JadxSettings): AutoCompletion { + val provider = ScriptCompleteProvider(this, scriptNode.pluginContext) + provider.setAutoActivationRules(false, ".") + val ac = JadxAutoCompletion(provider) + ac.setListCellRenderer(ScriptCompletionRenderer(settings)) + ac.isAutoActivationEnabled = true + ac.autoCompleteSingleChoices = true + ac.install(this) + shortcutsController.bindImmediate(ac) + return ac + } + + override fun getCodeInfo(): ICodeInfo { + return node.codeInfo + } + + override fun getLoadTask(): IBackgroundTask { + return LoadTask( + { node.codeInfo.getCodeStr() }, + { code -> + text = code + setCaretPosition(0) + setLoaded() + }, + ) + } + + override fun refresh() { + text = node.codeInfo.getCodeStr() + } + + fun updateCode(newCode: String?) { + val caretPos = caretPosition + text = newCode + setCaretPosition(caretPos) + scriptNode.isChanged = true + } + + fun save() { + scriptNode.save(getText()) + scriptNode.isChanged = false + } + + override fun dispose() { + shortcutsController.unbindActionsForComponent(this) + autoCompletion.uninstall() + super.dispose() + } +} diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompleteProvider.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompleteProvider.kt new file mode 100644 index 000000000..8e885d889 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompleteProvider.kt @@ -0,0 +1,137 @@ +package jadx.plugins.script.kotlin.gui + +import io.github.oshai.kotlinlogging.KotlinLogging +import jadx.api.plugins.JadxPluginContext +import jadx.core.utils.exceptions.JadxRuntimeException +import jadx.gui.ui.codearea.AbstractCodeArea +import jadx.gui.utils.Icons +import jadx.plugins.script.kotlin.ScriptCompletionResult +import jadx.plugins.script.kotlin.ScriptServices +import jadx.plugins.script.kotlin.ScriptServices.Companion.AUTO_COMPLETE_INSERT_STR +import org.fife.ui.autocomplete.Completion +import org.fife.ui.autocomplete.CompletionProviderBase +import org.fife.ui.autocomplete.ParameterizedCompletion +import java.awt.Point +import javax.swing.Icon +import javax.swing.text.BadLocationException +import javax.swing.text.JTextComponent +import kotlin.script.experimental.api.ScriptDiagnostic +import kotlin.script.experimental.api.SourceCodeCompletionVariant + +private val log = KotlinLogging.logger {} + +private val ICONS_MAP = mapOf( + "class" to Icons.CLASS, + "method" to Icons.METHOD, + "field" to Icons.FIELD, + "property" to Icons.PROPERTY, + "parameter" to Icons.PARAMETER, + "package" to Icons.PACKAGE, +) + +class ScriptCompleteProvider( + private val codeArea: AbstractCodeArea, + private val pluginContext: JadxPluginContext, +) : CompletionProviderBase() { + + private val completions: List + get() { + try { + val code = codeArea.getText() + val caretPos = codeArea.caretPosition + val scriptServices = ScriptServices(pluginContext) + val scriptName = codeArea.getNode().getName() + val result = scriptServices.complete(scriptName, code, caretPos) + if (result.completions.isEmpty()) { + return listOf() + } + val replacePos = getReplacePos(caretPos, result) + if (!result.reports.isEmpty()) { + log.debug { "Script completion reports: ${result.reports}" } + } + log.debug { "Completions:\n${result.completions.joinToString(separator = "\n")}" } + return convertCompletions(result.completions, code, replacePos) + } catch (e: Exception) { + log.error(e) { "Code completion failed" } + return listOf() + } + } + + private fun convertCompletions( + completions: List, + code: String, + replacePos: Int, + ): List { + val count = completions.size + val list = ArrayList(count) + for (i in 0.. + if (report.severity == ScriptDiagnostic.Severity.ERROR) { + report.location?.let { location -> + location.start.line == line && report.message.endsWith(AUTO_COMPLETE_INSERT_STR) + } ?: false + } else { + false + } + } + if (completeReport == null) { + log.warn { "Failed to find completion report in: ${result.reports}" } + return caretPos + } + result.reports.remove(completeReport) + val col = caretPos - lineStart + 1 + return caretPos - (col - completeReport.location!!.start.col) + } + + override fun getAlreadyEnteredText(comp: JTextComponent?): String? { + try { + val pos = codeArea.caretPosition + return codeArea.getText(0, pos) + } catch (e: Exception) { + throw JadxRuntimeException("Failed to get text before caret", e) + } + } + + override fun getCompletionsAt(comp: JTextComponent, p: Point): List { + return this.completions + } + + override fun getCompletionsImpl(comp: JTextComponent): List { + return this.completions + } + + override fun getParameterizedCompletions(tc: JTextComponent): List? { + return null + } +} diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompletionData.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompletionData.kt new file mode 100644 index 000000000..2e4398763 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompletionData.kt @@ -0,0 +1,58 @@ +package jadx.plugins.script.kotlin.gui + +import org.fife.ui.autocomplete.Completion +import org.fife.ui.autocomplete.CompletionProvider +import javax.swing.Icon +import javax.swing.text.JTextComponent + +class ScriptCompletionData( + private val provider: CompletionProvider, + private val relevance: Int, + private val input: String, + private val code: String, + private val replacePos: Int, + private val icon: Icon, + private val toolTip: String, + private val summary: String, +) : Completion { + + override fun getInputText(): String { + return input + } + + override fun getProvider(): CompletionProvider { + return provider + } + + override fun getAlreadyEntered(comp: JTextComponent?): String? { + return provider.getAlreadyEnteredText(comp) + } + + override fun getRelevance(): Int { + return relevance + } + + override fun getReplacementText(): String { + return code.substring(0, replacePos) + input + } + + override fun getIcon(): Icon { + return icon + } + + override fun getSummary(): String { + return summary + } + + override fun getToolTipText(): String { + return toolTip + } + + override fun compareTo(other: Completion): Int { + return relevance.compareTo(other.relevance) + } + + override fun toString(): String { + return input + } +} diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompletionRenderer.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompletionRenderer.kt new file mode 100644 index 000000000..f09e5a171 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptCompletionRenderer.kt @@ -0,0 +1,26 @@ +package jadx.plugins.script.kotlin.gui + +import jadx.gui.settings.JadxSettings +import jadx.gui.utils.UiUtils +import org.fife.ui.autocomplete.Completion +import org.fife.ui.autocomplete.CompletionCellRenderer +import javax.swing.JList + +class ScriptCompletionRenderer(settings: JadxSettings) : CompletionCellRenderer() { + init { + displayFont = settings.codeFont + } + + override fun prepareForOtherCompletion( + list: JList<*>?, + c: Completion?, + index: Int, + selected: Boolean, + hasFocus: Boolean, + ) { + val cmpl = c as ScriptCompletionData + setText( + UiUtils.wrapHtml((UiUtils.escapeHtml(cmpl.inputText) + " " + UiUtils.fadeHtml(UiUtils.escapeHtml(cmpl.summary)))), + ) + } +} diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptContentPanel.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptContentPanel.kt new file mode 100644 index 000000000..a65c8cbb4 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptContentPanel.kt @@ -0,0 +1,251 @@ +package jadx.plugins.script.kotlin.gui + +import jadx.api.plugins.JadxPluginContext +import jadx.gui.logs.LogOptions +import jadx.gui.settings.LineNumbersMode +import jadx.gui.ui.action.ActionModel +import jadx.gui.ui.action.JadxGuiAction +import jadx.gui.ui.codearea.AbstractCodeArea +import jadx.gui.ui.codearea.AbstractCodeContentPanel +import jadx.gui.ui.codearea.SearchBar +import jadx.gui.ui.tab.TabbedPane +import jadx.gui.utils.Icons +import jadx.gui.utils.NLS +import jadx.gui.utils.UiUtils +import jadx.gui.utils.ui.NodeLabel +import jadx.plugins.script.kotlin.ScriptServices +import jadx.plugins.script.kotlin.runtime.JadxScriptData.Companion.JADX_SCRIPT_LOG_PREFIX +import org.fife.ui.rsyntaxtextarea.ErrorStrip +import org.fife.ui.rtextarea.RTextScrollPane +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.awt.BorderLayout +import java.awt.Component +import java.awt.Dimension +import java.awt.event.KeyEvent +import javax.swing.Box +import javax.swing.BoxLayout +import javax.swing.JButton +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.KeyStroke +import javax.swing.border.EmptyBorder +import kotlin.script.experimental.api.ScriptDiagnostic + +class ScriptContentPanel( + private val pluginContext: JadxPluginContext, + panel: TabbedPane, + scriptNode: JInputScript, +) : AbstractCodeContentPanel(panel, scriptNode) { + private val scriptArea: ScriptCodeArea = ScriptCodeArea(this, scriptNode) + private val searchBar: SearchBar + private val codeScrollPane: RTextScrollPane + private val actionPanel: JPanel + private val resultLabel: JLabel = NodeLabel("") + private val errorService: ScriptErrorService = ScriptErrorService(scriptArea) + private val scriptLog: Logger = LoggerFactory.getLogger(JADX_SCRIPT_LOG_PREFIX + scriptNode.name) + + init { + actionPanel = buildScriptActionsPanel() + searchBar = SearchBar(scriptArea) + codeScrollPane = RTextScrollPane(scriptArea) + + initUI() + applySettings() + scriptArea.load() + } + + private fun initUI() { + val topPanel = JPanel(BorderLayout()) + topPanel.setBorder(EmptyBorder(5, 5, 5, 5)) + topPanel.add(actionPanel, BorderLayout.NORTH) + topPanel.add(searchBar, BorderLayout.SOUTH) + + val codePanel = JPanel(BorderLayout()) + codePanel.setBorder(EmptyBorder(0, 0, 0, 0)) + codePanel.add(codeScrollPane) + codePanel.add(ErrorStrip(scriptArea), BorderLayout.LINE_END) + + setLayout(BorderLayout()) + setBorder(EmptyBorder(0, 0, 0, 0)) + add(topPanel, BorderLayout.NORTH) + add(codeScrollPane, BorderLayout.CENTER) + + val key = KeyStroke.getKeyStroke(KeyEvent.VK_F, UiUtils.ctrlButton()) + UiUtils.addKeyBinding(scriptArea, key, "SearchAction") { searchBar.toggle() } + } + + private fun buildScriptActionsPanel(): JPanel { + val runAction = JadxGuiAction(ActionModel.SCRIPT_RUN, Runnable { this.runScript() }) + val saveAction = JadxGuiAction(ActionModel.SCRIPT_SAVE, Runnable { scriptArea.save() }) + + runAction.shortcutComponent = scriptArea + saveAction.shortcutComponent = scriptArea + + tabbedPane.mainWindow.shortcutsController.bindImmediate(runAction) + tabbedPane.mainWindow.shortcutsController.bindImmediate(saveAction) + + val save = saveAction.makeButton() + scriptArea.scriptNode.addChangeListener { save.setEnabled(it) } + + val check = JButton(NLS.str("script.check"), Icons.CHECK) + check.addActionListener { checkScript() } + val format = JButton(NLS.str("script.format"), Icons.FORMAT) + format.addActionListener { reformatCode() } + val scriptLog = JButton(NLS.str("script.log"), Icons.FORMAT) + scriptLog.addActionListener { showScriptLog() } + + val panel = JPanel() + panel.setLayout(BoxLayout(panel, BoxLayout.LINE_AXIS)) + panel.setBorder(EmptyBorder(0, 0, 0, 0)) + panel.add(runAction.makeButton()) + panel.add(Box.createRigidArea(Dimension(10, 0))) + panel.add(save) + panel.add(Box.createRigidArea(Dimension(10, 0))) + panel.add(check) + panel.add(Box.createRigidArea(Dimension(10, 0))) + panel.add(format) + panel.add(Box.createRigidArea(Dimension(30, 0))) + panel.add(resultLabel) + panel.add(Box.createHorizontalGlue()) + panel.add(scriptLog) + return panel + } + + private fun runScript() { + scriptArea.save() + if (!checkScript(runScript = true)) { + return + } + resetResultLabel() + + val tabbedPane = getTabbedPane() + val mainWindow = tabbedPane.mainWindow + mainWindow.backgroundExecutor.execute(NLS.str("script.run"), { + try { + mainWindow.wrapper.reloadPasses() + } catch (e: Exception) { + scriptLog.error("Passes reload failed", e) + } + }, { + mainWindow.passesReloaded() + }) + } + + private fun checkScript(runScript: Boolean = false): Boolean { + try { + resetResultLabel() + val code = scriptArea.getText() + + if (code.contains("@file:DependsOn")) { + if (!runScript) { + resultLabel.setText("Checks disabled for scripts with external dependencies") + } + return true + } + + val fileName = scriptArea.getNode().getName() + val scriptServices = ScriptServices(pluginContext) + val result = scriptServices.analyze(fileName, code) + var success = result.success + val issues: List = result.issues + for (issue in issues) { + val severity = issue.severity + if (severity == ScriptDiagnostic.Severity.ERROR || severity == ScriptDiagnostic.Severity.FATAL) { + scriptLog.error( + issue.render( + withSeverity = false, + withLocation = true, + withException = true, + withStackTrace = true, + ), + ) + success = false + } else if (severity == ScriptDiagnostic.Severity.WARNING) { + scriptLog.warn("Compile issue: {}", issue) + } + } + val lintErrs: List = when { + success -> getLintIssues(code) + else -> listOf() + } + + errorService.clearErrors() + errorService.addCompilerIssues(issues) + errorService.addLintErrors(lintErrs) + if (!success) { + resultLabel.setText("Compile issues: " + issues.size) + showScriptLog() + } else if (!lintErrs.isEmpty()) { + resultLabel.setText("Lint issues: " + lintErrs.size) + } else { + resultLabel.setText("OK") + } + errorService.apply() + return success + } catch (e: Throwable) { + scriptLog.error("Failed to check code", e) + return true + } + } + + private fun getLintIssues(code: String): List { + try { + val lintErrs = KtLintUtils.lint(code) + for (error in lintErrs) { + scriptLog.warn("Lint issue: {} ({}:{})(ruleId={})", error.detail, error.line, error.col, error.ruleId) + } + return lintErrs + } catch (e: Throwable) { // can throw initialization error + scriptLog.warn("KtLint failed", e) + return listOf() + } + } + + private fun reformatCode() { + resetResultLabel() + try { + val code = scriptArea.getText() + val formattedCode = KtLintUtils.format(code) + if (code != formattedCode) { + scriptArea.updateCode(formattedCode) + resultLabel.setText("Code updated") + errorService.clearErrors() + } + } catch (e: Throwable) { // can throw initialization error + scriptLog.error("Failed to reformat code", e) + } + } + + private fun resetResultLabel() { + resultLabel.setText("") + } + + private fun applySettings() { + val settings = getSettings() + codeScrollPane.setLineNumbersEnabled(settings.lineNumbersMode != LineNumbersMode.DISABLE) + codeScrollPane.gutter.setLineNumberFont(settings.codeFont) + scriptArea.loadSettings() + } + + private fun showScriptLog() { + mainWindow.showLogViewer(LogOptions.forScript(getNode().getName())) + } + + override fun getCodeArea(): AbstractCodeArea { + return scriptArea + } + + override fun getChildrenComponent(): Component { + return codeArea + } + + override fun loadSettings() { + applySettings() + updateUI() + } + + override fun dispose() { + scriptArea.dispose() + } +} diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptErrorService.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptErrorService.kt new file mode 100644 index 000000000..d702113d4 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/gui/ScriptErrorService.kt @@ -0,0 +1,108 @@ +package jadx.plugins.script.kotlin.gui + +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument +import org.fife.ui.rsyntaxtextarea.parser.AbstractParser +import org.fife.ui.rsyntaxtextarea.parser.DefaultParseResult +import org.fife.ui.rsyntaxtextarea.parser.DefaultParserNotice +import org.fife.ui.rsyntaxtextarea.parser.ParseResult +import org.fife.ui.rsyntaxtextarea.parser.ParserNotice +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import kotlin.script.experimental.api.ScriptDiagnostic + +class ScriptErrorService(private val scriptArea: ScriptCodeArea) : AbstractParser() { + private val result: DefaultParseResult = DefaultParseResult(this) + + override fun parse(doc: RSyntaxDocument?, style: String?): ParseResult { + return result + } + + fun clearErrors() { + result.clearNotices() + scriptArea.removeParser(this) + } + + fun apply() { + scriptArea.removeParser(this) + scriptArea.addParser(this) + scriptArea.addNotify() + scriptArea.requestFocus() + jumpCaretToFirstError() + } + + private fun jumpCaretToFirstError() { + val parserNotices = result.notices + if (parserNotices.isEmpty()) { + return + } + val notice = parserNotices.get(0) + var offset = notice.offset + if (offset == -1) { + try { + offset = scriptArea.getLineStartOffset(notice.line) + } catch (e: Exception) { + LOG.error("Failed to jump to first error", e) + return + } + } + scriptArea.scrollToPos(offset) + } + + fun addCompilerIssues(issues: List) { + for (issue in issues) { + if (issue.severity == ScriptDiagnostic.Severity.DEBUG) { + continue + } + val notice: DefaultParserNotice? + val loc = issue.location + if (loc == null) { + notice = DefaultParserNotice(this, issue.message, 0) + } else { + try { + val line = loc.start.line + val offset = scriptArea.getLineStartOffset(line - 1) + loc.start.col + val len = if (loc.end == null) -1 else loc.end!!.col - loc.start.col + notice = DefaultParserNotice(this, issue.message, line, offset - 1, len) + notice.setLevel(convertLevel(issue.severity)) + } catch (e: Exception) { + LOG.error("Failed to convert script issue", e) + continue + } + } + addNotice(notice) + } + } + + fun addLintErrors(errors: List) { + for (error in errors) { + try { + val line = error.line + val offset = scriptArea.getLineStartOffset(line - 1) + error.col - 1 + val word = scriptArea.getWordByPosition(offset) + val len = word?.length ?: -1 + val notice = DefaultParserNotice(this, error.detail, line, offset, len) + notice.setLevel(ParserNotice.Level.WARNING) + addNotice(notice) + } catch (e: Exception) { + LOG.error("Failed to convert lint error", e) + } + } + } + + private fun addNotice(notice: DefaultParserNotice) { + LOG.debug("Add notice: {}:{}:{} - {}", notice.line, notice.offset, notice.length, notice.message) + result.addNotice(notice) + } + + companion object { + private val LOG: Logger = LoggerFactory.getLogger(ScriptErrorService::class.java) + + private fun convertLevel(severity: ScriptDiagnostic.Severity): ParserNotice.Level { + return when (severity) { + ScriptDiagnostic.Severity.FATAL, ScriptDiagnostic.Severity.ERROR -> ParserNotice.Level.ERROR + ScriptDiagnostic.Severity.WARNING -> ParserNotice.Level.WARNING + ScriptDiagnostic.Severity.INFO, ScriptDiagnostic.Severity.DEBUG -> ParserNotice.Level.INFO + } + } + } +} diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/passes/JadxScriptAfterLoadPass.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/passes/JadxScriptAfterLoadPass.kt similarity index 87% rename from jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/passes/JadxScriptAfterLoadPass.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/passes/JadxScriptAfterLoadPass.kt index 582b8f66e..d40fa47e5 100644 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/passes/JadxScriptAfterLoadPass.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/passes/JadxScriptAfterLoadPass.kt @@ -1,9 +1,9 @@ -package jadx.plugins.script.passes +package jadx.plugins.script.kotlin.passes import jadx.api.JadxDecompiler import jadx.api.plugins.pass.impl.SimpleJadxPassInfo import jadx.api.plugins.pass.types.JadxAfterLoadPass -import jadx.plugins.script.runtime.JadxScriptData +import jadx.plugins.script.kotlin.runtime.JadxScriptData class JadxScriptAfterLoadPass(private val scripts: List) : JadxAfterLoadPass { diff --git a/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/JadxScriptTemplate.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/JadxScriptTemplate.kt new file mode 100644 index 000000000..30c543b01 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/JadxScriptTemplate.kt @@ -0,0 +1,29 @@ +package jadx.plugins.script.kotlin.runtime + +import jadx.plugins.script.kotlin.DefCompileConf +import kotlin.script.experimental.annotations.KotlinScript + +@KotlinScript( + displayName = "Jadx Script", + fileExtension = "jadx.kts", + filePathPattern = ".*\\.jadx\\.kts", + compilationConfiguration = DefCompileConf::class, +) +open class JadxScriptTemplate( + scriptData: JadxScriptData, +) { + val scriptName = scriptData.scriptName + val log = scriptData.log + + private val scriptInstance = JadxScriptInstance(scriptData, log) + + fun getJadxInstance() = scriptInstance + + fun println(message: Any?) { + log.info { message } + } + + fun print(message: Any?) { + log.info { message } + } +} diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/ScriptRuntime.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/ScriptRuntime.kt similarity index 73% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/ScriptRuntime.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/ScriptRuntime.kt index e6c32d60b..680f8ec79 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/ScriptRuntime.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/ScriptRuntime.kt @@ -1,7 +1,7 @@ @file:JvmName("ScriptRuntime") @file:Suppress("unused", "MemberVisibilityCanBePrivate") -package jadx.plugins.script.runtime +package jadx.plugins.script.kotlin.runtime import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KotlinLogging @@ -11,29 +11,30 @@ import jadx.api.JavaClass import jadx.api.plugins.JadxPluginContext import jadx.api.plugins.events.IJadxEvents import jadx.api.plugins.pass.JadxPass -import jadx.plugins.script.runtime.data.Debug -import jadx.plugins.script.runtime.data.Decompile -import jadx.plugins.script.runtime.data.Gui -import jadx.plugins.script.runtime.data.JadxScriptAllOptions -import jadx.plugins.script.runtime.data.JadxScriptOptions -import jadx.plugins.script.runtime.data.Rename -import jadx.plugins.script.runtime.data.Replace -import jadx.plugins.script.runtime.data.Search -import jadx.plugins.script.runtime.data.Stages +import jadx.plugins.script.kotlin.runtime.data.Debug +import jadx.plugins.script.kotlin.runtime.data.Decompile +import jadx.plugins.script.kotlin.runtime.data.Gui +import jadx.plugins.script.kotlin.runtime.data.JadxScriptAllOptions +import jadx.plugins.script.kotlin.runtime.data.JadxScriptOptions +import jadx.plugins.script.kotlin.runtime.data.Rename +import jadx.plugins.script.kotlin.runtime.data.Replace +import jadx.plugins.script.kotlin.runtime.data.Search +import jadx.plugins.script.kotlin.runtime.data.Stages import org.jetbrains.annotations.ApiStatus.Internal import java.io.File -const val JADX_SCRIPT_LOG_PREFIX = "JadxScript:" - class JadxScriptData( val jadxInstance: JadxDecompiler, val pluginContext: JadxPluginContext, val options: JadxScriptAllOptions, val scriptFile: File, ) { + companion object { + const val JADX_SCRIPT_LOG_PREFIX = "JadxScript:" + } val scriptName = scriptFile.name.removeSuffix(".jadx.kts") val log = KotlinLogging.logger("$JADX_SCRIPT_LOG_PREFIX$scriptName") - val afterLoad: MutableList<() -> Unit> = ArrayList() + val afterLoad = mutableListOf<() -> Unit>() var error: Boolean = false } diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/Utils.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/Utils.kt similarity index 100% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/Utils.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/Utils.kt diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Debug.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Debug.kt similarity index 88% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Debug.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Debug.kt index 6cc08be5f..0cdfe69f0 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Debug.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Debug.kt @@ -1,9 +1,9 @@ -package jadx.plugins.script.runtime.data +package jadx.plugins.script.kotlin.runtime.data import jadx.core.dex.nodes.MethodNode import jadx.core.dex.visitors.DotGraphVisitor import jadx.core.utils.DebugUtils -import jadx.plugins.script.runtime.JadxScriptInstance +import jadx.plugins.script.kotlin.runtime.JadxScriptInstance import java.io.File class Debug(private val jadx: JadxScriptInstance) { diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Decompile.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Decompile.kt similarity index 63% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Decompile.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Decompile.kt index ce63fd8d6..7f4dc0259 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Decompile.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Decompile.kt @@ -1,14 +1,18 @@ -package jadx.plugins.script.runtime.data +package jadx.plugins.script.kotlin.runtime.data import jadx.api.JadxArgs import jadx.api.JavaClass -import jadx.plugins.script.runtime.JadxScriptInstance +import jadx.plugins.script.kotlin.runtime.JadxScriptInstance import java.util.concurrent.Executors class Decompile(private val jadx: JadxScriptInstance) { - fun all() { - jadx.classes.forEach(JavaClass::decompile) + fun all(ignoreCache: Boolean = false) { + if (ignoreCache) { + jadx.classes.forEach(JavaClass::reload) + } else { + jadx.classes.forEach(JavaClass::decompile) + } } fun allThreaded(threadsCount: Int = JadxArgs.DEFAULT_THREADS_COUNT) { diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Gui.kt similarity index 93% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Gui.kt index d4baf5674..07c6bad39 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Gui.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Gui.kt @@ -1,8 +1,8 @@ -package jadx.plugins.script.runtime.data +package jadx.plugins.script.kotlin.runtime.data import jadx.api.metadata.ICodeNodeRef import jadx.api.plugins.gui.JadxGuiContext -import jadx.plugins.script.runtime.JadxScriptInstance +import jadx.plugins.script.kotlin.runtime.JadxScriptInstance class Gui( private val jadx: JadxScriptInstance, diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Options.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Options.kt similarity index 96% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Options.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Options.kt index 5d5e92c97..56038e958 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Options.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Options.kt @@ -1,11 +1,11 @@ -package jadx.plugins.script.runtime.data +package jadx.plugins.script.kotlin.runtime.data import jadx.api.plugins.options.JadxPluginOptions import jadx.api.plugins.options.OptionDescription import jadx.api.plugins.options.OptionFlag import jadx.api.plugins.options.OptionType import jadx.api.plugins.options.impl.JadxOptionDescription -import jadx.plugins.script.runtime.JadxScriptInstance +import jadx.plugins.script.kotlin.runtime.JadxScriptInstance class JadxScriptAllOptions : JadxPluginOptions { lateinit var values: Map diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Rename.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Rename.kt similarity index 91% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Rename.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Rename.kt index 421f670ae..869594e84 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Rename.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Rename.kt @@ -1,10 +1,10 @@ -package jadx.plugins.script.runtime.data +package jadx.plugins.script.kotlin.runtime.data import jadx.core.dex.attributes.AFlag import jadx.core.dex.attributes.IAttributeNode import jadx.core.dex.nodes.IDexNode import jadx.core.dex.nodes.RootNode -import jadx.plugins.script.runtime.JadxScriptInstance +import jadx.plugins.script.kotlin.runtime.JadxScriptInstance class Rename(private val jadx: JadxScriptInstance) { diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Replace.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Replace.kt similarity index 90% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Replace.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Replace.kt index 774bb008f..80475cfb9 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Replace.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Replace.kt @@ -1,11 +1,11 @@ -package jadx.plugins.script.runtime.data +package jadx.plugins.script.kotlin.runtime.data import jadx.core.dex.instructions.args.InsnArg import jadx.core.dex.instructions.args.InsnWrapArg import jadx.core.dex.nodes.InsnNode import jadx.core.dex.nodes.MethodNode import jadx.core.utils.InsnRemover -import jadx.plugins.script.runtime.JadxScriptInstance +import jadx.plugins.script.kotlin.runtime.JadxScriptInstance class Replace(private val jadx: JadxScriptInstance) { diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Search.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Search.kt similarity index 76% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Search.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Search.kt index 88bd10c62..ba68d722d 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Search.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Search.kt @@ -1,7 +1,7 @@ -package jadx.plugins.script.runtime.data +package jadx.plugins.script.kotlin.runtime.data import jadx.core.dex.nodes.ClassNode -import jadx.plugins.script.runtime.JadxScriptInstance +import jadx.plugins.script.kotlin.runtime.JadxScriptInstance class Search(jadx: JadxScriptInstance) { private val dec = jadx.internalDecompiler diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Stages.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Stages.kt similarity index 94% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Stages.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Stages.kt index 75c1d2e75..640737153 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Stages.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Stages.kt @@ -1,11 +1,11 @@ -package jadx.plugins.script.runtime.data +package jadx.plugins.script.kotlin.runtime.data import jadx.core.dex.nodes.BlockNode import jadx.core.dex.nodes.InsnNode import jadx.core.dex.nodes.MethodNode import jadx.core.dex.nodes.RootNode import jadx.core.dex.regions.Region -import jadx.plugins.script.runtime.JadxScriptInstance +import jadx.plugins.script.kotlin.runtime.JadxScriptInstance class Stages(private val jadx: JadxScriptInstance) { diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Wrappers.kt b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Wrappers.kt similarity index 95% rename from jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Wrappers.kt rename to jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Wrappers.kt index 6ceb9c3da..0fd0fb4f9 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/data/Wrappers.kt +++ b/jadx-plugins/jadx-script-kotlin/src/main/kotlin/jadx/plugins/script/kotlin/runtime/data/Wrappers.kt @@ -1,4 +1,4 @@ -package jadx.plugins.script.runtime.data +package jadx.plugins.script.kotlin.runtime.data import jadx.api.plugins.pass.JadxPass import jadx.api.plugins.pass.impl.OrderedJadxPassInfo @@ -8,7 +8,7 @@ import jadx.api.plugins.pass.types.JadxPreparePass import jadx.core.dex.nodes.ClassNode import jadx.core.dex.nodes.MethodNode import jadx.core.dex.nodes.RootNode -import jadx.plugins.script.runtime.JadxScriptInstance +import jadx.plugins.script.kotlin.runtime.JadxScriptInstance private fun buildScriptName(jadx: JadxScriptInstance, name: String) = "JadxScript$name(${jadx.scriptName})" diff --git a/jadx-plugins/jadx-script-kotlin/src/main/resources/META-INF/kotlin/script/templates/jadx.plugins.script.kotlin.runtime.JadxScriptTemplate.classname b/jadx-plugins/jadx-script-kotlin/src/main/resources/META-INF/kotlin/script/templates/jadx.plugins.script.kotlin.runtime.JadxScriptTemplate.classname new file mode 100644 index 000000000..e69de29bb diff --git a/jadx-plugins/jadx-script-kotlin/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/jadx-plugins/jadx-script-kotlin/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin new file mode 100644 index 000000000..d1a553813 --- /dev/null +++ b/jadx-plugins/jadx-script-kotlin/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin @@ -0,0 +1 @@ +jadx.plugins.script.kotlin.JadxScriptKotlinPlugin diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/test/kotlin/JadxScriptPluginTest.kt b/jadx-plugins/jadx-script-kotlin/src/test/kotlin/JadxScriptPluginTest.kt similarity index 100% rename from jadx-plugins/jadx-script/jadx-script-plugin/src/test/kotlin/JadxScriptPluginTest.kt rename to jadx-plugins/jadx-script-kotlin/src/test/kotlin/JadxScriptPluginTest.kt diff --git a/jadx-plugins/jadx-script/jadx-script-ide/src/test/kotlin/ScriptServicesTest.kt b/jadx-plugins/jadx-script-kotlin/src/test/kotlin/ScriptServicesTest.kt similarity index 53% rename from jadx-plugins/jadx-script/jadx-script-ide/src/test/kotlin/ScriptServicesTest.kt rename to jadx-plugins/jadx-script-kotlin/src/test/kotlin/ScriptServicesTest.kt index 5b1ae8775..0a46f78c1 100644 --- a/jadx-plugins/jadx-script/jadx-script-ide/src/test/kotlin/ScriptServicesTest.kt +++ b/jadx-plugins/jadx-script-kotlin/src/test/kotlin/ScriptServicesTest.kt @@ -1,7 +1,10 @@ -package jadx.plugins.script.ide +package jadx.plugins.script +import jadx.plugins.script.kotlin.ScriptServices import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import kotlin.script.experimental.api.ScriptDiagnostic.Severity.ERROR class ScriptServicesTest { @@ -12,8 +15,10 @@ class ScriptServicesTest { val result = ScriptServices().analyze(name, script) println(result) assertThat(result.success).isTrue() + assertThat(result.issues).noneMatch { it.severity == ERROR } } + @Disabled("External dependencies not resolved") @Test fun testAnalyzeDeps() { val name = "test-deps" @@ -21,6 +26,7 @@ class ScriptServicesTest { val result = ScriptServices().analyze(name, script) println(result) assertThat(result.success).isTrue() + assertThat(result.issues).noneMatch { it.severity == ERROR } } @Test @@ -38,6 +44,22 @@ class ScriptServicesTest { .allMatch { c -> c.text == "log" } } + @Disabled("External dependencies not resolved") + @Test + fun testCompleteDeps() { + val sampleName = "test-deps" + val script = getSampleScript(sampleName) + val startPos = script.indexOf("StringEscapeUtils.escapeJava") + val completePos = startPos + 26 // StringEscapeUtils.escapeJa| <- complete 'escapeJava(' + val exprEnd = script.indexOf('}', startIndex = completePos) + val curScript = script.removeRange(completePos, exprEnd) + val result = ScriptServices().complete(sampleName, curScript, completePos) + println(result) + assertThat(result.completions) + .hasSize(1) + .allMatch { c -> c.text == "escapeJava(" } + } + private fun getSampleScript(scriptName: String): String { val resFile = javaClass.classLoader.getResource("samples/$scriptName.jadx.kts") return resFile!!.readText() diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/test/resources/samples/hello.smali b/jadx-plugins/jadx-script-kotlin/src/test/resources/samples/hello.smali similarity index 100% rename from jadx-plugins/jadx-script/jadx-script-plugin/src/test/resources/samples/hello.smali rename to jadx-plugins/jadx-script-kotlin/src/test/resources/samples/hello.smali diff --git a/jadx-plugins/jadx-script/jadx-script-ide/src/test/resources/samples/simple.jadx.kts b/jadx-plugins/jadx-script-kotlin/src/test/resources/samples/simple.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/jadx-script-ide/src/test/resources/samples/simple.jadx.kts rename to jadx-plugins/jadx-script-kotlin/src/test/resources/samples/simple.jadx.kts diff --git a/jadx-plugins/jadx-script/jadx-script-ide/src/test/resources/samples/test-deps.jadx.kts b/jadx-plugins/jadx-script-kotlin/src/test/resources/samples/test-deps.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/jadx-script-ide/src/test/resources/samples/test-deps.jadx.kts rename to jadx-plugins/jadx-script-kotlin/src/test/resources/samples/test-deps.jadx.kts diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/test/resources/samples/test.jadx.kts b/jadx-plugins/jadx-script-kotlin/src/test/resources/samples/test.jadx.kts similarity index 100% rename from jadx-plugins/jadx-script/jadx-script-plugin/src/test/resources/samples/test.jadx.kts rename to jadx-plugins/jadx-script-kotlin/src/test/resources/samples/test.jadx.kts diff --git a/jadx-plugins/jadx-script/README.md b/jadx-plugins/jadx-script/README.md deleted file mode 100644 index 7d1855d11..000000000 --- a/jadx-plugins/jadx-script/README.md +++ /dev/null @@ -1,29 +0,0 @@ -## JADX scripting support - -:exclamation: Work still in progress! Script API is not stable! - -### Examples - -Check script examples in [`examples/scripts/`](https://github.com/skylot/jadx/tree/master/jadx-plugins/jadx-script/examples/scripts)(start with [`hello`](https://github.com/skylot/jadx/blob/master/jadx-plugins/jadx-script/examples/scripts/hello.jadx.kts)) - -### Script usage - -#### In jadx-cli - -Just add script file as input - -#### In jadx-gui - -1. Add script file to the project (using `Add files` or `New script` by right-click menu on `Inputs/Scripts`) -2. Script will appear in `Inputs/Scripts` section -3. After script change, you can run it using `Run` button in script editor toolbar or reload whole project (`Reload` button in toolbar or `F5`). - Also, you can enable `Live reload` option in `File` menu to reload project automatically on scripts change - -### Script development - -Jadx-gui for now don't support ~~autocompletion~~, ~~errors highlighting~~, code navigation and docs, -so the best approach for script editing is to open jadx project in IntelliJ IDEA and write your script in `examples/scripts/` folder. -Also, this allows to debug your scripts: for that you need to create run configuration for jadx-cli or jadx-gui -add breakpoints and next run it in debug mode (jadx-gui is preferred because of faster script reload). - -Script logs and compilation errors will appear in `Log viewer` (try filter for show only script related logs) diff --git a/jadx-plugins/jadx-script/examples/build.gradle.kts b/jadx-plugins/jadx-script/examples/build.gradle.kts deleted file mode 100644 index 5110ef83f..000000000 --- a/jadx-plugins/jadx-script/examples/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - id("jadx-kotlin") -} - -dependencies { - implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime")) - - implementation(kotlin("stdlib-common")) - implementation(kotlin("script-runtime")) - - implementation("io.github.oshai:kotlin-logging-jvm:7.0.13") - - // script context support in IDE is poor, use stubs and manual imports for now - // kotlinScriptDef(project(":jadx-plugins:jadx-script:jadx-script-runtime")) - - // manual imports (IDE can't import dependencies by scripts annotations) - implementation("com.github.javafaker:javafaker:1.0.2") - implementation("org.apache.commons:commons-text:1.15.0") -} - -sourceSets { - main { - kotlin.srcDirs( - "scripts", - "scripts/deobf", - "scripts/gui", - "context", - ) - } -} diff --git a/jadx-plugins/jadx-script/examples/context/Stubs.kt b/jadx-plugins/jadx-script/examples/context/Stubs.kt deleted file mode 100644 index 3532b08de..000000000 --- a/jadx-plugins/jadx-script/examples/context/Stubs.kt +++ /dev/null @@ -1,28 +0,0 @@ -@file:Suppress("MayBeConstant", "unused") - -import io.github.oshai.kotlinlogging.KotlinLogging -import jadx.plugins.script.runtime.JadxScriptInstance - -/** - * Stubs for JadxScriptBaseClass script super class - */ - -val log = KotlinLogging.logger("JadxScript") -val scriptName = "script" - -fun getJadxInstance(): JadxScriptInstance { - throw IllegalStateException("Stub method!") -} - -/** - * Annotations for maven imports - */ -@Target(AnnotationTarget.FILE) -@Repeatable -@Retention(AnnotationRetention.SOURCE) -annotation class DependsOn(vararg val artifactsCoordinates: String, val options: Array = []) - -@Target(AnnotationTarget.FILE) -@Repeatable -@Retention(AnnotationRetention.SOURCE) -annotation class Repository(vararg val repositoriesCoordinates: String, val options: Array = []) diff --git a/jadx-plugins/jadx-script/jadx-script-ide/build.gradle.kts b/jadx-plugins/jadx-script/jadx-script-ide/build.gradle.kts deleted file mode 100644 index 235601cab..000000000 --- a/jadx-plugins/jadx-script/jadx-script-ide/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - id("jadx-kotlin") - id("jadx-library") -} - -dependencies { - implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime")) - implementation(project(":jadx-plugins:jadx-script:jadx-script-plugin")) - - implementation(kotlin("scripting-common")) - implementation(kotlin("scripting-jvm")) - implementation(kotlin("scripting-compiler-embeddable")) - implementation(kotlin("scripting-ide-services")) - - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") - implementation("io.github.oshai:kotlin-logging-jvm:7.0.13") -} diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/build.gradle.kts b/jadx-plugins/jadx-script/jadx-script-plugin/build.gradle.kts deleted file mode 100644 index 8e1bbdc89..000000000 --- a/jadx-plugins/jadx-script/jadx-script-plugin/build.gradle.kts +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - id("jadx-library") - id("jadx-kotlin") -} - -dependencies { - implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime")) - implementation(project(":jadx-commons:jadx-app-commons")) - - implementation(kotlin("scripting-common")) - implementation(kotlin("scripting-jvm")) - implementation(kotlin("scripting-jvm-host")) - - implementation("io.github.oshai:kotlin-logging-jvm:7.0.13") - - testImplementation(project(":jadx-core")) -} diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptOptionsUI.kt b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptOptionsUI.kt deleted file mode 100644 index 4fd452603..000000000 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptOptionsUI.kt +++ /dev/null @@ -1,30 +0,0 @@ -package jadx.plugins.script - -import jadx.api.plugins.gui.ISettingsGroup -import jadx.api.plugins.gui.JadxGuiContext -import jadx.plugins.script.runtime.data.JadxScriptAllOptions -import javax.swing.JPanel - -object JadxScriptOptionsUI { - - fun setup(guiContext: JadxGuiContext, scriptOptions: JadxScriptAllOptions) { - val settings = guiContext.settings() - val subGroups = scriptOptions.descriptions - .groupBy { it.script } - .map { (script, options) -> settings.buildSettingsGroupForOptions(script, options) } - .toList() - settings.setCustomSettingsGroup(EmptyRootGroup("Scripts", subGroups)) - } -} - -private class EmptyRootGroup( - private val title: String, - private val subGroups: List, -) : ISettingsGroup { - - override fun getTitle() = title - - override fun buildComponent() = JPanel() - - override fun getSubGroups() = subGroups -} diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptPlugin.kt b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptPlugin.kt deleted file mode 100644 index 13e2c3511..000000000 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/JadxScriptPlugin.kt +++ /dev/null @@ -1,21 +0,0 @@ -package jadx.plugins.script - -import jadx.api.plugins.JadxPlugin -import jadx.api.plugins.JadxPluginContext -import jadx.api.plugins.JadxPluginInfo -import jadx.plugins.script.passes.JadxScriptAfterLoadPass -import jadx.plugins.script.runtime.data.JadxScriptAllOptions - -class JadxScriptPlugin : JadxPlugin { - override fun getPluginInfo() = JadxPluginInfo("jadx-script", "Jadx Script", "Scripting support for jadx") - - override fun init(context: JadxPluginContext) { - val scriptOptions = JadxScriptAllOptions() - context.registerOptions(scriptOptions) - val scripts = ScriptEval().process(context, scriptOptions) - if (scripts.isNotEmpty()) { - context.addPass(JadxScriptAfterLoadPass(scripts)) - context.guiContext?.let { JadxScriptOptionsUI.setup(it, scriptOptions) } - } - } -} diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptEval.kt b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptEval.kt deleted file mode 100644 index 1f6c1b7f5..000000000 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/ScriptEval.kt +++ /dev/null @@ -1,102 +0,0 @@ -package jadx.plugins.script - -import jadx.api.plugins.JadxPluginContext -import jadx.plugins.script.runtime.JadxScriptData -import jadx.plugins.script.runtime.JadxScriptTemplate -import jadx.plugins.script.runtime.data.JadxScriptAllOptions -import kotlin.script.experimental.api.EvaluationResult -import kotlin.script.experimental.api.ResultValue -import kotlin.script.experimental.api.ResultWithDiagnostics -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.ScriptDiagnostic.Severity -import kotlin.script.experimental.api.ScriptEvaluationConfiguration -import kotlin.script.experimental.api.constructorArgs -import kotlin.script.experimental.host.ScriptingHostConfiguration -import kotlin.script.experimental.host.toScriptSource -import kotlin.script.experimental.jvm.compilationCache -import kotlin.script.experimental.jvm.jvm -import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost -import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate -import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate -import kotlin.system.measureTimeMillis -import kotlin.time.DurationUnit -import kotlin.time.toDuration - -class ScriptEval { - - fun process(context: JadxPluginContext, scriptOptions: JadxScriptAllOptions): List { - val jadx = context.decompiler - val scripts = jadx.args.inputFiles.filter { f -> f.name.endsWith(".jadx.kts") } - if (scripts.isEmpty()) { - return emptyList() - } - val scriptingHost = buildScriptingHost(context) - val compileConf = buildCompileConf() - val scriptDataList = mutableListOf() - for (scriptFile in scripts) { - val scriptData = JadxScriptData(jadx, context, scriptOptions, scriptFile) - scriptDataList.add(scriptData) - eval(scriptingHost, compileConf, scriptData) - } - return scriptDataList - } - - private fun eval( - scriptingHost: BasicJvmScriptingHost, - compileConf: ScriptCompilationConfiguration, - scriptData: JadxScriptData, - ) { - scriptData.log.debug { "Loading script: ${scriptData.scriptFile.absolutePath}" } - val evalConf = buildEvalConf(scriptData) - val execTime = measureTimeMillis { - val result = scriptingHost.eval(scriptData.scriptFile.toScriptSource(), compileConf, evalConf) - processEvalResult(result, scriptData) - } - scriptData.log.debug { "Script '${scriptData.scriptName}' executed in ${execTime.toDuration(DurationUnit.MILLISECONDS)}" } - } - - private fun processEvalResult(res: ResultWithDiagnostics, scriptData: JadxScriptData) { - val log = scriptData.log - for (r in res.reports) { - val msg = r.render(withSeverity = false) - when (r.severity) { - Severity.FATAL, Severity.ERROR -> log.error(r.exception) { "Script execution error: $msg" } - Severity.WARNING -> log.warn { "Script execution issue: $msg" } - Severity.INFO -> log.info { "Script report: $msg" } - Severity.DEBUG -> {} // ignore, too verbose - } - } - when (res) { - is ResultWithDiagnostics.Success -> { - when (val retVal = res.value.returnValue) { - is ResultValue.Error -> log.error(retVal.error) { "Script execution error:" } - is ResultValue.Value -> log.info { "Script execution result: $retVal" } - is ResultValue.Unit -> {} - ResultValue.NotEvaluated -> {} - } - } - - is ResultWithDiagnostics.Failure -> { - scriptData.error = true - log.error { "Script execution failed: ${scriptData.scriptName}" } - } - } - } - - fun buildScriptingHost(context: JadxPluginContext) = BasicJvmScriptingHost( - baseHostConfiguration = ScriptingHostConfiguration { - jvm { - compilationCache(ScriptCache().build(context)) - } - }, - ) - - fun buildCompileConf() = createJvmCompilationConfigurationFromTemplate() - - fun buildEvalConf(scriptData: JadxScriptData): ScriptEvaluationConfiguration { - val baseEvalConf = createJvmEvaluationConfigurationFromTemplate() - return ScriptEvaluationConfiguration(baseEvalConf) { - constructorArgs(scriptData) - } - } -} diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin deleted file mode 100644 index d354b7a6d..000000000 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/resources/META-INF/services/jadx.api.plugins.JadxPlugin +++ /dev/null @@ -1 +0,0 @@ -jadx.plugins.script.JadxScriptPlugin diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/test/resources/samples/test-deps.jadx.kts b/jadx-plugins/jadx-script/jadx-script-plugin/src/test/resources/samples/test-deps.jadx.kts deleted file mode 100644 index 39953db1c..000000000 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/test/resources/samples/test-deps.jadx.kts +++ /dev/null @@ -1,11 +0,0 @@ -@file:DependsOn("org.apache.commons:commons-text:1.10.0") - -import org.apache.commons.text.StringEscapeUtils - -val jadx = getJadxInstance() - -jadx.afterLoad { - jadx.classes.forEach { - jadx.log.info { "Escaped name: ${StringEscapeUtils.escapeJava(it.fullName)}" } - } -} diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/build.gradle.kts b/jadx-plugins/jadx-script/jadx-script-runtime/build.gradle.kts deleted file mode 100644 index b23a3799d..000000000 --- a/jadx-plugins/jadx-script/jadx-script-runtime/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - id("jadx-library") - id("jadx-kotlin") -} - -dependencies { - api(project(":jadx-core")) - - implementation(kotlin("stdlib")) - implementation(kotlin("scripting-common")) - implementation(kotlin("scripting-jvm")) - - // allow to use maven dependencies in scripts - implementation(kotlin("scripting-dependencies")) - implementation(kotlin("scripting-dependencies-maven")) - - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") - implementation("io.github.oshai:kotlin-logging-jvm:7.0.13") - - runtimeOnly(project(":jadx-plugins:jadx-dex-input")) - runtimeOnly(project(":jadx-plugins:jadx-smali-input")) - runtimeOnly(project(":jadx-plugins:jadx-java-convert")) - runtimeOnly(project(":jadx-plugins:jadx-java-input")) - runtimeOnly(project(":jadx-plugins:jadx-raung-input")) -} diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/JadxScriptTemplate.kt b/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/JadxScriptTemplate.kt deleted file mode 100644 index 5f41a4c33..000000000 --- a/jadx-plugins/jadx-script/jadx-script-runtime/src/main/kotlin/jadx/plugins/script/runtime/JadxScriptTemplate.kt +++ /dev/null @@ -1,91 +0,0 @@ -package jadx.plugins.script.runtime - -import kotlinx.coroutines.runBlocking -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ResultWithDiagnostics -import kotlin.script.experimental.api.ScriptAcceptedLocation -import kotlin.script.experimental.api.ScriptCollectedData -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.ScriptConfigurationRefinementContext -import kotlin.script.experimental.api.acceptedLocations -import kotlin.script.experimental.api.asSuccess -import kotlin.script.experimental.api.collectedAnnotations -import kotlin.script.experimental.api.compilerOptions -import kotlin.script.experimental.api.defaultImports -import kotlin.script.experimental.api.dependencies -import kotlin.script.experimental.api.ide -import kotlin.script.experimental.api.isStandalone -import kotlin.script.experimental.api.onSuccess -import kotlin.script.experimental.api.refineConfiguration -import kotlin.script.experimental.api.with -import kotlin.script.experimental.dependencies.CompoundDependenciesResolver -import kotlin.script.experimental.dependencies.DependsOn -import kotlin.script.experimental.dependencies.FileSystemDependenciesResolver -import kotlin.script.experimental.dependencies.Repository -import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver -import kotlin.script.experimental.dependencies.resolveFromScriptSourceAnnotations -import kotlin.script.experimental.jvm.JvmDependency -import kotlin.script.experimental.jvm.dependenciesFromCurrentContext -import kotlin.script.experimental.jvm.jvm - -@KotlinScript( - displayName = "Jadx Script", - fileExtension = "jadx.kts", - compilationConfiguration = JadxScriptConfiguration::class, -) -abstract class JadxScriptTemplate( - scriptData: JadxScriptData, -) { - val scriptName = scriptData.scriptName - val log = scriptData.log - - private val scriptInstance = JadxScriptInstance(scriptData, log) - - fun getJadxInstance() = scriptInstance - - fun println(message: Any?) { - log.info { message } - } - - fun print(message: Any?) { - log.info { message } - } -} - -object JadxScriptConfiguration : ScriptCompilationConfiguration({ - defaultImports(DependsOn::class, Repository::class) - - jvm { - dependenciesFromCurrentContext( - wholeClasspath = true, - ) - } - ide { - acceptedLocations(ScriptAcceptedLocation.Everywhere) - } - - refineConfiguration { - onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations) - } - - isStandalone(true) - - // forcing compiler to not use modules while building script classpath - // because shadow jar remove all modules-info.class (https://github.com/GradleUp/shadow/issues/710) - compilerOptions.append("-Xjdk-release=1.8") -}) - -private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver()) - -fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics { - val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations) - ?.takeIf { it.isNotEmpty() } - ?: return context.compilationConfiguration.asSuccess() - return runBlocking { - resolver.resolveFromScriptSourceAnnotations(annotations) - }.onSuccess { - context.compilationConfiguration.with { - dependencies.append(JvmDependency(it)) - }.asSuccess() - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 26a711fab..c4353380e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,12 +26,8 @@ include("jadx-plugins:jadx-java-convert") include("jadx-plugins:jadx-rename-mappings") include("jadx-plugins:jadx-kotlin-metadata") include("jadx-plugins:jadx-kotlin-source-debug-extension") +include("jadx-plugins:jadx-script-kotlin") include("jadx-plugins:jadx-xapk-input") include("jadx-plugins:jadx-aab-input") include("jadx-plugins:jadx-apkm-input") include("jadx-plugins:jadx-apks-input") - -include("jadx-plugins:jadx-script:jadx-script-plugin") -include("jadx-plugins:jadx-script:jadx-script-runtime") -include("jadx-plugins:jadx-script:jadx-script-ide") -include("jadx-plugins:jadx-script:examples")