This commit is contained in:
Smallhacker 2019-01-11 11:35:35 -05:00
parent 32f3cdabff
commit 9881bf9e0b
17 changed files with 277 additions and 116 deletions

View File

@ -1,39 +1,155 @@
{ {
"008000" : { "code" : {
"label" : "ResetVector" "008000" : {
}, "label" : "ResetVector"
"008034" : { },
"label" : "MainGameLoop" "00801b" : {
}, "comment" : "\\ Turn off emulation mode"
"0080b5" : { },
"label" : "JumpToGameMode" "00801c" : {
}, "comment" : "/"
"0080c6" : { },
"flags" : [ { "00801f" : {
"flagType" : "JmpIndirectLongInterleavedTable", "comment" : "\\ Set direct page"
"start" : "008061", },
"entries" : 28 "008022" : {
} ] "comment" : "/"
}, },
"00841e" : { "008023" : {
"label" : "ClearOam", "comment" : "\\ Set stack position"
"comment" : "Test3" },
}, "008026" : {
"00879c" : { "comment" : "/"
"flags" : [ { },
"flagType" : "NonReturningRoutine" "008034" : {
} ] "label" : "MainGameLoop",
}, "comment" : "Wait for NMI"
"0287d0" : { },
"flags" : [ { "00805d" : {
"flagType" : "JslTableRoutine", "comment" : "Clear NMI flag"
"entries" : 4 },
} ] "0080b5" : {
}, "label" : "JumpToGameMode",
"0cc115" : { "comment" : "Y = Current game mode"
"flags" : [ { },
"flagType" : "JslTableRoutine", "0080b7" : {
"entries" : 12 "comment" : "\\ Load routine low byte"
} ] },
"0080ba" : {
"comment" : "/"
},
"0080bc" : {
"comment" : "\\ Load routine mid byte"
},
"0080bf" : {
"comment" : "/"
},
"0080c1" : {
"comment" : "\\ Load routine high byte"
},
"0080c4" : {
"comment" : "/"
},
"0080c6" : {
"flags" : [ {
"flagType" : "JmpIndirectLongInterleavedTable",
"start" : "008061",
"entries" : 28
} ]
},
"00841e" : {
"label" : "ClearOam",
"comment" : "Test3"
},
"00879c" : {
"comment" : "Preserve Y value for later",
"flags" : [ {
"flagType" : "NonReturningRoutine"
} ]
},
"00879e" : {
"comment" : "Y = Ret.Bank"
},
"00879f" : {
"comment" : "$02 = Ret.Bank"
},
"0087a3" : {
"comment" : "\\"
},
"0087a6" : {
"comment" : "|"
},
"0087a8" : {
"comment" : "| Y = In.A * 3"
},
"0087a9" : {
"comment" : "|"
},
"0087ab" : {
"comment" : "/"
},
"0087ac" : {
"comment" : "\\ $03-$04 = Ret.Offset"
},
"0087ad" : {
"comment" : "/"
},
"0087af" : {
"comment" : "Increase Y to compensate for Ret being off by one"
},
"0087b0" : {
"comment" : "\\"
},
"0087b2" : {
"comment" : "|"
},
"0087b4" : {
"comment" : "| Load target pointer into $00-03 (last byte unused)"
},
"0087b5" : {
"comment" : "|"
},
"0087b7" : {
"comment" : "/"
},
"0087bb" : {
"comment" : "Restore initial Y value"
},
"0087bd" : {
"comment" : "Jump to pointer"
},
"008901" : {
"comment" : "\\"
},
"008903" : {
"comment" : "|"
},
"008905" : {
"comment" : "| Write #$19:8000 to $00"
},
"008907" : {
"comment" : "|"
},
"008909" : {
"comment" : "|"
},
"00890b" : {
"comment" : "/"
},
"0287d0" : {
"flags" : [ {
"flagType" : "JslTableRoutine",
"entries" : 4
} ]
},
"029ee3" : {
"label" : "GM_TriforceRoom"
},
"0cc115" : {
"flags" : [ {
"flagType" : "JslTableRoutine",
"entries" : 12
} ]
}
} }
} }

