diff --git a/src/main/java/com/smallhacker/disbrowser/Service.kt b/src/main/java/com/smallhacker/disbrowser/Service.kt index ea5f349..62a7904 100644 --- a/src/main/java/com/smallhacker/disbrowser/Service.kt +++ b/src/main/java/com/smallhacker/disbrowser/Service.kt @@ -3,7 +3,6 @@ package com.smallhacker.disbrowser import com.smallhacker.disbrowser.asm.* import com.smallhacker.disbrowser.game.Game import com.smallhacker.disbrowser.disassembler.Disassembler -import com.smallhacker.disbrowser.game.GameData import com.smallhacker.disbrowser.util.toUInt24 import kotlin.reflect.KMutableProperty1 @@ -34,14 +33,13 @@ object Service { return showDisassembly(game, initialAddress, flags) } - fun showDisassembly(game: Game, initialAddress: SnesAddress, flags: VagueNumber): HtmlNode? { + fun showDisassembly(game: Game, initialAddress: SnesAddress, flags: VagueNumber, global: Boolean = false): HtmlNode? { val initialState = State(memory = game.memory, address = initialAddress, flags = flags, gameData = game.gameData) - val disassembly = Disassembler.disassemble(initialState, game.gameData, false) + val disassembly = Disassembler.disassemble(initialState, game.gameData, global) return print(disassembly, game) } - private fun print(disassembly: Disassembly, game: Game): HtmlNode { val grid = Grid() disassembly.forEach { diff --git a/src/main/java/com/smallhacker/disbrowser/disassembler/Disassembler.kt b/src/main/java/com/smallhacker/disbrowser/disassembler/Disassembler.kt index 15d1e17..5fa94d9 100644 --- a/src/main/java/com/smallhacker/disbrowser/disassembler/Disassembler.kt +++ b/src/main/java/com/smallhacker/disbrowser/disassembler/Disassembler.kt @@ -62,7 +62,7 @@ object Disassembler { } } - remoteFlags.forFlag { + remoteFlags.forFlag { ins.continuation = Continuation.STOP if (pointerTableEntries != null) { @@ -158,7 +158,7 @@ object Disassembler { end.local.forEach { queue.add(it) } end.remote.forEach { - + queue.add(it) } } @@ -191,12 +191,11 @@ object Disassembler { val ins = disassembleInstruction(state) instructions.add(ins) - println(ins) + //println(ins) val segmentEnd = ins.opcode.ender(ins) if (segmentEnd != null) { - return finalize(Segment(initialState.address, segmentEnd, instructions)) } diff --git a/src/main/java/com/smallhacker/disbrowser/game/GameData.kt b/src/main/java/com/smallhacker/disbrowser/game/GameData.kt index d981fe8..bf38c0b 100644 --- a/src/main/java/com/smallhacker/disbrowser/game/GameData.kt +++ b/src/main/java/com/smallhacker/disbrowser/game/GameData.kt @@ -77,6 +77,7 @@ operator fun GameData.get(address: SnesAddress?) = if (address == null) null els Type(value = NonReturningRoutine::class, name = "NonReturningRoutine"), Type(value = JmpIndirectLongInterleavedTable::class, name = "JmpIndirectLongInterleavedTable"), Type(value = JslTableRoutine::class, name = "JslTableRoutine"), + Type(value = JsrTableRoutine::class, name = "JsrTableRoutine"), Type(value = PointerTableLength::class, name = "PointerTableLength") ) interface InstructionFlag @@ -130,16 +131,15 @@ class JmpIndirectLongInterleavedTable @JsonCreator constructor( override fun toString() = "JmpIndirectLongInterleavedTable($start, $entries)" } -class JslTableRoutine : InstructionFlag { - fun readTable(postJsr: State, entryCount: Int): Sequence { - val data = postJsr.memory +interface DynamicJumpRoutine : InstructionFlag { + fun readTable(postJump: State, entryCount: Int): Sequence { return (0 until entryCount) .asSequence() - .map { postJsr.address + (it * 3) } - .map { address -> joinNullableBytes(data[address], data[address + 1], data[address + 2])?.toUInt24() } - .map { pointer -> pointer?.let { SnesAddress(it) } } + .map { readEntry(postJump, it) } } + fun readEntry(postJump: State, index: Int): SnesAddress? + fun generatePointerTable(jumpInstruction: Instruction, entryCount: UInt?): Sequence { val count: UInt var certainty: Certainty @@ -155,41 +155,93 @@ class JslTableRoutine : InstructionFlag { certaintyDecrease = 0 } - val start = jumpInstruction.postState.address - val memory = jumpInstruction.memory - - return (0u until count.toUInt()) + return (0u until count) .asSequence() - .map { index -> index * 3u } - .mapNotNull { offset -> - val pointerLoc= start + offset.toInt() - val addressRange = memory.range(pointerLoc, 3u).validate() - - if (addressRange == null) { - null - } else { - val target = addressRange.getLong(0u) - - val block = DataBlock( - Opcode.CODE_POINTER_LONG, - addressRange, - pointerLoc, - jumpInstruction.relativeAddress, - jumpInstruction.opcode.mutate(jumpInstruction) - .mutateAddress { SnesAddress(target) } - .withOrigin(jumpInstruction), - memory, - certainty - ) - certainty -= certaintyDecrease - block - } + .mapNotNull { index -> + generatePointer(jumpInstruction, index, certainty) + ?.also { + certainty -= certaintyDecrease + } } } + fun generatePointer(jumpInstruction: Instruction, index: UInt, certainty: Certainty): DataBlock? + + override fun toString(): String +} + +class JslTableRoutine : DynamicJumpRoutine { + override fun readEntry(postJump: State, index: Int): SnesAddress? { + val data = postJump.memory + val address = postJump.address + (index * 3) + return joinNullableBytes(data[address], data[address + 1], data[address + 2]) + ?.toUInt24() + ?.let { SnesAddress(it) } + } + + override fun generatePointer(jumpInstruction: Instruction, index: UInt, certainty: Certainty): DataBlock? { + val offset = index * 3u + val pointerLoc = jumpInstruction.postState.address + offset.toInt() + val addressRange = jumpInstruction.memory.range(pointerLoc, 3u).validate() + + if (addressRange == null) { + return null + } else { + val target = addressRange.getLong(0u) + + return DataBlock( + Opcode.CODE_POINTER_LONG, + addressRange, + pointerLoc, + jumpInstruction.relativeAddress, + jumpInstruction.opcode.mutate(jumpInstruction) + .mutateAddress { SnesAddress(target) } + .withOrigin(jumpInstruction), + jumpInstruction.memory, + certainty + ) + } + } + override fun toString() = "JslTableRoutine" } +class JsrTableRoutine : DynamicJumpRoutine { + override fun readEntry(postJump: State, index: Int): SnesAddress? { + val data = postJump.memory + val address = postJump.address + (index * 2) + return joinNullableBytes(data[address], data[address + 1], postJump.pb) + ?.toUInt24() + ?.let { SnesAddress(it) } + } + + override fun generatePointer(jumpInstruction: Instruction, index: UInt, certainty: Certainty): DataBlock? { + val offset = index * 2u + val pointerLoc = jumpInstruction.postState.address + offset.toInt() + val addressRange = jumpInstruction.memory.range(pointerLoc, 2u).validate() + + if (addressRange == null) { + return null + } else { + val target = addressRange.getWord(0u).toUInt24() or (jumpInstruction.postState.pb.toUInt24() shl 16) + + return DataBlock( + Opcode.CODE_POINTER_WORD, + addressRange, + pointerLoc, + jumpInstruction.relativeAddress, + jumpInstruction.opcode.mutate(jumpInstruction) + .mutateAddress { SnesAddress(target) } + .withOrigin(jumpInstruction), + jumpInstruction.memory, + certainty + ) + } + } + + override fun toString() = "JsrTableRoutine" +} + class PointerTableLength @JsonCreator constructor( @field:JsonProperty @JsonProperty val entries: Int ) : InstructionFlag { diff --git a/src/main/java/com/smallhacker/disbrowser/resource/DisassemblyResource.kt b/src/main/java/com/smallhacker/disbrowser/resource/DisassemblyResource.kt index 01bee9d..00695ed 100644 --- a/src/main/java/com/smallhacker/disbrowser/resource/DisassemblyResource.kt +++ b/src/main/java/com/smallhacker/disbrowser/resource/DisassemblyResource.kt @@ -3,7 +3,6 @@ package com.smallhacker.disbrowser.resource import com.smallhacker.disbrowser.* import com.smallhacker.disbrowser.asm.SnesAddress import com.smallhacker.disbrowser.asm.VagueNumber -import com.smallhacker.disbrowser.game.GameSource import com.smallhacker.disbrowser.game.getGameSource import java.nio.charset.StandardCharsets import javax.ws.rs.GET @@ -70,6 +69,32 @@ class DisassemblyResource { } } + @GET + @Path("global/{address}") + @Produces(MediaType.TEXT_HTML) + fun getItGlobal( + @PathParam("game") gameName: String, + @PathParam("address") address: String + ) = getItGlobal(gameName, address, "") + + @GET + @Path("global/{address}/{state}") + @Produces(MediaType.TEXT_HTML) + fun getItGlobal( + @PathParam("game") gameName: String, + @PathParam("address") address: String, + @PathParam("state") state: String + ): Response { + return handle { + games.getGame(gameName)?.let { game -> + SnesAddress.parse(address)?.let { + val flags = parseState(state) + Service.showDisassembly(game, it, flags, true) + } + } + } + } + private fun handle(runner: () -> HtmlNode?): Response { try { val output = runner()