feat: add base scripting support
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.core.nodes.IMethodNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.dex.visitors.DotGraphVisitor
|
||||
import jadx.core.utils.DebugUtils
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
import java.io.File
|
||||
|
||||
class Debug(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun printMethodRegions(mth: IMethodNode, printInsns: Boolean = false) {
|
||||
DebugUtils.printRegions(mth as MethodNode, printInsns)
|
||||
}
|
||||
|
||||
fun saveCFG(mth: IMethodNode, file: File = File("dump-mth-raw")) {
|
||||
DotGraphVisitor.dumpRaw().save(file, mth as MethodNode)
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.JadxArgs
|
||||
import jadx.api.JavaClass
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class Decompile(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun all() {
|
||||
jadx.classes.forEach(JavaClass::decompile)
|
||||
}
|
||||
|
||||
fun allThreaded(threadsCount: Int = JadxArgs.DEFAULT_THREADS_COUNT) {
|
||||
val executor = Executors.newFixedThreadPool(threadsCount)
|
||||
val dec = jadx.internalDecompiler
|
||||
val batches = dec.decompileScheduler.buildBatches(jadx.classes)
|
||||
for (batch in batches) {
|
||||
executor.submit {
|
||||
batch.forEach(JavaClass::decompile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.plugins.gui.JadxGuiContext
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
class Gui(
|
||||
private val jadx: JadxScriptInstance,
|
||||
private val guiContext: JadxGuiContext?
|
||||
) {
|
||||
|
||||
fun isAvailable() = guiContext != null
|
||||
|
||||
fun ifAvailable(block: Gui.() -> Unit) {
|
||||
guiContext?.let { this.apply(block) }
|
||||
}
|
||||
|
||||
fun ui(block: () -> Unit) {
|
||||
guiContext?.uiRun(block)
|
||||
}
|
||||
|
||||
fun addMenuAction(name: String, action: () -> Unit) {
|
||||
guiContext?.addMenuAction(name, action)
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.core.nodes.IRootNode
|
||||
import jadx.core.dex.nodes.IDexNode
|
||||
import jadx.core.dex.nodes.RootNode
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
class RenamePass(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun all(makeNewName: (String) -> String?) {
|
||||
all { name, _ -> makeNewName.invoke(name) }
|
||||
}
|
||||
|
||||
fun all(makeNewName: (String, IDexNode) -> String?) {
|
||||
jadx.addPass(object : ScriptPreparePass(jadx, "RenameAll") {
|
||||
override fun init(root: IRootNode) {
|
||||
val rootNode = root as RootNode
|
||||
for (cls in rootNode.classes) {
|
||||
makeNewName.invoke(cls.classInfo.shortName, cls)?.let {
|
||||
cls.classInfo.changeShortName(it)
|
||||
}
|
||||
for (mth in cls.methods) {
|
||||
if (mth.isConstructor) {
|
||||
continue
|
||||
}
|
||||
makeNewName.invoke(mth.name, mth)?.let {
|
||||
mth.rename(it)
|
||||
}
|
||||
}
|
||||
for (fld in cls.fields) {
|
||||
makeNewName.invoke(fld.name, fld)?.let {
|
||||
fld.fieldInfo.alias = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package jadx.plugins.script.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
|
||||
|
||||
class Replace(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun insns(replace: (MethodNode, InsnNode) -> InsnNode?) {
|
||||
jadx.stages.mthBlocks { mth, blocks ->
|
||||
for (block in blocks) {
|
||||
val insns = block.instructions
|
||||
for ((i, insn) in insns.withIndex()) {
|
||||
replaceSubInsns(mth, insn, replace)
|
||||
replace.invoke(mth, insn)?.let {
|
||||
insns[i] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceSubInsns(mth: MethodNode, insn: InsnNode, replace: (MethodNode, InsnNode) -> InsnNode?) {
|
||||
val argsCount = insn.argsCount
|
||||
if (argsCount == 0) {
|
||||
return
|
||||
}
|
||||
for (i in 0 until argsCount) {
|
||||
val arg = insn.getArg(i)
|
||||
if (arg is InsnWrapArg) {
|
||||
val wrapInsn = arg.wrapInsn
|
||||
replaceSubInsns(mth, wrapInsn, replace)
|
||||
replace.invoke(mth, wrapInsn)?.let {
|
||||
InsnRemover.unbindArgUsage(mth, arg)
|
||||
insn.setArg(i, InsnArg.wrapInsnIntoArg(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
class Search(private val jadx: JadxScriptInstance) {
|
||||
private val dec = jadx.internalDecompiler
|
||||
|
||||
fun classByFullName(fullName: String): ClassNode? {
|
||||
return dec.searchClassNodeByOrigFullName(fullName)
|
||||
}
|
||||
|
||||
fun classesByShortName(fullName: String): List<ClassNode> {
|
||||
return dec.root.searchClassByShortName(fullName)
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.core.nodes.IMethodNode
|
||||
import jadx.core.dex.nodes.BlockNode
|
||||
import jadx.core.dex.nodes.InsnNode
|
||||
import jadx.core.dex.nodes.MethodNode
|
||||
import jadx.core.dex.regions.Region
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
class Stages(private val jadx: JadxScriptInstance) {
|
||||
|
||||
fun rawInsns(block: (MethodNode, Array<InsnNode?>) -> Unit) {
|
||||
jadx.addPass(object : ScriptOrderedDecompilePass(
|
||||
jadx,
|
||||
"StageRawInsns",
|
||||
runAfter = listOf("start")
|
||||
) {
|
||||
override fun visit(mth: IMethodNode) {
|
||||
val mthNode = mth as MethodNode
|
||||
mthNode.instructions?.let {
|
||||
block.invoke(mthNode, it)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun mthEarlyBlocks(block: (MethodNode, List<BlockNode>) -> Unit) {
|
||||
mthBlocks(beforePass = "SSATransform", block)
|
||||
}
|
||||
|
||||
fun mthBlocks(
|
||||
beforePass: String = "RegionMakerVisitor",
|
||||
block: (MethodNode, List<BlockNode>) -> Unit
|
||||
) {
|
||||
jadx.addPass(object : ScriptOrderedDecompilePass(
|
||||
jadx,
|
||||
"StageMthBlocks",
|
||||
runBefore = listOf(beforePass)
|
||||
) {
|
||||
override fun visit(mth: IMethodNode) {
|
||||
val mthNode = mth as MethodNode
|
||||
mthNode.basicBlocks?.let {
|
||||
block.invoke(mthNode, it)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun mthRegions(block: (MethodNode, Region) -> Unit) {
|
||||
jadx.addPass(object : ScriptOrderedDecompilePass(
|
||||
jadx,
|
||||
"StageMthRegions",
|
||||
runBefore = listOf("PrepareForCodeGen")
|
||||
) {
|
||||
override fun visit(mth: IMethodNode) {
|
||||
val mthNode = mth as MethodNode
|
||||
mthNode.region?.let {
|
||||
block.invoke(mthNode, it)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package jadx.plugins.script.runtime.data
|
||||
|
||||
import jadx.api.core.nodes.IClassNode
|
||||
import jadx.api.core.nodes.IMethodNode
|
||||
import jadx.api.core.nodes.IRootNode
|
||||
import jadx.api.plugins.pass.JadxPass
|
||||
import jadx.api.plugins.pass.impl.OrderedJadxPassInfo
|
||||
import jadx.api.plugins.pass.impl.SimpleJadxPassInfo
|
||||
import jadx.api.plugins.pass.types.JadxDecompilePass
|
||||
import jadx.api.plugins.pass.types.JadxPreparePass
|
||||
import jadx.plugins.script.runtime.JadxScriptInstance
|
||||
|
||||
private fun buildScriptName(jadx: JadxScriptInstance, name: String) = "JadxScript${name}(${jadx.scriptName})"
|
||||
|
||||
private fun buildSimplePassInfo(jadx: JadxScriptInstance, name: String) =
|
||||
SimpleJadxPassInfo(buildScriptName(jadx, name))
|
||||
|
||||
abstract class ScriptPreparePass(
|
||||
private val jadx: JadxScriptInstance, private val name: String
|
||||
) : JadxPreparePass {
|
||||
override fun getInfo() = buildSimplePassInfo(jadx, name)
|
||||
}
|
||||
|
||||
abstract class ScriptDecompilePass(
|
||||
private val jadx: JadxScriptInstance, private val name: String
|
||||
) : JadxDecompilePass {
|
||||
override fun getInfo() = buildSimplePassInfo(jadx, name)
|
||||
|
||||
override fun init(root: IRootNode) {
|
||||
}
|
||||
|
||||
override fun visit(cls: IClassNode): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun visit(mth: IMethodNode) {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ScriptOrderedPass(
|
||||
private val jadx: JadxScriptInstance,
|
||||
private val name: String,
|
||||
private val runAfter: List<String> = listOf(),
|
||||
private val runBefore: List<String> = listOf()
|
||||
) : JadxPass {
|
||||
override fun getInfo(): OrderedJadxPassInfo {
|
||||
val scriptName = buildScriptName(jadx, name)
|
||||
return OrderedJadxPassInfo(scriptName, scriptName, runAfter, runBefore)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ScriptOrderedPreparePass(
|
||||
jadx: JadxScriptInstance, name: String, runAfter: List<String> = listOf(), runBefore: List<String> = listOf()
|
||||
) : ScriptOrderedPass(jadx, name, runAfter, runBefore), JadxPreparePass {}
|
||||
|
||||
abstract class ScriptOrderedDecompilePass(
|
||||
jadx: JadxScriptInstance, name: String, runAfter: List<String> = listOf(), runBefore: List<String> = listOf()
|
||||
) : ScriptOrderedPass(jadx, name, runAfter, runBefore), JadxDecompilePass {
|
||||
|
||||
override fun init(root: IRootNode) {
|
||||
}
|
||||
|
||||
override fun visit(cls: IClassNode): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun visit(mth: IMethodNode) {
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package jadx.plugins.script.runtime
|
||||
|
||||
import jadx.api.JadxArgs
|
||||
import jadx.api.JadxDecompiler
|
||||
import jadx.api.JavaClass
|
||||
import jadx.api.plugins.JadxPluginContext
|
||||
import jadx.api.plugins.pass.JadxPass
|
||||
import jadx.plugins.script.runtime.data.*
|
||||
import mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
import java.io.File
|
||||
|
||||
|
||||
open class JadxScriptBaseClass(private val scriptData: JadxScriptData) {
|
||||
val scriptName = scriptData.scriptName
|
||||
val log = KotlinLogging.logger("JadxScript:${scriptName}")
|
||||
|
||||
fun getJadxInstance() = JadxScriptInstance(scriptData, log)
|
||||
|
||||
fun println(message: Any?) {
|
||||
log.info(message?.toString())
|
||||
}
|
||||
|
||||
fun print(message: Any?) {
|
||||
log.info(message?.toString())
|
||||
}
|
||||
}
|
||||
|
||||
class JadxScriptData(
|
||||
val jadxInstance: JadxDecompiler,
|
||||
val pluginContext: JadxPluginContext,
|
||||
val scriptFile: File
|
||||
) {
|
||||
val afterLoad: MutableList<() -> Unit> = ArrayList()
|
||||
|
||||
val scriptName get() = scriptFile.name.removeSuffix(".jadx.kts")
|
||||
}
|
||||
|
||||
class JadxScriptInstance(
|
||||
private val scriptData: JadxScriptData,
|
||||
val log: KLogger
|
||||
) {
|
||||
private val decompiler = scriptData.jadxInstance
|
||||
|
||||
val rename: RenamePass by lazy { RenamePass(this) }
|
||||
val stages: Stages by lazy { Stages(this) }
|
||||
val replace: Replace by lazy { Replace(this) }
|
||||
val decompile: Decompile by lazy { Decompile(this) }
|
||||
val search: Search by lazy { Search(this) }
|
||||
val gui: Gui by lazy { Gui(this, scriptData.pluginContext.guiContext) }
|
||||
val debug: Debug by lazy { Debug(this) }
|
||||
|
||||
val args: JadxArgs
|
||||
get() = decompiler.args
|
||||
|
||||
val classes: List<JavaClass>
|
||||
get() = decompiler.classes
|
||||
|
||||
val scriptFile get() = scriptData.scriptFile
|
||||
|
||||
val scriptName get() = scriptData.scriptName
|
||||
|
||||
fun afterLoad(block: () -> Unit) {
|
||||
scriptData.afterLoad.add(block)
|
||||
}
|
||||
|
||||
fun addPass(pass: JadxPass) {
|
||||
scriptData.pluginContext.passContext.addPass(pass)
|
||||
}
|
||||
|
||||
val internalDecompiler: JadxDecompiler
|
||||
get() = decompiler
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package jadx.plugins.script.runtime
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.dependencies.*
|
||||
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
|
||||
import kotlin.script.experimental.jvm.JvmDependency
|
||||
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
|
||||
import kotlin.script.experimental.jvm.jvm
|
||||
|
||||
@KotlinScript(
|
||||
fileExtension = "jadx.kts",
|
||||
compilationConfiguration = JadxScriptConfiguration::class
|
||||
)
|
||||
abstract class JadxScript
|
||||
|
||||
object JadxScriptConfiguration : ScriptCompilationConfiguration({
|
||||
defaultImports(DependsOn::class, Repository::class)
|
||||
|
||||
jvm {
|
||||
dependenciesFromCurrentContext(
|
||||
wholeClasspath = true
|
||||
)
|
||||
}
|
||||
|
||||
baseClass(JadxScriptBaseClass::class)
|
||||
|
||||
refineConfiguration {
|
||||
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
||||
}
|
||||
})
|
||||
|
||||
private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())
|
||||
|
||||
fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Utils for use in scripts.
|
||||
* Located in default package to reduce imports.
|
||||
*/
|
||||
|
||||
import java.io.File
|
||||
|
||||
fun String.asFile(): File = File(this)
|
||||
|
||||
fun Int.hex(): String = Integer.toHexString(this)
|
||||
Reference in New Issue
Block a user