View File

@ -9,13 +9,13 @@ class Grid {
private val content = HashMap<Pair<Int, Int>, HtmlNode?>() private val content = HashMap<Pair<Int, Int>, HtmlNode?>()
private val cellClasses = HashMap<Pair<Int, Int>, String>() private val cellClasses = HashMap<Pair<Int, Int>, String>()
private val addresses = HashMap<Address, Int>() private val addresses = HashMap<SnesAddress, Int>()
private val rowClasses = HashMap<Int, String>() private val rowClasses = HashMap<Int, String>()
private val rowId = HashMap<Int, String>() private val rowId = HashMap<Int, String>()
private var height = 0 private var height = 0
private var nextAddress: Address? = null private var nextAddress: SnesAddress? = null
fun arrow(from: Address, to: Address) { fun arrow(from: SnesAddress, to: SnesAddress) {
val yStart = addresses[from] val yStart = addresses[from]
val yEnd = addresses[to] val yEnd = addresses[to]
if (yStart == null || yEnd == null) { if (yStart == null || yEnd == null) {
@ -60,7 +60,7 @@ class Grid {
} }
fun add(ins: CodeUnit, metadata: Metadata, disassembly: Disassembly) { fun add(ins: CodeUnit, metadata: Metadata, disassembly: Disassembly) {
val insMetadata = ins.address ?.let { metadata[it] } val insMetadata = ins.address?.let { metadata[it] }
val actualAddress = ins.address val actualAddress = ins.address
val presentedAddress = ins.presentedAddress val presentedAddress = ins.presentedAddress
@ -80,7 +80,7 @@ class Grid {
text(ins.bytesToString()), text(ins.bytesToString()),
editableField(presentedAddress, "label", insMetadata?.label), editableField(presentedAddress, "label", insMetadata?.label),
fragment { fragment {
text(ins.printOpcodeAndSuffix()) opcodeNode(ins)
text(" ") text(" ")
var operands = ins.printOperands() var operands = ins.printOperands()
@ -111,7 +111,19 @@ class Grid {
} }
} }
private fun editableField(address: Address, type: String, value: String?): HtmlNode { private fun HtmlArea.opcodeNode(ins: CodeUnit) {
val alt = ins.printAlternativeOpcodeAndSuffix()
if (alt == null) {
text(ins.printOpcodeAndSuffix())
} else {
span {
text(ins.printOpcodeAndSuffix())
}.attr("title", alt).addClass("opcode-info")
}
}
private fun editableField(address: SnesAddress, type: String, value: String?): HtmlNode {
return input(value = value ?: "") return input(value = value ?: "")
.addClass("field-$type") .addClass("field-$type")
.addClass("field-editable") .addClass("field-editable")
@ -124,7 +136,7 @@ class Grid {
add(y, null, null, null, null, text("..."), null, null) add(y, null, null, null, null, text("..."), null, null)
} }
private fun add(y: Int, address: Address?, private fun add(y: Int, address: SnesAddress?,
cAddress: HtmlNode?, cAddress: HtmlNode?,
cBytes: HtmlNode?, cBytes: HtmlNode?,
cLabel: HtmlNode?, cLabel: HtmlNode?,

View File

@ -99,6 +99,8 @@ fun body(inner: InnerHtml = {}) = parent("body", inner)
fun HtmlArea.body(inner: InnerHtml = {}) = com.smallhacker.disbrowser.body(inner).appendTo(parent) fun HtmlArea.body(inner: InnerHtml = {}) = com.smallhacker.disbrowser.body(inner).appendTo(parent)
fun div(inner: InnerHtml = {}) = parent("div", inner) fun div(inner: InnerHtml = {}) = parent("div", inner)
fun HtmlArea.div(inner: InnerHtml = {}) = com.smallhacker.disbrowser.div(inner).appendTo(parent) fun HtmlArea.div(inner: InnerHtml = {}) = com.smallhacker.disbrowser.div(inner).appendTo(parent)
fun span(inner: InnerHtml = {}) = parent("span", inner)
fun HtmlArea.span(inner: InnerHtml = {}) = com.smallhacker.disbrowser.span(inner).appendTo(parent)
fun table(inner: InnerHtml = {}) = parent("table", inner) fun table(inner: InnerHtml = {}) = parent("table", inner)
fun HtmlArea.table(inner: InnerHtml = {}) = com.smallhacker.disbrowser.table(inner).appendTo(parent) fun HtmlArea.table(inner: InnerHtml = {}) = com.smallhacker.disbrowser.table(inner).appendTo(parent)
fun tr(inner: InnerHtml = {}) = parent("tr", inner) fun tr(inner: InnerHtml = {}) = parent("tr", inner)

View File

@ -23,12 +23,12 @@ object Service {
fun showDisassemblyFromReset(): HtmlNode? { fun showDisassemblyFromReset(): HtmlNode? {
val resetVectorLocation = RESET_VECTOR_LOCATION val resetVectorLocation = RESET_VECTOR_LOCATION
val initialAddress = Address(romData.value.getWord(resetVectorLocation.pc).toUInt24()) val initialAddress = SnesAddress(romData.value.getWord(resetVectorLocation.pc).toUInt24())
val flags = VagueNumber(0x30u) val flags = VagueNumber(0x30u)
return showDisassembly(initialAddress, flags) return showDisassembly(initialAddress, flags)
} }
fun showDisassembly(initialAddress: Address, flags: VagueNumber): HtmlNode? { fun showDisassembly(initialAddress: SnesAddress, flags: VagueNumber): HtmlNode? {
val rom = romData.value val rom = romData.value
val initialState = State(data = rom, address = initialAddress, flags = flags) val initialState = State(data = rom, address = initialAddress, flags = flags)
@ -66,7 +66,7 @@ object Service {
} }
} }
fun updateMetadata(address: Address, field: KMutableProperty1<MetadataLine, String?>, value: String) { fun updateMetadata(address: SnesAddress, field: KMutableProperty1<MetadataLine, String?>, value: String) {
if (value.isEmpty()) { if (value.isEmpty()) {
if (address in metadata) { if (address in metadata) {
doUpdateMetadata(address, field, null) doUpdateMetadata(address, field, null)
@ -76,7 +76,7 @@ object Service {
} }
} }
private fun doUpdateMetadata(address: Address, field: KMutableProperty1<MetadataLine, String?>, value: String?) { private fun doUpdateMetadata(address: SnesAddress, field: KMutableProperty1<MetadataLine, String?>, value: String?) {
val line = metadata.getOrCreate(address) val line = metadata.getOrCreate(address)
field.set(line, value) field.set(line, value)

View File

@ -3,7 +3,7 @@ package com.smallhacker.disbrowser.asm
class Disassembly(lines: List<CodeUnit>) : Iterable<CodeUnit> { class Disassembly(lines: List<CodeUnit>) : Iterable<CodeUnit> {
override fun iterator() = lineList.iterator() as Iterator<CodeUnit> override fun iterator() = lineList.iterator() as Iterator<CodeUnit>
private val knownAddresses = HashSet<Address>() private val knownAddresses = HashSet<SnesAddress>()
private val lineList = ArrayList<CodeUnit>() private val lineList = ArrayList<CodeUnit>()
init { init {
@ -16,5 +16,5 @@ class Disassembly(lines: List<CodeUnit>) : Iterable<CodeUnit> {
} }
} }
operator fun contains(address: Address) = address in knownAddresses operator fun contains(address: SnesAddress) = address in knownAddresses
} }

View File

@ -3,10 +3,10 @@ package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.util.* import com.smallhacker.disbrowser.util.*
interface CodeUnit { interface CodeUnit {
val address: Address? val address: SnesAddress?
val relativeAddress: Address val relativeAddress: SnesAddress
val presentedAddress: Address val presentedAddress: SnesAddress
val nextPresentedAddress: Address val nextPresentedAddress: SnesAddress
val linkedState: State? val linkedState: State?
val preState: State? val preState: State?
@ -22,6 +22,11 @@ interface CodeUnit {
val suffix = lengthSuffix ?: "" val suffix = lengthSuffix ?: ""
return "$mnemonic$suffix" return "$mnemonic$suffix"
} }
fun printAlternativeOpcodeAndSuffix(): String? {
val mnemonic = opcode.mnemonic.alternativeName ?: return null
val suffix = lengthSuffix ?: ""
return "$mnemonic$suffix"
}
fun printOperands() = opcode.mode.print(this) fun printOperands() = opcode.mode.print(this)
fun bytesToString(): String { fun bytesToString(): String {
@ -58,22 +63,22 @@ interface CodeUnit {
class DataBlock( class DataBlock(
override val opcode: Opcode, override val opcode: Opcode,
override val bytes: RomData, override val bytes: RomData,
override val presentedAddress: Address, override val presentedAddress: SnesAddress,
override val relativeAddress: Address, override val relativeAddress: SnesAddress,
override val linkedState: State? override val linkedState: State?
) : CodeUnit { ) : CodeUnit {
override val nextPresentedAddress: Address override val nextPresentedAddress: SnesAddress
get() = presentedAddress + operandLength.toInt() get() = presentedAddress + operandLength.toInt()
override val operandLength get() = bytes.size override val operandLength get() = bytes.size
override val address: Address? = null override val address: SnesAddress? = null
override val preState: State? = null override val preState: State? = null
override val postState: State? = null override val postState: State? = null
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: RomData, override val opcode: Opcode, override val preState: State) : CodeUnit {
override val address: Address 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
override val nextPresentedAddress get() = postState.address override val nextPresentedAddress get() = postState.address
@ -105,18 +110,18 @@ class Instruction(override val bytes: RomData, override val opcode: Opcode, over
override val operandLength override val operandLength
get() = opcode.mode.operandLength(preState) get() = opcode.mode.operandLength(preState)
private fun link(): Address? { private fun link(): SnesAddress? {
if (!opcode.link) { if (!opcode.link) {
return null return null
} }
return when (opcode.mode) { return when (opcode.mode) {
Mode.ABSOLUTE -> relativeAddress.withinBank(word) Mode.ABSOLUTE -> relativeAddress.withinBank(word)
Mode.ABSOLUTE_LONG -> Address(long) Mode.ABSOLUTE_LONG -> SnesAddress(long)
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_WORD -> relativeAddress.withinBank(word)
Mode.DATA_LONG -> Address(long) Mode.DATA_LONG -> SnesAddress(long)
else -> null else -> null
} }
} }

View File

@ -7,36 +7,34 @@ import com.smallhacker.disbrowser.util.toUInt24
import java.util.* import java.util.*
class Metadata { class Metadata {
private val lines: MutableMap<Address, MetadataLine> @JsonProperty
private val code: TreeMap<SnesAddress, MetadataLine>
constructor() { constructor() {
this.lines = HashMap() this.code = TreeMap()
} }
@JsonCreator @JsonCreator
private constructor(@JsonProperty lines: Map<Address, MetadataLine>) { private constructor(@JsonProperty code: Map<SnesAddress, MetadataLine>) {
this.lines = HashMap(lines) this.code = TreeMap(code)
} }
@JsonValue operator fun set(address: SnesAddress, line: MetadataLine?): Metadata {
private fun serialize() = TreeMap(lines)
operator fun set(address: Address, line: MetadataLine?): Metadata {
if (line == null) { if (line == null) {
lines.remove(address) code.remove(address)
} else { } else {
lines[address] = line code[address] = line
} }
return this return this
} }
operator fun get(address: Address): MetadataLine? { operator fun get(address: SnesAddress): MetadataLine? {
return lines[address] return code[address]
} }
operator fun contains(address: Address) = lines[address] != null operator fun contains(address: SnesAddress) = code[address] != null
fun getOrCreate(address: Address): MetadataLine { fun getOrCreate(address: SnesAddress): MetadataLine {
val line = this[address] val line = this[address]
if (line != null) { if (line != null) {
return line return line
@ -60,17 +58,17 @@ object NonReturningRoutine : InstructionFlag {
} }
class JmpIndirectLongInterleavedTable @JsonCreator constructor( class JmpIndirectLongInterleavedTable @JsonCreator constructor(
@field:JsonProperty @JsonProperty private val start: Address, @field:JsonProperty @JsonProperty private val start: SnesAddress,
@field:JsonProperty @JsonProperty private val entries: Int @field:JsonProperty @JsonProperty private val entries: Int
) : InstructionFlag { ) : InstructionFlag {
private val uEntries get() = entries.toUInt() private val uEntries get() = entries.toUInt()
fun readTable(data: RomData): Sequence<Address> { fun readTable(data: RomData): Sequence<SnesAddress> {
return (0u until uEntries) return (0u until uEntries)
.asSequence() .asSequence()
.map { it + start.pc } .map { it + start.pc }
.map { pc -> joinBytes(data[pc], data[pc + uEntries], data[pc + uEntries + uEntries]).toUInt24() } .map { pc -> joinBytes(data[pc], data[pc + uEntries], data[pc + uEntries + uEntries]).toUInt24() }
.map { Address(it) } .map { SnesAddress(it) }
} }
fun generateCode(jumpInstruction: Instruction): Sequence<DataBlock> { fun generateCode(jumpInstruction: Instruction): Sequence<DataBlock> {
@ -92,7 +90,7 @@ class JmpIndirectLongInterleavedTable @JsonCreator constructor(
jumpInstruction.postState.address + offset.toInt(), jumpInstruction.postState.address + offset.toInt(),
jumpInstruction.relativeAddress, jumpInstruction.relativeAddress,
jumpInstruction.opcode.mutate(jumpInstruction) jumpInstruction.opcode.mutate(jumpInstruction)
.mutateAddress { Address(target) } .mutateAddress { SnesAddress(target) }
.withOrigin(jumpInstruction) .withOrigin(jumpInstruction)
) )
// Instruction( // Instruction(
@ -110,13 +108,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<Address> { fun readTable(postJsr: State): Sequence<SnesAddress> {
val data = postJsr.data val data = postJsr.data
return (0u until entries.toUInt()) return (0u until entries.toUInt())
.asSequence() .asSequence()
.map { postJsr.address.pc + (it * 3u) } .map { postJsr.address.pc + (it * 3u) }
.map { pc -> joinBytes(data[pc], data[pc + 1u], data[pc + 2u]).toUInt24() } .map { pc -> joinBytes(data[pc], data[pc + 1u], data[pc + 2u]).toUInt24() }
.map { Address(it) } .map { SnesAddress(it) }
} }
override fun toString() = "JslTableRoutine($entries)" override fun toString() = "JslTableRoutine($entries)"

View File

@ -8,8 +8,9 @@ data class MetadataLine(
var label: String? = null, var label: String? = null,
var comment: String? = null, var comment: String? = null,
var preComment: String? = null, var preComment: String? = null,
var length: Int? = null,
val flags: MutableList<InstructionFlag> = ArrayList() val flags: MutableList<InstructionFlag> = ArrayList()
) { ) {
@JsonIgnore @JsonIgnore
fun isEmpty() = (label == null) && (comment == null) && (preComment == null) && (flags.isEmpty()) fun isEmpty() = (label == null) && (comment == null) && (preComment == null) && (length == 0) && (flags.isEmpty())
} }

View File

@ -0,0 +1,13 @@
package com.smallhacker.disbrowser.asm
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonInclude
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
data class MetadataRam(
var label: String? = null,
var length: Int?
) {
@JsonIgnore
fun isEmpty() = (label == null) && (length == null)
}

View File

@ -1,7 +1,7 @@
package com.smallhacker.disbrowser.asm package com.smallhacker.disbrowser.asm
enum class Mnemonic(private val nameOverride: String? = null) { enum class Mnemonic(private val nameOverride: String? = null, val alternativeName: String? = null) {
ADC, AND, ASL, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRA, ADC, AND, ASL, BCC(alternativeName = "BLT"), BCS(alternativeName = "BGE"), BEQ, BIT, BMI, BNE, BPL, BRA,
BRK, BRL, BVC, BVS, CLC, CLD, CLI, CLV, CMP, COP, CPX, BRK, BRL, BVC, BVS, CLC, CLD, CLI, CLV, CMP, COP, CPX,
CPY, DEC, DEX, DEY, EOR, INC, INX, INY, JMP, JML, JSL, CPY, DEC, DEX, DEY, EOR, INC, INX, INY, JMP, JML, JSL,
JSR, LDA, LDX, LDY, LSR, MVN, MVP, NOP, ORA, PEA, PEI, JSR, LDA, LDX, LDY, LSR, MVN, MVP, NOP, ORA, PEA, PEI,
@ -11,7 +11,7 @@ enum class Mnemonic(private val nameOverride: String? = null) {
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(".db"), DW(".dw"), DL(".dl"); DB(nameOverride = ".db"), DW(nameOverride = ".dw"), DL(nameOverride = ".dl");
val displayName get() = nameOverride ?: name val displayName get() = nameOverride ?: name
} }

View File

@ -1,25 +1,25 @@
package com.smallhacker.disbrowser.asm package com.smallhacker.disbrowser.asm
class Segment (val start: Address, val end: SegmentEnd, val instructions: List<Instruction>) class Segment (val start: SnesAddress, val end: SegmentEnd, val instructions: List<Instruction>)
class SegmentEnd(val address: Address, val local: List<State> = emptyList(), val remote: List<State> = emptyList(), val returnAddress: Address? = null, val returning: Boolean = false) class SegmentEnd(val address: SnesAddress, val local: List<State> = emptyList(), val remote: List<State> = emptyList(), val returnAddress: SnesAddress? = null, val returning: Boolean = false)
fun stoppingSegmentEnd(address: Address) fun stoppingSegmentEnd(address: SnesAddress)
= SegmentEnd(address) = SegmentEnd(address)
fun branchingSegmentEnd(address: Address, continueState: State, branchState: State) fun branchingSegmentEnd(address: SnesAddress, continueState: State, branchState: State)
= SegmentEnd(address, local = listOf(continueState, branchState)) = SegmentEnd(address, local = listOf(continueState, branchState))
fun alwaysBranchingSegmentEnd(address: Address, branchState: State) fun alwaysBranchingSegmentEnd(address: SnesAddress, branchState: State)
= SegmentEnd(address, local = listOf(branchState)) = SegmentEnd(address, local = listOf(branchState))
fun jumpSegmentEnd(address: Address, targetState: State) fun jumpSegmentEnd(address: SnesAddress, targetState: State)
= SegmentEnd(address, remote = listOf(targetState)) = SegmentEnd(address, remote = listOf(targetState))
fun subroutineSegmentEnd(address: Address, targetState: State, returnAddress: Address) fun subroutineSegmentEnd(address: SnesAddress, targetState: State, returnAddress: SnesAddress)
= SegmentEnd(address, remote = listOf(targetState), returnAddress = returnAddress) = SegmentEnd(address, remote = listOf(targetState), returnAddress = returnAddress)
fun returnSegmentEnd(address: Address) fun returnSegmentEnd(address: SnesAddress)
= SegmentEnd(address, returning = true) = SegmentEnd(address, returning = true)
fun continuationSegmentEnd(state: State) fun continuationSegmentEnd(state: State)

View File

@ -1,19 +1,16 @@
package com.smallhacker.disbrowser.asm package com.smallhacker.disbrowser.asm
import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.annotation.JsonValue
import com.smallhacker.disbrowser.util.UInt24 import com.smallhacker.disbrowser.util.UInt24
import com.smallhacker.disbrowser.util.toUInt24 import com.smallhacker.disbrowser.util.toUInt24
import com.smallhacker.disbrowser.util.tryParseInt import com.smallhacker.disbrowser.util.tryParseInt
data class Address(val value: UInt24) : Comparable<Address> { data class SnesAddress(val value: UInt24) : Comparable<SnesAddress> {
val rom = (value and 0x8000u).toUInt() == 0u
val pc = snesToPc(value) val pc = snesToPc(value)
operator fun plus(offset: Int) = Address(value + offset) operator fun plus(offset: Int) = SnesAddress(value + offset)
operator fun minus(offset: Int) = Address(value - offset) operator fun minus(offset: Int) = SnesAddress(value - offset)
operator fun inc() = plus(1) operator fun inc() = plus(1)
operator fun dec() = minus(1) operator fun dec() = minus(1)
@ -22,21 +19,21 @@ data class Address(val value: UInt24) : Comparable<Address> {
@JsonValue @JsonValue
fun toSimpleString(): String = String.format("%06x", value.toInt()) fun toSimpleString(): String = String.format("%06x", value.toInt())
fun withinBank(value: UShort): Address = Address((this.value and 0xFF_0000u) or value.toUInt24()) fun withinBank(value: UShort): SnesAddress = SnesAddress((this.value and 0xFF_0000u) or value.toUInt24())
override fun compareTo(other: Address) = value.toUInt().compareTo(other.value.toUInt()) override fun compareTo(other: SnesAddress) = value.toUInt().compareTo(other.value.toUInt())
infix fun distanceTo(other: Address) = Math.abs(value.toInt() - other.value.toInt()).toUInt() infix fun distanceTo(other: SnesAddress) = Math.abs(value.toInt() - other.value.toInt()).toUInt()
companion object { companion object {
@JvmStatic @JvmStatic
@JsonCreator @JsonCreator
fun parse(address: String): Address? = tryParseInt(address, 16) fun parse(address: String): SnesAddress? = tryParseInt(address, 16)
?.let { Address(it.toUInt24()) } ?.let { SnesAddress(it.toUInt24()) }
} }
} }
fun address(snesAddress: Int) = Address(snesAddress.toUInt24()) fun address(snesAddress: Int) = SnesAddress(snesAddress.toUInt24())
private fun snesToPc(value: UInt24): UInt { private fun snesToPc(value: UInt24): UInt {
// TODO: This is incredibly oversimplified. Anything that isn't a small LoROM will crash and burn // TODO: This is incredibly oversimplified. Anything that isn't a small LoROM will crash and burn

View File

@ -2,10 +2,14 @@ package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.ImmStack import com.smallhacker.disbrowser.ImmStack
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: Address, val flags: VagueNumber = VagueNumber(), val stack: ImmStack<VagueNumber> = immStack()) { data class State(val origin: Instruction? = null, val data: RomData, 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 dp: UShort? get() = 0x7e00u // TODO
val pb: UByte get() = (address.value shr 16).toUByte()
val mWidth: UInt? get() = toWidth(m) val mWidth: UInt? get() = toWidth(m)
val xWidth: UInt? get() = toWidth(x) val xWidth: UInt? get() = toWidth(x)
@ -16,7 +20,7 @@ data class State(val origin: Instruction? = null, val data: RomData, val address
private fun withFlags(flags: VagueNumber) = copy(flags = flags) private fun withFlags(flags: VagueNumber) = copy(flags = flags)
fun mutateAddress(mutator: (Address) -> Address) = copy(address = mutator(address)) fun mutateAddress(mutator: (SnesAddress) -> SnesAddress) = copy(address = mutator(address))
fun withOrigin(instruction: Instruction?) = copy(origin = instruction) fun withOrigin(instruction: Instruction?) = copy(origin = instruction)
fun push(value: UInt) = push(VagueNumber(value)) fun push(value: UInt) = push(VagueNumber(value))
@ -52,6 +56,16 @@ 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 ->
val ptr = (dp.toUInt24() shl 8) or (directPage.toUInt24())
SnesAddress(ptr)
}
fun resolve(absolute: UShort)= db?.let { db ->
val ptr = (db.toUInt24() shl 16) or (absolute.toUInt24())
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

@ -6,7 +6,7 @@ import kotlin.collections.ArrayList
object Disassembler { object Disassembler {
fun disassemble(initialState: State, metadata: Metadata, global: Boolean): Disassembly { fun disassemble(initialState: State, metadata: Metadata, global: Boolean): Disassembly {
val seen = HashSet<Address>() val seen = HashSet<SnesAddress>()
val queue = ArrayDeque<State>() val queue = ArrayDeque<State>()
fun tryAdd(state: State) { fun tryAdd(state: State) {
@ -79,7 +79,7 @@ object Disassembler {
} }
fun disassembleSegments(initialState: State): List<Segment> { fun disassembleSegments(initialState: State): List<Segment> {
val mapping = HashMap<Address, Segment>() val mapping = HashMap<SnesAddress, Segment>()
val queue = ArrayDeque<State>() val queue = ArrayDeque<State>()
val segments = ArrayList<Segment>() val segments = ArrayList<Segment>()
@ -119,12 +119,12 @@ object Disassembler {
return segments return segments
} }
fun disassembleSegment(initialState: State, mapping: MutableMap<Address, Segment>): Segment { fun disassembleSegment(initialState: State, mapping: MutableMap<SnesAddress, Segment>): Segment {
val instructions = ArrayList<Instruction>() val instructions = ArrayList<Instruction>()
var lastState = initialState var lastState = initialState
val queue = ArrayDeque<State>() val queue = ArrayDeque<State>()
val seen = HashSet<Address>() val seen = HashSet<SnesAddress>()
fun finalize(segment: Segment): Segment { fun finalize(segment: Segment): Segment {
instructions.forEach { instructions.forEach {

View File

@ -2,7 +2,7 @@ package com.smallhacker.disbrowser.resource
import com.smallhacker.disbrowser.HtmlNode import com.smallhacker.disbrowser.HtmlNode
import com.smallhacker.disbrowser.Service import com.smallhacker.disbrowser.Service
import com.smallhacker.disbrowser.asm.Address import com.smallhacker.disbrowser.asm.SnesAddress
import com.smallhacker.disbrowser.asm.VagueNumber import com.smallhacker.disbrowser.asm.VagueNumber
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import javax.ws.rs.GET import javax.ws.rs.GET
@ -30,7 +30,7 @@ class DisassemblyResource {
@Produces(MediaType.TEXT_HTML) @Produces(MediaType.TEXT_HTML)
fun getIt(@PathParam("address") address: String, @PathParam("state") state: String): Response { fun getIt(@PathParam("address") address: String, @PathParam("state") state: String): Response {
return handle { return handle {
Address.parse(address)?.let { SnesAddress.parse(address)?.let {
val flags = parseState(state) val flags = parseState(state)
Service.showDisassembly(it, flags) Service.showDisassembly(it, flags)
} }

View File

@ -1,7 +1,7 @@
package com.smallhacker.disbrowser.resource package com.smallhacker.disbrowser.resource
import com.smallhacker.disbrowser.Service import com.smallhacker.disbrowser.Service
import com.smallhacker.disbrowser.asm.Address import com.smallhacker.disbrowser.asm.SnesAddress
import com.smallhacker.disbrowser.asm.MetadataLine import com.smallhacker.disbrowser.asm.MetadataLine
import javax.ws.rs.Consumes import javax.ws.rs.Consumes
import javax.ws.rs.POST import javax.ws.rs.POST
@ -16,7 +16,7 @@ class RestResource {
@Path("{address}/{field}") @Path("{address}/{field}")
@Consumes(MediaType.TEXT_PLAIN) @Consumes(MediaType.TEXT_PLAIN)
fun getIt(@PathParam("address") address: String, @PathParam("field") fieldName: String, body: String): Response { fun getIt(@PathParam("address") address: String, @PathParam("field") fieldName: String, body: String): Response {
val parsedAddress = Address.parse(address) ?: return Response.status(400).build() val parsedAddress = SnesAddress.parse(address) ?: return Response.status(400).build()
val field = when (fieldName) { val field = when (fieldName) {
"preComment" -> MetadataLine::preComment "preComment" -> MetadataLine::preComment
"comment" -> MetadataLine::comment "comment" -> MetadataLine::comment

View File

@ -99,9 +99,12 @@ tr.line-active {
border: none; border: none;
background: none; background: none;
padding: 0; padding: 0;
width: 100%;
} }
.field-comment { .field-comment {
width: 500px; width: 500px;
}
.opcode-info{
text-decoration: green underline dotted;
} }