mirror of
https://github.com/Smallhacker/disbrowser.git
synced 2025-04-09 07:39:20 +00:00
More stuff
This commit is contained in:
parent
9881bf9e0b
commit
e6445575d7
@ -3,6 +3,7 @@
|
||||
"008000" : {
|
||||
"label" : "ResetVector"
|
||||
},
|
||||
"00800a" : { },
|
||||
"00801b" : {
|
||||
"comment" : "\\ Turn off emulation mode"
|
||||
},
|
||||
|
@ -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<Metadata>(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)
|
||||
|
@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
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
|
||||
|
||||
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<SnesAddress> {
|
||||
return (0u until uEntries)
|
||||
fun readTable(data: MemorySpace): Sequence<SnesAddress?> {
|
||||
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<DataBlock> {
|
||||
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<SnesAddress> {
|
||||
val data = postJsr.data
|
||||
return (0u until entries.toUInt())
|
||||
fun readTable(postJsr: State): Sequence<SnesAddress?> {
|
||||
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)"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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<Opcode>
|
||||
|
||||
@ -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()
|
||||
|
@ -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
|
||||
|
||||
data class SnesAddress(val value: UInt24) : Comparable<SnesAddress> {
|
||||
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<SnesAddress> {
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
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.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 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) }
|
||||
|
@ -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 ->
|
||||
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)
|
||||
}
|
||||
|
@ -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 <T> List<T>.asReverseSequence(): Sequence<T> =
|
||||
((size - 1) downTo 0).asSequence().map { this[it] }
|
Loading…
x
Reference in New Issue
Block a user