From e6445575d7e31fa25ea190b5956f83849866dfa3 Mon Sep 17 00:00:00 2001 From: Smallhacker Date: Fri, 11 Jan 2019 21:16:50 -0500 Subject: [PATCH] More stuff --- ...nsetsu - Kamigami no Triforce (Japan).json | 1 + .../com/smallhacker/disbrowser/Service.kt | 15 ++- .../smallhacker/disbrowser/asm/Instruction.kt | 57 ++++++++++-- .../smallhacker/disbrowser/asm/MemorySpace.kt | 93 +++++++++++++++++++ .../smallhacker/disbrowser/asm/Metadata.kt | 44 +++++---- .../smallhacker/disbrowser/asm/Mnemonic.kt | 3 +- .../com/smallhacker/disbrowser/asm/Mode.kt | 3 + .../com/smallhacker/disbrowser/asm/Opcode.kt | 12 ++- .../com/smallhacker/disbrowser/asm/RomData.kt | 66 ------------- .../smallhacker/disbrowser/asm/SnesAddress.kt | 10 +- .../smallhacker/disbrowser/asm/SnesMapper.kt | 86 +++++++++++++++++ .../com/smallhacker/disbrowser/asm/State.kt | 11 ++- .../disbrowser/datatype/RangeMap.kt | 27 ++++++ .../disbrowser/disassembler/Disassembler.kt | 14 ++- .../com/smallhacker/disbrowser/util/misc.kt | 16 +++- 15 files changed, 330 insertions(+), 128 deletions(-) create mode 100644 src/main/java/com/smallhacker/disbrowser/asm/MemorySpace.kt delete mode 100644 src/main/java/com/smallhacker/disbrowser/asm/RomData.kt create mode 100644 src/main/java/com/smallhacker/disbrowser/asm/SnesMapper.kt create mode 100644 src/main/java/com/smallhacker/disbrowser/datatype/RangeMap.kt diff --git a/Zelda no Densetsu - Kamigami no Triforce (Japan).json b/Zelda no Densetsu - Kamigami no Triforce (Japan).json index 21e1e9e..4fbddb6 100644 --- a/Zelda no Densetsu - Kamigami no Triforce (Japan).json +++ b/Zelda no Densetsu - Kamigami no Triforce (Japan).json @@ -3,6 +3,7 @@ "008000" : { "label" : "ResetVector" }, + "00800a" : { }, "00801b" : { "comment" : "\\ Turn off emulation mode" }, diff --git a/src/main/java/com/smallhacker/disbrowser/Service.kt b/src/main/java/com/smallhacker/disbrowser/Service.kt index e76c849..7412ec1 100644 --- a/src/main/java/com/smallhacker/disbrowser/Service.kt +++ b/src/main/java/com/smallhacker/disbrowser/Service.kt @@ -10,28 +10,27 @@ import kotlin.reflect.KMutableProperty1 private val RESET_VECTOR_LOCATION = address(0x00_FFFC) object Service { - private val romName = "Zelda no Densetsu - Kamigami no Triforce (Japan)" + private const val romName = "Zelda no Densetsu - Kamigami no Triforce (Japan)" private val romDir = Paths.get("""P:\Emulation\ROMs\SNES""") private val metaDir = Paths.get("""P:\Programming\dis-browser""") private val metaFile = jsonFile(metaDir.resolve("$romName.json"), true) private val metadata by lazy { metaFile.load() } - private val romData = lazy { + private val snesMemory by lazy { val path = romDir.resolve("$romName.sfc") - RomData.load(path) + SnesLoRom(loadRomData(path)) } fun showDisassemblyFromReset(): HtmlNode? { - val resetVectorLocation = RESET_VECTOR_LOCATION - val initialAddress = SnesAddress(romData.value.getWord(resetVectorLocation.pc).toUInt24()) + val resetVector = snesMemory.getWord(RESET_VECTOR_LOCATION) + val fullResetVector = resetVector!!.toUInt24() + val initialAddress = SnesAddress(fullResetVector) val flags = VagueNumber(0x30u) return showDisassembly(initialAddress, flags) } fun showDisassembly(initialAddress: SnesAddress, flags: VagueNumber): HtmlNode? { - val rom = romData.value - - val initialState = State(data = rom, address = initialAddress, flags = flags) + val initialState = State(memory = snesMemory, address = initialAddress, flags = flags) val disassembly = Disassembler.disassemble(initialState, metadata, false) return print(disassembly, metadata) diff --git a/src/main/java/com/smallhacker/disbrowser/asm/Instruction.kt b/src/main/java/com/smallhacker/disbrowser/asm/Instruction.kt index 3a7eb20..1f9a6ca 100644 --- a/src/main/java/com/smallhacker/disbrowser/asm/Instruction.kt +++ b/src/main/java/com/smallhacker/disbrowser/asm/Instruction.kt @@ -12,7 +12,7 @@ interface CodeUnit { val preState: State? val postState: State? - val bytes: RomData + val bytes: ValidMemorySpace val opcode: Opcode val lengthSuffix: String? @@ -62,7 +62,7 @@ interface CodeUnit { class DataBlock( override val opcode: Opcode, - override val bytes: RomData, + override val bytes: ValidMemorySpace, override val presentedAddress: SnesAddress, override val relativeAddress: SnesAddress, override val linkedState: State? @@ -77,7 +77,7 @@ class DataBlock( override val lengthSuffix: String? = null } -class Instruction(override val bytes: RomData, override val opcode: Opcode, override val preState: State) : CodeUnit { +class Instruction(override val bytes: ValidMemorySpace, override val opcode: Opcode, override val preState: State) : CodeUnit { override val address: SnesAddress get() = preState.address override val relativeAddress get() = address override val presentedAddress get() = address @@ -115,18 +115,59 @@ class Instruction(override val bytes: RomData, override val opcode: Opcode, over return null } + return referencedAddress() + +// return when (opcode.mode) { +// Mode.ABSOLUTE_CODE -> preState.resolveAbsoluteCode(word) +// Mode.ABSOLUTE_LONG -> SnesAddress(long) +// Mode.RELATIVE -> relativeAddress + 2 + signedByte.toInt() +// Mode.RELATIVE_LONG -> relativeAddress + 3 + signedWord.toInt() +// Mode.CODE_WORD -> preState.resolveAbsoluteCode(word) +// Mode.CODE_LONG -> SnesAddress(long) +// Mode.DATA_WORD -> preState.resolveAbsoluteData(word) +// Mode.DATA_LONG -> SnesAddress(long) +// else -> null +// } + } + + private fun referencedAddress(): SnesAddress? { return when (opcode.mode) { - Mode.ABSOLUTE -> relativeAddress.withinBank(word) + Mode.ABSOLUTE -> preState.resolveAbsoluteData(word) + Mode.ABSOLUTE_CODE -> preState.resolveAbsoluteCode(word) + Mode.ABSOLUTE_INDIRECT -> preState.resolveAbsoluteData(word) + Mode.ABSOLUTE_INDIRECT_LONG -> preState.resolveAbsoluteData(word) Mode.ABSOLUTE_LONG -> SnesAddress(long) + Mode.ABSOLUTE_LONG_X -> SnesAddress(long) + Mode.ABSOLUTE_X -> preState.resolveAbsoluteData(word) + Mode.ABSOLUTE_X_INDIRECT -> preState.resolveAbsoluteData(word) + Mode.ABSOLUTE_Y -> preState.resolveAbsoluteData(word) + Mode.BLOCK_MOVE -> null + Mode.CODE_WORD -> preState.resolveAbsoluteCode(word) + Mode.CODE_LONG -> SnesAddress(long) + Mode.DATA_BYTE -> null + Mode.DATA_WORD -> preState.resolveAbsoluteData(word) + Mode.DATA_LONG -> SnesAddress(long) + Mode.DIRECT -> preState.resolveDirectPage(byte) + Mode.DIRECT_X -> preState.resolveDirectPage(byte) + Mode.DIRECT_Y -> preState.resolveDirectPage(byte) + Mode.DIRECT_S -> null + Mode.DIRECT_INDIRECT -> preState.resolveDirectPage(byte) + Mode.DIRECT_INDIRECT_Y -> preState.resolveDirectPage(byte) + Mode.DIRECT_X_INDIRECT -> preState.resolveDirectPage(byte) + Mode.DIRECT_S_INDIRECT_Y -> null + Mode.DIRECT_INDIRECT_LONG -> preState.resolveDirectPage(byte) + Mode.DIRECT_INDIRECT_LONG_Y -> preState.resolveDirectPage(byte) + Mode.IMMEDIATE_8 -> null + Mode.IMMEDIATE_16 -> null + Mode.IMMEDIATE_M -> null + Mode.IMMEDIATE_X -> null + Mode.IMPLIED -> null Mode.RELATIVE -> relativeAddress + 2 + signedByte.toInt() Mode.RELATIVE_LONG -> relativeAddress + 3 + signedWord.toInt() - Mode.DATA_WORD -> relativeAddress.withinBank(word) - Mode.DATA_LONG -> SnesAddress(long) - else -> null } } override fun toString(): String { return "$address ${bytesToString()} ${opcode.mnemonic.displayName} ${opcode.mode.print(this).padEnd(100, ' ')} ($preState -> $postState)" } -} +} \ No newline at end of file diff --git a/src/main/java/com/smallhacker/disbrowser/asm/MemorySpace.kt b/src/main/java/com/smallhacker/disbrowser/asm/MemorySpace.kt new file mode 100644 index 0000000..57a6bf0 --- /dev/null +++ b/src/main/java/com/smallhacker/disbrowser/asm/MemorySpace.kt @@ -0,0 +1,93 @@ +package com.smallhacker.disbrowser.asm + +import com.smallhacker.disbrowser.util.UInt24 +import com.smallhacker.disbrowser.util.joinBytes +import com.smallhacker.disbrowser.util.joinNullableBytes +import com.smallhacker.disbrowser.util.toUInt24 +import java.nio.file.Files +import java.nio.file.Path + +interface MemorySpace { + val size: UInt + operator fun get(address: UInt): UByte? +} + +interface ValidMemorySpace : MemorySpace { + override operator fun get(address: UInt): UByte +} + +object EmptyMemorySpace : ValidMemorySpace { + override val size get() = 0u + override fun get(address: UInt) = throw IllegalStateException() +} + +fun MemorySpace.asSequence(): Sequence = (0u until size).asSequence().map { this[it] } +fun MemorySpace.getWord(address: UInt): UShort? = joinNullableBytes(this[address], this[address + 1u])?.toUShort() +fun MemorySpace.getLong(address: UInt): UInt24? = joinNullableBytes(this[address], this[address + 1u], this[address + 2u])?.toUInt24() +fun MemorySpace.range(start: UInt, length: UInt): MemorySpace = MemoryRange(this, start, length) +fun MemorySpace.range(start: SnesAddress, length: UInt): MemorySpace = range(start.value.toUInt(), length) +fun MemorySpace.validate(): ValidMemorySpace? { + if (asSequence().any { it == null }) { + return null + } + return ValidatedMemorySpace(this) +} + +fun ValidMemorySpace.asSequence(): Sequence = (0u until size).asSequence().map { this[it] } +fun ValidMemorySpace.getWord(address: UInt): UShort = joinBytes(this[address], this[address + 1u]).toUShort() +fun ValidMemorySpace.getLong(address: UInt): UInt24 = joinBytes(this[address], this[address + 1u], this[address + 2u]).toUInt24() +fun ValidMemorySpace.range(start: UInt, length: UInt): ValidMemorySpace = MemoryRange(this, start, length).validate()!! +fun ValidMemorySpace.range(start: SnesAddress, length: UInt): ValidMemorySpace = range(start.value.toUInt(), length).validate()!! + +fun loadRomData(path: Path): MemorySpace { + val bytes = Files.readAllBytes(path).toUByteArray() + return ArrayMemorySpace(bytes) +} + +class ArrayMemorySpace(private val bytes: UByteArray) : MemorySpace { + override val size = bytes.size.toUInt() + + override fun get(address: UInt): UByte? { + if (address >= size) { + return null + } + return bytes[address.toInt()] + } +} + +class UnreadableMemory(override val size: UInt) : MemorySpace { + override fun get(address: UInt): UByte? = null +} + +private class MemoryRange(private val parent: MemorySpace, private val start: UInt, override val size: UInt) : MemorySpace { + override fun get(address: UInt) = if (address < size) parent[start + address] else null +} + + +private class ValidatedMemorySpace(private val parent: MemorySpace) : ValidMemorySpace { + override val size get() = parent.size + override fun get(address: UInt) = parent[address]!! +} + +private class ReindexedMemorySpace( + private val parent: MemorySpace, + override val size: UInt, + private val mapper: (UInt) -> UInt +) : MemorySpace { + override fun get(address: UInt): UByte? { + if (address >= size) { + return null + } + val mapped = mapper(address) + return parent[mapped] + } +} + +fun MemorySpace.deinterleave(entries: UInt, vararg startOffsets: UInt): MemorySpace { + val fieldCount = startOffsets.size.toUInt() + return ReindexedMemorySpace(this, entries * fieldCount) { + val entry = it / fieldCount + val field = it.rem(fieldCount) + startOffsets[field.toInt()] + entry + } +} \ No newline at end of file diff --git a/src/main/java/com/smallhacker/disbrowser/asm/Metadata.kt b/src/main/java/com/smallhacker/disbrowser/asm/Metadata.kt index 412975d..c924780 100644 --- a/src/main/java/com/smallhacker/disbrowser/asm/Metadata.kt +++ b/src/main/java/com/smallhacker/disbrowser/asm/Metadata.kt @@ -1,8 +1,11 @@ package com.smallhacker.disbrowser.asm -import com.fasterxml.jackson.annotation.* +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonSubTypes import com.fasterxml.jackson.annotation.JsonSubTypes.Type -import com.smallhacker.disbrowser.util.joinBytes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.smallhacker.disbrowser.util.joinNullableBytes import com.smallhacker.disbrowser.util.toUInt24 import java.util.* @@ -63,20 +66,20 @@ class JmpIndirectLongInterleavedTable @JsonCreator constructor( ) : InstructionFlag { private val uEntries get() = entries.toUInt() - fun readTable(data: RomData): Sequence { - return (0u until uEntries) + fun readTable(data: MemorySpace): Sequence { + return (0 until entries) .asSequence() - .map { it + start.pc } - .map { pc -> joinBytes(data[pc], data[pc + uEntries], data[pc + uEntries + uEntries]).toUInt24() } - .map { SnesAddress(it) } + .map { start + it } + .map { address -> joinNullableBytes(data[address], data[address + entries], data[address + entries + entries])?.toUInt24() } + .map { pointer -> pointer?.let { SnesAddress(it) } } } fun generateCode(jumpInstruction: Instruction): Sequence { - val table = jumpInstruction.preState.data.deinterleave(uEntries, - start.pc, - (start + entries).pc, - (start + (2u * uEntries).toInt()).pc - ) + val table = jumpInstruction.preState.memory.deinterleave(uEntries, + start.value.toUInt(), + (start + entries).value.toUInt(), + (start + (2 * uEntries.toInt())).value.toUInt() + ).validate() ?: return emptySequence() return (0u until uEntries) .asSequence() @@ -93,11 +96,6 @@ class JmpIndirectLongInterleavedTable @JsonCreator constructor( .mutateAddress { SnesAddress(target) } .withOrigin(jumpInstruction) ) -// Instruction( -// data.range((start.value + offset).toUInt(), 3u), -// Opcode.POINTER_LONG, -// preState.mutateAddress { start -> start + offset.toInt() } -// ) } } @@ -108,13 +106,13 @@ class JslTableRoutine @JsonCreator constructor( @field:JsonProperty @JsonProperty private val entries: Int ) : InstructionFlag { - fun readTable(postJsr: State): Sequence { - val data = postJsr.data - return (0u until entries.toUInt()) + fun readTable(postJsr: State): Sequence { + val data = postJsr.memory + return (0 until entries) .asSequence() - .map { postJsr.address.pc + (it * 3u) } - .map { pc -> joinBytes(data[pc], data[pc + 1u], data[pc + 2u]).toUInt24() } - .map { SnesAddress(it) } + .map { postJsr.address + (it * 3) } + .map { address -> joinNullableBytes(data[address], data[address + 1], data[address + 2])?.toUInt24() } + .map { pointer -> pointer?.let { SnesAddress(it) } } } override fun toString() = "JslTableRoutine($entries)" diff --git a/src/main/java/com/smallhacker/disbrowser/asm/Mnemonic.kt b/src/main/java/com/smallhacker/disbrowser/asm/Mnemonic.kt index 0bd0ad0..ec45b70 100644 --- a/src/main/java/com/smallhacker/disbrowser/asm/Mnemonic.kt +++ b/src/main/java/com/smallhacker/disbrowser/asm/Mnemonic.kt @@ -11,7 +11,8 @@ enum class Mnemonic(private val nameOverride: String? = null, val alternativeNam TCS, TDC, TRB, TSB, TSC, TSX, TXA, TXS, TXY, TYA, TYX, WAI, WDM, XBA, XCE, - DB(nameOverride = ".db"), DW(nameOverride = ".dw"), DL(nameOverride = ".dl"); + DB(nameOverride = ".db"), DW(nameOverride = ".dw"), DL(nameOverride = ".dl"), + UNKNOWN(nameOverride = "???"); val displayName get() = nameOverride ?: name } diff --git a/src/main/java/com/smallhacker/disbrowser/asm/Mode.kt b/src/main/java/com/smallhacker/disbrowser/asm/Mode.kt index dae4d9e..f15b98e 100644 --- a/src/main/java/com/smallhacker/disbrowser/asm/Mode.kt +++ b/src/main/java/com/smallhacker/disbrowser/asm/Mode.kt @@ -15,11 +15,14 @@ enum class Mode { DATA_BYTE("$00", dataMode = true, showLengthSuffix = false), DATA_WORD("$0000", dataMode = true, showLengthSuffix = false), DATA_LONG("$000000", dataMode = true, showLengthSuffix = false), + CODE_WORD("$0000", dataMode = true, showLengthSuffix = false), + CODE_LONG("$000000", dataMode = true, showLengthSuffix = false), IMPLIED("", showLengthSuffix = false), IMMEDIATE_8("#$00", showLengthSuffix = false), IMMEDIATE_16("#$0000", showLengthSuffix = false), ABSOLUTE("$0000"), + ABSOLUTE_CODE("$0000"), ABSOLUTE_X("$0000,x"), ABSOLUTE_Y("$0000,y"), ABSOLUTE_LONG("$000000"), diff --git a/src/main/java/com/smallhacker/disbrowser/asm/Opcode.kt b/src/main/java/com/smallhacker/disbrowser/asm/Opcode.kt index a9afe1b..de94a5b 100644 --- a/src/main/java/com/smallhacker/disbrowser/asm/Opcode.kt +++ b/src/main/java/com/smallhacker/disbrowser/asm/Opcode.kt @@ -4,7 +4,6 @@ import java.util.HashMap import com.smallhacker.disbrowser.asm.Mnemonic.* import com.smallhacker.disbrowser.asm.Mode.* -import com.smallhacker.disbrowser.util.OldUByte typealias SegmentEnder = Instruction.() -> SegmentEnd? @@ -51,8 +50,10 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end val DATA_WORD = Opcode(Mnemonic.DW, Mode.DATA_WORD, { null }, { it.preState }) val DATA_LONG = Opcode(Mnemonic.DL, Mode.DATA_LONG, { null }, { it.preState }) - val POINTER_WORD = Opcode(Mnemonic.DW, Mode.DATA_WORD, { null }, { it.preState }).linking() - val POINTER_LONG = Opcode(Mnemonic.DL, Mode.DATA_LONG, { null }, { it.preState }).linking() + val POINTER_WORD = Opcode(Mnemonic.DW, Mode.CODE_WORD, { null }, { it.preState }).linking() + val POINTER_LONG = Opcode(Mnemonic.DL, Mode.CODE_LONG, { null }, { it.preState }).linking() + + val UNKNOWN_OPCODE: Opcode private val OPCODES: Array @@ -79,6 +80,7 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end val dynamicSubJumping: SegmentEnder = { stoppingSegmentEnd(address) } val returning: SegmentEnder = { returnSegmentEnd(address) } + UNKNOWN_OPCODE = Opcode(UNKNOWN, IMPLIED, alwaysStop, Instruction::preState).stop() add(0x00, BRK, IMMEDIATE_8, alwaysStop).stop() add(0x02, COP, IMMEDIATE_8, alwaysStop).stop() @@ -100,14 +102,14 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end add(0xF0, BEQ, RELATIVE, branching).branching() add(0x82, BRL, RELATIVE_LONG, alwaysBranching).stop().branching() - add(0x4C, JMP, ABSOLUTE, jumping).linking().stop() + add(0x4C, JMP, ABSOLUTE_CODE, jumping).linking().stop() add(0x5C, JML, ABSOLUTE_LONG, jumping).linking().stop() add(0x6C, JMP, ABSOLUTE_INDIRECT, dynamicJumping).stop() add(0x7C, JMP, ABSOLUTE_X_INDIRECT, dynamicJumping).stop() add(0xDC, JMP, ABSOLUTE_INDIRECT_LONG, dynamicJumping).stop() add(0x22, JSL, ABSOLUTE_LONG, subJumping).linking().mayStop() - add(0x20, JSR, ABSOLUTE, subJumping).linking().mayStop() + add(0x20, JSR, ABSOLUTE_CODE, subJumping).linking().mayStop() add(0xFC, JSR, ABSOLUTE_X_INDIRECT, dynamicSubJumping).mayStop() add(0x60, RTS, IMPLIED, returning).stop() diff --git a/src/main/java/com/smallhacker/disbrowser/asm/RomData.kt b/src/main/java/com/smallhacker/disbrowser/asm/RomData.kt deleted file mode 100644 index 4abb5d4..0000000 --- a/src/main/java/com/smallhacker/disbrowser/asm/RomData.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.smallhacker.disbrowser.asm - -import com.smallhacker.disbrowser.util.* -import java.nio.file.Files -import java.nio.file.Path - -interface RomData { - val size: UInt - - operator fun get(address: UInt): UByte - - fun range(start: UInt, length: UInt): RomData = RomRange(this, start, length) - - fun asSequence(): Sequence = (0u until size).asSequence().map { this[it] } - - companion object { - fun load(path: Path): RomData = ArrayRomData(Files.readAllBytes(path).toUByteArray()) - } -} - -fun romData(vararg bytes: UByte): RomData = ArrayRomData(bytes) - -fun RomData.getWord(address: UInt) = joinBytes(this[address], this[address + 1u]).toUShort() -fun RomData.getLong(address: UInt) = joinBytes(this[address], this[address + 1u], this[address + 2u]).toUInt24() - -private class ArrayRomData(private val bytes: UByteArray) : RomData { - override val size = bytes.size.toUInt() - - override fun get(address: UInt) = rangeChecked(address) { - bytes[address.toInt()] - } - -} - -private class RomRange(private val parent: RomData, private val start: UInt, override val size: UInt) : RomData { - override fun get(address: UInt) = rangeChecked(address) { - parent[start + address] - } -} - -private fun RomData.rangeChecked(address: UInt, action: () -> T): T { - if (address >= size) { - throw IndexOutOfBoundsException("Index $address out of range: [0, $size)") - } - return action() -} - -private class ReindexedRomData( - private val parent: RomData, - override val size: UInt, - private val mapper: (UInt) -> UInt -) : RomData { - override fun get(address: UInt) = rangeChecked(address) { - val mapped = mapper(address) - parent[mapped] - } -} - -fun RomData.deinterleave(entries: UInt, vararg startOffsets: UInt): RomData { - val fieldCount = startOffsets.size.toUInt() - return ReindexedRomData(this, entries * fieldCount) { - val entry = it / fieldCount - val field = it.rem(fieldCount) - startOffsets[field.toInt()] + entry - } -} \ No newline at end of file diff --git a/src/main/java/com/smallhacker/disbrowser/asm/SnesAddress.kt b/src/main/java/com/smallhacker/disbrowser/asm/SnesAddress.kt index be01ae0..30e06ae 100644 --- a/src/main/java/com/smallhacker/disbrowser/asm/SnesAddress.kt +++ b/src/main/java/com/smallhacker/disbrowser/asm/SnesAddress.kt @@ -7,8 +7,6 @@ import com.smallhacker.disbrowser.util.toUInt24 import com.smallhacker.disbrowser.util.tryParseInt data class SnesAddress(val value: UInt24) : Comparable { - val pc = snesToPc(value) - operator fun plus(offset: Int) = SnesAddress(value + offset) operator fun minus(offset: Int) = SnesAddress(value - offset) operator fun inc() = plus(1) @@ -33,10 +31,4 @@ data class SnesAddress(val value: UInt24) : Comparable { } } -fun address(snesAddress: Int) = SnesAddress(snesAddress.toUInt24()) - -private fun snesToPc(value: UInt24): UInt { - // TODO: This is incredibly oversimplified. Anything that isn't a small LoROM will crash and burn - val intVal = value.toUInt() - return (intVal and 0x7FFFu) or ((intVal and 0x7F_0000u) shr 1) -} +fun address(snesAddress: Int) = SnesAddress(snesAddress.toUInt24()) \ No newline at end of file diff --git a/src/main/java/com/smallhacker/disbrowser/asm/SnesMapper.kt b/src/main/java/com/smallhacker/disbrowser/asm/SnesMapper.kt new file mode 100644 index 0000000..67f08ba --- /dev/null +++ b/src/main/java/com/smallhacker/disbrowser/asm/SnesMapper.kt @@ -0,0 +1,86 @@ +package com.smallhacker.disbrowser.asm + +import com.smallhacker.disbrowser.datatype.MutableRangeMap +import com.smallhacker.disbrowser.datatype.NaiveRangeMap +import com.smallhacker.disbrowser.util.toUInt24 + +abstract class SnesMapper: MemorySpace { + override val size = 0x100_0000u + private val areas: MutableRangeMap = NaiveRangeMap() + + protected fun add(start: UInt, canonicalStart: UInt, memorySpace: MemorySpace) { + val range = start until (start + memorySpace.size) + areas[range] = MapperEntry(start, SnesAddress(canonicalStart.toUInt24()), memorySpace) + } + + override fun get(address: UInt): UByte? { + val entry = areas[address] ?: return null + val offset = address - entry.start + return entry.space[offset] + } + + fun toCanonical(address: SnesAddress): SnesAddress? { + val entry = areas[address.value.toUInt()] ?: return null + val offset = address.value - entry.start + return entry.canonicalStart + offset.toInt() + } +} + +operator fun MemorySpace.get(address: SnesAddress) = get(address.value.toUInt()) +fun MemorySpace.getWord(address: SnesAddress) = getWord(address.value.toUInt()) +fun MemorySpace.getLong(address: SnesAddress) = getLong(address.value.toUInt()) + +data class MapperEntry(val start: UInt, val canonicalStart: SnesAddress, val space: MemorySpace) + +class SnesLoRom(romData: MemorySpace): SnesMapper() { + init { + val ram = UnreadableMemory(0x2_0000u) + val ramMirror = ram.range(0x00_0000u, 0x00_2000u) + val registers = UnreadableMemory(0x6_0000u) + val sram = UnreadableMemory(0x0_8000u) + val ramStart = 0x7e_0000u + val regStart = 0x00_2000u + val srmStart = 0x70_0000u + var pc = 0x00_0000u + val high = 0x80_0000u + + for (bank in 0x00u..0x3Fu) { + val ramArea = (bank shl 16) + val regArea = (bank shl 16) or 0x00_2000u + val romArea = (bank shl 16) or 0x00_8000u + add(ramArea, ramStart, ramMirror) + add(regArea, regStart, registers) + add(romArea, romArea, romData.range(pc, 0x00_8000u)) + add(ramArea + high, ramStart, ramMirror) + add(regArea + high, regStart, registers) + add(romArea + high, romArea, romData.range(pc, 0x00_8000u)) + pc += 0x00_8000u + } + + for (bank in 0x40u..0x6Fu) { + val romArea = (bank shl 16) or 0x00_8000u + // Lower half unmapped + add(romArea, romArea, romData.range(pc, 0x00_8000u)) + add(romArea + high, romArea, romData.range(pc, 0x00_8000u)) + pc += 0x00_8000u + } + + for (bank in 0x70u..0x7Du) { + val srmArea = (bank shl 16) + val romArea = (bank shl 16) or 0x00_8000u + add(srmArea, srmStart, sram) + add(srmArea + high, srmStart, sram) + add(romArea, romArea, romData.range(pc, 0x00_8000u)) + add(romArea + high, romArea, romData.range(pc, 0x00_8000u)) + pc += 0x00_8000u + } + + for (bank in 0xFEu..0xFFu) { + val romArea = (bank shl 16) or 0x00_8000u + add(romArea, romArea, romData.range(pc, 0x00_8000u)) + pc += 0x00_8000u + } + + add(ramStart, ramStart, ram) + } +} \ No newline at end of file diff --git a/src/main/java/com/smallhacker/disbrowser/asm/State.kt b/src/main/java/com/smallhacker/disbrowser/asm/State.kt index 416dbb7..d714e96 100644 --- a/src/main/java/com/smallhacker/disbrowser/asm/State.kt +++ b/src/main/java/com/smallhacker/disbrowser/asm/State.kt @@ -4,7 +4,7 @@ import com.smallhacker.disbrowser.ImmStack import com.smallhacker.disbrowser.immStack import com.smallhacker.disbrowser.util.toUInt24 -data class State(val origin: Instruction? = null, val data: RomData, val address: SnesAddress, val flags: VagueNumber = VagueNumber(), val stack: ImmStack = immStack()) { +data class State(val origin: Instruction? = null, val memory: SnesMapper, val address: SnesAddress, val flags: VagueNumber = VagueNumber(), val stack: ImmStack = immStack()) { val m: Boolean? get() = flags.getBoolean(0x20u) val x: Boolean? get() = flags.getBoolean(0x10u) val db: UByte? get() = pb // TODO @@ -56,16 +56,21 @@ data class State(val origin: Instruction? = null, val data: RomData, val address return "A:${printSize(m)} XY:${printSize(x)} S:" + stackToString() } - fun resolve(directPage: UByte) = dp?.let { dp -> + fun resolveDirectPage(directPage: UByte) = dp?.let { dp -> val ptr = (dp.toUInt24() shl 8) or (directPage.toUInt24()) SnesAddress(ptr) } - fun resolve(absolute: UShort)= db?.let { db -> + fun resolveAbsoluteData(absolute: UShort) = db?.let { db -> val ptr = (db.toUInt24() shl 16) or (absolute.toUInt24()) SnesAddress(ptr) } + fun resolveAbsoluteCode(absolute: UShort): SnesAddress { + val ptr = (pb.toUInt24() shl 16) or (absolute.toUInt24()) + return SnesAddress(ptr) + } + private fun stackToString(): String { return stack.reversed().asSequence() .map { stackByteToString(it) } diff --git a/src/main/java/com/smallhacker/disbrowser/datatype/RangeMap.kt b/src/main/java/com/smallhacker/disbrowser/datatype/RangeMap.kt new file mode 100644 index 0000000..52f0459 --- /dev/null +++ b/src/main/java/com/smallhacker/disbrowser/datatype/RangeMap.kt @@ -0,0 +1,27 @@ +package com.smallhacker.disbrowser.datatype + +import com.smallhacker.disbrowser.util.asReverseSequence + +interface RangeMap, R : ClosedRange, V : Any> { + operator fun get(key: K): V? +} + + +interface MutableRangeMap, R : ClosedRange, V: Any> : RangeMap { + operator fun set(keyRange: R, value: V): RangeMap +} + +class NaiveRangeMap, R : ClosedRange, V: Any> : MutableRangeMap { + private val entries = ArrayList>() + + override fun get(key: K) = entries + .asReverseSequence() + .filter { it.first.contains(key) } + .map { it.second } + .firstOrNull() + + override fun set(keyRange: R, value: V): RangeMap { + entries += keyRange to value + return this + } +} \ No newline at end of file diff --git a/src/main/java/com/smallhacker/disbrowser/disassembler/Disassembler.kt b/src/main/java/com/smallhacker/disbrowser/disassembler/Disassembler.kt index 3a38900..2a055a2 100644 --- a/src/main/java/com/smallhacker/disbrowser/disassembler/Disassembler.kt +++ b/src/main/java/com/smallhacker/disbrowser/disassembler/Disassembler.kt @@ -29,7 +29,8 @@ object Disassembler { metadata[ins.address]?.flags?.forEach { flag -> if (flag is JmpIndirectLongInterleavedTable) { if (global) { - flag.readTable(state.data) + flag.readTable(state.memory) + .filterNotNull() .map { ins.postState.copy(address = it) } .forEach { tryAdd(it) } } @@ -41,6 +42,7 @@ object Disassembler { } else if (flag is JslTableRoutine) { if (global) { flag.readTable(ins.postState) + .filterNotNull() .map { ins.postState.copy(address = it) } .forEach { tryAdd(it) } } @@ -162,10 +164,14 @@ object Disassembler { } private fun disassembleInstruction(state: State): Instruction { - val pc = state.address.pc - val opcode = Opcode.opcode(state.data[pc]) + val opcodeValue = state.memory[state.address] ?: return unreadableInstruction(state) + val opcode = Opcode.opcode(opcodeValue) val length = opcode.mode.instructionLength(state) ?: 1u - val bytes = state.data.range(pc, length) + val bytes = state.memory.range(state.address.value.toUInt(), length).validate() + ?: return unreadableInstruction(state) return Instruction(bytes, opcode, state) } + + private fun unreadableInstruction(state: State) = + Instruction(EmptyMemorySpace, Opcode.UNKNOWN_OPCODE, state) } diff --git a/src/main/java/com/smallhacker/disbrowser/util/misc.kt b/src/main/java/com/smallhacker/disbrowser/util/misc.kt index f974ad8..f7e35bd 100644 --- a/src/main/java/com/smallhacker/disbrowser/util/misc.kt +++ b/src/main/java/com/smallhacker/disbrowser/util/misc.kt @@ -30,6 +30,17 @@ fun joinBytes(vararg bytes: UByte) = bytes .mapIndexed { index, v -> v.toUInt() shl (index * 8) } .reduce { a, b -> a or b } +fun joinNullableBytes(vararg bytes: UByte?): UInt? { + if (bytes.any { it == null }) { + return null + } + + return bytes + .asSequence() + .mapIndexed { index, v -> v!!.toUInt() shl (index * 8) } + .reduce { a, b -> a or b } +} + inline class UInt24(private val data: UInt) { fun toUInt() = data and 0x00FF_FFFFu fun toUShort() = toUInt().toUShort() @@ -43,7 +54,7 @@ inline class UInt24(private val data: UInt) { infix fun or(v: UInt24) = (data or v.data).toUInt24() infix fun or(v: UInt) = (data or v).toUInt24() infix fun shl(v: Int) = (data shl v).toUInt24() - infix fun shr(v: Int)= (toUInt() shr v).toUInt24() + infix fun shr(v: Int) = (toUInt() shr v).toUInt24() operator fun plus(v: UInt24) = (toUInt() + v.toUInt()).toUInt24() operator fun plus(v: UInt) = (toUInt() + v).toUInt24() @@ -61,3 +72,6 @@ fun UByte.toUInt24() = this.toUInt().toUInt24() fun Int.toUInt24() = this.toUInt().toUInt24() fun Short.toUInt24() = this.toUInt().toUInt24() fun Byte.toUInt24() = this.toUInt().toUInt24() + +fun List.asReverseSequence(): Sequence = + ((size - 1) downTo 0).asSequence().map { this[it] } \ No newline at end of file