From 59e0f20af36764aa5a90c41118109d0e59671831 Mon Sep 17 00:00:00 2001 From: TarCV Date: Sun, 22 Sep 2019 04:34:20 +0300 Subject: [PATCH 1/6] Support writing to a file --- .../tarcv/zandronum/debotc/Decompiler.kt | 21 +++++++------ .../com/github/tarcv/zandronum/debotc/Util.kt | 11 ++++++- .../com/github/tarcv/zandronum/debotc/Main.kt | 16 ++++++++-- .../github/tarcv/zandronum/debotc/UtilsJvm.kt | 31 +++++++++++++++++++ .../tarcv/zandronum/debotc/UtilsNative.kt | 26 ++++++++++++++++ 5 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index e01f023..e062995 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -11,14 +11,15 @@ import kotlin.math.max @ExperimentalUnsignedTypes class Decompiler { - fun print() { + + fun print(printer: Printer) { if (!alreadyParsed) { throw IllegalStateException("print() can be called only after parsing") } - println("#!botc 1.0.0") - println("#include \"debotc_defs.bts\"") - println() + printer.println("#!botc 1.0.0") + printer.println("#include \"debotc_defs.bts\"") + printer.println() val globalVariables = HashSet() val globalArrays = HashSet() @@ -57,12 +58,12 @@ class Decompiler { stateBuilder.appendLine() } .let { - printBlockForGlobal(globalVariables, globalArrays) - println() + printBlockForGlobal(printer, globalVariables, globalArrays) + printer.println() it } .forEach { - println(it) + printer.println(it.toString()) } } @@ -76,16 +77,16 @@ class Decompiler { .appendLine() } - private fun printBlockForGlobal(globalVariables: Set, globalArrays: Set) { + private fun printBlockForGlobal(printer: Printer, globalVariables: Set, globalArrays: Set) { globalVariables .sorted() .forEach { - println("var int \$global$it;") + printer.println("var int \$global$it;") } globalArrays .sorted() .forEach { - println("var int \$globalArray$it[];") + printer.println("var int \$globalArray$it[];") } } diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt index af999dd..eaaa7ed 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt @@ -13,4 +13,13 @@ expect fun StringBuilder.appendLine(line: StringBuilder): StringBuilder expect fun StringBuilder.insertString(index: Int, string: String): StringBuilder -expect fun ByteArray.stringFromUtf8BytesOrThrow(start: Int = 0, size: Int = this.size): String \ No newline at end of file +expect fun ByteArray.stringFromUtf8BytesOrThrow(start: Int = 0, size: Int = this.size): String + +interface Printer { + fun println(msg: String = "") + + fun close() + fun print(msg: String) +} +expect class FilePrinter(path: String): Printer +expect object consolePrinter: Printer \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt index 915776f..b4e86a3 100644 --- a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt +++ b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt @@ -5,8 +5,8 @@ import java.nio.file.Paths @ExperimentalUnsignedTypes fun main(args: Array) { - if (args.size != 1) { - throw IllegalArgumentException("Only one argument - path to compiled script should be passed") + if (args.size != 1 && args.size != 2) { + throw IllegalArgumentException("Wrong arguments${System.lineSeparator()}debotc []") } val data = Files.readAllBytes(Paths.get(args[0])) @@ -14,5 +14,15 @@ fun main(args: Array) { val decompiler = Decompiler() decompiler.parse(data0) - decompiler.print() + + val printer = if (args.size == 2) { + FilePrinter(args[1]) + } else { + consolePrinter + } + try { + decompiler.print(printer) + } finally { + printer.close() + } } \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt index 5a83698..cf948a0 100644 --- a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt +++ b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt @@ -1,5 +1,6 @@ package com.github.tarcv.zandronum.debotc +import java.io.File import java.nio.charset.StandardCharsets /** @@ -29,4 +30,34 @@ actual fun StringBuilder.insertString(index: Int, string: String): StringBuilder actual fun assert(value: Boolean) { kotlin.assert(value) +} + +actual class FilePrinter actual constructor(path: String) : Printer { + private val writer = File(path).printWriter() + + override fun close() { + writer.close() + } + + override fun print(msg: String) { + writer.print(msg) + } + + override fun println(msg: String) { + writer.println(msg) + } +} + +actual object consolePrinter: Printer { + override fun close() { + // no op + } + + override fun print(msg: String) { + kotlin.io.print(msg) + } + + override fun println(msg: String) { + kotlin.io.println(msg) + } } \ No newline at end of file diff --git a/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt b/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt index 299c43d..aa6149f 100644 --- a/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt +++ b/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt @@ -27,4 +27,30 @@ actual val lineSeparator: String = "\n" // TODO: get system line separator actual fun assert(value: Boolean) { kotlin.assert(value) +} + +actual class FilePrinter actual constructor(private val path: String) : Printer { + private val file = fopen(args[0], "a") + + override fun close() { + fclose(file) + } + + override fun println(msg: String) { + TODO() + } +} + +actual object consolePrinter: Printer { + override fun close() { + // no op + } + + override fun print(msg: String) { + kotlin.io.print(msg) + } + + override fun println(msg: String) { + kotlin.io.println(msg) + } } \ No newline at end of file From 3456713f3f1b078c357b4ec298397afca5903be7 Mon Sep 17 00:00:00 2001 From: TarCV Date: Sun, 13 Oct 2019 03:30:16 +0300 Subject: [PATCH 2/6] Refactor parsing commands for SRP --- .../tarcv/zandronum/debotc/BotCommand.kt | 2 +- .../tarcv/zandronum/debotc/DataHeaders.kt | 306 ++++++++++++++++-- .../tarcv/zandronum/debotc/Decompiler.kt | 96 +++--- .../github/tarcv/zandronum/debotc/Nodes.kt | 4 +- .../zandronum/debotc/CounterexamplesTest.kt | 2 +- 5 files changed, 325 insertions(+), 85 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt index 5efd09d..ffd37d7 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt @@ -121,7 +121,7 @@ enum class BotCommand( val addsTo = when(returnType) { RETURNVAL_INT, RETURNVAL_BOOLEAN -> ADDS_TO_NORMAL_STACK RETURNVAL_STRING -> ADDS_TO_STRING_STACK - RETURNVAL_VOID -> DONT_PUSHES_TO_STACK + RETURNVAL_VOID -> DOES_NOT_PUSH_TO_STACK } return FunctionNode(readableName, args, addsTo) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt index 28f46a0..bc903c5 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt @@ -4,9 +4,21 @@ import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo.* import com.github.tarcv.zandronum.debotc.StackChangingNode.ArgumentHolder.Companion.createNormalStackArgument import com.github.tarcv.zandronum.debotc.StackChangingNode.Companion.consumesNormalStack +@ExperimentalUnsignedTypes enum class DataHeaders(requiredArgs: Int = 0) { DH_COMMAND(2) { + override fun parseCommand(parseState: ParseState): Command? { + val positionBefore = parseState.data.offset - 4 + val command = parseState.data.readSigned32() + if (command < 0 || command >= BotCommand.NUM_BOTCMDS.ordinal) { + throw IllegalStateException("Illegal command $command at offset $positionBefore") + } + val argument = parseState.data.readSigned32() + + return Command(positionBefore, parseState.data.offset, DH_COMMAND, command, argument) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { val botCommand: BotCommand = toEnum(command.arguments[0]) val expectedSecondArg = botCommand.numArgs + botCommand.numStringArgs @@ -49,149 +61,252 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode = throw IllegalStateException() }, DH_IFGOTO(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1, isGoto = true) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return IfGotoNode(createNormalStackArgument(0), command.arguments[0]) } }, DH_IFNOTGOTO(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1, isGoto = true) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return IfNotGotoNode(createNormalStackArgument(0), command.arguments[0]) } }, DH_GOTO(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1, isGoto = true) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return GotoNode(command.arguments[0]) } }, DH_ORLOGICAL { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("||", 2) } }, DH_ANDLOGICAL { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("&&", 2) } }, DH_ORBITWISE { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("|", 2) } }, DH_EORBITWISE { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("^", 2) } }, DH_ANDBITWISE { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("&", 2) } }, DH_EQUALS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("==", 2) } }, DH_NOTEQUALS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("!=", 2) } }, DH_LESSTHAN { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("<", 2) } }, DH_LESSTHANEQUALS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("<=", 2) } }, DH_GREATERTHAN { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode(">", 2) } }, DH_GREATERTHANEQUALS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode(">=", 2) } }, DH_NEGATELOGICAL { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("!", 1) } }, DH_LSHIFT { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("<<", 2) } }, DH_RSHIFT { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode(">>", 2) } }, DH_ADD { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("+", 2) } }, DH_SUBTRACT { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("-", 2) } }, DH_UNARYMINUS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("-", 1) } }, DH_MULTIPLY { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("*", 2) } }, DH_DIVIDE { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("/", 2) } }, DH_MODULUS { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return OperatorNode("%", 2) } }, DH_PUSHNUMBER { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return LiteralNode(command.arguments[0].toString(), ADDS_TO_NORMAL_STACK) } }, DH_PUSHSTRINGINDEX { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return LiteralNode("\"${vmState.strings[command.arguments[0]]}\"", ADDS_TO_STRING_STACK) - } }, DH_PUSHGLOBALVAR { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return LiteralNode("\$global${command.arguments[0]}", ADDS_TO_NORMAL_STACK) @@ -199,6 +314,10 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_PUSHLOCALVAR { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return LiteralNode("\$local${command.arguments[0]}", ADDS_TO_NORMAL_STACK) @@ -206,6 +325,10 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_DROPSTACKPOSITION { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return dropFromStack() } @@ -221,102 +344,170 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_INCGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CommandNode("\$global${command.arguments[0]}++;") } }, DH_DECGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CommandNode("\$global${command.arguments[0]}--;") } }, DH_ASSIGNGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} = ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} = ${stackArgs[0]};" } } }, DH_ADDGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} += ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} += ${stackArgs[0]};" } } }, DH_SUBGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} -= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} -= ${stackArgs[0]};" } } }, DH_MULGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} *= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} *= ${stackArgs[0]};" } } }, DH_DIVGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} /= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} /= ${stackArgs[0]};" } } }, DH_MODGLOBALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} %= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} %= ${stackArgs[0]};" } } }, DH_INCLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK) { "\$local${command.arguments[0]}++;" } + return CustomStackConsumingNode(0, DOES_NOT_PUSH_TO_STACK) { "\$local${command.arguments[0]}++;" } } }, DH_DECLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK) { "\$local${command.arguments[0]}--;" } + return CustomStackConsumingNode(0, DOES_NOT_PUSH_TO_STACK) { "\$local${command.arguments[0]}--;" } } }, DH_ASSIGNLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} = ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} = ${stackArgs[0]};" } } }, DH_ADDLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} += ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} += ${stackArgs[0]};" } } }, DH_SUBLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} -= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} -= ${stackArgs[0]};" } } }, DH_MULLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} *= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} *= ${stackArgs[0]};" } } }, DH_DIVLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} /= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} /= ${stackArgs[0]};" } } }, DH_MODLOCALVAR(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} %= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} %= ${stackArgs[0]};" } } }, DH_CASEGOTO(2) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 2, isGoto = true) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return if (vmState.previousCommand != DH_CASEGOTO) { SwitchAndCaseNode(createNormalStackArgument(0), command.arguments[0].toString(), command.arguments[1]) @@ -326,68 +517,108 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_DROP { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return dropFromStack() } }, DH_INCGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]++;" } } }, DH_DECGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(1, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]--;" } } }, DH_ASSIGNGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] = ${stackArgs[1]};" } } }, DH_ADDGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] += ${stackArgs[1]};" } } }, DH_SUBGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] -= ${stackArgs[1]};" } } }, DH_MULGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] *= ${stackArgs[1]};" } } }, DH_DIVGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] /= ${stackArgs[1]};" } } }, DH_MODGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DOES_NOT_PUSH_TO_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] %= ${stackArgs[1]};" } } }, DH_PUSHGLOBALARRAY(1) { + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 1) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(1, ADDS_TO_NORMAL_STACK) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) @@ -395,6 +626,10 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_SWAP{ + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return object : StackChangingNode( consumesNormalStack(2), @@ -424,20 +659,43 @@ enum class DataHeaders(requiredArgs: Int = 0) } }, DH_ARRAYSET{ + override fun parseCommand(parseState: ParseState): Command? { + return parseCommandWithArg(parseState, this, 0) + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return FunctionNode("memset", consumesNormalStack(3), DONT_PUSHES_TO_STACK) + return FunctionNode("memset", consumesNormalStack(3), DOES_NOT_PUSH_TO_STACK) } }, NUM_DATAHEADERS { + override fun parseCommand(parseState: ParseState): Command? { + throw IllegalStateException("Must not be called") + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { throw IllegalStateException("Must not be called") } }; abstract fun processAndCreateNode(command: Command, vmState: VmState): BaseNode + + open fun parseCommand(parseState: ParseState): Command? = null } private fun dropFromStack(): BaseNode { return DropStackNode("// item dropped from stack") } + +@ExperimentalUnsignedTypes +private fun parseCommandWithArg(parseState: ParseState, commandHeader: DataHeaders, numberArguments: Int, isGoto: Boolean = false): Command { + val positionBefore = parseState.data.offset - 4 + val args = IntArray(numberArguments) + for (i in 0 until numberArguments) { + args[i] = parseState.data.readSigned32() + } + if (isGoto) { + parseState.labelPositions.add(args.last()) + } + return Command(positionBefore, parseState.data.offset, commandHeader, *args) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index e062995..9965ee7 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -1,6 +1,5 @@ package com.github.tarcv.zandronum.debotc -import com.github.tarcv.zandronum.debotc.BotCommand.NUM_BOTCMDS import com.github.tarcv.zandronum.debotc.DataHeaders.* import com.github.tarcv.zandronum.debotc.LiteralNode.Companion.consumedMarker import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo.* @@ -343,7 +342,7 @@ class Decompiler { data = Data(data0) - parseCommands() + parseCommands(consolePrinter) normalizeStates() } @@ -357,51 +356,49 @@ class Decompiler { } } - private fun parseCommands() { + private fun parseCommands(disasmDumper: Printer? = null) { + val parseState = ParseState(data, labelPositions) while (data.offset < data.data.size) { + val offset = data.offset val index = data.readSigned32() val commandHeader: DataHeaders = toEnum(index) - when (commandHeader) { - DH_COMMAND -> parseBotCommand(data) - DH_ONENTER, DH_MAINLOOP, DH_ONEXIT -> parseEventHandler(commandHeader) - DH_EVENT -> parseEvent(data) - DH_ENDONENTER, DH_ENDMAINLOOP, DH_ENDONEXIT, DH_ENDEVENT -> finalizeEvent() - DH_STATENAME -> parseStateName(data) - DH_STATEIDX -> throw IllegalStateException("DH_STATEIDX without DH_STATENAME is not supported") - DH_PUSHNUMBER, DH_PUSHSTRINGINDEX, DH_PUSHGLOBALVAR, DH_PUSHLOCALVAR, DH_INCGLOBALVAR, DH_DECGLOBALVAR, - DH_ASSIGNGLOBALVAR, DH_ADDGLOBALVAR, DH_SUBGLOBALVAR, DH_MULGLOBALVAR, - DH_DIVGLOBALVAR, DH_MODGLOBALVAR, DH_INCLOCALVAR, DH_DECLOCALVAR, DH_ASSIGNLOCALVAR, - DH_ADDLOCALVAR, DH_SUBLOCALVAR, DH_MULLOCALVAR, DH_DIVLOCALVAR, DH_MODLOCALVAR, - DH_INCGLOBALARRAY, DH_DECGLOBALARRAY, DH_ASSIGNGLOBALARRAY, DH_ADDGLOBALARRAY, - DH_SUBGLOBALARRAY, DH_MULGLOBALARRAY, DH_DIVGLOBALARRAY, DH_MODGLOBALARRAY, - DH_PUSHGLOBALARRAY - -> parseCommandWithArg(commandHeader, 1) - DH_IFGOTO, DH_IFNOTGOTO, DH_GOTO - -> parseCommandWithArg(commandHeader, 1, isGoto = true) - DH_SCRIPTVARLIST -> parseScriptVarList() - DH_CASEGOTO -> parseCommandWithArg(commandHeader, 2, isGoto = true) - DH_ORLOGICAL, DH_ANDLOGICAL, DH_ORBITWISE, DH_EORBITWISE, DH_ANDBITWISE, - DH_EQUALS, DH_NOTEQUALS, DH_DROPSTACKPOSITION, DH_LESSTHAN, DH_LESSTHANEQUALS, - DH_GREATERTHAN, DH_GREATERTHANEQUALS, DH_NEGATELOGICAL, DH_LSHIFT, DH_RSHIFT, - DH_ADD, DH_SUBTRACT, DH_UNARYMINUS, DH_MULTIPLY, DH_DIVIDE, DH_MODULUS, DH_DROP, - DH_SWAP, DH_ARRAYSET - -> parseCommandWithArg(commandHeader, 0) - DH_STRINGLIST -> addStrings() - else -> throw IllegalStateException("Unexpected header $commandHeader at ${data.offset}") + + if (disasmDumper != null) { + disasmDumper.println() + disasmDumper.print("$offset: ${commandHeader.name} ") + } + + val command = commandHeader.parseCommand(parseState) + if (command == null) { + when (commandHeader) { + DH_ONENTER, DH_MAINLOOP, DH_ONEXIT -> parseEventHandler(commandHeader) + DH_EVENT -> parseEvent(data) + DH_ENDONENTER, DH_ENDMAINLOOP, DH_ENDONEXIT, DH_ENDEVENT -> finalizeEvent() + DH_STATENAME -> parseStateName(data) + DH_STATEIDX -> throw IllegalStateException("DH_STATEIDX without DH_STATENAME is not supported") + DH_SCRIPTVARLIST -> parseScriptVarList() + DH_STRINGLIST -> addStrings() + + else -> throw IllegalStateException("Unexpected header $commandHeader at ${data.offset}") + } + } else { + currentState.currentEvent.addCommand(command) } } } - private fun parseScriptVarList() { + private fun parseScriptVarList(): Command? { currentState.currentEvent.varList = data.readSigned32() + return null } - private fun finalizeEvent() { + private fun finalizeEvent(): Command? { eventEnds.add((data.offset - 4).toString()) currentState.currentEvent.finalize() + return null } - private fun addStrings() { + private fun addStrings(): Command? { val numStrings = data.readSigned32() if (strings.isNotEmpty()) { throw IllegalStateException("Strings were already added") @@ -411,18 +408,7 @@ class Decompiler { val str = data.readSzString(length) strings.add(str) } - } - - private fun parseCommandWithArg(commandHeader: DataHeaders, numberArguments: Int, isGoto: Boolean = false) { - val positionBefore = data.offset - 4 - val args = IntArray(numberArguments) - for (i in 0 until numberArguments) { - args[i] = data.readSigned32() - } - if (isGoto) { - labelPositions.add(args.last()) - } - currentState.currentEvent.addCommand(Command(positionBefore, data.offset, commandHeader, *args)) + return null } private fun parseStateName(data: Data) { @@ -441,16 +427,6 @@ class Decompiler { states.add(com.github.tarcv.zandronum.debotc.State(name, stateIndex)) } - private fun parseBotCommand(data: Data) { - val positionBefore = data.offset - 4 - val command = data.readSigned32() - if (command < 0 || command >= NUM_BOTCMDS.ordinal) { - throw IllegalStateException("Illegal command $command in ${currentState.name}") - } - val argument = data.readSigned32() - currentState.currentEvent.addCommand(Command(positionBefore, data.offset, DH_COMMAND, command, argument)) - } - private fun parseEvent(data: Data) { val eventType: BotEventType = toEnum(data.readSigned32()) if (currentState.global) { @@ -543,7 +519,7 @@ class Data constructor( private set } -val changingAddTos = StackChangingNode.AddsTo.values().filter { it != DONT_PUSHES_TO_STACK } +val changingAddTos = StackChangingNode.AddsTo.values().filter { it != DOES_NOT_PUSH_TO_STACK } fun inlineStackArgs(node: BaseNode): Boolean { var changed = false @@ -624,7 +600,7 @@ private fun tryLiteralizeNextNode(node: BaseNode): Boolean { assert(BotCommand.BOTCMD_DELAY.returnType == BotCommandReturnType.RETURNVAL_VOID) val returns = nextNode.returns() - val replacingNode = when (returns.count { it.addsTo != DONT_PUSHES_TO_STACK }) { + val replacingNode = when (returns.count { it.addsTo != DOES_NOT_PUSH_TO_STACK }) { 0 -> { if (returns.size != 1) throw AssertionError() CommandNode(returns[0].addsTo.asText() + returns[0].value) @@ -965,5 +941,11 @@ fun packSwitchBlockToText(node: BaseNode): Boolean { return changed } +@ExperimentalUnsignedTypes +class ParseState( + val data: Data, + val labelPositions: HashSet +) + private fun String.indent() = this.replace(Regex("^", MULTILINE), "\t") \ No newline at end of file diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index a943dc3..b770974 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -163,7 +163,7 @@ abstract class StackChangingNode( ) : BaseNode("", outputNum), ConsumesStack { enum class AddsTo { - DONT_PUSHES_TO_STACK { + DOES_NOT_PUSH_TO_STACK { override fun asText(): String = "" }, ADDS_TO_NORMAL_STACK { @@ -313,7 +313,7 @@ class LiteralNode(pairs: List>) class DropStackNode(override val asText: String) : StackChangingNode( listOf(createNormalStackArgument(0)), - arrayOf(ReturnPrototype(DONT_PUSHES_TO_STACK, { arguments -> "// dropped '${arguments[0]}'" })) + arrayOf(ReturnPrototype(DOES_NOT_PUSH_TO_STACK, { arguments -> "// dropped '${arguments[0]}'" })) ) open class LabelNode(byte: Int) : BaseNode("label$byte", 1) diff --git a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt index bcc62e3..d4326d4 100644 --- a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt +++ b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt @@ -12,7 +12,7 @@ class CounterexamplesTest { val rootNode = BeginNode() rootNode .attachNode(FunctionNode("IsEnemyVisible", emptyList(), StackChangingNode.AddsTo.ADDS_TO_NORMAL_STACK)) - .attachNode(FunctionNode("ClearEnemy", emptyList(), StackChangingNode.AddsTo.DONT_PUSHES_TO_STACK)) + .attachNode(FunctionNode("ClearEnemy", emptyList(), StackChangingNode.AddsTo.DOES_NOT_PUSH_TO_STACK)) .attachNode(FunctionNode("Function", StackChangingNode.consumesNormalStack(1), StackChangingNode.AddsTo.ADDS_TO_NORMAL_STACK)) .attachNode(EndNode()) return rootNode From 405483b97d8f987d60188b6bb7ec7d1b13edd622 Mon Sep 17 00:00:00 2001 From: TarCV Date: Sun, 26 Apr 2020 01:07:05 +0300 Subject: [PATCH 3/6] Add --disam argument --- .../tarcv/zandronum/debotc/Decompiler.kt | 4 +-- .../com/github/tarcv/zandronum/debotc/Main.kt | 27 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index 9965ee7..051cad9 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -334,7 +334,7 @@ class Decompiler { } } - fun parse(data0: UByteArray) { + fun parse(data0: UByteArray, disasmPrinter: Printer?) { if (alreadyParsed) { throw IllegalStateException("parse() can be called only once") } @@ -342,7 +342,7 @@ class Decompiler { data = Data(data0) - parseCommands(consolePrinter) + parseCommands(disasmPrinter) normalizeStates() } diff --git a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt index b4e86a3..cc63568 100644 --- a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt +++ b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt @@ -4,7 +4,15 @@ import java.nio.file.Files import java.nio.file.Paths @ExperimentalUnsignedTypes -fun main(args: Array) { +fun main(rawArgs: Array) { + val args = rawArgs.toMutableList() + + val disasmMode = if (args[0] == "--disasm") { + args.removeAt(0) + true + } else { + false + } if (args.size != 1 && args.size != 2) { throw IllegalArgumentException("Wrong arguments${System.lineSeparator()}debotc []") } @@ -12,17 +20,20 @@ fun main(args: Array) { val data = Files.readAllBytes(Paths.get(args[0])) val data0 = data.toUByteArray() - val decompiler = Decompiler() - decompiler.parse(data0) - val printer = if (args.size == 2) { FilePrinter(args[1]) } else { consolePrinter } - try { - decompiler.print(printer) - } finally { - printer.close() + + printer.use { p -> + val decompiler = Decompiler() + + if (disasmMode) { + decompiler.parse(data0, p) + } else { + decompiler.parse(data0, null) + decompiler.print(p) + } } } \ No newline at end of file From e54190e26fc2023d5e1bd01855cf83e27f8aeef6 Mon Sep 17 00:00:00 2001 From: TarCV Date: Sun, 26 Apr 2020 02:32:35 +0300 Subject: [PATCH 4/6] Restore native support --- build.gradle | 2 +- .../com/github/tarcv/zandronum/debotc/Main.kt | 20 ++++--- .../com/github/tarcv/zandronum/debotc/Util.kt | 7 ++- .../github/tarcv/zandronum/debotc/UtilsJvm.kt | 19 ++++++- src/nativeMain/kotlin/Main.kt | 31 ----------- src/nativeMain/kotlin/MainNative.kt | 3 ++ .../tarcv/zandronum/debotc/UtilsNative.kt | 52 +++++++++++++++++-- 7 files changed, 86 insertions(+), 48 deletions(-) rename src/{jvmMain => commonMain}/kotlin/com/github/tarcv/zandronum/debotc/Main.kt (56%) delete mode 100644 src/nativeMain/kotlin/Main.kt create mode 100644 src/nativeMain/kotlin/MainNative.kt diff --git a/build.gradle b/build.gradle index 5f4a317..f0211fe 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.jetbrains.kotlin.multiplatform' version '1.3.72' + id 'org.jetbrains.kotlin.multiplatform' version '1.3.70' id 'com.github.johnrengelman.shadow' version '5.0.0' } diff --git a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt similarity index 56% rename from src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt rename to src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt index cc63568..378b75b 100644 --- a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Main.kt @@ -1,24 +1,20 @@ package com.github.tarcv.zandronum.debotc -import java.nio.file.Files -import java.nio.file.Paths - @ExperimentalUnsignedTypes fun main(rawArgs: Array) { val args = rawArgs.toMutableList() - val disasmMode = if (args[0] == "--disasm") { + val disasmMode = if (args.isNotEmpty() && args[0] == "--disasm") { args.removeAt(0) true } else { false } if (args.size != 1 && args.size != 2) { - throw IllegalArgumentException("Wrong arguments${System.lineSeparator()}debotc []") + throw IllegalArgumentException("Wrong arguments${lineSeparator}debotc [--disasm] []") } - val data = Files.readAllBytes(Paths.get(args[0])) - val data0 = data.toUByteArray() + val data0: UByteArray = readAllBytes(args[0]) val printer = if (args.size == 2) { FilePrinter(args[1]) @@ -26,14 +22,16 @@ fun main(rawArgs: Array) { consolePrinter } - printer.use { p -> + try { val decompiler = Decompiler() if (disasmMode) { - decompiler.parse(data0, p) + decompiler.parse(data0, printer) } else { decompiler.parse(data0, null) - decompiler.print(p) + decompiler.print(printer) } + } finally { + printer.close() } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt index eaaa7ed..d020c0b 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Util.kt @@ -13,9 +13,14 @@ expect fun StringBuilder.appendLine(line: StringBuilder): StringBuilder expect fun StringBuilder.insertString(index: Int, string: String): StringBuilder +expect fun String.Companion.format(format: String, arg: Int): String + expect fun ByteArray.stringFromUtf8BytesOrThrow(start: Int = 0, size: Int = this.size): String -interface Printer { +@ExperimentalUnsignedTypes +expect fun readAllBytes(path: String): UByteArray + +expect interface Printer { fun println(msg: String = "") fun close() diff --git a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt index cf948a0..8c00119 100644 --- a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt +++ b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt @@ -1,13 +1,20 @@ package com.github.tarcv.zandronum.debotc +import java.io.Closeable import java.io.File import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Paths /** * JVM implementations of platform-dependent helpers */ actual val lineSeparator: String = System.lineSeparator() +actual fun String.Companion.format(format: String, arg: Int): String { + return String.format(format, arg) +} + actual fun StringBuilder.appendLine(): StringBuilder { return this.appendln() } @@ -32,6 +39,12 @@ actual fun assert(value: Boolean) { kotlin.assert(value) } +actual interface Printer: Closeable { + actual fun println(msg: String) + actual override fun close() + actual fun print(msg: String) +} + actual class FilePrinter actual constructor(path: String) : Printer { private val writer = File(path).printWriter() @@ -60,4 +73,8 @@ actual object consolePrinter: Printer { override fun println(msg: String) { kotlin.io.println(msg) } -} \ No newline at end of file +} + +actual fun readAllBytes(path: String): UByteArray + return Files.readAllBytes(filePath).toUByteArray() +} diff --git a/src/nativeMain/kotlin/Main.kt b/src/nativeMain/kotlin/Main.kt deleted file mode 100644 index 5e94eec..0000000 --- a/src/nativeMain/kotlin/Main.kt +++ /dev/null @@ -1,31 +0,0 @@ -import kotlinx.cinterop.* -import platform.posix.* -import com.github.tarcv.zandronum.debotc.Decompiler - -fun main(args: Array) { - if (args.size != 1) { - throw IllegalArgumentException("Only one argument - path to compiled script should be passed") - } - var data0 = UByteArray(0) - - val file = fopen(args[0], "rb") - try { - fseek(file, 0, SEEK_END) - val size = ftell(file).toInt(); - rewind(file) - memScoped { - val buffer = allocArray(size+1) - val elementsRead = fread(buffer, size.convert(), 1, file).toInt() - if (elementsRead != 1) { - throw IllegalStateException("Failed to read file") - } - data0 = buffer.readBytes(size).toUByteArray() - } - } finally { - fclose(file) - } - - val decompiler = Decompiler() - decompiler.parse(data0) - decompiler.print() -} \ No newline at end of file diff --git a/src/nativeMain/kotlin/MainNative.kt b/src/nativeMain/kotlin/MainNative.kt new file mode 100644 index 0000000..2d5b85a --- /dev/null +++ b/src/nativeMain/kotlin/MainNative.kt @@ -0,0 +1,3 @@ +fun main(args: Array) { + com.github.tarcv.zandronum.debotc.main(args) +} \ No newline at end of file diff --git a/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt b/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt index aa6149f..fe5c984 100644 --- a/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt +++ b/src/nativeMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsNative.kt @@ -1,8 +1,21 @@ package com.github.tarcv.zandronum.debotc +import platform.posix.* +import kotlinx.cinterop.* + /** * Native implementations of platform-dependent helpers */ + +actual fun String.Companion.format(format: String, arg: Int): String { + return memScoped { + val size = snprintf(null, 0, format, arg) + 1 + val buffer = allocArray(size) + snprintf(buffer, size.toULong(), format, arg) + buffer.toKString() + } +} + actual fun StringBuilder.appendLine(): StringBuilder { return this.appendln() } @@ -29,15 +42,26 @@ actual fun assert(value: Boolean) { kotlin.assert(value) } -actual class FilePrinter actual constructor(private val path: String) : Printer { - private val file = fopen(args[0], "a") +actual interface Printer { + actual fun println(msg: String) + actual fun close() + actual fun print(msg: String) +} + +actual class FilePrinter actual constructor(path: String) : Printer { + private val file = fopen(path, "a") override fun close() { fclose(file) } + override fun print(msg: String) { + fputs(msg, file) + } + override fun println(msg: String) { - TODO() + fputs(msg, file) + fputs(lineSeparator, file) } } @@ -53,4 +77,26 @@ actual object consolePrinter: Printer { override fun println(msg: String) { kotlin.io.println(msg) } +} + +actual fun readAllBytes(path: String): UByteArray { + val file = fopen(path, "rb") ?: throw IllegalStateException("Failed to read file") + + try { + fseek(file, 0, SEEK_END) + val size = ftell(file).toInt(); + rewind(file) + return memScoped { + val buffer = allocArray(size + 1) + val elementsRead = fread(buffer, size.convert(), 1, file).toInt() + if (elementsRead != 1) { + throw IllegalStateException("Failed to read file") + } + + buffer.readBytes(size).toUByteArray() + } + } finally { + fclose(file) + } + } \ No newline at end of file From 9f3d39b66cc18f7580bf3ca8ec10772ae2f94e23 Mon Sep 17 00:00:00 2001 From: TarCV Date: Sun, 26 Apr 2020 02:39:06 +0300 Subject: [PATCH 5/6] Add readable descriptions for disasm --- .../tarcv/zandronum/debotc/DataHeaders.kt | 49 +++++++++++++ .../tarcv/zandronum/debotc/Decompiler.kt | 71 ++++++++++++++----- .../github/tarcv/zandronum/debotc/Nodes.kt | 4 +- 3 files changed, 106 insertions(+), 18 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt index bc903c5..bdde726 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt @@ -47,6 +47,10 @@ enum class DataHeaders(requiredArgs: Int = 0) }, DH_EVENT { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode = throw IllegalStateException() + + override fun describeReadableForDisasm(command: Command): String { + throw IllegalStateException("Should not happen") + } }, DH_ENDONENTER { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode = throw IllegalStateException() @@ -59,6 +63,8 @@ enum class DataHeaders(requiredArgs: Int = 0) }, DH_ENDEVENT { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode = throw IllegalStateException() + + override fun describeReadableForDisasm(command: Command): String = "}" }, DH_IFGOTO(1) { override fun parseCommand(parseState: ParseState): Command? { @@ -301,6 +307,11 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return LiteralNode("\"${vmState.strings[command.arguments[0]]}\"", ADDS_TO_STRING_STACK) } + + override fun describeReadableForDisasm(command: Command): String { + val cmd = command as Command + return "stringStack.push(strings[${cmd.arguments[0]}])" + } }, DH_PUSHGLOBALVAR { override fun parseCommand(parseState: ParseState): Command? { @@ -508,6 +519,10 @@ enum class DataHeaders(requiredArgs: Int = 0) return parseCommandWithArg(parseState, this, 2, isGoto = true) } + override fun describeReadableForDisasm(command: Command): String { + return "if (stack[0] == ${command.arguments[0]}) goto label${String.format("%X", command.arguments[1])}" + } + override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return if (vmState.previousCommand != DH_CASEGOTO) { SwitchAndCaseNode(createNormalStackArgument(0), command.arguments[0].toString(), command.arguments[1]) @@ -681,8 +696,42 @@ enum class DataHeaders(requiredArgs: Int = 0) abstract fun processAndCreateNode(command: Command, vmState: VmState): BaseNode open fun parseCommand(parseState: ParseState): Command? = null + + open fun describeReadableForDisasm(command: Command): String { + var text = processAndCreateNode(command, VmState(emptyList(), HashSet(), HashSet(), HashSet())).asText + while (text.endsWith(";")) { + text = text.removeSuffix(";").trim() + } + while( text.startsWith("(") && text.endsWith(")")) { + text = text.removePrefix("(").removeSuffix(")").trim() + } + if (text.startsWith("stack = ")) { + text = "stack.push( ${text.removePrefix("stack = ")} )" + } + if (text.contains("stack[") || text.contains("stringStack")) { + text += " // stack arguments are popped by the function" + } + text = text.replace(Regex("(?<=[\\s|^])goto label(?=[\\dA-F])"), "goto 0x") + return text + } + + fun describeForDisasm(command: Command?): DisasmDescription { + if (command != null) { + return DisasmDescription( + command.arguments.joinToString(", ") ?: "", + describeReadableForDisasm(command) + ) + } else { + throw IllegalStateException("Should not be called for non commands") + } + } } +class DisasmDescription( + val arguments: String, + val readable: String +) + private fun dropFromStack(): BaseNode { return DropStackNode("// item dropped from stack") } diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index 051cad9..822abee 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -312,7 +312,7 @@ class Decompiler { gotos.forEach { val targetByte = it.targetByte - it.jumpTargetNode = labels[targetByte] ?: throw IllegalStateException("Label$targetByte not found") + it.jumpTargetNode = labels[targetByte] ?: throw IllegalStateException("Label${String.format("%X", targetByte)} not found") if (it is GotoNode) { replaceGotoWithEdge(it) @@ -365,12 +365,14 @@ class Decompiler { if (disasmDumper != null) { disasmDumper.println() - disasmDumper.print("$offset: ${commandHeader.name} ") + disasmDumper.print("${String.format("%08X", offset)} | ${commandHeader.name} ") } val command = commandHeader.parseCommand(parseState) + + val disasmDescription: DisasmDescription if (command == null) { - when (commandHeader) { + disasmDescription = when (commandHeader) { DH_ONENTER, DH_MAINLOOP, DH_ONEXIT -> parseEventHandler(commandHeader) DH_EVENT -> parseEvent(data) DH_ENDONENTER, DH_ENDMAINLOOP, DH_ENDONEXIT, DH_ENDEVENT -> finalizeEvent() @@ -382,23 +384,37 @@ class Decompiler { else -> throw IllegalStateException("Unexpected header $commandHeader at ${data.offset}") } } else { + disasmDescription = commandHeader.describeForDisasm(command) currentState.currentEvent.addCommand(command) } + + if (disasmDumper != null) { + disasmDumper.print(disasmDescription.arguments) + disasmDumper.print(" ".repeat(max(0, 30 - disasmDescription.arguments.length - commandHeader.name.length))) + disasmDumper.print(" | ${disasmDescription.readable}") + } } } - private fun parseScriptVarList(): Command? { - currentState.currentEvent.varList = data.readSigned32() - return null + private fun parseScriptVarList(): DisasmDescription { + val varList = data.readSigned32() + currentState.currentEvent.varList = varList + return DisasmDescription( + varList.toString(), + "// define $varList script variables" + ) } - private fun finalizeEvent(): Command? { + private fun finalizeEvent(): DisasmDescription { eventEnds.add((data.offset - 4).toString()) currentState.currentEvent.finalize() - return null + return DisasmDescription( + "", + "}" + ) } - private fun addStrings(): Command? { + private fun addStrings(): DisasmDescription { val numStrings = data.readSigned32() if (strings.isNotEmpty()) { throw IllegalStateException("Strings were already added") @@ -408,10 +424,15 @@ class Decompiler { val str = data.readSzString(length) strings.add(str) } - return null + + val joinedStrings = strings.joinToString(", ") { "\"$it\"" } + return DisasmDescription( + "$numStrings, $joinedStrings", + "strings = {$joinedStrings}" + ) } - private fun parseStateName(data: Data) { + private fun parseStateName(data: Data): DisasmDescription { val size = data.readSigned32() val name = data.readSzString(size) val stateIndexHeader = data.readSigned32() @@ -424,10 +445,16 @@ class Decompiler { throw IllegalStateException("Illegal stateIndex: $stateIndex") } - states.add(com.github.tarcv.zandronum.debotc.State(name, stateIndex)) + val state = State(name, stateIndex) + states.add(state) + + return DisasmDescription( + "\"$name\" (DH_STATEIDX $stateIndex)", + "state ${state.name} {" + ) } - private fun parseEvent(data: Data) { + private fun parseEvent(data: Data): DisasmDescription { val eventType: BotEventType = toEnum(data.readSigned32()) if (currentState.global) { if (currentState.events.size >= MAX_NUM_GLOBAL_EVENTS) { @@ -438,15 +465,27 @@ class Decompiler { throw IllegalStateException("Too many local events in the bot script") } } - currentState.events.add(BotEvent(eventType)) + val event = BotEvent(eventType) + currentState.events.add(event) + + return DisasmDescription( + eventType.name, + "${event.botcTitle} {" + ) } - private fun parseEventHandler(event: DataHeaders) { + private fun parseEventHandler(event: DataHeaders): DisasmDescription { if (currentState.global) { throw IllegalStateException("$event outside of a state definition") } - currentState.events.add(WorldEvent(event)) + val event = WorldEvent(event) + currentState.events.add(event) + + return DisasmDescription( + "", + event.botcTitle + " {" + ) } private var alreadyParsed: Boolean = false diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index b770974..9691319 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -316,13 +316,13 @@ class DropStackNode(override val asText: String) arrayOf(ReturnPrototype(DOES_NOT_PUSH_TO_STACK, { arguments -> "// dropped '${arguments[0]}'" })) ) -open class LabelNode(byte: Int) : BaseNode("label$byte", 1) +open class LabelNode(byte: Int) : BaseNode("label${String.format("%X", byte)}", 1) interface JumpingNode { var jumpTargetNode: BaseNode } -open class AbstractGotoNode(val targetByte: Int) : BaseNode("goto label$targetByte", 2), JumpingNode { +open class AbstractGotoNode(val targetByte: Int) : BaseNode("goto label${String.format("%X", targetByte)}", 2), JumpingNode { override var jumpTargetNode: BaseNode get() = outputs[1] set(value) { From 8fa5fecb08d924797006c0e7118d1764be6827ed Mon Sep 17 00:00:00 2001 From: TarCV Date: Sun, 26 Apr 2020 02:50:14 +0300 Subject: [PATCH 6/6] Fix JVM build --- .../kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt index 8c00119..8a722b4 100644 --- a/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt +++ b/src/jvmMain/kotlin/com/github/tarcv/zandronum/debotc/UtilsJvm.kt @@ -12,7 +12,7 @@ import java.nio.file.Paths actual val lineSeparator: String = System.lineSeparator() actual fun String.Companion.format(format: String, arg: Int): String { - return String.format(format, arg) + return String.format(format, *arrayOf(arg)) } actual fun StringBuilder.appendLine(): StringBuilder { @@ -75,6 +75,7 @@ actual object consolePrinter: Printer { } } -actual fun readAllBytes(path: String): UByteArray - return Files.readAllBytes(filePath).toUByteArray() +@ExperimentalUnsignedTypes +actual fun readAllBytes(path: String): UByteArray { + return Files.readAllBytes(Paths.get(path)).toUByteArray() }