More stuff

This commit is contained in:
Smallhacker 2019-01-11 21:16:50 -05:00
parent 9881bf9e0b
commit e6445575d7
15 changed files with 330 additions and 128 deletions

View File

@ -3,6 +3,7 @@
"008000" : { "008000" : {
"label" : "ResetVector" "label" : "ResetVector"
}, },
"00800a" : { },
"00801b" : { "00801b" : {
"comment" : "\\ Turn off emulation mode" "comment" : "\\ Turn off emulation mode"
}, },

View File

@ -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)

View File

@ -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)"
} }
} }

View 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
}
}

View File

@ -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)"

View File

@ -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
} }

View File

@ -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"),

View File

@ -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()

View File

@ -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
}
}

View File

@ -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)
}

View 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)
}
}

View File

@ -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) }

View File

@ -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
}
}

View File

@ -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)
} }

View File

@ -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] }