mirror of
https://github.com/Smallhacker/disbrowser.git
synced 2024-12-17 18:29:57 +00:00
Stuff
This commit is contained in:
parent
7a4b94e907
commit
188d53a768
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
/.idea
|
||||
/target
|
||||
/src/main/resources/public/disbrowser.*
|
64
Zelda no Densetsu - Kamigami no Triforce (Japan).json
Normal file
64
Zelda no Densetsu - Kamigami no Triforce (Japan).json
Normal file
@ -0,0 +1,64 @@
|
||||
[
|
||||
{
|
||||
"address": 32768,
|
||||
"label": "ResetVector",
|
||||
"comment": null,
|
||||
"flags": []
|
||||
},
|
||||
{
|
||||
"address": 32820,
|
||||
"label": "MainGameLoop",
|
||||
"comment": null,
|
||||
"flags": []
|
||||
},
|
||||
{
|
||||
"address": 32949,
|
||||
"label": "JumpToGameMode",
|
||||
"comment": null,
|
||||
"flags": []
|
||||
},
|
||||
{
|
||||
"address": 165840,
|
||||
"label": null,
|
||||
"comment": null,
|
||||
"flags": [
|
||||
{
|
||||
"flagType": "JslTableRoutine",
|
||||
"entries": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": 32966,
|
||||
"label": null,
|
||||
"comment": null,
|
||||
"flags": [
|
||||
{
|
||||
"flagType": "JmpIndirectLongInterleavedTable",
|
||||
"start": 32865,
|
||||
"entries": 28
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": 835861,
|
||||
"label": null,
|
||||
"comment": null,
|
||||
"flags": [
|
||||
{
|
||||
"flagType": "JslTableRoutine",
|
||||
"entries": 12
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": 34716,
|
||||
"label": null,
|
||||
"comment": null,
|
||||
"flags": [
|
||||
{
|
||||
"flagType": "NonReturningRoutine"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
21
pom.xml
21
pom.xml
@ -22,6 +22,26 @@
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-kotlin</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.containers</groupId>
|
||||
<artifactId>jersey-container-grizzly2-http</artifactId>
|
||||
@ -134,5 +154,6 @@
|
||||
<jersey.version>2.27</jersey.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<kotlin.version>1.3.11</kotlin.version>
|
||||
<jackson.version>2.9.8</jackson.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
@ -59,28 +59,34 @@ class Grid {
|
||||
.first()
|
||||
}
|
||||
|
||||
fun add(ins: Instruction, metadata: MetadataLine?, disassembly: Disassembly) {
|
||||
val address = ins.address
|
||||
fun add(ins: CodeUnit, metadata: Metadata, disassembly: Disassembly) {
|
||||
val insMetadata = ins.address ?.let { metadata[it] }
|
||||
|
||||
val actualAddress = ins.address
|
||||
val presentedAddress = ins.presentedAddress
|
||||
|
||||
if (nextAddress != null) {
|
||||
if (address != nextAddress) {
|
||||
if (presentedAddress != nextAddress) {
|
||||
addDummy()
|
||||
}
|
||||
}
|
||||
nextAddress = ins.postState.address
|
||||
nextAddress = ins.nextPresentedAddress
|
||||
|
||||
val y = (height++)
|
||||
addresses[address] = y
|
||||
addresses[presentedAddress] = y
|
||||
|
||||
add(y, ins.address,
|
||||
text(ins.address.toFormattedString()),
|
||||
text(actualAddress?.toFormattedString() ?: ""),
|
||||
text(ins.bytesToString()),
|
||||
text(metadata?.label?.plus(":") ?: ""),
|
||||
input(value = insMetadata?.label ?: "")
|
||||
.addClass("field-label")
|
||||
.addClass("field-editable")
|
||||
.attr("data-field", "label")
|
||||
.attr("data-address", presentedAddress.value.toString()),
|
||||
fragment {
|
||||
text("${ins.opcode.mnemonic}")
|
||||
text(ins.lengthSuffix)
|
||||
text(ins.printOpcodeAndSuffix())
|
||||
text(" ")
|
||||
val operands = ins.opcode.mode.print(ins)
|
||||
var operands = ins.printOperands()
|
||||
|
||||
val link = ins.linkedState
|
||||
if (link == null) {
|
||||
@ -93,13 +99,19 @@ class Grid {
|
||||
else -> "/${link.address.toSimpleString()}/${link.urlString}"
|
||||
}
|
||||
|
||||
operands = metadata[link.address]?.label ?: operands
|
||||
|
||||
a {
|
||||
text(operands)
|
||||
}.attr("href", url)
|
||||
}
|
||||
},
|
||||
text(ins.postState.toString())
|
||||
text(ins.postState?.toString() ?: ""),
|
||||
input(value = insMetadata?.comment ?: "")
|
||||
.addClass("field-comment")
|
||||
.addClass("field-editable")
|
||||
.attr("data-field", "comment")
|
||||
.attr("data-address", presentedAddress.value.toString())
|
||||
)
|
||||
|
||||
if (ins.opcode.continuation == Continuation.NO) {
|
||||
@ -109,7 +121,7 @@ class Grid {
|
||||
|
||||
private fun addDummy() {
|
||||
val y = (height++)
|
||||
add(y, null, null, null, null, text("..."), null)
|
||||
add(y, null, null, null, null, text("..."), null, null)
|
||||
}
|
||||
|
||||
private fun add(y: Int, address: Address?,
|
||||
@ -117,7 +129,8 @@ class Grid {
|
||||
cBytes: HtmlNode?,
|
||||
cLabel: HtmlNode?,
|
||||
cCode: HtmlNode?,
|
||||
cState: HtmlNode?
|
||||
cState: HtmlNode?,
|
||||
cComment: HtmlNode?
|
||||
) {
|
||||
if (address != null) {
|
||||
rowId[y] = address.toSimpleString()
|
||||
@ -127,9 +140,15 @@ class Grid {
|
||||
content[2 to y] = cLabel
|
||||
content[3 to y] = cCode
|
||||
content[4 to y] = cState
|
||||
content[5 to y] = cComment
|
||||
}
|
||||
|
||||
fun output(): HtmlNode {
|
||||
val contentMaxX = content.keys.asSequence()
|
||||
.map { it.first }
|
||||
.max()
|
||||
?: -1
|
||||
|
||||
return table {
|
||||
for (y in 0 until height) {
|
||||
tr {
|
||||
@ -145,7 +164,7 @@ class Grid {
|
||||
arrowCells[x to y]?.appendTo(parent)
|
||||
}.addClass(cssClass)
|
||||
}
|
||||
for (x in 4..4) {
|
||||
for (x in 4..contentMaxX) {
|
||||
val cssClass = cellClasses[x to y]
|
||||
td {
|
||||
content[x to y]?.appendTo(parent)
|
||||
|
@ -107,6 +107,8 @@ fun a(inner: InnerHtml = {}) = parent("a", inner)
|
||||
fun HtmlArea.a(inner: InnerHtml = {}) = com.smallhacker.disbrowser.a(inner).appendTo(parent)
|
||||
fun script(inner: InnerHtml = {}) = parent("script", inner)
|
||||
fun HtmlArea.script(inner: InnerHtml = {}) = com.smallhacker.disbrowser.script(inner).appendTo(parent)
|
||||
fun input(type: String = "text", value: String = "") = leaf("input").attr("type", type).attr("value", value)
|
||||
fun HtmlArea.input(type: String = "text", value: String = "") = com.smallhacker.disbrowser.input(type, value).appendTo(parent)
|
||||
|
||||
fun HtmlNode.appendTo(node: HtmlNode) = apply { node.append(this) }
|
||||
fun HtmlNode.addClass(c: String?) = attrAdd("class", c)
|
||||
|
@ -21,8 +21,8 @@ public class Main {
|
||||
*/
|
||||
public static HttpServer startServer() {
|
||||
// create a resource config that scans for JAX-RS resources and providers
|
||||
// in com.smallhacker.disbrowser package
|
||||
final ResourceConfig rc = new ResourceConfig().packages("com.smallhacker.disbrowser");
|
||||
// in com.smallhacker.disbrowser.resource package
|
||||
final ResourceConfig rc = new ResourceConfig().packages("com.smallhacker.disbrowser.resource");
|
||||
|
||||
// create and start a new instance of grizzly http server
|
||||
// exposing the Jersey application at BASE_URI
|
||||
|
@ -1,159 +0,0 @@
|
||||
package com.smallhacker.disbrowser
|
||||
|
||||
import com.smallhacker.disbrowser.asm.*
|
||||
import com.smallhacker.disbrowser.disassembler.Disassembler
|
||||
import com.smallhacker.disbrowser.util.tryParseInt
|
||||
import java.nio.file.Paths
|
||||
import javax.ws.rs.GET
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.PathParam
|
||||
import javax.ws.rs.Produces
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
private val RESET_VECTOR_LOCATION = Address(0x00_FFFC)
|
||||
|
||||
@Path("/")
|
||||
class MyResource {
|
||||
private val romData = lazy {
|
||||
//RomData.load(Paths.get("P:\\Emulation\\ROMs\\SNES\\Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"))
|
||||
RomData.load(Paths.get("""P:\Emulation\ROMs\SNES\Super Mario World.sfc"""))
|
||||
}
|
||||
|
||||
@GET
|
||||
fun getIt(): Response {
|
||||
return handle {
|
||||
val resetVectorLocation = RESET_VECTOR_LOCATION
|
||||
val initialAddress = Address(romData.value.getWord(resetVectorLocation.pc).value)
|
||||
val flags = parseState("MX")
|
||||
showDisassembly(initialAddress, flags)
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{address}")
|
||||
fun getIt(@PathParam("address") address: String): Response {
|
||||
return handle {
|
||||
parseAddress(address)?.let {
|
||||
val flags = parseState("")
|
||||
showDisassembly(it, flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@GET
|
||||
@Path("{address}/{state}")
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt(@PathParam("address") address: String, @PathParam("state") state: String): Response {
|
||||
return handle {
|
||||
parseAddress(address)?.let {
|
||||
val flags = parseState(state)
|
||||
showDisassembly(it, flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseAddress(address: String): Address? {
|
||||
return tryParseInt(address, 16)
|
||||
?.let { Address(it) }
|
||||
}
|
||||
|
||||
private fun handle(runner: () -> HtmlNode?): Response {
|
||||
try {
|
||||
val disassembly = runner()
|
||||
|
||||
return if (disassembly == null)
|
||||
Response.status(404).build()
|
||||
else
|
||||
Response.ok(disassembly.toString(), MediaType.TEXT_HTML).build()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDisassembly(initialAddress: Address, flags: VagueNumber): HtmlNode? {
|
||||
val rom = romData.value
|
||||
|
||||
val metadata = getMetadata()
|
||||
|
||||
val initialState = State(data = rom, address = initialAddress, flags = flags)
|
||||
val disassembly = Disassembler.disassemble(initialState, metadata, false)
|
||||
|
||||
return print(disassembly, metadata)
|
||||
}
|
||||
|
||||
private fun parseState(state: String): VagueNumber {
|
||||
var flags = VagueNumber()
|
||||
flags = parseFlag(state, flags, 'M', 'm', 0x20)
|
||||
flags = parseFlag(state, flags, 'X', 'x', 0x10)
|
||||
return flags
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("resources/{file}.{ext}")
|
||||
fun getCss(@PathParam("file") file: String, @PathParam("ext") ext: String): Response {
|
||||
val mime = when (ext) {
|
||||
"js" -> "application/javascript"
|
||||
"css" -> "text/css"
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (mime != null) {
|
||||
javaClass.getResourceAsStream("/$file.$ext")
|
||||
?.bufferedReader()
|
||||
?.use {
|
||||
return Response.ok(it.readText())
|
||||
.type(mime)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
return Response.status(404).build()
|
||||
}
|
||||
|
||||
|
||||
private fun parseFlag(state: String, flags: VagueNumber, set: Char, clear: Char, mask: Int): VagueNumber = when {
|
||||
state.contains(set) -> flags.withBits(mask)
|
||||
state.contains(clear) -> flags.withoutBits(mask)
|
||||
else -> flags
|
||||
}
|
||||
|
||||
private fun print(disassembly: Disassembly, metadata: Metadata): HtmlNode {
|
||||
val grid = Grid()
|
||||
disassembly.forEach { grid.add(it, metadata[it.address], disassembly) }
|
||||
disassembly.asSequence()
|
||||
.mapNotNull {
|
||||
it.linkedState
|
||||
?.let { link ->
|
||||
it.address to link.address
|
||||
}
|
||||
}
|
||||
.sortedBy { Math.abs(it.first.value - it.second.value) }
|
||||
.forEach { grid.arrow(it.first, it.second) }
|
||||
|
||||
return html {
|
||||
head {
|
||||
title { text("Disassembly Browser") }
|
||||
link {}.attr("rel", "stylesheet").attr("href", "/resources/style.css")
|
||||
}
|
||||
body {
|
||||
grid.output().appendTo(parent)
|
||||
script().attr("src", "/resources/main.js")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMetadata(): Metadata {
|
||||
return metadata {
|
||||
at(0x00_8000) { label = "RESET_VECTOR" }
|
||||
at(0x00_80c6) { flags.add(JmpIndirectLongInterleavedTable(Address(0x00_8061), 28)) }
|
||||
at(0x00_879c) { flags.add(NonReturningRoutine) }
|
||||
at(0x02_87d0) { flags.add(JslTableRoutine(4)) }
|
||||
at(0x0c_c115) { flags.add(JslTableRoutine(12)) }
|
||||
//at(0x0c_c115) { stop = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
78
src/main/java/com/smallhacker/disbrowser/Service.kt
Normal file
78
src/main/java/com/smallhacker/disbrowser/Service.kt
Normal file
@ -0,0 +1,78 @@
|
||||
package com.smallhacker.disbrowser
|
||||
|
||||
import com.smallhacker.disbrowser.asm.*
|
||||
import com.smallhacker.disbrowser.disassembler.Disassembler
|
||||
import com.smallhacker.disbrowser.util.jsonFile
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
import java.nio.file.Paths
|
||||
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 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"))
|
||||
private val metadata by lazy { metaFile.load() }
|
||||
|
||||
private val romData = lazy {
|
||||
val path = romDir.resolve("$romName.sfc")
|
||||
RomData.load(path)
|
||||
}
|
||||
|
||||
fun showDisassemblyFromReset(): HtmlNode? {
|
||||
val resetVectorLocation = RESET_VECTOR_LOCATION
|
||||
val initialAddress = Address(romData.value.getWord(resetVectorLocation.pc).toUInt24())
|
||||
val flags = VagueNumber(0x30u)
|
||||
return showDisassembly(initialAddress, flags)
|
||||
}
|
||||
|
||||
fun showDisassembly(initialAddress: Address, flags: VagueNumber): HtmlNode? {
|
||||
val rom = romData.value
|
||||
|
||||
val initialState = State(data = rom, address = initialAddress, flags = flags)
|
||||
val disassembly = Disassembler.disassemble(initialState, metadata, false)
|
||||
|
||||
return print(disassembly, metadata)
|
||||
}
|
||||
|
||||
|
||||
private fun print(disassembly: Disassembly, metadata: Metadata): HtmlNode {
|
||||
val grid = Grid()
|
||||
disassembly.forEach {
|
||||
grid.add(it, metadata, disassembly)
|
||||
}
|
||||
disassembly.asSequence()
|
||||
.mapNotNull {
|
||||
it.linkedState
|
||||
?.let { link ->
|
||||
it.presentedAddress to link.address
|
||||
}
|
||||
}
|
||||
.sortedBy { it.first distanceTo it.second }
|
||||
.forEach { grid.arrow(it.first, it.second) }
|
||||
|
||||
return html {
|
||||
head {
|
||||
title { text("Disassembly Browser") }
|
||||
link {}.attr("rel", "stylesheet").attr("href", "/resources/style.css")
|
||||
}
|
||||
body {
|
||||
grid.output().appendTo(parent)
|
||||
script().attr("src", "/resources/disbrowser.js")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMetadata(address: Address, field: KMutableProperty1<MetadataLine, String?>, value: String) {
|
||||
if (value.isEmpty()) {
|
||||
metadata[address]?.run {
|
||||
field.set(this, null)
|
||||
}
|
||||
} else {
|
||||
field.set(metadata.getOrAdd(address), value)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +1,36 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
data class Address(val value: Int): Comparable<Address> {
|
||||
val rom = (value and 0x8000) == 0
|
||||
val pc = (value and 0x7FFF) or ((value and 0x7F_0000) shr 1)
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonValue
|
||||
import com.smallhacker.disbrowser.util.UInt24
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
|
||||
data class Address(@JsonValue val value: UInt24): Comparable<Address> {
|
||||
@JsonIgnore
|
||||
val rom = (value and 0x8000u).toUInt() == 0u
|
||||
@JsonIgnore
|
||||
val pc = snesToPc(value)
|
||||
|
||||
operator fun plus(offset: Int) = Address(value + offset)
|
||||
operator fun minus(offset: Int) = Address(value - offset)
|
||||
operator fun inc() = plus(1)
|
||||
operator fun dec() = plus(1)
|
||||
operator fun dec() = minus(1)
|
||||
|
||||
override fun toString(): String = toFormattedString()
|
||||
fun toFormattedString(): String = String.format("$%02x:%04x", value shr 16, value and 0xFFFF)
|
||||
fun toSimpleString(): String = String.format("%06x", value)
|
||||
fun toFormattedString(): String = String.format("$%02x:%04x", (value shr 16).toInt(), (value and 0xFFFFu).toInt())
|
||||
fun toSimpleString(): String = String.format("%06x", value.toInt())
|
||||
|
||||
fun withinBank(value: Int): Address = Address((this.value and 0xFF_0000) or value)
|
||||
fun withinBank(value: UShort): Address = Address((this.value and 0xFF_0000u) or value.toUInt24())
|
||||
|
||||
override fun compareTo(other: Address) = value.compareTo(other.value)
|
||||
override fun compareTo(other: Address) = value.toUInt().compareTo(other.value.toUInt())
|
||||
|
||||
infix fun distanceTo(other: Address)= Math.abs(value.toInt() - other.value.toInt()).toUInt()
|
||||
}
|
||||
|
||||
fun address(snesAddress: Int) = Address(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)
|
||||
}
|
||||
|
@ -1,13 +1,20 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
class Disassembly(lines: List<Instruction>) : Iterable<Instruction> {
|
||||
override fun iterator() = lines.values.iterator() as Iterator<Instruction>
|
||||
class Disassembly(lines: List<CodeUnit>) : Iterable<CodeUnit> {
|
||||
override fun iterator() = lineList.iterator() as Iterator<CodeUnit>
|
||||
|
||||
private val lines = LinkedHashMap<Address, Instruction>()
|
||||
private val knownAddresses = HashSet<Address>()
|
||||
private val lineList = ArrayList<CodeUnit>()
|
||||
|
||||
init {
|
||||
lines.forEach { this.lines[it.address] = it }
|
||||
lines.forEach {
|
||||
val address = it.address
|
||||
if (address != null) {
|
||||
knownAddresses += address
|
||||
}
|
||||
lineList.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun contains(address: Address) = address in lines
|
||||
operator fun contains(address: Address) = address in knownAddresses
|
||||
}
|
@ -2,86 +2,107 @@ package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.smallhacker.disbrowser.util.*
|
||||
|
||||
class Instruction(val bytes: RomData, val opcode: Opcode, val preState: State) {
|
||||
val address: Address
|
||||
get() = preState.address
|
||||
interface CodeUnit {
|
||||
val address: Address?
|
||||
val relativeAddress: Address
|
||||
val presentedAddress: Address
|
||||
val nextPresentedAddress: Address
|
||||
|
||||
val postState = opcode.mutate(this)
|
||||
.mutateAddress { it + bytes.size }
|
||||
val linkedState: State?
|
||||
val preState: State?
|
||||
val postState: State?
|
||||
|
||||
val bytes: RomData
|
||||
val opcode: Opcode
|
||||
val lengthSuffix: String?
|
||||
|
||||
fun operandByte(index: UInt): UByte = bytes[opcode.operandIndex + index]
|
||||
fun printOpcodeAndSuffix(): String {
|
||||
val mnemonic = opcode.mnemonic.displayName
|
||||
val suffix = lengthSuffix ?: ""
|
||||
return "$mnemonic$suffix"
|
||||
}
|
||||
fun printOperands() = opcode.mode.print(this)
|
||||
|
||||
fun bytesToString(): String {
|
||||
return bytes.asSequence()
|
||||
.map { toHex(it.toUInt(), 1u) }
|
||||
.joinToString(" ")
|
||||
.padEnd(11, ' ')
|
||||
}
|
||||
|
||||
val operandLength: UInt?
|
||||
|
||||
val signedByte get() = byte.toByte()
|
||||
|
||||
val signedWord get() = word.toShort()
|
||||
|
||||
val byte get() = operandByte(0u)
|
||||
|
||||
val byte2 get() = operandByte(1u)
|
||||
|
||||
val word get() = joinBytes(operandByte(0u), operandByte(1u)).toUShort()
|
||||
|
||||
val long get() = joinBytes(operandByte(0u), operandByte(1u), operandByte(2u)).toUInt24()
|
||||
|
||||
val value
|
||||
get() = when (operandLength?.toInt()) {
|
||||
0 -> 0u
|
||||
1 -> byte.toUInt()
|
||||
2 -> word.toUInt()
|
||||
3 -> long.toUInt()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
class DataBlock(
|
||||
override val opcode: Opcode,
|
||||
override val bytes: RomData,
|
||||
override val presentedAddress: Address,
|
||||
override val relativeAddress: Address,
|
||||
override val linkedState: State?
|
||||
) : CodeUnit {
|
||||
override val nextPresentedAddress: Address
|
||||
get() = presentedAddress + operandLength.toInt()
|
||||
override val operandLength get() = bytes.size
|
||||
|
||||
override val address: Address? = null
|
||||
override val preState: State? = null
|
||||
override val postState: State? = null
|
||||
override val lengthSuffix: String? = null
|
||||
}
|
||||
|
||||
class Instruction(override val bytes: RomData, override val opcode: Opcode, override val preState: State) : CodeUnit {
|
||||
override val address: Address get() = preState.address
|
||||
override val relativeAddress get() = address
|
||||
override val presentedAddress get() = address
|
||||
override val nextPresentedAddress get() = postState.address
|
||||
|
||||
override val postState = opcode.mutate(this)
|
||||
.mutateAddress { it + bytes.size.toInt() }
|
||||
.withOrigin(this)
|
||||
val linkedState = link()?.let { link ->
|
||||
override val linkedState = link()?.let { link ->
|
||||
opcode.mutate(this)
|
||||
.mutateAddress { link }
|
||||
.withOrigin(this)
|
||||
}
|
||||
|
||||
operator fun get(index: Int): UByte {
|
||||
return bytes[index]
|
||||
}
|
||||
|
||||
val signedByte: Int
|
||||
get() = byte.value.toByte().toInt()
|
||||
|
||||
val signedWord: Int
|
||||
get() = word.value.toShort().toInt()
|
||||
|
||||
val byte: UByte
|
||||
get() = get(1)
|
||||
|
||||
val byte2: UByte
|
||||
get() = get(2)
|
||||
|
||||
val dataByte: UByte
|
||||
get() = get(0)
|
||||
|
||||
val word: UWord
|
||||
get() = (get(2).toWord() left 8) or get(1).toWord()
|
||||
|
||||
val dataWord: UWord
|
||||
get() = (get(1).toWord() left 8) or get(0).toWord()
|
||||
|
||||
val long: ULong
|
||||
get() = (get(3).toLong() left 16) or (get(2).toLong() left 8) or get(1).toLong()
|
||||
|
||||
val dataLong: ULong
|
||||
get() = (get(2).toLong() left 16) or (get(1).toLong() left 8) or get(0).toLong()
|
||||
|
||||
fun bytesToString(): String {
|
||||
return bytes.asSequence().map { it.toHex() }.joinToString(" ").padEnd(11, ' ')
|
||||
}
|
||||
|
||||
//val value: ULong
|
||||
// get() {
|
||||
// val len = operandLength
|
||||
// val start = opcode.operandIndex
|
||||
|
||||
//}
|
||||
|
||||
//fun getOperand(index: Int, length: Int) {
|
||||
// val v = uLong(0)
|
||||
// for (i in (index + length) downTo index)
|
||||
//}
|
||||
|
||||
val lengthSuffix: String
|
||||
override val lengthSuffix: String?
|
||||
get() {
|
||||
return when (opcode.mode) {
|
||||
Mode.IMPLIED -> ""
|
||||
Mode.IMMEDIATE_8 -> ""
|
||||
Mode.IMMEDIATE_16 -> ""
|
||||
Mode.RELATIVE -> ""
|
||||
Mode.RELATIVE_LONG -> ""
|
||||
Mode.BLOCK_MOVE -> ""
|
||||
else -> when (operandLength) {
|
||||
null -> ".?"
|
||||
1 -> ".b"
|
||||
2 -> ".w"
|
||||
3 -> ".l"
|
||||
else -> ""
|
||||
}
|
||||
if (!opcode.mode.showLengthSuffix) {
|
||||
return null
|
||||
}
|
||||
|
||||
return when (operandLength?.toInt()) {
|
||||
null -> ".?"
|
||||
1 -> ".b"
|
||||
2 -> ".w"
|
||||
3 -> ".l"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private val operandLength
|
||||
override val operandLength
|
||||
get() = opcode.mode.operandLength(preState)
|
||||
|
||||
private fun link(): Address? {
|
||||
@ -90,17 +111,17 @@ class Instruction(val bytes: RomData, val opcode: Opcode, val preState: State) {
|
||||
}
|
||||
|
||||
return when (opcode.mode) {
|
||||
Mode.ABSOLUTE -> address.withinBank(word.value)
|
||||
Mode.ABSOLUTE_LONG -> Address(long.value)
|
||||
Mode.RELATIVE -> address + 2 + signedByte
|
||||
Mode.RELATIVE_LONG -> address + 3 + signedWord
|
||||
Mode.DATA_WORD -> address.withinBank(dataWord.value)
|
||||
Mode.DATA_LONG -> Address(dataLong.value)
|
||||
Mode.ABSOLUTE -> relativeAddress.withinBank(word)
|
||||
Mode.ABSOLUTE_LONG -> Address(long)
|
||||
Mode.RELATIVE -> relativeAddress + 2 + signedByte.toInt()
|
||||
Mode.RELATIVE_LONG -> relativeAddress + 3 + signedWord.toInt()
|
||||
Mode.DATA_WORD -> relativeAddress.withinBank(word)
|
||||
Mode.DATA_LONG -> Address(long)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "$address ${bytesToString()} ${opcode.mnemonic} ${opcode.mode.print(this).padEnd(100, ' ')} ($preState -> $postState)"
|
||||
return "$address ${bytesToString()} ${opcode.mnemonic.displayName} ${opcode.mode.print(this).padEnd(100, ' ')} ($preState -> $postState)"
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,21 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.smallhacker.disbrowser.util.left
|
||||
import com.smallhacker.disbrowser.util.or
|
||||
import com.smallhacker.disbrowser.util.toLong
|
||||
import com.fasterxml.jackson.annotation.*
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
|
||||
import com.smallhacker.disbrowser.util.joinBytes
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
|
||||
class Metadata {
|
||||
class Metadata() {
|
||||
private val lines = HashMap<Address, MetadataLine>()
|
||||
|
||||
@JsonValue
|
||||
private fun linesAsList() = lines.values.toList()
|
||||
|
||||
@JsonCreator
|
||||
private constructor(@JsonProperty lines: List<MetadataLine>) : this() {
|
||||
lines.forEach { add(it) }
|
||||
}
|
||||
|
||||
fun add(line: MetadataLine): Metadata {
|
||||
lines[line.address] = line
|
||||
return this
|
||||
@ -15,10 +24,20 @@ class Metadata {
|
||||
operator fun get(address: Address): MetadataLine? {
|
||||
return lines[address]
|
||||
}
|
||||
|
||||
fun getOrAdd(address: Address): MetadataLine {
|
||||
val line = this[address]
|
||||
if (line != null) {
|
||||
return line
|
||||
}
|
||||
val newLine = MetadataLine(address)
|
||||
add(newLine)
|
||||
return newLine
|
||||
}
|
||||
}
|
||||
|
||||
fun Metadata.at(address: Int, runner: MetadataLine.() -> Unit) {
|
||||
val line = MetadataLine(Address(address))
|
||||
val line = MetadataLine(address(address))
|
||||
this.add(line)
|
||||
runner(line)
|
||||
}
|
||||
@ -29,38 +48,77 @@ fun metadata(runner: Metadata.() -> Unit): Metadata {
|
||||
return metadata
|
||||
}
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "flagType")
|
||||
@JsonSubTypes(
|
||||
Type(value = NonReturningRoutine::class, name = "NonReturningRoutine"),
|
||||
Type(value = JmpIndirectLongInterleavedTable::class, name = "JmpIndirectLongInterleavedTable"),
|
||||
Type(value = JslTableRoutine::class, name = "JslTableRoutine")
|
||||
)
|
||||
interface InstructionFlag
|
||||
|
||||
object NonReturningRoutine : InstructionFlag
|
||||
|
||||
class JmpIndirectLongInterleavedTable(private val start: Address, private val entries: Int) : InstructionFlag {
|
||||
fun readTable(data: RomData): Sequence<Address> {
|
||||
return (0 until entries)
|
||||
.asSequence()
|
||||
.map { it + start.pc }
|
||||
.map { pc -> data[pc].toLong() or (data[pc + entries].toLong() left 8) or (data[pc + entries + entries].toLong() left 16) }
|
||||
.map { Address(it.value) }
|
||||
|
||||
}
|
||||
object NonReturningRoutine : InstructionFlag {
|
||||
override fun toString() = "NonReturningRoutine"
|
||||
}
|
||||
|
||||
class JslTableRoutine(private val entries: Int) : InstructionFlag {
|
||||
fun readTable(postJsr: State): Sequence<Address> {
|
||||
val data = postJsr.data
|
||||
return (0 until entries)
|
||||
class JmpIndirectLongInterleavedTable @JsonCreator constructor(
|
||||
@field:JsonProperty @JsonProperty private val start: Address,
|
||||
@field:JsonProperty @JsonProperty private val entries: Int
|
||||
) : InstructionFlag {
|
||||
private val uEntries get() = entries.toUInt()
|
||||
|
||||
fun readTable(data: RomData): Sequence<Address> {
|
||||
return (0u until uEntries)
|
||||
.asSequence()
|
||||
.map { postJsr.address.pc + (it * 3) }
|
||||
.map { pc -> data[pc].toLong() or (data[pc + 1].toLong() left 8) or (data[pc + 2].toLong() left 16) }
|
||||
.map { Address(it.value) }
|
||||
.map { it + start.pc }
|
||||
.map { pc -> joinBytes(data[pc], data[pc + uEntries], data[pc + uEntries + uEntries]).toUInt24() }
|
||||
.map { Address(it) }
|
||||
}
|
||||
|
||||
// fun dataInstructions(postJsr: State) {
|
||||
// val data = postJsr.data
|
||||
// return (0 until entries)
|
||||
// .asSequence()
|
||||
// .map {
|
||||
// val offset = it * 3
|
||||
// Instruction(data.range(postJsr.address.pc + offset, 3), Opcode.POINTER_LONG, postJsr.mutateAddress { it + offset })
|
||||
// }
|
||||
// }
|
||||
fun generateCode(jumpInstruction: Instruction): Sequence<DataBlock> {
|
||||
val table = jumpInstruction.preState.data.deinterleave(uEntries,
|
||||
start.pc,
|
||||
(start + entries).pc,
|
||||
(start + (2u * uEntries).toInt()).pc
|
||||
)
|
||||
|
||||
return (0u until uEntries)
|
||||
.asSequence()
|
||||
.map { index -> index * 3u }
|
||||
.map { offset ->
|
||||
val target = table.getLong(offset)
|
||||
|
||||
DataBlock(
|
||||
Opcode.POINTER_LONG,
|
||||
table.range(offset, 3u),
|
||||
jumpInstruction.postState.address + offset.toInt(),
|
||||
jumpInstruction.relativeAddress,
|
||||
jumpInstruction.opcode.mutate(jumpInstruction)
|
||||
.mutateAddress { Address(target) }
|
||||
.withOrigin(jumpInstruction)
|
||||
)
|
||||
// Instruction(
|
||||
// data.range((start.value + offset).toUInt(), 3u),
|
||||
// Opcode.POINTER_LONG,
|
||||
// preState.mutateAddress { start -> start + offset.toInt() }
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = "JmpIndirectLongInterleavedTable($start, $entries)"
|
||||
}
|
||||
|
||||
class JslTableRoutine @JsonCreator constructor(
|
||||
@field:JsonProperty @JsonProperty private val entries: Int
|
||||
) : InstructionFlag {
|
||||
|
||||
fun readTable(postJsr: State): Sequence<Address> {
|
||||
val data = postJsr.data
|
||||
return (0u until entries.toUInt())
|
||||
.asSequence()
|
||||
.map { postJsr.address.pc + (it * 3u) }
|
||||
.map { pc -> joinBytes(data[pc], data[pc + 1u], data[pc + 2u]).toUInt24() }
|
||||
.map { Address(it) }
|
||||
}
|
||||
|
||||
override fun toString() = "JslTableRoutine($entries)"
|
||||
}
|
@ -4,5 +4,6 @@ data class MetadataLine(
|
||||
val address: Address,
|
||||
var label: String? = null,
|
||||
var comment: String? = null,
|
||||
var preComment: String? = null,
|
||||
val flags: MutableList<InstructionFlag> = ArrayList()
|
||||
)
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
enum class Mnemonic {
|
||||
enum class Mnemonic(private val nameOverride: String? = null) {
|
||||
ADC, AND, ASL, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRA,
|
||||
BRK, BRL, BVC, BVS, CLC, CLD, CLI, CLV, CMP, COP, CPX,
|
||||
CPY, DEC, DEX, DEY, EOR, INC, INX, INY, JMP, JML, JSL,
|
||||
@ -11,5 +11,7 @@ enum class Mnemonic {
|
||||
TCS, TDC, TRB, TSB, TSC, TSX, TXA, TXS, TXY, TYA, TYX,
|
||||
WAI, WDM, XBA, XCE,
|
||||
|
||||
DB, DW, DL
|
||||
DB(".db"), DW(".dw"), DL(".dl");
|
||||
|
||||
val displayName get() = nameOverride ?: name
|
||||
}
|
||||
|
@ -2,76 +2,112 @@ package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.smallhacker.disbrowser.util.*
|
||||
|
||||
fun format(format: String, value: UVal<*>): String {
|
||||
return format.replace(Regex("[0]+"), value.toHex())
|
||||
}
|
||||
private val ZEROES = Regex("[0]+")
|
||||
private fun countBytes(format: String) = (ZEROES.find(format)?.groupValues?.firstOrNull()?.length?.toUInt() ?: 0u) / 2u
|
||||
|
||||
private const val ACCUMULATOR_SIZE = -1
|
||||
private const val INDEX_SIZE = -2
|
||||
|
||||
fun format(format: String, value: UInt, operandBytes: UInt = countBytes(format)) =
|
||||
format.replace(ZEROES, toHex(value, operandBytes))
|
||||
|
||||
enum class Mode {
|
||||
DATA_BYTE(1, "$00", { dataByte }, dataMode = true),
|
||||
DATA_WORD(2, "$0000", { dataWord }, dataMode = true),
|
||||
DATA_LONG(3, "$000000", { dataLong }, dataMode = true),
|
||||
DATA_BYTE("$00", dataMode = true, showLengthSuffix = false),
|
||||
DATA_WORD("$0000", dataMode = true, showLengthSuffix = false),
|
||||
DATA_LONG("$000000", dataMode = true, showLengthSuffix = false),
|
||||
|
||||
IMPLIED(1, { "" }),
|
||||
IMMEDIATE_8(2, "#$00", { byte }),
|
||||
IMMEDIATE_16(3, "#$0000", { word }),
|
||||
IMMEDIATE_M(-1, {
|
||||
when (preState.m) {
|
||||
null -> "????"
|
||||
true -> format("#$00", byte)
|
||||
false -> format("#$0000", word)
|
||||
}
|
||||
}),
|
||||
IMMEDIATE_X(-2, {
|
||||
when (preState.x) {
|
||||
null -> "???"
|
||||
true -> format("#$00", byte)
|
||||
false -> format("#$0000", word)
|
||||
}
|
||||
}),
|
||||
ABSOLUTE(3, "$0000", { word }),
|
||||
ABSOLUTE_X(3, "$0000,x", { word }),
|
||||
ABSOLUTE_Y(3, "$0000,y", { word }),
|
||||
ABSOLUTE_LONG(4, "$000000", { long }),
|
||||
ABSOLUTE_LONG_X(4, "$000000,x", { long }),
|
||||
ABSOLUTE_INDIRECT(3, "($0000)", { word }),
|
||||
ABSOLUTE_INDIRECT_LONG(3, "[$0000]", { word }),
|
||||
ABSOLUTE_X_INDIRECT(3, "($0000,x)", { word }),
|
||||
DIRECT(2, "$00", { byte }),
|
||||
DIRECT_X(2, "$00,x", { byte }),
|
||||
DIRECT_Y(2, "$00,y", { byte }),
|
||||
DIRECT_S(2, "$00,s", { byte }),
|
||||
DIRECT_INDIRECT(2, "($00)", { byte }),
|
||||
DIRECT_INDIRECT_Y(2, "($00),y", { byte }),
|
||||
DIRECT_X_INDIRECT(2, "($00,x)", { byte }),
|
||||
DIRECT_S_INDIRECT_Y(2, "($00,s),y", { byte }),
|
||||
DIRECT_INDIRECT_LONG(2, "[$00]", { byte }),
|
||||
DIRECT_INDIRECT_LONG_Y(2, "[$00],y", { byte }),
|
||||
RELATIVE(2, {
|
||||
val rel = signedByte.toInt() + 2
|
||||
format("$000000", uLong((address + rel).value))
|
||||
}),
|
||||
//RELATIVE_LONG(3, "$0000", { word }),
|
||||
RELATIVE_LONG(3, {
|
||||
val rel = signedWord.toInt() + 3
|
||||
format("$000000", uLong((address + rel).value))
|
||||
}),
|
||||
BLOCK_MOVE(3, { String.format("#$%02x,#$%02x", byte.value, byte2.value) })
|
||||
IMPLIED("", showLengthSuffix = false),
|
||||
IMMEDIATE_8("#$00", showLengthSuffix = false),
|
||||
IMMEDIATE_16("#$0000", showLengthSuffix = false),
|
||||
ABSOLUTE("$0000"),
|
||||
ABSOLUTE_X("$0000,x"),
|
||||
ABSOLUTE_Y("$0000,y"),
|
||||
ABSOLUTE_LONG("$000000"),
|
||||
ABSOLUTE_LONG_X("$000000,x"),
|
||||
ABSOLUTE_INDIRECT("($0000)"),
|
||||
ABSOLUTE_INDIRECT_LONG("[$0000]"),
|
||||
ABSOLUTE_X_INDIRECT("($0000,x)"),
|
||||
DIRECT("$00"),
|
||||
DIRECT_X("$00,x"),
|
||||
DIRECT_Y("$00,y"),
|
||||
DIRECT_S("$00,s"),
|
||||
DIRECT_INDIRECT("($00)"),
|
||||
DIRECT_INDIRECT_Y("($00),y"),
|
||||
DIRECT_X_INDIRECT("($00,x)"),
|
||||
DIRECT_S_INDIRECT_Y("($00,s),y"),
|
||||
DIRECT_INDIRECT_LONG("[$00]"),
|
||||
DIRECT_INDIRECT_LONG_Y("[$00],y"),
|
||||
|
||||
IMMEDIATE_M(
|
||||
operandLength = ACCUMULATOR_SIZE,
|
||||
print = {
|
||||
when (preState?.m) {
|
||||
null -> "????"
|
||||
true -> format("#$00", byte.toUInt())
|
||||
false -> format("#$0000", word.toUInt())
|
||||
}
|
||||
}
|
||||
),
|
||||
IMMEDIATE_X(
|
||||
operandLength = INDEX_SIZE,
|
||||
print = {
|
||||
when (preState?.x) {
|
||||
null -> "???"
|
||||
true -> format("#$00", byte.toUInt())
|
||||
false -> format("#$0000", word.toUInt())
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
RELATIVE(
|
||||
format = "$000000",
|
||||
operandLength = 1u,
|
||||
valueGetter = {
|
||||
val rel = signedByte.toInt() + 2
|
||||
(relativeAddress + rel).value.toUInt()
|
||||
},
|
||||
showLengthSuffix = false
|
||||
),
|
||||
RELATIVE_LONG(
|
||||
format = "$000000",
|
||||
operandLength = 2u,
|
||||
valueGetter = {
|
||||
val rel = signedWord.toInt() + 3
|
||||
(relativeAddress + rel).value.toUInt()
|
||||
},
|
||||
showLengthSuffix = false
|
||||
),
|
||||
BLOCK_MOVE(
|
||||
operandLength = 2,
|
||||
print = { String.format("#$%02x,#$%02x", byte.toInt(), byte2.toInt()) },
|
||||
showLengthSuffix = false
|
||||
)
|
||||
;
|
||||
|
||||
private val length: Int
|
||||
val print: Instruction.() -> String
|
||||
private val operandLength: Int
|
||||
val print: CodeUnit.() -> String
|
||||
val dataMode: Boolean
|
||||
val showLengthSuffix: Boolean
|
||||
|
||||
constructor(length: Int, print: Instruction.() -> String) {
|
||||
this.length = length
|
||||
constructor(operandLength: Int, print: CodeUnit.() -> String, showLengthSuffix: Boolean = true) {
|
||||
this.operandLength = operandLength
|
||||
this.print = print
|
||||
this.dataMode = false
|
||||
this.showLengthSuffix = showLengthSuffix
|
||||
}
|
||||
|
||||
constructor(length: Int, format: String, valueGetter: Instruction.() -> UVal<*>, dataMode: Boolean = false) {
|
||||
this.length = length
|
||||
this.print = { format(format, valueGetter(this)) }
|
||||
constructor(
|
||||
format: String,
|
||||
printedLength: UInt = countBytes(format),
|
||||
operandLength: UInt = printedLength,
|
||||
valueGetter: CodeUnit.() -> UInt = { value!! },
|
||||
dataMode: Boolean = false,
|
||||
showLengthSuffix: Boolean = true
|
||||
) {
|
||||
this.operandLength = operandLength.toInt()
|
||||
this.print = { format(format, valueGetter(this), printedLength) }
|
||||
this.dataMode = dataMode
|
||||
this.showLengthSuffix = showLengthSuffix
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,12 +118,10 @@ enum class Mode {
|
||||
*
|
||||
* If the length cannot be determined based on the current [State], `null` is returned.
|
||||
*/
|
||||
fun instructionLength(state: State): Int? {
|
||||
return when (length) {
|
||||
-1 -> state.mWidth?.plus(1)
|
||||
-2 -> state.xWidth?.plus(1)
|
||||
else -> length
|
||||
}
|
||||
fun instructionLength(state: State): UInt? {
|
||||
val operatorLength = if (this.dataMode) 0u else 1u
|
||||
return operandLength(state)
|
||||
?.plus(operatorLength)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,14 +132,11 @@ enum class Mode {
|
||||
*
|
||||
* If the length cannot be determined based on the current [State], `null` is returned.
|
||||
*/
|
||||
fun operandLength(state: State): Int? {
|
||||
val len = instructionLength(state) ?: return null
|
||||
|
||||
return when(this) {
|
||||
DATA_BYTE,
|
||||
DATA_WORD,
|
||||
DATA_LONG -> len
|
||||
else -> len - 1
|
||||
fun operandLength(state: State): UInt? {
|
||||
return when (operandLength) {
|
||||
ACCUMULATOR_SIZE -> state.mWidth
|
||||
INDEX_SIZE -> state.xWidth
|
||||
else -> operandLength.toUInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
abstract class ModeFormat {
|
||||
abstract fun print(instruction: Instruction, metadata: Metadata): String
|
||||
|
||||
//fun wrap(prefix: String = "", suffix: String = ""): ModeFormat {
|
||||
//
|
||||
//}
|
||||
}
|
||||
|
||||
//private class AddressModeFormat: ModeFormat() {
|
||||
// override fun print(instruction: Instruction, metadata: Metadata): String {
|
||||
// val mode = instruction.opcode.mode
|
||||
// val operandLength = mode.operandLength(instruction.preState)
|
||||
// instruction.
|
||||
//
|
||||
// TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
// }
|
||||
//}
|
@ -4,7 +4,7 @@ import java.util.HashMap
|
||||
|
||||
import com.smallhacker.disbrowser.asm.Mnemonic.*
|
||||
import com.smallhacker.disbrowser.asm.Mode.*
|
||||
import com.smallhacker.disbrowser.util.UByte
|
||||
import com.smallhacker.disbrowser.util.OldUByte
|
||||
|
||||
typealias SegmentEnder = Instruction.() -> SegmentEnd?
|
||||
|
||||
@ -14,7 +14,7 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end
|
||||
private var _branch = false
|
||||
|
||||
val operandIndex
|
||||
get() = if (mode.dataMode) 0 else 1
|
||||
get() = if (mode.dataMode) 0u else 1u
|
||||
|
||||
val continuation: Continuation
|
||||
get() = _continuation
|
||||
@ -57,7 +57,7 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end
|
||||
private val OPCODES: Array<Opcode>
|
||||
|
||||
fun opcode(byteValue: UByte): Opcode {
|
||||
return OPCODES[byteValue.value]
|
||||
return OPCODES[byteValue.toInt()]
|
||||
}
|
||||
|
||||
init {
|
||||
@ -135,8 +135,8 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end
|
||||
add(0xF8, SED, IMPLIED, alwaysContinue)
|
||||
add(0xD8, CLD, IMPLIED, alwaysContinue)
|
||||
add(0xB8, CLV, IMPLIED, alwaysContinue)
|
||||
add(0xE2, SEP, IMMEDIATE_8, alwaysContinue) { it.preState.sep(it.bytes[1]) }
|
||||
add(0xC2, REP, IMMEDIATE_8, alwaysContinue) { it.preState.rep(it.bytes[1]) }
|
||||
add(0xE2, SEP, IMMEDIATE_8, alwaysContinue) { it.preState.sep(it.bytes[1u]) }
|
||||
add(0xC2, REP, IMMEDIATE_8, alwaysContinue) { it.preState.rep(it.bytes[1u]) }
|
||||
add(0xFB, XCE, IMPLIED, alwaysContinue)
|
||||
|
||||
add(0xC1, CMP, DIRECT_X_INDIRECT, alwaysContinue)
|
||||
@ -218,11 +218,11 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end
|
||||
add(0xFA, PLX, IMPLIED, alwaysContinue) { it.preState.pull(it.preState.xWidth) }
|
||||
add(0x7A, PLY, IMPLIED, alwaysContinue) { it.preState.pull(it.preState.xWidth) }
|
||||
|
||||
add(0x8B, PHB, IMPLIED, alwaysContinue) { it.preState.pushUnknown(1) }
|
||||
add(0xAB, PLB, IMPLIED, alwaysContinue) { it.preState.pull(1) }
|
||||
add(0x0B, PHD, IMPLIED, alwaysContinue) { it.preState.pushUnknown(1) }
|
||||
add(0x2B, PLD, IMPLIED, alwaysContinue) { it.preState.pull(1) }
|
||||
add(0x4B, PHK, IMPLIED, alwaysContinue) { it.preState.push(it.address.value shr 16) }
|
||||
add(0x8B, PHB, IMPLIED, alwaysContinue) { it.preState.pushUnknown(1u) }
|
||||
add(0xAB, PLB, IMPLIED, alwaysContinue) { it.preState.pull(1u) }
|
||||
add(0x0B, PHD, IMPLIED, alwaysContinue) { it.preState.pushUnknown(1u) }
|
||||
add(0x2B, PLD, IMPLIED, alwaysContinue) { it.preState.pull(1u) }
|
||||
add(0x4B, PHK, IMPLIED, alwaysContinue) { it.preState.push((it.address.value shr 16).toUInt()) }
|
||||
add(0x08, PHP, IMPLIED, alwaysContinue) { it.preState.push(it.preState.flags) }
|
||||
add(0x28, PLP, IMPLIED, alwaysContinue) { it.preState.pull { copy(flags = it) } }
|
||||
|
||||
|
@ -5,40 +5,62 @@ import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
interface RomData {
|
||||
val size: Int
|
||||
val size: UInt
|
||||
|
||||
operator fun get(address: Int): UByte
|
||||
operator fun get(address: UInt): UByte
|
||||
|
||||
fun range(start: Int, length: Int): RomData = RomRange(this, start, length)
|
||||
fun range(start: UInt, length: UInt): RomData = RomRange(this, start, length)
|
||||
|
||||
fun asSequence(): Sequence<UByte> = (0 until size).asSequence().map { get(it) }
|
||||
fun asSequence(): Sequence<UByte> = (0u until size).asSequence().map { this[it] }
|
||||
|
||||
companion object {
|
||||
fun load(path: Path): RomData = Rom(Files.readAllBytes(path))
|
||||
fun load(path: Path): RomData = ArrayRomData(Files.readAllBytes(path).toUByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun RomData.getWord(address: Int): UWord = get(address).toWord() or (get(address + 1).toWord() left 8)
|
||||
fun romData(vararg bytes: UByte): RomData = ArrayRomData(bytes)
|
||||
|
||||
private class Rom(private val bytes: ByteArray): RomData {
|
||||
override val size = bytes.size
|
||||
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()
|
||||
|
||||
override fun get(address: Int): UByte {
|
||||
checkRange(address)
|
||||
return uByte(bytes[address].toInt())
|
||||
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: Int, override val size: Int): RomData {
|
||||
override fun get(address: Int): UByte {
|
||||
checkRange(address)
|
||||
return parent[start + address]
|
||||
private class RomRange(private val parent: RomData, private val start: UInt, override val size: UInt) : RomData {
|
||||
override fun get(address: UInt) = rangeChecked(address) {
|
||||
parent[start + address]
|
||||
}
|
||||
}
|
||||
|
||||
private fun RomData.checkRange(address: Int) {
|
||||
if (address < 0 || address >= size) {
|
||||
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
|
||||
}
|
||||
}
|
@ -2,17 +2,16 @@ package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.smallhacker.disbrowser.ImmStack
|
||||
import com.smallhacker.disbrowser.immStack
|
||||
import com.smallhacker.disbrowser.util.UByte
|
||||
|
||||
data class State(val origin: Instruction? = null, val data: RomData, val address: Address, val flags: VagueNumber = VagueNumber(), val stack: ImmStack<VagueNumber> = immStack()) {
|
||||
val m: Boolean? get() = flags.getBoolean(0x20)
|
||||
val x: Boolean? get() = flags.getBoolean(0x10)
|
||||
val m: Boolean? get() = flags.getBoolean(0x20u)
|
||||
val x: Boolean? get() = flags.getBoolean(0x10u)
|
||||
|
||||
val mWidth: Int? get() = toWidth(m)
|
||||
val xWidth: Int? get() = toWidth(x)
|
||||
val mWidth: UInt? get() = toWidth(m)
|
||||
val xWidth: UInt? get() = toWidth(x)
|
||||
|
||||
fun sep(i: UByte) = withFlags(flags.withBits(i.value))
|
||||
fun rep(i: UByte) = withFlags(flags.withoutBits(i.value))
|
||||
fun sep(i: UByte) = withFlags(flags.withBits(i.toUInt()))
|
||||
fun rep(i: UByte) = withFlags(flags.withoutBits(i.toUInt()))
|
||||
fun uncertain() = withFlags(VagueNumber())
|
||||
|
||||
private fun withFlags(flags: VagueNumber) = copy(flags = flags)
|
||||
@ -20,28 +19,28 @@ data class State(val origin: Instruction? = null, val data: RomData, val address
|
||||
fun mutateAddress(mutator: (Address) -> Address) = copy(address = mutator(address))
|
||||
fun withOrigin(instruction: Instruction?) = copy(origin = instruction)
|
||||
|
||||
fun push(value: Int) = push(VagueNumber(value))
|
||||
fun push(value: UInt) = push(VagueNumber(value))
|
||||
fun push(value: VagueNumber) = copy(stack = stack.push(value))
|
||||
fun pushUnknown(count: Int? = 1): State {
|
||||
fun pushUnknown(count: UInt? = 1u): State {
|
||||
if (count == null) {
|
||||
return copy(stack = immStack())
|
||||
}
|
||||
|
||||
var stack = this.stack
|
||||
for (i in 1..count) {
|
||||
for (i in 1u..count) {
|
||||
stack = stack.push(VagueNumber())
|
||||
}
|
||||
return copy(stack = stack)
|
||||
}
|
||||
|
||||
fun pull() = (stack.top ?: VagueNumber()) to copy(stack = stack.pop())
|
||||
fun pull(count: Int?): State {
|
||||
fun pull(count: UInt?): State {
|
||||
if (count == null) {
|
||||
return copy(stack = immStack())
|
||||
}
|
||||
|
||||
var stack = this.stack
|
||||
for (i in 1..count) {
|
||||
for (i in 1u..count) {
|
||||
stack = stack.pop()
|
||||
}
|
||||
return copy(stack = stack)
|
||||
@ -61,20 +60,20 @@ data class State(val origin: Instruction? = null, val data: RomData, val address
|
||||
|
||||
private fun stackByteToString(v: VagueNumber): String {
|
||||
if (v.certain) {
|
||||
return String.format("%02x", v.value)
|
||||
return String.format("%02x", v.value.toInt())
|
||||
}
|
||||
|
||||
if (v.certainty == 0) {
|
||||
if (v.certainty == 0u) {
|
||||
return "??"
|
||||
}
|
||||
|
||||
val c = v.certainty
|
||||
val high = (c and 0xF0) != 0
|
||||
val low = (c and 0x0F) != 0
|
||||
val high = (c and 0xF0u) != 0u
|
||||
val low = (c and 0x0Fu) != 0u
|
||||
|
||||
return StringBuilder()
|
||||
.append(if (high) String.format("%x", (v.value ushr 4) and 0xF) else "?")
|
||||
.append(if (low) String.format("%x", v.value and 0xF) else "?")
|
||||
.append(if (high) String.format("%x", ((v.value shr 4) and 0xFu).toInt()) else "?")
|
||||
.append(if (low) String.format("%x", (v.value and 0xFu).toInt()) else "?")
|
||||
.toString()
|
||||
}
|
||||
|
||||
@ -100,14 +99,14 @@ data class State(val origin: Instruction? = null, val data: RomData, val address
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
private fun toWidth(flag: Boolean?): Int? = when (flag) {
|
||||
private fun toWidth(flag: Boolean?): UInt? = when (flag) {
|
||||
null -> null
|
||||
true -> 1
|
||||
false -> 2
|
||||
true -> 1u
|
||||
false -> 2u
|
||||
}
|
||||
}
|
||||
|
||||
fun State.pushByte(value: Byte) = this.push(VagueNumber(value.toInt()))
|
||||
fun State.pushByte(value: Byte) = this.push(VagueNumber(value.toUInt()))
|
||||
fun State.pull(consumer: State.(VagueNumber) -> State): State {
|
||||
val (value, state) = this.pull()
|
||||
return consumer(state, value)
|
||||
|
@ -1,39 +1,42 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
@Suppress("DataClassPrivateConstructor")
|
||||
data class VagueNumber private constructor(val value: Int, val certainty: Int){
|
||||
constructor() : this(0, 0)
|
||||
constructor(value: Int) : this(value, -1)
|
||||
inline class VagueNumber(private val valueAndCertainty: ULong) {
|
||||
private constructor(value: UInt, certainty: UInt) : this(value.toULong() or (certainty.toULong() shl 32))
|
||||
constructor(value: UInt) : this(value, 0xFFFF_FFFFu)
|
||||
constructor() : this(0u, 0u)
|
||||
|
||||
fun withBits(value: Int) = VagueNumber(this.value or value, this.certainty or value)
|
||||
fun withoutBits(value: Int) = VagueNumber(this.value and value.inv(), this.certainty or value)
|
||||
val value get() = valueAndCertainty.toUInt()
|
||||
val certainty get() = (valueAndCertainty shr 32).toUInt()
|
||||
|
||||
fun withBits(value: UInt) = VagueNumber(this.value or value, this.certainty or value)
|
||||
fun withoutBits(value: UInt) = VagueNumber(this.value and value.inv(), this.certainty or value)
|
||||
|
||||
val certain: Boolean
|
||||
get() = certainty == -1
|
||||
get() = certainty == 0xFFFF_FFFFu
|
||||
|
||||
fun get(mask: Int): Int? {
|
||||
fun get(mask: UInt): UInt? {
|
||||
if ((certainty and mask) != mask) {
|
||||
return null
|
||||
}
|
||||
return value and mask
|
||||
}
|
||||
|
||||
fun getBoolean(mask: Int): Boolean? {
|
||||
fun getBoolean(mask: UInt): Boolean? {
|
||||
val value = get(mask) ?: return null
|
||||
return value == mask
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
var i = 1 shl 31
|
||||
var i = 1u shl 31
|
||||
val out = StringBuilder()
|
||||
while (i != 0) {
|
||||
while (i != 0u) {
|
||||
val b = getBoolean(i)
|
||||
when (b) {
|
||||
true -> out.append('1')
|
||||
false -> out.append('0')
|
||||
null -> out.append('?')
|
||||
}
|
||||
i = i ushr 1
|
||||
i = i shr 1
|
||||
}
|
||||
return out.toString()
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ object Disassembler {
|
||||
}
|
||||
tryAdd(initialState)
|
||||
|
||||
val instructions = ArrayList<Instruction>()
|
||||
val instructions = ArrayList<CodeUnit>()
|
||||
while (queue.isNotEmpty()) {
|
||||
val state = queue.remove()
|
||||
|
||||
@ -26,17 +26,21 @@ object Disassembler {
|
||||
var stop = (ins.opcode.continuation == Continuation.NO) or
|
||||
(ins.opcode.mode.instructionLength(state) == null)
|
||||
|
||||
metadata[ins.address]?.flags?.forEach {
|
||||
if (it is JmpIndirectLongInterleavedTable) {
|
||||
metadata[ins.address]?.flags?.forEach { flag ->
|
||||
if (flag is JmpIndirectLongInterleavedTable) {
|
||||
if (global) {
|
||||
it.readTable(state.data)
|
||||
flag.readTable(state.data)
|
||||
.map { ins.postState.copy(address = it) }
|
||||
.forEach { tryAdd(it) }
|
||||
}
|
||||
|
||||
flag.generateCode(ins)
|
||||
.forEach { instructions.add(it) }
|
||||
|
||||
stop = true
|
||||
} else if (it is JslTableRoutine) {
|
||||
} else if (flag is JslTableRoutine) {
|
||||
if (global) {
|
||||
it.readTable(ins.postState)
|
||||
flag.readTable(ins.postState)
|
||||
.map { ins.postState.copy(address = it) }
|
||||
.forEach { tryAdd(it) }
|
||||
}
|
||||
@ -68,7 +72,7 @@ object Disassembler {
|
||||
}
|
||||
|
||||
val instructionList = instructions
|
||||
.sortedBy { it.address }
|
||||
.sortedBy { it.presentedAddress }
|
||||
.toList()
|
||||
|
||||
return Disassembly(instructionList)
|
||||
@ -160,7 +164,7 @@ object Disassembler {
|
||||
private fun disassembleInstruction(state: State): Instruction {
|
||||
val pc = state.address.pc
|
||||
val opcode = Opcode.opcode(state.data[pc])
|
||||
val length = opcode.mode.instructionLength(state) ?: 1
|
||||
val length = opcode.mode.instructionLength(state) ?: 1u
|
||||
val bytes = state.data.range(pc, length)
|
||||
return Instruction(bytes, opcode, state)
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
package com.smallhacker.disbrowser.resource
|
||||
|
||||
import com.smallhacker.disbrowser.HtmlNode
|
||||
import com.smallhacker.disbrowser.Service
|
||||
import com.smallhacker.disbrowser.asm.Address
|
||||
import com.smallhacker.disbrowser.asm.VagueNumber
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
import com.smallhacker.disbrowser.util.tryParseInt
|
||||
import javax.ws.rs.GET
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.PathParam
|
||||
import javax.ws.rs.Produces
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
@Path("/")
|
||||
class DisassemblyResource {
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt() = handle {
|
||||
Service.showDisassemblyFromReset()
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{address}")
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt(@PathParam("address") address: String) = getIt(address, "")
|
||||
|
||||
@GET
|
||||
@Path("{address}/{state}")
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt(@PathParam("address") address: String, @PathParam("state") state: String): Response {
|
||||
return handle {
|
||||
parseAddress(address)?.let {
|
||||
val flags = parseState(state)
|
||||
Service.showDisassembly(it, flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseAddress(address: String): Address? {
|
||||
return tryParseInt(address, 16)
|
||||
?.let { Address(it.toUInt24()) }
|
||||
}
|
||||
|
||||
private fun handle(runner: () -> HtmlNode?): Response {
|
||||
try {
|
||||
val disassembly = runner()
|
||||
|
||||
return if (disassembly == null)
|
||||
Response.status(404).build()
|
||||
else
|
||||
Response.ok(disassembly.toString(), MediaType.TEXT_HTML).build()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseState(state: String): VagueNumber {
|
||||
var flags = VagueNumber()
|
||||
flags = parseFlag(state, flags, 'M', 'm', 0x20u)
|
||||
flags = parseFlag(state, flags, 'X', 'x', 0x10u)
|
||||
return flags
|
||||
}
|
||||
|
||||
private fun parseFlag(state: String, flags: VagueNumber, set: Char, clear: Char, mask: UInt): VagueNumber = when {
|
||||
state.contains(set) -> flags.withBits(mask)
|
||||
state.contains(clear) -> flags.withoutBits(mask)
|
||||
else -> flags
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.smallhacker.disbrowser.resource
|
||||
|
||||
import com.smallhacker.disbrowser.Service
|
||||
import com.smallhacker.disbrowser.asm.Address
|
||||
import com.smallhacker.disbrowser.asm.MetadataLine
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
import com.smallhacker.disbrowser.util.tryParseInt
|
||||
import javax.ws.rs.Consumes
|
||||
import javax.ws.rs.POST
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.PathParam
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
@Path("/rest")
|
||||
class RestResource {
|
||||
@POST
|
||||
@Path("{address}/{field}")
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
fun getIt(@PathParam("address") address: String, @PathParam("field") fieldName: String, body: String): Response {
|
||||
val parsedAddress = parseAddress(address) ?: return Response.status(400).build()
|
||||
val field = when (fieldName) {
|
||||
"preComment" -> MetadataLine::preComment
|
||||
"comment" -> MetadataLine::comment
|
||||
"label" -> MetadataLine::label
|
||||
else -> return Response.status(404).build()
|
||||
}
|
||||
|
||||
Service.updateMetadata(parsedAddress, field, body)
|
||||
|
||||
return Response.noContent().build()
|
||||
}
|
||||
|
||||
|
||||
private fun parseAddress(address: String): Address? {
|
||||
return tryParseInt(address, 16)
|
||||
?.let { Address(it.toUInt24()) }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.smallhacker.disbrowser.resource
|
||||
|
||||
import javax.ws.rs.GET
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.PathParam
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
@Path("/")
|
||||
class StaticResource {
|
||||
@GET
|
||||
@Path("resources/{file}.{ext}")
|
||||
fun getStatic(@PathParam("file") file: String, @PathParam("ext") ext: String): Response {
|
||||
val mime = when (ext) {
|
||||
"js" -> "application/javascript"
|
||||
"css" -> "text/css"
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (mime != null) {
|
||||
javaClass.getResourceAsStream("/public/$file.$ext")
|
||||
?.bufferedReader()
|
||||
?.use {
|
||||
return Response.ok(it.readText())
|
||||
.type(mime)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
return Response.status(404).build()
|
||||
}
|
||||
}
|
@ -27,27 +27,27 @@ class UVal<U : UType>(value: Int, val type: U) : Comparable<UVal<U>> {
|
||||
//fun value(value: Int) = UVal(value, type)
|
||||
//fun mutate(mutator: (Int) -> Int) = UVal(mutator(value), type)
|
||||
|
||||
object U1 : UType(1, "UByte")
|
||||
object U2 : UType(2, "UWord")
|
||||
object U3 : UType(3, "ULong")
|
||||
object U1 : UType(1, "OldUByte")
|
||||
object U2 : UType(2, "OldUWord")
|
||||
object U3 : UType(3, "OldULong")
|
||||
}
|
||||
|
||||
inline fun <reified U: UVal<*>> U.value(value: Int): U = UVal(value, type) as U
|
||||
inline infix fun <reified U: UVal<*>> U.mutate(mutator: (Int) -> Int): U = value(mutator(value))
|
||||
|
||||
typealias UByte = UVal<UVal.U1>
|
||||
typealias UWord = UVal<UVal.U2>
|
||||
typealias ULong = UVal<UVal.U3>
|
||||
typealias OldUByte = UVal<UVal.U1>
|
||||
typealias OldUWord = UVal<UVal.U2>
|
||||
typealias OldULong = UVal<UVal.U3>
|
||||
|
||||
fun UVal<*>.toByte() = uByte(value)
|
||||
fun UVal<*>.toWord() = uWord(value)
|
||||
fun UVal<*>.toLong() = uLong(value)
|
||||
|
||||
private val UBYTE_CACHE = Array(256) { UByte(it, UVal.U1) }
|
||||
private val UBYTE_CACHE = Array(256) { OldUByte(it, UVal.U1) }
|
||||
|
||||
fun uByte(value: Int): UByte = UBYTE_CACHE[value and 0xFF]
|
||||
fun uWord(value: Int): UWord = UVal(value, UVal.U2)
|
||||
fun uLong(value: Int): ULong = UVal(value, UVal.U3)
|
||||
fun uByte(value: Int): OldUByte = UBYTE_CACHE[value and 0xFF]
|
||||
fun uWord(value: Int): OldUWord = UVal(value, UVal.U2)
|
||||
fun uLong(value: Int): OldULong = UVal(value, UVal.U3)
|
||||
|
||||
inline infix fun <reified U: UVal<V>, V: UType> U.left(count: Int): U = mutate { it shl count }
|
||||
inline infix fun <reified U: UVal<V>, V: UType> U.right(count: Int): U = mutate { it ushr count }
|
||||
|
22
src/main/java/com/smallhacker/disbrowser/util/json.kt
Normal file
22
src/main/java/com/smallhacker/disbrowser/util/json.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package com.smallhacker.disbrowser.util
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import java.nio.file.Path
|
||||
|
||||
val jsonMapper: ObjectMapper by lazy {
|
||||
jacksonObjectMapper()
|
||||
}
|
||||
|
||||
interface JsonFile<T> {
|
||||
fun load(): T
|
||||
fun save(value: T)
|
||||
}
|
||||
|
||||
inline fun <reified T> jsonFile(path: Path): JsonFile<T> {
|
||||
return object : JsonFile<T> {
|
||||
override fun load() = jsonMapper.readValue<T>(path.toFile())
|
||||
override fun save(value: T) = jsonMapper.writeValue(path.toFile(), value)
|
||||
}
|
||||
}
|
@ -14,3 +14,50 @@ fun tryParseInt(s: String, radix: Int = 10): Int? {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun toHex(value: UInt, bytes: UInt): String {
|
||||
if (bytes == 0u) {
|
||||
return ""
|
||||
}
|
||||
|
||||
val digits = 2u * bytes
|
||||
val pattern = "%0${digits}x"
|
||||
return String.format(pattern, value.toInt())
|
||||
}
|
||||
|
||||
fun joinBytes(vararg bytes: UByte) = 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()
|
||||
fun toUByte() = toUInt().toUByte()
|
||||
fun toInt() = toUInt().toInt()
|
||||
fun toShort() = toUShort().toShort()
|
||||
fun toByte() = toUByte().toByte()
|
||||
|
||||
infix fun and(v: UInt24) = (data and v.data).toUInt24()
|
||||
infix fun and(v: UInt) = (data and v).toUInt24()
|
||||
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()
|
||||
|
||||
operator fun plus(v: UInt24) = (toUInt() + v.toUInt()).toUInt24()
|
||||
operator fun plus(v: UInt) = (toUInt() + v).toUInt24()
|
||||
operator fun plus(v: Int) = (toInt() + v).toUInt24()
|
||||
operator fun minus(v: UInt24) = (toUInt() - v.toUInt()).toUInt24()
|
||||
operator fun minus(v: UInt) = (toUInt() - v).toUInt24()
|
||||
operator fun minus(v: Int) = (toInt() - v).toUInt24()
|
||||
|
||||
override fun toString() = data.toString()
|
||||
}
|
||||
|
||||
fun UInt.toUInt24() = UInt24(this and 0x00FF_FFFFu)
|
||||
fun UShort.toUInt24() = this.toUInt().toUInt24()
|
||||
fun UByte.toUInt24() = this.toUInt().toUInt24()
|
||||
fun Int.toUInt24() = this.toUInt().toUInt24()
|
||||
fun Short.toUInt24() = this.toUInt().toUInt24()
|
||||
fun Byte.toUInt24() = this.toUInt().toUInt24()
|
||||
|
@ -1,48 +0,0 @@
|
||||
function center(el) {
|
||||
function documentOffsetTop (el) {
|
||||
return el.offsetTop + ( el.offsetParent ? documentOffsetTop(el.offsetParent) : 0 );
|
||||
}
|
||||
|
||||
var top = documentOffsetTop(el) - ( window.innerHeight / 2 );
|
||||
window.scrollTo( 0, top );
|
||||
}
|
||||
|
||||
function highlight(el) {
|
||||
if (el) {
|
||||
//el.scrollIntoView();
|
||||
center(el);
|
||||
var cl = el.classList;
|
||||
var activeClass = "line-active";
|
||||
cl.add(activeClass);
|
||||
setTimeout(cl.remove.bind(cl, activeClass), 100);
|
||||
}
|
||||
}
|
||||
|
||||
function fromUrlPart(regex, input) {
|
||||
var match = regex.exec(input);
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return document.getElementById(match[1].toLowerCase());
|
||||
}
|
||||
|
||||
|
||||
function fromHash() {
|
||||
return fromUrlPart(/^#([0-9A-Fa-f]{6})$/, location.hash);
|
||||
}
|
||||
|
||||
function fromPath() {
|
||||
return fromUrlPart(/^\/([0-9A-Fa-f]{6})(?:\/.*)?/, location.pathname);
|
||||
}
|
||||
|
||||
function fromUrl() {
|
||||
return fromHash() || fromPath();
|
||||
}
|
||||
|
||||
highlight(fromUrl());
|
||||
window.addEventListener("hashchange", function () { highlight(fromHash()) }, false);
|
||||
|
||||
|
||||
|
@ -92,4 +92,16 @@ tr.line-active {
|
||||
|
||||
.routine-end > td {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
.field-editable {
|
||||
font-family: monospace;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field-comment {
|
||||
width: 500px;
|
||||
}
|
62
src/main/ts/main.ts
Normal file
62
src/main/ts/main.ts
Normal file
@ -0,0 +1,62 @@
|
||||
function center(el: HTMLElement) {
|
||||
function documentOffsetTop (el: HTMLElement) {
|
||||
let top = el.offsetTop;
|
||||
let parent = el.offsetParent;
|
||||
if (parent && parent instanceof HTMLElement) {
|
||||
top += documentOffsetTop(parent);
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
let top = documentOffsetTop(el) - (window.innerHeight / 2);
|
||||
window.scrollTo( 0, top );
|
||||
}
|
||||
|
||||
function highlight(el: HTMLElement | null) {
|
||||
if (el) {
|
||||
//el.scrollIntoView();
|
||||
center(el);
|
||||
let cl = el.classList;
|
||||
let activeClass = "line-active";
|
||||
cl.add(activeClass);
|
||||
setTimeout(cl.remove.bind(cl, activeClass), 100);
|
||||
}
|
||||
}
|
||||
|
||||
function fromUrlPart(regex: RegExp, input: string) {
|
||||
let match = regex.exec(input);
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return document.getElementById(match[1].toLowerCase());
|
||||
}
|
||||
|
||||
function fromHash() {
|
||||
return fromUrlPart(/^#([0-9A-Fa-f]{6})$/, location.hash);
|
||||
}
|
||||
|
||||
function fromPath() {
|
||||
return fromUrlPart(/^\/([0-9A-Fa-f]{6})(?:\/.*)?/, location.pathname);
|
||||
}
|
||||
|
||||
function fromUrl() {
|
||||
return fromHash() || fromPath();
|
||||
}
|
||||
|
||||
highlight(fromUrl());
|
||||
window.addEventListener("hashchange", function () { highlight(fromHash()) }, false);
|
||||
|
||||
let comments = document.getElementsByClassName("field-editable");
|
||||
for (let i = 0; i < comments.length; i++) {
|
||||
let comment = comments[i];
|
||||
comment.addEventListener("change", e => {
|
||||
let target = <HTMLInputElement>(e.target);
|
||||
let field = target.dataset.field || "";
|
||||
let address = parseInt(target.dataset.address || "-1");
|
||||
let value = (target).value;
|
||||
alert(field + "/" + address + "=" + value);
|
||||
return false;
|
||||
});
|
||||
}
|
14
src/main/ts/tsconfig.json
Normal file
14
src/main/ts/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "amd",
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"removeComments": true,
|
||||
"sourceMap": true,
|
||||
"outFile": "../resources/public/disbrowser.js"
|
||||
},
|
||||
"include": [
|
||||
"*.ts"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user