Overdue for a proper repo

This commit is contained in:
Smallhacker 2019-01-07 13:19:37 -05:00
commit 7a4b94e907
27 changed files with 2041 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/.idea
/target

138
pom.xml Normal file
View File

@ -0,0 +1,138 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.smallhacker.dis-browser</groupId>
<artifactId>dis-browser</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>dis-browser</name>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>${jersey.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<!-- uncomment this to get JSON support:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
</dependency>
-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.smallhacker.dis-browser.Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
<args>
<arg>-XXLanguage:+InlineClasses</arg>
<arg>-Xexperimental=kotlin.ExperimentalUnsignedTypes</arg>
</args>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<inherited>true</inherited>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<jersey.version>2.27</jersey.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.3.11</kotlin.version>
</properties>
</project>

View File

@ -0,0 +1,158 @@
package com.smallhacker.disbrowser
import com.smallhacker.disbrowser.asm.*
class Grid {
private val arrowCells = HashMap<Pair<Int, Int>, HtmlNode?>()
private val arrowClasses = HashMap<Pair<Int, Int>, String>()
private var arrowWidth = 0
private val content = HashMap<Pair<Int, Int>, HtmlNode?>()
private val cellClasses = HashMap<Pair<Int, Int>, String>()
private val addresses = HashMap<Address, Int>()
private val rowClasses = HashMap<Int, String>()
private val rowId = HashMap<Int, String>()
private var height = 0
private var nextAddress: Address? = null
fun arrow(from: Address, to: Address) {
val yStart = addresses[from]
val yEnd = addresses[to]
if (yStart == null || yEnd == null) {
return
}
val y1: Int
val y2: Int
val dir: String
if (yStart > yEnd) {
dir = "up"
y1 = yEnd
y2 = yStart
} else {
dir = "down"
y1 = yStart
y2 = yEnd
}
val x = nextArrowX(y1, y2)
if ((x + 1) > arrowWidth) {
arrowWidth = x + 1
}
arrowClasses[x to y1] = "arrow arrow-$dir-start"
for (y in (y1 + 1)..(y2 - 1)) {
arrowClasses[x to y] = "arrow arrow-$dir-middle"
}
arrowClasses[x to y2] = "arrow arrow-$dir-end"
//arrowCells[x to yStart] = a().addClass("arrow-link").attr("href", "#${to.toSimpleString()}")
arrowCells[x to yEnd] = div().addClass("arrow-head")
}
private fun nextArrowX(y1: Int, y2: Int): Int {
return generateSequence(0) { it + 1 }
.filter { x ->
(y1..y2).asSequence()
.map { y -> arrowClasses[x to y] }
.all { it == null }
}
.first()
}
fun add(ins: Instruction, metadata: MetadataLine?, disassembly: Disassembly) {
val address = ins.address
if (nextAddress != null) {
if (address != nextAddress) {
addDummy()
}
}
nextAddress = ins.postState.address
val y = (height++)
addresses[address] = y
add(y, ins.address,
text(ins.address.toFormattedString()),
text(ins.bytesToString()),
text(metadata?.label?.plus(":") ?: ""),
fragment {
text("${ins.opcode.mnemonic}")
text(ins.lengthSuffix)
text(" ")
val operands = ins.opcode.mode.print(ins)
val link = ins.linkedState
if (link == null) {
text(operands)
} else {
val local = link.address in disassembly
val url = when {
local -> "#${link.address.toSimpleString()}"
else -> "/${link.address.toSimpleString()}/${link.urlString}"
}
a {
text(operands)
}.attr("href", url)
}
},
text(ins.postState.toString())
)
if (ins.opcode.continuation == Continuation.NO) {
rowClasses[y] = "routine-end"
}
}
private fun addDummy() {
val y = (height++)
add(y, null, null, null, null, text("..."), null)
}
private fun add(y: Int, address: Address?,
cAddress: HtmlNode?,
cBytes: HtmlNode?,
cLabel: HtmlNode?,
cCode: HtmlNode?,
cState: HtmlNode?
) {
if (address != null) {
rowId[y] = address.toSimpleString()
}
content[0 to y] = cAddress
content[1 to y] = cBytes
content[2 to y] = cLabel
content[3 to y] = cCode
content[4 to y] = cState
}
fun output(): HtmlNode {
return table {
for (y in 0 until height) {
tr {
for (x in 0..3) {
val cssClass = cellClasses[x to y]
td {
content[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
for (x in 0 until arrowWidth) {
val cssClass = arrowClasses[x to y]
td {
arrowCells[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
for (x in 4..4) {
val cssClass = cellClasses[x to y]
td {
content[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
}.addClass(rowClasses[y]).attr("id", rowId[y])
}
}
}
}

View File

@ -0,0 +1,126 @@
package com.smallhacker.disbrowser
interface HtmlNode {
fun print(): String {
val out = StringBuilder()
printTo(out)
return out.toString()
}
fun printTo(out: StringBuilder): StringBuilder
fun attr(key: String): String? = null
fun attr(key: String, value: String?): HtmlNode = this
fun append(node: HtmlNode): HtmlNode = this
}
open class HtmlElement(protected val tag: String) : HtmlNode {
private val attributes = LinkedHashMap<String, String>()
final override fun toString(): String = print()
override fun printTo(out: StringBuilder): StringBuilder {
out.append("<", tag)
attributes.forEach { key, value ->
out.append(" ", key, "=\"", value, "\"")
}
return out.append(">")
}
final override fun attr(key: String): String? = attributes[key]
final override fun attr(key: String, value: String?) = apply {
if (value != null) {
attributes[key] = value
} else {
attributes.remove("key")
}
}
}
private class ParentHtmlElement(tag: String, inner: InnerHtml) : HtmlElement(tag) {
private val children = ArrayList<HtmlNode>()
init {
inner(HtmlArea(this))
}
override fun printTo(out: StringBuilder): StringBuilder {
super.printTo(out)
children.forEach { it.printTo(out) }
out.append("</", tag, ">")
return out
}
override fun append(node: HtmlNode) = apply { children.add(node) }
}
private fun parent(tag: String, inner: InnerHtml): HtmlNode = ParentHtmlElement(tag, inner)
private fun leaf(tag: String): HtmlNode = HtmlElement(tag)
typealias InnerHtml = HtmlArea.() -> Unit
class HtmlArea(val parent: HtmlNode)
fun text(text: String): HtmlNode = object : HtmlNode {
override fun printTo(out: StringBuilder) = out.append(text)
override fun attr(key: String, value: String?) = this
override fun append(node: HtmlNode): HtmlNode = this
}
fun HtmlArea.text(text: String) = com.smallhacker.disbrowser.text(text).appendTo(parent)
fun fragment(inner: InnerHtml = {}) = object : HtmlNode {
private val children = ArrayList<HtmlNode>()
init {
inner(HtmlArea(this))
}
override fun printTo(out: StringBuilder) = out.apply { children.forEach { it.printTo(out) } }
override fun append(node: HtmlNode) = apply { children.add(node) }
}
fun HtmlArea.fragment(inner: InnerHtml = {}) = com.smallhacker.disbrowser.fragment(inner).appendTo(parent)
fun html(inner: InnerHtml = {}) = parent("html", inner)
fun HtmlArea.html(inner: InnerHtml = {}) = com.smallhacker.disbrowser.html(inner).appendTo(parent)
fun head(inner: InnerHtml = {}) = parent("head", inner)
fun HtmlArea.head(inner: InnerHtml = {}) = com.smallhacker.disbrowser.head(inner).appendTo(parent)
fun title(inner: InnerHtml = {}) = parent("title", inner)
fun HtmlArea.title(inner: InnerHtml = {}) = com.smallhacker.disbrowser.title(inner).appendTo(parent)
fun link(inner: InnerHtml = {}) = leaf("link")
fun HtmlArea.link(inner: InnerHtml = {}) = com.smallhacker.disbrowser.link(inner).appendTo(parent)
fun body(inner: InnerHtml = {}) = parent("body", inner)
fun HtmlArea.body(inner: InnerHtml = {}) = com.smallhacker.disbrowser.body(inner).appendTo(parent)
fun div(inner: InnerHtml = {}) = parent("div", inner)
fun HtmlArea.div(inner: InnerHtml = {}) = com.smallhacker.disbrowser.div(inner).appendTo(parent)
fun table(inner: InnerHtml = {}) = parent("table", inner)
fun HtmlArea.table(inner: InnerHtml = {}) = com.smallhacker.disbrowser.table(inner).appendTo(parent)
fun tr(inner: InnerHtml = {}) = parent("tr", inner)
fun HtmlArea.tr(inner: InnerHtml = {}) = com.smallhacker.disbrowser.tr(inner).appendTo(parent)
fun td(inner: InnerHtml = {}) = parent("td", inner)
fun HtmlArea.td(inner: InnerHtml = {}) = com.smallhacker.disbrowser.td(inner).appendTo(parent)
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 HtmlNode.appendTo(node: HtmlNode) = apply { node.append(this) }
fun HtmlNode.addClass(c: String?) = attrAdd("class", c)
fun HtmlNode.attrSet(name: String): MutableSet<String> = (attr(name) ?: "")
.split(" ")
.asSequence()
.filterNot { it.isEmpty() }
.toMutableSet()
fun HtmlNode.attrAdd(name: String, value: String?) = apply {
if (value != null) {
val set = attrSet(name)
set.add(value)
attr(name, set.joinToString(" "))
}
}

View File

@ -0,0 +1,39 @@
//package com.smallhacker.disbrowser
//
//class HtmlContext(val out: StringBuilder)
//
//fun html(inner: HtmlContext.() -> Unit = {}): String {
// val html = HtmlContext(StringBuilder().append("<!DOCTYPE html>"))
// element(html, "html") { inner(html) }
// return html.out.toString()
//}
//
//private fun element(html: HtmlContext, tag: String, inner: HtmlContext.() -> Unit) = element(html, tag, inner, *emptyArray())
//
//private fun element(html: HtmlContext, tag: String, inner: HtmlContext.() -> Unit, vararg args: String) {
// html.out.append("<$tag")
// html.out.append(args.asSequence().map { " $it" }.joinToString())
// html.out.append(">")
// html.inner()
// html.out.append("</$tag>")
//}
//
//
//fun HtmlContext.text(text: String) {
// this.out.append(text)
//}
//
//fun HtmlContext.head(inner: HtmlContext.() -> Unit = {}) = element(this, "head", inner)
//fun HtmlContext.title(inner: HtmlContext.() -> Unit = {}) = element(this, "title", inner)
//fun HtmlContext.body(inner: HtmlContext.() -> Unit = {}) = element(this, "body", inner)
//fun HtmlContext.style(inner: HtmlContext.() -> Unit = {}) = element(this, "style", inner)
//fun HtmlContext.link(href: String, inner: HtmlContext.() -> Unit = {}) = element(this, "link", inner, "rel=\"stylesheet\" href=\"$href\"")
//fun HtmlContext.div(inner: HtmlContext.() -> Unit = {}) = element(this, "div", inner)
//fun HtmlContext.div(cssClass: String, inner: HtmlContext.() -> Unit = {}) = element(this, "div", inner, "class=\"$cssClass\"")
//
//fun HtmlContext.table(inner: HtmlContext.() -> Unit = {}) = element(this, "table", inner)
//fun HtmlContext.tr(inner: HtmlContext.() -> Unit = {}) = element(this, "tr", inner)
//fun HtmlContext.tr(cssClass: String?, inner: HtmlContext.() -> Unit = {}) = element(this, "tr", inner, (if (cssClass == null) "" else "class=\"$cssClass\""))
//fun HtmlContext.td(inner: HtmlContext.() -> Unit = {}) = element(this, "td", inner)
//fun HtmlContext.td(cssClass: String?, inner: HtmlContext.() -> Unit = {}) = element(this, "td", inner, (if (cssClass == null) "" else "class=\"$cssClass\""))
//fun HtmlContext.a(href: String, inner: HtmlContext.() -> Unit = {}) = element(this, "a", inner, "href=\"$href\"")

View File

@ -0,0 +1,37 @@
package com.smallhacker.disbrowser
interface ImmStack<E>: Iterable<E> {
fun isEmpty(): Boolean
val top: E?
fun pop(): ImmStack<E>
fun push(value: E): ImmStack<E> = ImmStackImpl(this, value)
}
fun <T> immStack(): ImmStack<T> {
@Suppress("UNCHECKED_CAST")
return EmptyImmStack as ImmStack<T>
}
private class ImmStackImpl<E>(private val parent: ImmStack<E>, override val top: E): ImmStack<E> {
override fun isEmpty() = false
override fun pop(): ImmStack<E> = parent
override fun iterator(): Iterator<E> {
return sequenceOf(top).plus(parent).iterator()
}
}
private object EmptyImmStack: ImmStack<Any?> {
override fun isEmpty() = true
override val top: Any? = null
override fun pop() = this
override fun iterator() = emptySequence<Any?>().iterator()
}

View File

@ -0,0 +1,45 @@
package com.smallhacker.disbrowser;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.io.IOException;
import java.net.URI;
/**
* Main class.
*
*/
public class Main {
// Base URI the Grizzly HTTP server will listen on
public static final String BASE_URI = "http://localhost:8080/";
/**
* Starts Grizzly HTTP server exposing JAX-RS resources defined in this application.
* @return Grizzly HTTP server.
*/
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");
// create and start a new instance of grizzly http server
// exposing the Jersey application at BASE_URI
return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
}
/**
* Main method.
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
final HttpServer server = startServer();
System.out.println(String.format("Jersey app started with WADL available at "
+ "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
System.in.read();
server.stop();
}
}

View File

@ -0,0 +1,159 @@
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 }
}
}
}

View File

@ -0,0 +1,19 @@
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)
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)
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 withinBank(value: Int): Address = Address((this.value and 0xFF_0000) or value)
override fun compareTo(other: Address) = value.compareTo(other.value)
}

View File

@ -0,0 +1,5 @@
package com.smallhacker.disbrowser.asm
enum class Continuation {
NO, YES, MAYBE
}

View File

@ -0,0 +1,13 @@
package com.smallhacker.disbrowser.asm
class Disassembly(lines: List<Instruction>) : Iterable<Instruction> {
override fun iterator() = lines.values.iterator() as Iterator<Instruction>
private val lines = LinkedHashMap<Address, Instruction>()
init {
lines.forEach { this.lines[it.address] = it }
}
operator fun contains(address: Address) = address in lines
}

View File

@ -0,0 +1,106 @@
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
val postState = opcode.mutate(this)
.mutateAddress { it + bytes.size }
.withOrigin(this)
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
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 -> ""
}
}
}
private val operandLength
get() = opcode.mode.operandLength(preState)
private fun link(): Address? {
if (!opcode.link) {
return null
}
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)
else -> null
}
}
override fun toString(): String {
return "$address ${bytesToString()} ${opcode.mnemonic} ${opcode.mode.print(this).padEnd(100, ' ')} ($preState -> $postState)"
}
}

View File

@ -0,0 +1,66 @@
package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.util.left
import com.smallhacker.disbrowser.util.or
import com.smallhacker.disbrowser.util.toLong
class Metadata {
private val lines = HashMap<Address, MetadataLine>()
fun add(line: MetadataLine): Metadata {
lines[line.address] = line
return this
}
operator fun get(address: Address): MetadataLine? {
return lines[address]
}
}
fun Metadata.at(address: Int, runner: MetadataLine.() -> Unit) {
val line = MetadataLine(Address(address))
this.add(line)
runner(line)
}
fun metadata(runner: Metadata.() -> Unit): Metadata {
val metadata = Metadata()
runner(metadata)
return metadata
}
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) }
}
}
class JslTableRoutine(private val entries: Int) : InstructionFlag {
fun readTable(postJsr: State): Sequence<Address> {
val data = postJsr.data
return (0 until entries)
.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) }
}
// 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 })
// }
// }
}

View File

@ -0,0 +1,8 @@
package com.smallhacker.disbrowser.asm
data class MetadataLine(
val address: Address,
var label: String? = null,
var comment: String? = null,
val flags: MutableList<InstructionFlag> = ArrayList()
)

View File

@ -0,0 +1,15 @@
package com.smallhacker.disbrowser.asm
enum class Mnemonic {
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,
JSR, LDA, LDX, LDY, LSR, MVN, MVP, NOP, ORA, PEA, PEI,
PER, PHA, PHB, PHD, PHK, PHP, PHX, PHY, PLA, PLB, PLD,
PLP, PLX, PLY, REP, ROL, ROR, RTI, RTL, RTS, SBC, SEC,
SED, SEI, SEP, STA, STP, STX, STY, STZ, TAX, TAY, TCD,
TCS, TDC, TRB, TSB, TSC, TSX, TXA, TXS, TXY, TYA, TYX,
WAI, WDM, XBA, XCE,
DB, DW, DL
}

View File

@ -0,0 +1,111 @@
package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.util.*
fun format(format: String, value: UVal<*>): String {
return format.replace(Regex("[0]+"), value.toHex())
}
enum class Mode {
DATA_BYTE(1, "$00", { dataByte }, dataMode = true),
DATA_WORD(2, "$0000", { dataWord }, dataMode = true),
DATA_LONG(3, "$000000", { dataLong }, dataMode = true),
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) })
;
private val length: Int
val print: Instruction.() -> String
val dataMode: Boolean
constructor(length: Int, print: Instruction.() -> String) {
this.length = length
this.print = print
this.dataMode = false
}
constructor(length: Int, format: String, valueGetter: Instruction.() -> UVal<*>, dataMode: Boolean = false) {
this.length = length
this.print = { format(format, valueGetter(this)) }
this.dataMode = dataMode
}
/**
* Returns the total length, in bytes, of an instruction of this mode and its operands.
*
* This is usually one greater than [operandLength], except in the cases when the instruction is just pure data
* without an opcode (in which case the two are equal).
*
* 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
}
}
/**
* Returns the length, in bytes, of the operands of an instruction of this mode.
*
* This is usually one less than [operandLength], except in the cases when the instruction is just pure data
* without an opcode (in which case the two are equal).
*
* 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
}
}
}

View File

@ -0,0 +1,19 @@
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.
// }
//}

View File

@ -0,0 +1,363 @@
package com.smallhacker.disbrowser.asm
import java.util.HashMap
import com.smallhacker.disbrowser.asm.Mnemonic.*
import com.smallhacker.disbrowser.asm.Mode.*
import com.smallhacker.disbrowser.util.UByte
typealias SegmentEnder = Instruction.() -> SegmentEnd?
class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val ender: SegmentEnder, val mutate: (Instruction) -> State) {
private var _continuation = Continuation.YES
private var _link = false
private var _branch = false
val operandIndex
get() = if (mode.dataMode) 0 else 1
val continuation: Continuation
get() = _continuation
val link: Boolean
get() = _link
val branch: Boolean
get() = _branch
private fun stop(): Opcode {
this._continuation = Continuation.NO
return this
}
private fun mayStop(): Opcode {
this._continuation = Continuation.MAYBE
return this
}
private fun linking(): Opcode {
this._link = true
return this
}
private fun branching(): Opcode {
linking()
this._branch = true
return this
}
companion object {
val DATA_BYTE = Opcode(Mnemonic.DB, Mode.DATA_BYTE, { null }, { it.preState })
val DATA_WORD = Opcode(Mnemonic.DW, Mode.DATA_WORD, { null }, { it.preState })
val DATA_LONG = Opcode(Mnemonic.DL, Mode.DATA_LONG, { null }, { it.preState })
val POINTER_WORD = Opcode(Mnemonic.DW, Mode.DATA_WORD, { null }, { it.preState }).linking()
val POINTER_LONG = Opcode(Mnemonic.DL, Mode.DATA_LONG, { null }, { it.preState }).linking()
private val OPCODES: Array<Opcode>
fun opcode(byteValue: UByte): Opcode {
return OPCODES[byteValue.value]
}
init {
val ocs = HashMap<Int, Opcode>()
fun add(value: Int, mnemonic: Mnemonic, mode: Mode, ender: SegmentEnder, mutate: (Instruction) -> State = { it.preState }): Opcode {
val opcode = Opcode(mnemonic, mode, ender, mutate)
ocs[value] = opcode
return opcode
}
val alwaysContinue: SegmentEnder = { null }
val alwaysStop: SegmentEnder = { stoppingSegmentEnd(address) }
val branching: SegmentEnder = { branchingSegmentEnd(address, postState, linkedState!!) }
val alwaysBranching: SegmentEnder = { alwaysBranchingSegmentEnd(address, linkedState!!) }
val jumping: SegmentEnder = { jumpSegmentEnd(address, linkedState!!) }
val dynamicJumping: SegmentEnder = { stoppingSegmentEnd(address) }
val subJumping: SegmentEnder = { subroutineSegmentEnd(address, linkedState!!, postState.address) }
val dynamicSubJumping: SegmentEnder = { stoppingSegmentEnd(address) }
val returning: SegmentEnder = { returnSegmentEnd(address) }
add(0x00, BRK, IMMEDIATE_8, alwaysStop).stop()
add(0x02, COP, IMMEDIATE_8, alwaysStop).stop()
add(0x42, WDM, IMMEDIATE_8, alwaysStop).stop()
add(0xEA, NOP, IMPLIED, alwaysContinue)
add(0xDB, STP, IMPLIED, alwaysStop).stop()
add(0xCB, WAI, IMPLIED, alwaysContinue)
add(0x10, BPL, RELATIVE, branching).branching()
add(0x30, BMI, RELATIVE, branching).branching()
add(0x50, BVC, RELATIVE, branching).branching()
add(0x70, BVS, RELATIVE, branching).branching()
add(0x80, BRA, RELATIVE, alwaysBranching).stop().branching()
add(0x90, BCC, RELATIVE, branching).branching()
add(0xB0, BCS, RELATIVE, branching).branching()
add(0xD0, BNE, RELATIVE, branching).branching()
add(0xF0, BEQ, RELATIVE, branching).branching()
add(0x82, BRL, RELATIVE_LONG, alwaysBranching).stop().branching()
add(0x4C, JMP, ABSOLUTE, jumping).linking().stop()
add(0x5C, JML, ABSOLUTE_LONG, jumping).linking().stop()
add(0x6C, JMP, ABSOLUTE_INDIRECT, dynamicJumping).stop()
add(0x7C, JMP, ABSOLUTE_X_INDIRECT, dynamicJumping).stop()
add(0xDC, JMP, ABSOLUTE_INDIRECT_LONG, dynamicJumping).stop()
add(0x22, JSL, ABSOLUTE_LONG, subJumping).linking().mayStop()
add(0x20, JSR, ABSOLUTE, subJumping).linking().mayStop()
add(0xFC, JSR, ABSOLUTE_X_INDIRECT, dynamicSubJumping).mayStop()
add(0x60, RTS, IMPLIED, returning).stop()
add(0x6B, RTL, IMPLIED, returning).stop()
add(0x40, RTI, IMPLIED, returning).stop()
add(0x1B, TCS, IMPLIED, alwaysContinue)
add(0x3B, TSC, IMPLIED, alwaysContinue)
add(0x5B, TCD, IMPLIED, alwaysContinue)
add(0x7B, TDC, IMPLIED, alwaysContinue)
add(0xAA, TAX, IMPLIED, alwaysContinue)
add(0xA8, TAY, IMPLIED, alwaysContinue)
add(0xBA, TSX, IMPLIED, alwaysContinue)
add(0x8A, TXA, IMPLIED, alwaysContinue)
add(0x9A, TXS, IMPLIED, alwaysContinue)
add(0x9B, TXY, IMPLIED, alwaysContinue)
add(0x98, TYA, IMPLIED, alwaysContinue)
add(0xBB, TYX, IMPLIED, alwaysContinue)
add(0xEB, XBA, IMPLIED, alwaysContinue)
add(0x18, CLC, IMPLIED, alwaysContinue)
add(0x38, SEC, IMPLIED, alwaysContinue)
add(0x58, CLI, IMPLIED, alwaysContinue)
add(0x78, SEI, IMPLIED, alwaysContinue)
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(0xFB, XCE, IMPLIED, alwaysContinue)
add(0xC1, CMP, DIRECT_X_INDIRECT, alwaysContinue)
add(0xC3, CMP, DIRECT_S, alwaysContinue)
add(0xC5, CMP, DIRECT, alwaysContinue)
add(0xC7, CMP, DIRECT_INDIRECT_LONG, alwaysContinue)
add(0xC9, CMP, IMMEDIATE_M, alwaysContinue)
add(0xCD, CMP, ABSOLUTE, alwaysContinue)
add(0xCF, CMP, ABSOLUTE_LONG, alwaysContinue)
add(0xD1, CMP, DIRECT_INDIRECT_Y, alwaysContinue)
add(0xD2, CMP, DIRECT_INDIRECT, alwaysContinue)
add(0xD3, CMP, DIRECT_S_INDIRECT_Y, alwaysContinue)
add(0xD5, CMP, DIRECT_X, alwaysContinue)
add(0xD7, CMP, DIRECT_INDIRECT_LONG_Y, alwaysContinue)
add(0xD9, CMP, ABSOLUTE_Y, alwaysContinue)
add(0xDD, CMP, ABSOLUTE_X, alwaysContinue)
add(0xDF, CMP, ABSOLUTE_LONG_X, alwaysContinue)
add(0xE0, CPX, IMMEDIATE_X, alwaysContinue)
add(0xE4, CPX, DIRECT, alwaysContinue)
add(0xEC, CPX, ABSOLUTE, alwaysContinue)
add(0xC0, CPY, IMMEDIATE_X, alwaysContinue)
add(0xC4, CPY, DIRECT, alwaysContinue)
add(0xCC, CPY, ABSOLUTE, alwaysContinue)
add(0xA1, LDA, DIRECT_X_INDIRECT, alwaysContinue)
add(0xA3, LDA, DIRECT_S, alwaysContinue)
add(0xA5, LDA, DIRECT, alwaysContinue)
add(0xA7, LDA, DIRECT_INDIRECT_LONG, alwaysContinue)
add(0xA9, LDA, IMMEDIATE_M, alwaysContinue)
add(0xAD, LDA, ABSOLUTE, alwaysContinue)
add(0xAF, LDA, ABSOLUTE_LONG, alwaysContinue)
add(0xB1, LDA, DIRECT_INDIRECT_Y, alwaysContinue)
add(0xB2, LDA, DIRECT_INDIRECT, alwaysContinue)
add(0xB3, LDA, DIRECT_S_INDIRECT_Y, alwaysContinue)
add(0xB5, LDA, DIRECT_X, alwaysContinue)
add(0xB7, LDA, DIRECT_INDIRECT_LONG_Y, alwaysContinue)
add(0xB9, LDA, ABSOLUTE_Y, alwaysContinue)
add(0xBD, LDA, ABSOLUTE_X, alwaysContinue)
add(0xBF, LDA, ABSOLUTE_LONG_X, alwaysContinue)
add(0xA2, LDX, IMMEDIATE_X, alwaysContinue)
add(0xA6, LDX, DIRECT, alwaysContinue)
add(0xAE, LDX, ABSOLUTE, alwaysContinue)
add(0xB6, LDX, DIRECT_Y, alwaysContinue)
add(0xBE, LDX, ABSOLUTE_Y, alwaysContinue)
add(0xA0, LDY, IMMEDIATE_X, alwaysContinue)
add(0xA4, LDY, DIRECT, alwaysContinue)
add(0xAC, LDY, ABSOLUTE, alwaysContinue)
add(0xB4, LDY, DIRECT_X, alwaysContinue)
add(0xBC, LDY, ABSOLUTE_X, alwaysContinue)
add(0x81, STA, DIRECT_X_INDIRECT, alwaysContinue)
add(0x83, STA, DIRECT_S, alwaysContinue)
add(0x85, STA, DIRECT, alwaysContinue)
add(0x87, STA, DIRECT_INDIRECT_LONG, alwaysContinue)
add(0x8D, STA, ABSOLUTE, alwaysContinue)
add(0x8F, STA, ABSOLUTE_LONG, alwaysContinue)
add(0x91, STA, DIRECT_INDIRECT_Y, alwaysContinue)
add(0x92, STA, DIRECT_INDIRECT, alwaysContinue)
add(0x93, STA, DIRECT_S_INDIRECT_Y, alwaysContinue)
add(0x95, STA, DIRECT_X, alwaysContinue)
add(0x97, STA, DIRECT_INDIRECT_LONG_Y, alwaysContinue)
add(0x99, STA, ABSOLUTE_Y, alwaysContinue)
add(0x9D, STA, ABSOLUTE_X, alwaysContinue)
add(0x9F, STA, ABSOLUTE_LONG_X, alwaysContinue)
add(0x86, STX, DIRECT, alwaysContinue)
add(0x8E, STX, ABSOLUTE, alwaysContinue)
add(0x96, STX, DIRECT_Y, alwaysContinue)
add(0x84, STY, DIRECT, alwaysContinue)
add(0x8C, STY, ABSOLUTE, alwaysContinue)
add(0x94, STY, DIRECT_X, alwaysContinue)
add(0x64, STZ, DIRECT, alwaysContinue)
add(0x74, STZ, DIRECT_X, alwaysContinue)
add(0x9C, STZ, ABSOLUTE, alwaysContinue)
add(0x9E, STZ, ABSOLUTE_X, alwaysContinue)
add(0x48, PHA, IMPLIED, alwaysContinue) { it.preState.pushUnknown(it.preState.mWidth) }
add(0xDA, PHX, IMPLIED, alwaysContinue) { it.preState.pushUnknown(it.preState.xWidth) }
add(0x5A, PHY, IMPLIED, alwaysContinue) { it.preState.pushUnknown(it.preState.xWidth) }
add(0x68, PLA, IMPLIED, alwaysContinue) { it.preState.pull(it.preState.mWidth) }
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(0x08, PHP, IMPLIED, alwaysContinue) { it.preState.push(it.preState.flags) }
add(0x28, PLP, IMPLIED, alwaysContinue) { it.preState.pull { copy(flags = it) } }
add(0x3A, DEC, IMPLIED, alwaysContinue)
add(0xC6, DEC, DIRECT, alwaysContinue)
add(0xCE, DEC, ABSOLUTE, alwaysContinue)
add(0xD6, DEC, DIRECT_X, alwaysContinue)
add(0xDE, DEC, ABSOLUTE_X, alwaysContinue)
add(0xCA, DEX, IMPLIED, alwaysContinue)
add(0x88, DEY, IMPLIED, alwaysContinue)
add(0x1A, INC, IMPLIED, alwaysContinue)
add(0xE6, INC, DIRECT, alwaysContinue)
add(0xEE, INC, ABSOLUTE, alwaysContinue)
add(0xF6, INC, DIRECT_X, alwaysContinue)
add(0xFE, INC, ABSOLUTE_X, alwaysContinue)
add(0xE8, INX, IMPLIED, alwaysContinue)
add(0xC8, INY, IMPLIED, alwaysContinue)
add(0x06, ASL, DIRECT, alwaysContinue)
add(0x0A, ASL, IMPLIED, alwaysContinue)
add(0x0E, ASL, ABSOLUTE, alwaysContinue)
add(0x16, ASL, DIRECT_X, alwaysContinue)
add(0x1E, ASL, ABSOLUTE_X, alwaysContinue)
add(0x46, LSR, DIRECT, alwaysContinue)
add(0x4A, LSR, IMPLIED, alwaysContinue)
add(0x4E, LSR, ABSOLUTE, alwaysContinue)
add(0x56, LSR, DIRECT_X, alwaysContinue)
add(0x5E, LSR, ABSOLUTE_X, alwaysContinue)
add(0x26, ROL, DIRECT, alwaysContinue)
add(0x2A, ROL, IMPLIED, alwaysContinue)
add(0x2E, ROL, ABSOLUTE, alwaysContinue)
add(0x36, ROL, DIRECT_X, alwaysContinue)
add(0x3E, ROL, ABSOLUTE_X, alwaysContinue)
add(0x66, ROR, DIRECT, alwaysContinue)
add(0x6A, ROR, IMPLIED, alwaysContinue)
add(0x6E, ROR, ABSOLUTE, alwaysContinue)
add(0x76, ROR, DIRECT_X, alwaysContinue)
add(0x7E, ROR, ABSOLUTE_X, alwaysContinue)
add(0x61, ADC, DIRECT_X_INDIRECT, alwaysContinue)
add(0x63, ADC, DIRECT_S, alwaysContinue)
add(0x65, ADC, DIRECT, alwaysContinue)
add(0x67, ADC, DIRECT_INDIRECT_LONG, alwaysContinue)
add(0x69, ADC, IMMEDIATE_M, alwaysContinue)
add(0x6D, ADC, ABSOLUTE, alwaysContinue)
add(0x6F, ADC, ABSOLUTE_LONG, alwaysContinue)
add(0x71, ADC, DIRECT_INDIRECT_Y, alwaysContinue)
add(0x72, ADC, DIRECT_INDIRECT, alwaysContinue)
add(0x73, ADC, DIRECT_S_INDIRECT_Y, alwaysContinue)
add(0x75, ADC, DIRECT_X, alwaysContinue)
add(0x77, ADC, DIRECT_INDIRECT_LONG_Y, alwaysContinue)
add(0x79, ADC, ABSOLUTE_Y, alwaysContinue)
add(0x7D, ADC, ABSOLUTE_X, alwaysContinue)
add(0x7F, ADC, ABSOLUTE_LONG_X, alwaysContinue)
add(0xE1, SBC, DIRECT_X_INDIRECT, alwaysContinue)
add(0xE3, SBC, DIRECT_S, alwaysContinue)
add(0xE5, SBC, DIRECT, alwaysContinue)
add(0xE7, SBC, DIRECT_INDIRECT_LONG, alwaysContinue)
add(0xE9, SBC, IMMEDIATE_M, alwaysContinue)
add(0xED, SBC, ABSOLUTE, alwaysContinue)
add(0xEF, SBC, ABSOLUTE_LONG, alwaysContinue)
add(0xF1, SBC, DIRECT_INDIRECT_Y, alwaysContinue)
add(0xF2, SBC, DIRECT_INDIRECT, alwaysContinue)
add(0xF3, SBC, DIRECT_S_INDIRECT_Y, alwaysContinue)
add(0xF5, SBC, DIRECT_X, alwaysContinue)
add(0xF7, SBC, DIRECT_INDIRECT_LONG_Y, alwaysContinue)
add(0xF9, SBC, ABSOLUTE_Y, alwaysContinue)
add(0xFD, SBC, ABSOLUTE_X, alwaysContinue)
add(0xFF, SBC, ABSOLUTE_LONG_X, alwaysContinue)
add(0x21, AND, DIRECT_X_INDIRECT, alwaysContinue)
add(0x23, AND, DIRECT_S, alwaysContinue)
add(0x25, AND, DIRECT, alwaysContinue)
add(0x27, AND, DIRECT_INDIRECT_LONG, alwaysContinue)
add(0x29, AND, IMMEDIATE_M, alwaysContinue)
add(0x2D, AND, ABSOLUTE, alwaysContinue)
add(0x2F, AND, ABSOLUTE_LONG, alwaysContinue)
add(0x31, AND, DIRECT_INDIRECT_Y, alwaysContinue)
add(0x32, AND, DIRECT_INDIRECT, alwaysContinue)
add(0x33, AND, DIRECT_S_INDIRECT_Y, alwaysContinue)
add(0x35, AND, DIRECT_X, alwaysContinue)
add(0x37, AND, DIRECT_INDIRECT_LONG_Y, alwaysContinue)
add(0x39, AND, ABSOLUTE_Y, alwaysContinue)
add(0x3D, AND, ABSOLUTE_X, alwaysContinue)
add(0x3F, AND, ABSOLUTE_LONG_X, alwaysContinue)
add(0x41, EOR, DIRECT_X_INDIRECT, alwaysContinue)
add(0x43, EOR, DIRECT_S, alwaysContinue)
add(0x45, EOR, DIRECT, alwaysContinue)
add(0x47, EOR, DIRECT_INDIRECT_LONG, alwaysContinue)
add(0x49, EOR, IMMEDIATE_M, alwaysContinue)
add(0x4D, EOR, ABSOLUTE, alwaysContinue)
add(0x4F, EOR, ABSOLUTE_LONG, alwaysContinue)
add(0x51, EOR, DIRECT_INDIRECT_Y, alwaysContinue)
add(0x52, EOR, DIRECT_INDIRECT, alwaysContinue)
add(0x53, EOR, DIRECT_S_INDIRECT_Y, alwaysContinue)
add(0x55, EOR, DIRECT_X, alwaysContinue)
add(0x57, EOR, DIRECT_INDIRECT_LONG_Y, alwaysContinue)
add(0x59, EOR, ABSOLUTE_Y, alwaysContinue)
add(0x5D, EOR, ABSOLUTE_X, alwaysContinue)
add(0x5F, EOR, ABSOLUTE_LONG_X, alwaysContinue)
add(0x01, ORA, DIRECT_X_INDIRECT, alwaysContinue)
add(0x03, ORA, DIRECT_S, alwaysContinue)
add(0x05, ORA, DIRECT, alwaysContinue)
add(0x07, ORA, DIRECT_INDIRECT_LONG, alwaysContinue)
add(0x09, ORA, IMMEDIATE_M, alwaysContinue)
add(0x0D, ORA, ABSOLUTE, alwaysContinue)
add(0x0F, ORA, ABSOLUTE_LONG, alwaysContinue)
add(0x11, ORA, DIRECT_INDIRECT_Y, alwaysContinue)
add(0x12, ORA, DIRECT_INDIRECT, alwaysContinue)
add(0x13, ORA, DIRECT_S_INDIRECT_Y, alwaysContinue)
add(0x15, ORA, DIRECT_X, alwaysContinue)
add(0x17, ORA, DIRECT_INDIRECT_LONG_Y, alwaysContinue)
add(0x19, ORA, ABSOLUTE_Y, alwaysContinue)
add(0x1D, ORA, ABSOLUTE_X, alwaysContinue)
add(0x1F, ORA, ABSOLUTE_LONG_X, alwaysContinue)
add(0x14, TRB, DIRECT, alwaysContinue)
add(0x1C, TRB, ABSOLUTE, alwaysContinue)
add(0x04, TSB, DIRECT, alwaysContinue)
add(0x0C, TSB, ABSOLUTE, alwaysContinue)
add(0x24, BIT, DIRECT, alwaysContinue)
add(0x2C, BIT, ABSOLUTE, alwaysContinue)
add(0x34, BIT, DIRECT_X, alwaysContinue)
add(0x3C, BIT, ABSOLUTE_X, alwaysContinue)
add(0x89, BIT, IMMEDIATE_M, alwaysContinue)
add(0x54, MVN, BLOCK_MOVE, alwaysContinue)
add(0x44, MVP, BLOCK_MOVE, alwaysContinue)
add(0xF4, PEA, IMMEDIATE_16, alwaysContinue)
add(0xD4, PEI, DIRECT, alwaysContinue)
add(0x62, PER, RELATIVE_LONG, alwaysContinue)
OPCODES = Array(256) { ocs[it]!! }
}
}
}

View File

@ -0,0 +1,44 @@
package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.util.*
import java.nio.file.Files
import java.nio.file.Path
interface RomData {
val size: Int
operator fun get(address: Int): UByte
fun range(start: Int, length: Int): RomData = RomRange(this, start, length)
fun asSequence(): Sequence<UByte> = (0 until size).asSequence().map { get(it) }
companion object {
fun load(path: Path): RomData = Rom(Files.readAllBytes(path))
}
}
fun RomData.getWord(address: Int): UWord = get(address).toWord() or (get(address + 1).toWord() left 8)
private class Rom(private val bytes: ByteArray): RomData {
override val size = bytes.size
override fun get(address: Int): UByte {
checkRange(address)
return uByte(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 fun RomData.checkRange(address: Int) {
if (address < 0 || address >= size) {
throw IndexOutOfBoundsException("Index $address out of range: [0, $size)")
}
}

View File

@ -0,0 +1,29 @@
package com.smallhacker.disbrowser.asm
class Segment (val start: Address, 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)
fun stoppingSegmentEnd(address: Address)
= SegmentEnd(address)
fun branchingSegmentEnd(address: Address, continueState: State, branchState: State)
= SegmentEnd(address, local = listOf(continueState, branchState))
fun alwaysBranchingSegmentEnd(address: Address, branchState: State)
= SegmentEnd(address, local = listOf(branchState))
fun jumpSegmentEnd(address: Address, targetState: State)
= SegmentEnd(address, remote = listOf(targetState))
fun subroutineSegmentEnd(address: Address, targetState: State, returnAddress: Address)
= SegmentEnd(address, remote = listOf(targetState), returnAddress = returnAddress)
fun returnSegmentEnd(address: Address)
= SegmentEnd(address, returning = true)
fun continuationSegmentEnd(state: State)
= SegmentEnd(state.address, local = listOf(state))
fun Segment.toDisassembly() = Disassembly(instructions)

View File

@ -0,0 +1,114 @@
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 mWidth: Int? get() = toWidth(m)
val xWidth: Int? get() = toWidth(x)
fun sep(i: UByte) = withFlags(flags.withBits(i.value))
fun rep(i: UByte) = withFlags(flags.withoutBits(i.value))
fun uncertain() = withFlags(VagueNumber())
private fun withFlags(flags: VagueNumber) = copy(flags = flags)
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: VagueNumber) = copy(stack = stack.push(value))
fun pushUnknown(count: Int? = 1): State {
if (count == null) {
return copy(stack = immStack())
}
var stack = this.stack
for (i in 1..count) {
stack = stack.push(VagueNumber())
}
return copy(stack = stack)
}
fun pull() = (stack.top ?: VagueNumber()) to copy(stack = stack.pop())
fun pull(count: Int?): State {
if (count == null) {
return copy(stack = immStack())
}
var stack = this.stack
for (i in 1..count) {
stack = stack.pop()
}
return copy(stack = stack)
}
fun clearStack() = copy(stack = immStack())
override fun toString(): String {
return "A:${printSize(m)} XY:${printSize(x)} S:" + stackToString()
}
private fun stackToString(): String {
return stack.reversed().asSequence()
.map { stackByteToString(it) }
.joinToString(" ")
}
private fun stackByteToString(v: VagueNumber): String {
if (v.certain) {
return String.format("%02x", v.value)
}
if (v.certainty == 0) {
return "??"
}
val c = v.certainty
val high = (c and 0xF0) != 0
val low = (c and 0x0F) != 0
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 "?")
.toString()
}
private fun printSize(flag: Boolean?): String = when (flag) {
null -> "??"
true -> " 8"
false -> "16"
}
val urlString: String
get() {
val out = StringBuilder()
out.append(when (x) {
null -> ""
true -> "X"
false -> "x"
})
out.append(when (m) {
null -> ""
true -> "M"
false -> "m"
})
return out.toString()
}
private fun toWidth(flag: Boolean?): Int? = when (flag) {
null -> null
true -> 1
false -> 2
}
}
fun State.pushByte(value: Byte) = this.push(VagueNumber(value.toInt()))
fun State.pull(consumer: State.(VagueNumber) -> State): State {
val (value, state) = this.pull()
return consumer(state, value)
}

View File

@ -0,0 +1,40 @@
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)
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 certain: Boolean
get() = certainty == -1
fun get(mask: Int): Int? {
if ((certainty and mask) != mask) {
return null
}
return value and mask
}
fun getBoolean(mask: Int): Boolean? {
val value = get(mask) ?: return null
return value == mask
}
override fun toString(): String {
var i = 1 shl 31
val out = StringBuilder()
while (i != 0) {
val b = getBoolean(i)
when (b) {
true -> out.append('1')
false -> out.append('0')
null -> out.append('?')
}
i = i ushr 1
}
return out.toString()
}
}

View File

@ -0,0 +1,167 @@
package com.smallhacker.disbrowser.disassembler
import com.smallhacker.disbrowser.asm.*
import java.util.*
import kotlin.collections.ArrayList
object Disassembler {
fun disassemble(initialState: State, metadata: Metadata, global: Boolean): Disassembly {
val seen = HashSet<Address>()
val queue = ArrayDeque<State>()
fun tryAdd(state: State) {
if (seen.add(state.address)) {
queue.add(state)
}
}
tryAdd(initialState)
val instructions = ArrayList<Instruction>()
while (queue.isNotEmpty()) {
val state = queue.remove()
val ins = disassembleInstruction(state)
instructions.add(ins)
var stop = (ins.opcode.continuation == Continuation.NO) or
(ins.opcode.mode.instructionLength(state) == null)
metadata[ins.address]?.flags?.forEach {
if (it is JmpIndirectLongInterleavedTable) {
if (global) {
it.readTable(state.data)
.map { ins.postState.copy(address = it) }
.forEach { tryAdd(it) }
}
stop = true
} else if (it is JslTableRoutine) {
if (global) {
it.readTable(ins.postState)
.map { ins.postState.copy(address = it) }
.forEach { tryAdd(it) }
}
stop = true
}
}
val linkedState = ins.linkedState
if (linkedState != null) {
metadata[linkedState.address]?.flags?.forEach {
if (it === NonReturningRoutine) {
stop = true
println(ins.address.toFormattedString())
}
}
}
if (!stop) {
tryAdd(ins.postState)
}
if (linkedState != null) {
if (ins.opcode.branch || global) {
tryAdd(linkedState)
}
}
}
val instructionList = instructions
.sortedBy { it.address }
.toList()
return Disassembly(instructionList)
}
fun disassembleSegments(initialState: State): List<Segment> {
val mapping = HashMap<Address, Segment>()
val queue = ArrayDeque<State>()
val segments = ArrayList<Segment>()
fun tryAdd(state: State) {
if (!mapping.containsKey(state.address)) {
queue.add(state)
}
}
tryAdd(initialState)
while (queue.isNotEmpty()) {
val state = queue.remove()
if (mapping.containsKey(state.address)) {
continue
}
val segment = disassembleSegment(state, mapping)
if (segment.instructions.isEmpty()) {
continue
}
segments.add(segment)
val end = segment.end
end.local.forEach { queue.add(it) }
end.remote.forEach {
}
}
return segments
}
fun disassembleSegment(initialState: State, mapping: MutableMap<Address, Segment>): Segment {
val instructions = ArrayList<Instruction>()
var lastState = initialState
val queue = ArrayDeque<State>()
val seen = HashSet<Address>()
fun finalize(segment: Segment): Segment {
instructions.forEach {
mapping[it.address] = segment
}
return segment
}
fun tryAdd(state: State) {
if (seen.add(state.address)) {
queue.add(state)
}
}
tryAdd(initialState)
while (queue.isNotEmpty()) {
val state = queue.remove()
val ins = disassembleInstruction(state)
instructions.add(ins)
println(ins)
val segmentEnd = ins.opcode.ender(ins)
if (segmentEnd != null) {
return finalize(Segment(initialState.address, segmentEnd, instructions))
}
tryAdd(ins.postState)
lastState = ins.postState
}
return finalize(Segment(initialState.address, continuationSegmentEnd(lastState), instructions))
}
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 bytes = state.data.range(pc, length)
return Instruction(bytes, opcode, state)
}
}

View File

@ -0,0 +1,59 @@
package com.smallhacker.disbrowser.util
import java.util.*
abstract class UType(val bytes: Int, val name: String) {
val mask = (1 shl (bytes * 8)) - 1
}
class UVal<U : UType>(value: Int, val type: U) : Comparable<UVal<U>> {
val value = value and type.mask
override fun equals(other: Any?) = equalsBy(other, { value }, { type })
override fun toString() = "${type.name}(${toHex(true)})"
fun toHex(prefix: Boolean = false): String {
val digits = 2 * type.bytes
val start = if (prefix) "0x" else ""
val pattern = "%0${digits}x"
return start + String.format(pattern, value)
}
override fun compareTo(other: UVal<U>): Int = Integer.compare(value, other.value)
override fun hashCode() = Objects.hash(value, type)
//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")
}
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>
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) }
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)
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 }
inline infix fun <reified U: UVal<V>, V: UType> U.and(other: U): U = mutate { it and other.value }
inline infix fun <reified U: UVal<V>, V: UType> U.or(other: U): U = mutate { it or other.value }
inline operator fun <reified U: UVal<*>> U.not(): U = mutate { it.inv() }
inline infix operator fun <reified U: UVal<V>, V: UType> U.plus(other: U): U = mutate { it + other.value }
inline infix operator fun <reified U: UVal<V>, V: UType> U.minus(other: U): U = mutate { it - other.value }

View File

@ -0,0 +1,16 @@
package com.smallhacker.disbrowser.util
inline fun <reified T> T.equalsBy(other: Any?, vararg values: T.() -> Any?) = when (other) {
!is T -> false
else -> values.asSequence()
.map { it(this) to it(other) }
.all { it.first == it.second }
}
fun tryParseInt(s: String, radix: Int = 10): Int? {
return try {
Integer.parseInt(s, radix)
} catch (e: NumberFormatException) {
null
}
}

View File

@ -0,0 +1,48 @@
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);

View File

@ -0,0 +1,95 @@
table {
border-spacing: 0;
font-family: monospace;
white-space: pre
}
td {
padding-right: 12px;
}
.code-opcode {
width: 200px
}
tr:nth-child(2n) {
background-color: #eee
}
tr {
transition: background-color 1000ms 1500ms;
}
tr.line-active {
background-color: yellow !important;
transition: none;
}
.arrow {
width: 10px;
position: relative;
padding-right: 0;
}
.arrow::before {
content: "";
display: block;
position: absolute;
top: 0;
bottom: 0;
right: 2px;
width: 2px;
background: blue;
}
.arrow-up-start::after,
.arrow-up-end::after,
.arrow-down-start::after,
.arrow-down-end::after {
content: "";
background: blue;
height: 2px;
left: 25%;
right: 2px;
top: 50%;
margin: -1px 0;
position: absolute;
}
.arrow-down-end::before,
.arrow-up-end::before {
bottom: 50%;
margin-bottom: -1px;
}
.arrow-down-start::before,
.arrow-up-start::before {
top: 50%;
margin-top: -1px;
}
.arrow-head {
position: absolute;
left: 0;
top: 50%;
width: 0;
height: 0;
border: 5px solid transparent;
border-right-color: blue;
margin: -5px;
}
.arrow-link {
width: 6px;
height: 6px;
background-color: blue;
display: inline-block;
border-radius: 100px;
z-index: 1;
position: relative;
}
.routine-end > td {
border-bottom: 1px solid black;
}