From 1d64a932bb372d6c06db87a60a7c3badc40d015f Mon Sep 17 00:00:00 2001 From: Gary Tierney Date: Sun, 8 Mar 2020 20:10:22 +0000 Subject: [PATCH] Add a CLI tool to interact with the deobfuscator Signed-off-by: Gary Tierney --- buildSrc/src/main/java/Versions.kt | 2 + deob-cli/build.gradle.kts | 36 ++++++++++++++ .../deob/cli/DeobfuscatorClassLoader.kt | 49 +++++++++++++++++++ .../dev/openrs2/deob/cli/DeobfuscatorCli.kt | 36 ++++++++++++++ .../openrs2/deob/cli/DeobfuscatorOptions.kt | 3 ++ .../deob/cli/ir/MethodScopedCommand.kt | 26 ++++++++++ .../openrs2/deob/cli/ir/PrintCfgCommand.kt | 36 ++++++++++++++ settings.gradle.kts | 1 + 8 files changed, 189 insertions(+) create mode 100644 deob-cli/build.gradle.kts create mode 100644 deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorClassLoader.kt create mode 100644 deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorCli.kt create mode 100644 deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorOptions.kt create mode 100644 deob-cli/src/main/java/dev/openrs2/deob/cli/ir/MethodScopedCommand.kt create mode 100644 deob-cli/src/main/java/dev/openrs2/deob/cli/ir/PrintCfgCommand.kt diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 5ed8e94867..05639674ba 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -1,6 +1,7 @@ object Versions { const val asm = "7.3.1" const val bouncyCastle = "1.64" + const val clikt = "2.5.0" const val dependencyLicenseReport = "1.13" const val fernflower = "1.0.4-SNAPSHOT" const val guava = "28.2-jre" @@ -8,6 +9,7 @@ object Versions { const val inlineLogger = "1.0.2" const val javaParser = "3.15.14" const val jdom = "2.0.6" + const val jgrapht = "1.4.0" const val jimfs = "1.1" const val junit = "5.6.0" const val kotlin = "1.3.70" diff --git a/deob-cli/build.gradle.kts b/deob-cli/build.gradle.kts new file mode 100644 index 0000000000..158bed445a --- /dev/null +++ b/deob-cli/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + `maven-publish` + application + kotlin("jvm") +} + +application { + mainClassName = "dev.openrs2.deob.cli.DeobfuscatorCliKt" +} + +dependencies { + implementation(project(":asm")) + implementation(project(":deob")) + implementation(project(":deob-ir")) + implementation("com.github.ajalt:clikt:${Versions.clikt}") + implementation("com.google.guava:guava:${Versions.guava}") + implementation("org.jgrapht:jgrapht-io:${Versions.jgrapht}") + implementation("org.jgrapht:jgrapht-guava:${Versions.jgrapht}") +} + +publishing { + publications.create("maven") { + from(components["java"]) + + pom { + packaging = "jar" + name.set("OpenRS2 Deobfuscator CLI") + description.set( + """ + A command-line interface that provides access to the deobfuscators + analyis, deobfuscation, and decompilation tools. + """.trimIndent() + ) + } + } +} diff --git a/deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorClassLoader.kt b/deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorClassLoader.kt new file mode 100644 index 0000000000..373f00a400 --- /dev/null +++ b/deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorClassLoader.kt @@ -0,0 +1,49 @@ +package dev.openrs2.deob.cli + +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode +import java.io.File +import java.io.InputStream +import java.lang.IllegalArgumentException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + +private fun load(input: InputStream) = input.use { + val node = ClassNode() + val reader = ClassReader(input) + + reader.accept(node, ClassReader.SKIP_DEBUG) + + node +} + +interface DeobfuscatorClassLoader { + fun load(name: String): ClassNode +} + +object SystemClassLoader : DeobfuscatorClassLoader { + override fun load(name: String): ClassNode { + val classPath = "/${name.replace('.', File.separatorChar)}.class" + val classFile = this.javaClass.getResourceAsStream(classPath) + + return load(classFile) + } +} + +class ClasspathClassLoader(val classPath: List) : DeobfuscatorClassLoader { + override fun load(name: String): ClassNode { + val relativePath = Paths.get("/${name.replace('.', File.separatorChar)}.class") + + for (entry in classPath) { + val classFilePath = entry.resolve(relativePath) + if (!Files.exists(classFilePath)) { + continue + } + + return load(Files.newInputStream(classFilePath)) + } + + throw IllegalArgumentException("Unable to find class named $name") + } +} diff --git a/deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorCli.kt b/deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorCli.kt new file mode 100644 index 0000000000..428d1738ec --- /dev/null +++ b/deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorCli.kt @@ -0,0 +1,36 @@ +package dev.openrs2.deob.cli + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import dev.openrs2.deob.cli.ir.PrintCfgCommand +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths + +class DeobfuscatorCli : CliktCommand(name = "deob") { + val classpath: String by option(help = "Change the classpath used to resolve class files") + .default("system") + + override fun run() { + val loader = when (classpath) { + "system" -> SystemClassLoader + else -> { + val paths : List = classpath.split(File.pathSeparatorChar).map { + Paths.get(it) + } + + val classLoader = ClasspathClassLoader(paths) + + classLoader + } + } + + currentContext.obj = DeobfuscatorOptions(loader) + } +} + +fun main(args: Array) = DeobfuscatorCli() + .subcommands(PrintCfgCommand) + .main(args) diff --git a/deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorOptions.kt b/deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorOptions.kt new file mode 100644 index 0000000000..985321ea4a --- /dev/null +++ b/deob-cli/src/main/java/dev/openrs2/deob/cli/DeobfuscatorOptions.kt @@ -0,0 +1,3 @@ +package dev.openrs2.deob.cli + +data class DeobfuscatorOptions(val classLoader: DeobfuscatorClassLoader) diff --git a/deob-cli/src/main/java/dev/openrs2/deob/cli/ir/MethodScopedCommand.kt b/deob-cli/src/main/java/dev/openrs2/deob/cli/ir/MethodScopedCommand.kt new file mode 100644 index 0000000000..b9be35a02b --- /dev/null +++ b/deob-cli/src/main/java/dev/openrs2/deob/cli/ir/MethodScopedCommand.kt @@ -0,0 +1,26 @@ +package dev.openrs2.deob.cli.ir + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.requireObject +import com.github.ajalt.clikt.parameters.arguments.argument +import dev.openrs2.deob.cli.DeobfuscatorOptions +import dev.openrs2.deob.ir.Method +import dev.openrs2.deob.ir.translation.IrDecompiler + +abstract class MethodScopedCommand(command: String) : CliktCommand(name = command) { + val className: String by argument(help = "Fully qualified name of the class name to disassemble") + val methodName: String by argument(help = "Name of the method to print the control flow graph for") + val options by requireObject() + + final override fun run() { + val clazz = PrintCfgCommand.options.classLoader.load(PrintCfgCommand.className) + val method = clazz.methods.find { it.name == PrintCfgCommand.methodName }!! + + val decompiler = IrDecompiler(clazz, method) + val ir = decompiler.decompile() + + run(ir) + } + + abstract fun run(method: Method) +} diff --git a/deob-cli/src/main/java/dev/openrs2/deob/cli/ir/PrintCfgCommand.kt b/deob-cli/src/main/java/dev/openrs2/deob/cli/ir/PrintCfgCommand.kt new file mode 100644 index 0000000000..dc364d9e46 --- /dev/null +++ b/deob-cli/src/main/java/dev/openrs2/deob/cli/ir/PrintCfgCommand.kt @@ -0,0 +1,36 @@ +package dev.openrs2.deob.cli.ir + +import com.google.common.graph.EndpointPair +import dev.openrs2.deob.ir.Method +import dev.openrs2.deob.ir.flow.BasicBlock +import org.jgrapht.Graph +import org.jgrapht.graph.guava.MutableGraphAdapter +import org.jgrapht.nio.DefaultAttribute +import org.jgrapht.nio.GraphExporter +import org.jgrapht.nio.dot.DOTExporter + +typealias BlockGraph = Graph> +typealias BlockGraphExporter = GraphExporter> + +fun dotExporter(): BlockGraphExporter { + val exporter = DOTExporter>() + + exporter.setVertexAttributeProvider { + val label = it.toString().replace("\n", "\\l") + + mapOf( + "label" to DefaultAttribute.createAttribute(label) + ) + } + + return exporter +} + +object PrintCfgCommand : MethodScopedCommand("ir-print-cfg") { + override fun run(method: Method) { + val graph: BlockGraph = MutableGraphAdapter(method.cfg) + val exporter: BlockGraphExporter = dotExporter() + + exporter.exportGraph(graph, System.out) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9ed7a5d3d2..d2edb7f1ab 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,7 @@ include( "deob", "deob-annotations", "deob-ast", + "deob-cli", "deob-ir", "game" )