mirror of
https://github.com/Smallhacker/disbrowser.git
synced 2024-06-13 21:29:28 +00:00
More stuff
This commit is contained in:
parent
9881bf9e0b
commit
e6445575d7
|
@ -3,6 +3,7 @@
|
||||||
"008000" : {
|
"008000" : {
|
||||||
"label" : "ResetVector"
|
"label" : "ResetVector"
|
||||||
},
|
},
|
||||||
|
"00800a" : { },
|
||||||
"00801b" : {
|
"00801b" : {
|
||||||
"comment" : "\\ Turn off emulation mode"
|
"comment" : "\\ Turn off emulation mode"
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,28 +10,27 @@ import kotlin.reflect.KMutableProperty1
|
||||||
private val RESET_VECTOR_LOCATION = address(0x00_FFFC)
|
private val RESET_VECTOR_LOCATION = address(0x00_FFFC)
|
||||||
|
|
||||||
object Service {
|
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 romDir = Paths.get("""P:\Emulation\ROMs\SNES""")
|
||||||
private val metaDir = Paths.get("""P:\Programming\dis-browser""")
|
private val metaDir = Paths.get("""P:\Programming\dis-browser""")
|
||||||
private val metaFile = jsonFile<Metadata>(metaDir.resolve("$romName.json"), true)
|
private val metaFile = jsonFile<Metadata>(metaDir.resolve("$romName.json"), true)
|
||||||
private val metadata by lazy { metaFile.load() }
|
private val metadata by lazy { metaFile.load() }
|
||||||
|
|
||||||
private val romData = lazy {
|
private val snesMemory by lazy {
|
||||||
val path = romDir.resolve("$romName.sfc")
|
val path = romDir.resolve("$romName.sfc")
|
||||||
RomData.load(path)
|
SnesLoRom(loadRomData(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showDisassemblyFromReset(): HtmlNode? {
|
fun showDisassemblyFromReset(): HtmlNode? {
|
||||||
val resetVectorLocation = RESET_VECTOR_LOCATION
|
val resetVector = snesMemory.getWord(RESET_VECTOR_LOCATION)
|
||||||
val initialAddress = SnesAddress(romData.value.getWord(resetVectorLocation.pc).toUInt24())
|
val fullResetVector = resetVector!!.toUInt24()
|
||||||
|
val initialAddress = SnesAddress(fullResetVector)
|
||||||
val flags = VagueNumber(0x30u)
|
val flags = VagueNumber(0x30u)
|
||||||
return showDisassembly(initialAddress, flags)
|
return showDisassembly(initialAddress, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showDisassembly(initialAddress: SnesAddress, flags: VagueNumber): HtmlNode? {
|
fun showDisassembly(initialAddress: SnesAddress, flags: VagueNumber): HtmlNode? {
|
||||||
val rom = romData.value
|
val initialState = State(memory = snesMemory, address = initialAddress, flags = flags)
|
||||||
|
|
||||||
val initialState = State(data = rom, address = initialAddress, flags = flags)
|
|
||||||
val disassembly = Disassembler.disassemble(initialState, metadata, false)
|
val disassembly = Disassembler.disassemble(initialState, metadata, false)
|
||||||
|
|
||||||
return print(disassembly, metadata)
|
return print(disassembly, metadata)
|
||||||
|
|
|
@ -12,7 +12,7 @@ interface CodeUnit {
|
||||||
val preState: State?
|
val preState: State?
|
||||||
val postState: State?
|
val postState: State?
|
||||||
|
|
||||||
val bytes: RomData
|
val bytes: ValidMemorySpace
|
||||||
val opcode: Opcode
|
val opcode: Opcode
|
||||||
val lengthSuffix: String?
|
val lengthSuffix: String?
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ interface CodeUnit {
|
||||||
|
|
||||||
class DataBlock(
|
class DataBlock(
|
||||||
override val opcode: Opcode,
|
override val opcode: Opcode,
|
||||||
override val bytes: RomData,
|
override val bytes: ValidMemorySpace,
|
||||||
override val presentedAddress: SnesAddress,
|
override val presentedAddress: SnesAddress,
|
||||||
override val relativeAddress: SnesAddress,
|
override val relativeAddress: SnesAddress,
|
||||||
override val linkedState: State?
|
override val linkedState: State?
|
||||||
|
@ -77,7 +77,7 @@ class DataBlock(
|
||||||
override val lengthSuffix: String? = null
|
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 address: SnesAddress get() = preState.address
|
||||||
override val relativeAddress get() = address
|
override val relativeAddress get() = address
|
||||||
override val presentedAddress 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 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) {
|
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 -> 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 -> relativeAddress + 2 + signedByte.toInt()
|
||||||
Mode.RELATIVE_LONG -> relativeAddress + 3 + signedWord.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 {
|
override fun toString(): String {
|
||||||
return "$address ${bytesToString()} ${opcode.mnemonic.displayName} ${opcode.mode.print(this).padEnd(100, ' ')} ($preState -> $postState)"
|
return "$address ${bytesToString()} ${opcode.mnemonic.displayName} ${opcode.mode.print(this).padEnd(100, ' ')} ($preState -> $postState)"
|
||||||
}
|
}
|
||||||
}
|
}
|
93
src/main/java/com/smallhacker/disbrowser/asm/MemorySpace.kt
Normal file
93
src/main/java/com/smallhacker/disbrowser/asm/MemorySpace.kt
Normal file
|
@ -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<UByte?> = (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<UByte> = (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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
package com.smallhacker.disbrowser.asm
|
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.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 com.smallhacker.disbrowser.util.toUInt24
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -63,20 +66,20 @@ class JmpIndirectLongInterleavedTable @JsonCreator constructor(
|
||||||
) : InstructionFlag {
|
) : InstructionFlag {
|
||||||
private val uEntries get() = entries.toUInt()
|
private val uEntries get() = entries.toUInt()
|
||||||
|
|
||||||
fun readTable(data: RomData): Sequence<SnesAddress> {
|
fun readTable(data: MemorySpace): Sequence<SnesAddress?> {
|
||||||
return (0u until uEntries)
|
return (0 until entries)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map { it + start.pc }
|
.map { start + it }
|
||||||
.map { pc -> joinBytes(data[pc], data[pc + uEntries], data[pc + uEntries + uEntries]).toUInt24() }
|
.map { address -> joinNullableBytes(data[address], data[address + entries], data[address + entries + entries])?.toUInt24() }
|
||||||
.map { SnesAddress(it) }
|
.map { pointer -> pointer?.let { SnesAddress(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateCode(jumpInstruction: Instruction): Sequence<DataBlock> {
|
fun generateCode(jumpInstruction: Instruction): Sequence<DataBlock> {
|
||||||
val table = jumpInstruction.preState.data.deinterleave(uEntries,
|
val table = jumpInstruction.preState.memory.deinterleave(uEntries,
|
||||||
start.pc,
|
start.value.toUInt(),
|
||||||
(start + entries).pc,
|
(start + entries).value.toUInt(),
|
||||||
(start + (2u * uEntries).toInt()).pc
|
(start + (2 * uEntries.toInt())).value.toUInt()
|
||||||
)
|
).validate() ?: return emptySequence()
|
||||||
|
|
||||||
return (0u until uEntries)
|
return (0u until uEntries)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
|
@ -93,11 +96,6 @@ class JmpIndirectLongInterleavedTable @JsonCreator constructor(
|
||||||
.mutateAddress { SnesAddress(target) }
|
.mutateAddress { SnesAddress(target) }
|
||||||
.withOrigin(jumpInstruction)
|
.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
|
@field:JsonProperty @JsonProperty private val entries: Int
|
||||||
) : InstructionFlag {
|
) : InstructionFlag {
|
||||||
|
|
||||||
fun readTable(postJsr: State): Sequence<SnesAddress> {
|
fun readTable(postJsr: State): Sequence<SnesAddress?> {
|
||||||
val data = postJsr.data
|
val data = postJsr.memory
|
||||||
return (0u until entries.toUInt())
|
return (0 until entries)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map { postJsr.address.pc + (it * 3u) }
|
.map { postJsr.address + (it * 3) }
|
||||||
.map { pc -> joinBytes(data[pc], data[pc + 1u], data[pc + 2u]).toUInt24() }
|
.map { address -> joinNullableBytes(data[address], data[address + 1], data[address + 2])?.toUInt24() }
|
||||||
.map { SnesAddress(it) }
|
.map { pointer -> pointer?.let { SnesAddress(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = "JslTableRoutine($entries)"
|
override fun toString() = "JslTableRoutine($entries)"
|
||||||
|
|
|
@ -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,
|
TCS, TDC, TRB, TSB, TSC, TSX, TXA, TXS, TXY, TYA, TYX,
|
||||||
WAI, WDM, XBA, XCE,
|
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
|
val displayName get() = nameOverride ?: name
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,14 @@ enum class Mode {
|
||||||
DATA_BYTE("$00", dataMode = true, showLengthSuffix = false),
|
DATA_BYTE("$00", dataMode = true, showLengthSuffix = false),
|
||||||
DATA_WORD("$0000", dataMode = true, showLengthSuffix = false),
|
DATA_WORD("$0000", dataMode = true, showLengthSuffix = false),
|
||||||
DATA_LONG("$000000", 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),
|
IMPLIED("", showLengthSuffix = false),
|
||||||
IMMEDIATE_8("#$00", showLengthSuffix = false),
|
IMMEDIATE_8("#$00", showLengthSuffix = false),
|
||||||
IMMEDIATE_16("#$0000", showLengthSuffix = false),
|
IMMEDIATE_16("#$0000", showLengthSuffix = false),
|
||||||
ABSOLUTE("$0000"),
|
ABSOLUTE("$0000"),
|
||||||
|
ABSOLUTE_CODE("$0000"),
|
||||||
ABSOLUTE_X("$0000,x"),
|
ABSOLUTE_X("$0000,x"),
|
||||||
ABSOLUTE_Y("$0000,y"),
|
ABSOLUTE_Y("$0000,y"),
|
||||||
ABSOLUTE_LONG("$000000"),
|
ABSOLUTE_LONG("$000000"),
|
||||||
|
|
|
@ -4,7 +4,6 @@ import java.util.HashMap
|
||||||
|
|
||||||
import com.smallhacker.disbrowser.asm.Mnemonic.*
|
import com.smallhacker.disbrowser.asm.Mnemonic.*
|
||||||
import com.smallhacker.disbrowser.asm.Mode.*
|
import com.smallhacker.disbrowser.asm.Mode.*
|
||||||
import com.smallhacker.disbrowser.util.OldUByte
|
|
||||||
|
|
||||||
typealias SegmentEnder = Instruction.() -> SegmentEnd?
|
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_WORD = Opcode(Mnemonic.DW, Mode.DATA_WORD, { null }, { it.preState })
|
||||||
val DATA_LONG = Opcode(Mnemonic.DL, Mode.DATA_LONG, { 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_WORD = Opcode(Mnemonic.DW, Mode.CODE_WORD, { null }, { it.preState }).linking()
|
||||||
val POINTER_LONG = Opcode(Mnemonic.DL, Mode.DATA_LONG, { 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<Opcode>
|
private val OPCODES: Array<Opcode>
|
||||||
|
|
||||||
|
@ -79,6 +80,7 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end
|
||||||
val dynamicSubJumping: SegmentEnder = { stoppingSegmentEnd(address) }
|
val dynamicSubJumping: SegmentEnder = { stoppingSegmentEnd(address) }
|
||||||
val returning: SegmentEnder = { returnSegmentEnd(address) }
|
val returning: SegmentEnder = { returnSegmentEnd(address) }
|
||||||
|
|
||||||
|
UNKNOWN_OPCODE = Opcode(UNKNOWN, IMPLIED, alwaysStop, Instruction::preState).stop()
|
||||||
|
|
||||||
add(0x00, BRK, IMMEDIATE_8, alwaysStop).stop()
|
add(0x00, BRK, IMMEDIATE_8, alwaysStop).stop()
|
||||||
add(0x02, COP, 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(0xF0, BEQ, RELATIVE, branching).branching()
|
||||||
add(0x82, BRL, RELATIVE_LONG, alwaysBranching).stop().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(0x5C, JML, ABSOLUTE_LONG, jumping).linking().stop()
|
||||||
add(0x6C, JMP, ABSOLUTE_INDIRECT, dynamicJumping).stop()
|
add(0x6C, JMP, ABSOLUTE_INDIRECT, dynamicJumping).stop()
|
||||||
add(0x7C, JMP, ABSOLUTE_X_INDIRECT, dynamicJumping).stop()
|
add(0x7C, JMP, ABSOLUTE_X_INDIRECT, dynamicJumping).stop()
|
||||||
add(0xDC, JMP, ABSOLUTE_INDIRECT_LONG, dynamicJumping).stop()
|
add(0xDC, JMP, ABSOLUTE_INDIRECT_LONG, dynamicJumping).stop()
|
||||||
|
|
||||||
add(0x22, JSL, ABSOLUTE_LONG, subJumping).linking().mayStop()
|
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(0xFC, JSR, ABSOLUTE_X_INDIRECT, dynamicSubJumping).mayStop()
|
||||||
|
|
||||||
add(0x60, RTS, IMPLIED, returning).stop()
|
add(0x60, RTS, IMPLIED, returning).stop()
|
||||||
|
|
|
@ -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<UByte> = (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 <T> 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,8 +7,6 @@ import com.smallhacker.disbrowser.util.toUInt24
|
||||||
import com.smallhacker.disbrowser.util.tryParseInt
|
import com.smallhacker.disbrowser.util.tryParseInt
|
||||||
|
|
||||||
data class SnesAddress(val value: UInt24) : Comparable<SnesAddress> {
|
data class SnesAddress(val value: UInt24) : Comparable<SnesAddress> {
|
||||||
val pc = snesToPc(value)
|
|
||||||
|
|
||||||
operator fun plus(offset: Int) = SnesAddress(value + offset)
|
operator fun plus(offset: Int) = SnesAddress(value + offset)
|
||||||
operator fun minus(offset: Int) = SnesAddress(value - offset)
|
operator fun minus(offset: Int) = SnesAddress(value - offset)
|
||||||
operator fun inc() = plus(1)
|
operator fun inc() = plus(1)
|
||||||
|
@ -33,10 +31,4 @@ data class SnesAddress(val value: UInt24) : Comparable<SnesAddress> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun address(snesAddress: Int) = SnesAddress(snesAddress.toUInt24())
|
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)
|
|
||||||
}
|
|
86
src/main/java/com/smallhacker/disbrowser/asm/SnesMapper.kt
Normal file
86
src/main/java/com/smallhacker/disbrowser/asm/SnesMapper.kt
Normal file
|
@ -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<UInt, UIntRange, MapperEntry> = 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import com.smallhacker.disbrowser.ImmStack
|
||||||
import com.smallhacker.disbrowser.immStack
|
import com.smallhacker.disbrowser.immStack
|
||||||
import com.smallhacker.disbrowser.util.toUInt24
|
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<VagueNumber> = immStack()) {
|
data class State(val origin: Instruction? = null, val memory: SnesMapper, val address: SnesAddress, val flags: VagueNumber = VagueNumber(), val stack: ImmStack<VagueNumber> = immStack()) {
|
||||||
val m: Boolean? get() = flags.getBoolean(0x20u)
|
val m: Boolean? get() = flags.getBoolean(0x20u)
|
||||||
val x: Boolean? get() = flags.getBoolean(0x10u)
|
val x: Boolean? get() = flags.getBoolean(0x10u)
|
||||||
val db: UByte? get() = pb // TODO
|
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()
|
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())
|
val ptr = (dp.toUInt24() shl 8) or (directPage.toUInt24())
|
||||||
SnesAddress(ptr)
|
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())
|
val ptr = (db.toUInt24() shl 16) or (absolute.toUInt24())
|
||||||
SnesAddress(ptr)
|
SnesAddress(ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun resolveAbsoluteCode(absolute: UShort): SnesAddress {
|
||||||
|
val ptr = (pb.toUInt24() shl 16) or (absolute.toUInt24())
|
||||||
|
return SnesAddress(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
private fun stackToString(): String {
|
private fun stackToString(): String {
|
||||||
return stack.reversed().asSequence()
|
return stack.reversed().asSequence()
|
||||||
.map { stackByteToString(it) }
|
.map { stackByteToString(it) }
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.smallhacker.disbrowser.datatype
|
||||||
|
|
||||||
|
import com.smallhacker.disbrowser.util.asReverseSequence
|
||||||
|
|
||||||
|
interface RangeMap<K : Comparable<K>, R : ClosedRange<K>, V : Any> {
|
||||||
|
operator fun get(key: K): V?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface MutableRangeMap<K : Comparable<K>, R : ClosedRange<K>, V: Any> : RangeMap<K, R, V> {
|
||||||
|
operator fun set(keyRange: R, value: V): RangeMap<K, R, V>
|
||||||
|
}
|
||||||
|
|
||||||
|
class NaiveRangeMap<K : Comparable<K>, R : ClosedRange<K>, V: Any> : MutableRangeMap<K, R, V> {
|
||||||
|
private val entries = ArrayList<Pair<R, V>>()
|
||||||
|
|
||||||
|
override fun get(key: K) = entries
|
||||||
|
.asReverseSequence()
|
||||||
|
.filter { it.first.contains(key) }
|
||||||
|
.map { it.second }
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
override fun set(keyRange: R, value: V): RangeMap<K, R, V> {
|
||||||
|
entries += keyRange to value
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,7 +29,8 @@ object Disassembler {
|
||||||
metadata[ins.address]?.flags?.forEach { flag ->
|
metadata[ins.address]?.flags?.forEach { flag ->
|
||||||
if (flag is JmpIndirectLongInterleavedTable) {
|
if (flag is JmpIndirectLongInterleavedTable) {
|
||||||
if (global) {
|
if (global) {
|
||||||
flag.readTable(state.data)
|
flag.readTable(state.memory)
|
||||||
|
.filterNotNull()
|
||||||
.map { ins.postState.copy(address = it) }
|
.map { ins.postState.copy(address = it) }
|
||||||
.forEach { tryAdd(it) }
|
.forEach { tryAdd(it) }
|
||||||
}
|
}
|
||||||
|
@ -41,6 +42,7 @@ object Disassembler {
|
||||||
} else if (flag is JslTableRoutine) {
|
} else if (flag is JslTableRoutine) {
|
||||||
if (global) {
|
if (global) {
|
||||||
flag.readTable(ins.postState)
|
flag.readTable(ins.postState)
|
||||||
|
.filterNotNull()
|
||||||
.map { ins.postState.copy(address = it) }
|
.map { ins.postState.copy(address = it) }
|
||||||
.forEach { tryAdd(it) }
|
.forEach { tryAdd(it) }
|
||||||
}
|
}
|
||||||
|
@ -162,10 +164,14 @@ object Disassembler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun disassembleInstruction(state: State): Instruction {
|
private fun disassembleInstruction(state: State): Instruction {
|
||||||
val pc = state.address.pc
|
val opcodeValue = state.memory[state.address] ?: return unreadableInstruction(state)
|
||||||
val opcode = Opcode.opcode(state.data[pc])
|
val opcode = Opcode.opcode(opcodeValue)
|
||||||
val length = opcode.mode.instructionLength(state) ?: 1u
|
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)
|
return Instruction(bytes, opcode, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun unreadableInstruction(state: State) =
|
||||||
|
Instruction(EmptyMemorySpace, Opcode.UNKNOWN_OPCODE, state)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,17 @@ fun joinBytes(vararg bytes: UByte) = bytes
|
||||||
.mapIndexed { index, v -> v.toUInt() shl (index * 8) }
|
.mapIndexed { index, v -> v.toUInt() shl (index * 8) }
|
||||||
.reduce { a, b -> a or b }
|
.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) {
|
inline class UInt24(private val data: UInt) {
|
||||||
fun toUInt() = data and 0x00FF_FFFFu
|
fun toUInt() = data and 0x00FF_FFFFu
|
||||||
fun toUShort() = toUInt().toUShort()
|
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: UInt24) = (data or v.data).toUInt24()
|
||||||
infix fun or(v: UInt) = (data or v).toUInt24()
|
infix fun or(v: UInt) = (data or v).toUInt24()
|
||||||
infix fun shl(v: Int) = (data shl 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: UInt24) = (toUInt() + v.toUInt()).toUInt24()
|
||||||
operator fun plus(v: UInt) = (toUInt() + v).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 Int.toUInt24() = this.toUInt().toUInt24()
|
||||||
fun Short.toUInt24() = this.toUInt().toUInt24()
|
fun Short.toUInt24() = this.toUInt().toUInt24()
|
||||||
fun Byte.toUInt24() = this.toUInt().toUInt24()
|
fun Byte.toUInt24() = this.toUInt().toUInt24()
|
||||||
|
|
||||||
|
fun <T> List<T>.asReverseSequence(): Sequence<T> =
|
||||||
|
((size - 1) downTo 0).asSequence().map { this[it] }
|
Loading…
Reference in New Issue
Block a user