mirror of
https://github.com/Smallhacker/disbrowser.git
synced 2025-02-19 08:31:04 +00:00
Editing and persistence now works
This commit is contained in:
parent
188d53a768
commit
32f3cdabff
@ -1,64 +1,39 @@
|
||||
[
|
||||
{
|
||||
"address": 32768,
|
||||
"label": "ResetVector",
|
||||
"comment": null,
|
||||
"flags": []
|
||||
{
|
||||
"008000" : {
|
||||
"label" : "ResetVector"
|
||||
},
|
||||
{
|
||||
"address": 32820,
|
||||
"label": "MainGameLoop",
|
||||
"comment": null,
|
||||
"flags": []
|
||||
"008034" : {
|
||||
"label" : "MainGameLoop"
|
||||
},
|
||||
{
|
||||
"address": 32949,
|
||||
"label": "JumpToGameMode",
|
||||
"comment": null,
|
||||
"flags": []
|
||||
"0080b5" : {
|
||||
"label" : "JumpToGameMode"
|
||||
},
|
||||
{
|
||||
"address": 165840,
|
||||
"label": null,
|
||||
"comment": null,
|
||||
"flags": [
|
||||
{
|
||||
"flagType": "JslTableRoutine",
|
||||
"entries": 4
|
||||
}
|
||||
]
|
||||
"0080c6" : {
|
||||
"flags" : [ {
|
||||
"flagType" : "JmpIndirectLongInterleavedTable",
|
||||
"start" : "008061",
|
||||
"entries" : 28
|
||||
} ]
|
||||
},
|
||||
{
|
||||
"address": 32966,
|
||||
"label": null,
|
||||
"comment": null,
|
||||
"flags": [
|
||||
{
|
||||
"flagType": "JmpIndirectLongInterleavedTable",
|
||||
"start": 32865,
|
||||
"entries": 28
|
||||
}
|
||||
]
|
||||
"00841e" : {
|
||||
"label" : "ClearOam",
|
||||
"comment" : "Test3"
|
||||
},
|
||||
{
|
||||
"address": 835861,
|
||||
"label": null,
|
||||
"comment": null,
|
||||
"flags": [
|
||||
{
|
||||
"flagType": "JslTableRoutine",
|
||||
"entries": 12
|
||||
}
|
||||
]
|
||||
"00879c" : {
|
||||
"flags" : [ {
|
||||
"flagType" : "NonReturningRoutine"
|
||||
} ]
|
||||
},
|
||||
{
|
||||
"address": 34716,
|
||||
"label": null,
|
||||
"comment": null,
|
||||
"flags": [
|
||||
{
|
||||
"flagType": "NonReturningRoutine"
|
||||
}
|
||||
]
|
||||
"0287d0" : {
|
||||
"flags" : [ {
|
||||
"flagType" : "JslTableRoutine",
|
||||
"entries" : 4
|
||||
} ]
|
||||
},
|
||||
"0cc115" : {
|
||||
"flags" : [ {
|
||||
"flagType" : "JslTableRoutine",
|
||||
"entries" : 12
|
||||
} ]
|
||||
}
|
||||
]
|
||||
}
|
@ -78,11 +78,7 @@ class Grid {
|
||||
add(y, ins.address,
|
||||
text(actualAddress?.toFormattedString() ?: ""),
|
||||
text(ins.bytesToString()),
|
||||
input(value = insMetadata?.label ?: "")
|
||||
.addClass("field-label")
|
||||
.addClass("field-editable")
|
||||
.attr("data-field", "label")
|
||||
.attr("data-address", presentedAddress.value.toString()),
|
||||
editableField(presentedAddress, "label", insMetadata?.label),
|
||||
fragment {
|
||||
text(ins.printOpcodeAndSuffix())
|
||||
text(" ")
|
||||
@ -107,11 +103,7 @@ class Grid {
|
||||
}
|
||||
},
|
||||
text(ins.postState?.toString() ?: ""),
|
||||
input(value = insMetadata?.comment ?: "")
|
||||
.addClass("field-comment")
|
||||
.addClass("field-editable")
|
||||
.attr("data-field", "comment")
|
||||
.attr("data-address", presentedAddress.value.toString())
|
||||
editableField(presentedAddress, "comment", insMetadata?.comment)
|
||||
)
|
||||
|
||||
if (ins.opcode.continuation == Continuation.NO) {
|
||||
@ -119,6 +111,14 @@ class Grid {
|
||||
}
|
||||
}
|
||||
|
||||
private fun editableField(address: Address, type: String, value: String?): HtmlNode {
|
||||
return input(value = value ?: "")
|
||||
.addClass("field-$type")
|
||||
.addClass("field-editable")
|
||||
.attr("data-field", type)
|
||||
.attr("data-address", address.toSimpleString())
|
||||
}
|
||||
|
||||
private fun addDummy() {
|
||||
val y = (height++)
|
||||
add(y, null, null, null, null, text("..."), null, null)
|
||||
|
@ -93,6 +93,8 @@ 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 meta(inner: InnerHtml = {}) = leaf("meta")
|
||||
fun HtmlArea.meta(inner: InnerHtml = {}) = com.smallhacker.disbrowser.meta(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)
|
||||
|
@ -13,7 +13,7 @@ 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 metaFile = jsonFile<Metadata>(metaDir.resolve("$romName.json"), true)
|
||||
private val metadata by lazy { metaFile.load() }
|
||||
|
||||
private val romData = lazy {
|
||||
@ -57,6 +57,7 @@ object Service {
|
||||
head {
|
||||
title { text("Disassembly Browser") }
|
||||
link {}.attr("rel", "stylesheet").attr("href", "/resources/style.css")
|
||||
meta {}.attr("charset", "UTF-8")
|
||||
}
|
||||
body {
|
||||
grid.output().appendTo(parent)
|
||||
@ -67,12 +68,23 @@ object Service {
|
||||
|
||||
fun updateMetadata(address: Address, field: KMutableProperty1<MetadataLine, String?>, value: String) {
|
||||
if (value.isEmpty()) {
|
||||
metadata[address]?.run {
|
||||
field.set(this, null)
|
||||
if (address in metadata) {
|
||||
doUpdateMetadata(address, field, null)
|
||||
}
|
||||
} else {
|
||||
field.set(metadata.getOrAdd(address), value)
|
||||
doUpdateMetadata(address, field, value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doUpdateMetadata(address: Address, field: KMutableProperty1<MetadataLine, String?>, value: String?) {
|
||||
val line = metadata.getOrCreate(address)
|
||||
field.set(line, value)
|
||||
|
||||
if (line.isEmpty()) {
|
||||
metadata[address] = null
|
||||
}
|
||||
|
||||
metaFile.save(metadata)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.annotation.JsonValue
|
||||
import com.smallhacker.disbrowser.util.UInt24
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
import com.smallhacker.disbrowser.util.tryParseInt
|
||||
|
||||
data class Address(@JsonValue val value: UInt24): Comparable<Address> {
|
||||
@JsonIgnore
|
||||
data class Address(val value: UInt24) : Comparable<Address> {
|
||||
val rom = (value and 0x8000u).toUInt() == 0u
|
||||
@JsonIgnore
|
||||
val pc = snesToPc(value)
|
||||
|
||||
operator fun plus(offset: Int) = Address(value + offset)
|
||||
@ -18,13 +19,21 @@ data class Address(@JsonValue val value: UInt24): Comparable<Address> {
|
||||
|
||||
override fun toString(): String = toFormattedString()
|
||||
fun toFormattedString(): String = String.format("$%02x:%04x", (value shr 16).toInt(), (value and 0xFFFFu).toInt())
|
||||
@JsonValue
|
||||
fun toSimpleString(): String = String.format("%06x", value.toInt())
|
||||
|
||||
fun withinBank(value: UShort): Address = Address((this.value and 0xFF_0000u) or value.toUInt24())
|
||||
|
||||
override fun compareTo(other: Address) = value.toUInt().compareTo(other.value.toUInt())
|
||||
|
||||
infix fun distanceTo(other: Address)= Math.abs(value.toInt() - other.value.toInt()).toUInt()
|
||||
infix fun distanceTo(other: Address) = Math.abs(value.toInt() - other.value.toInt()).toUInt()
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@JsonCreator
|
||||
fun parse(address: String): Address? = tryParseInt(address, 16)
|
||||
?.let { Address(it.toUInt24()) }
|
||||
}
|
||||
}
|
||||
|
||||
fun address(snesAddress: Int) = Address(snesAddress.toUInt24())
|
||||
|
@ -4,20 +4,29 @@ import com.fasterxml.jackson.annotation.*
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
|
||||
import com.smallhacker.disbrowser.util.joinBytes
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
import java.util.*
|
||||
|
||||
class Metadata() {
|
||||
private val lines = HashMap<Address, MetadataLine>()
|
||||
class Metadata {
|
||||
private val lines: MutableMap<Address, MetadataLine>
|
||||
|
||||
@JsonValue
|
||||
private fun linesAsList() = lines.values.toList()
|
||||
|
||||
@JsonCreator
|
||||
private constructor(@JsonProperty lines: List<MetadataLine>) : this() {
|
||||
lines.forEach { add(it) }
|
||||
constructor() {
|
||||
this.lines = HashMap()
|
||||
}
|
||||
|
||||
fun add(line: MetadataLine): Metadata {
|
||||
lines[line.address] = line
|
||||
@JsonCreator
|
||||
private constructor(@JsonProperty lines: Map<Address, MetadataLine>) {
|
||||
this.lines = HashMap(lines)
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
private fun serialize() = TreeMap(lines)
|
||||
|
||||
operator fun set(address: Address, line: MetadataLine?): Metadata {
|
||||
if (line == null) {
|
||||
lines.remove(address)
|
||||
} else {
|
||||
lines[address] = line
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@ -25,29 +34,19 @@ class Metadata() {
|
||||
return lines[address]
|
||||
}
|
||||
|
||||
fun getOrAdd(address: Address): MetadataLine {
|
||||
operator fun contains(address: Address) = lines[address] != null
|
||||
|
||||
fun getOrCreate(address: Address): MetadataLine {
|
||||
val line = this[address]
|
||||
if (line != null) {
|
||||
return line
|
||||
}
|
||||
val newLine = MetadataLine(address)
|
||||
add(newLine)
|
||||
val newLine = MetadataLine()
|
||||
this[address] = newLine
|
||||
return newLine
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "flagType")
|
||||
@JsonSubTypes(
|
||||
Type(value = NonReturningRoutine::class, name = "NonReturningRoutine"),
|
||||
|
@ -1,9 +1,15 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||
data class MetadataLine(
|
||||
val address: Address,
|
||||
var label: String? = null,
|
||||
var comment: String? = null,
|
||||
var preComment: String? = null,
|
||||
val flags: MutableList<InstructionFlag> = ArrayList()
|
||||
)
|
||||
) {
|
||||
@JsonIgnore
|
||||
fun isEmpty() = (label == null) && (comment == null) && (preComment == null) && (flags.isEmpty())
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ 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 java.nio.charset.StandardCharsets
|
||||
import javax.ws.rs.GET
|
||||
import javax.ws.rs.Path
|
||||
import javax.ws.rs.PathParam
|
||||
@ -31,18 +30,13 @@ class DisassemblyResource {
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt(@PathParam("address") address: String, @PathParam("state") state: String): Response {
|
||||
return handle {
|
||||
parseAddress(address)?.let {
|
||||
Address.parse(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()
|
||||
@ -50,7 +44,9 @@ class DisassemblyResource {
|
||||
return if (disassembly == null)
|
||||
Response.status(404).build()
|
||||
else
|
||||
Response.ok(disassembly.toString(), MediaType.TEXT_HTML).build()
|
||||
Response.ok(disassembly.toString().toByteArray(StandardCharsets.UTF_8))
|
||||
.encoding("UTF-8")
|
||||
.build()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw e
|
||||
|
@ -3,8 +3,6 @@ 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
|
||||
@ -18,7 +16,7 @@ class RestResource {
|
||||
@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 parsedAddress = Address.parse(address) ?: return Response.status(400).build()
|
||||
val field = when (fieldName) {
|
||||
"preComment" -> MetadataLine::preComment
|
||||
"comment" -> MetadataLine::comment
|
||||
@ -30,11 +28,4 @@ class RestResource {
|
||||
|
||||
return Response.noContent().build()
|
||||
}
|
||||
|
||||
|
||||
private fun parseAddress(address: String): Address? {
|
||||
return tryParseInt(address, 16)
|
||||
?.let { Address(it.toUInt24()) }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,9 +14,10 @@ interface JsonFile<T> {
|
||||
fun save(value: T)
|
||||
}
|
||||
|
||||
inline fun <reified T> jsonFile(path: Path): JsonFile<T> {
|
||||
inline fun <reified T> jsonFile(path: Path, prettyPrint: Boolean = false): JsonFile<T> {
|
||||
val writer = if (prettyPrint) jsonMapper.writerWithDefaultPrettyPrinter() else jsonMapper.writer()
|
||||
return object : JsonFile<T> {
|
||||
override fun load() = jsonMapper.readValue<T>(path.toFile())
|
||||
override fun save(value: T) = jsonMapper.writeValue(path.toFile(), value)
|
||||
override fun save(value: T) = writer.writeValue(path.toFile(), value)
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
function center(el: HTMLElement) {
|
||||
function documentOffsetTop (el: HTMLElement) {
|
||||
function documentOffsetTop(el: HTMLElement) {
|
||||
let top = el.offsetTop;
|
||||
let parent = el.offsetParent;
|
||||
if (parent && parent instanceof HTMLElement) {
|
||||
@ -9,7 +9,7 @@ function center(el: HTMLElement) {
|
||||
}
|
||||
|
||||
let top = documentOffsetTop(el) - (window.innerHeight / 2);
|
||||
window.scrollTo( 0, top );
|
||||
window.scrollTo(0, top);
|
||||
}
|
||||
|
||||
function highlight(el: HTMLElement | null) {
|
||||
@ -45,8 +45,27 @@ function fromUrl() {
|
||||
return fromHash() || fromPath();
|
||||
}
|
||||
|
||||
function xhr(url: string, method: string = "GET", body: (string | null) = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onload = () => {
|
||||
if (xhr.status < 400) {
|
||||
resolve(xhr);
|
||||
} else {
|
||||
reject(xhr);
|
||||
}
|
||||
};
|
||||
xhr.onerror = () => reject(xhr);
|
||||
xhr.onabort = () => reject(xhr);
|
||||
xhr.open(method, url);
|
||||
xhr.send(body);
|
||||
});
|
||||
}
|
||||
|
||||
highlight(fromUrl());
|
||||
window.addEventListener("hashchange", function () { highlight(fromHash()) }, false);
|
||||
window.addEventListener("hashchange", function () {
|
||||
highlight(fromHash())
|
||||
}, false);
|
||||
|
||||
let comments = document.getElementsByClassName("field-editable");
|
||||
for (let i = 0; i < comments.length; i++) {
|
||||
@ -54,9 +73,12 @@ for (let i = 0; i < comments.length; i++) {
|
||||
comment.addEventListener("change", e => {
|
||||
let target = <HTMLInputElement>(e.target);
|
||||
let field = target.dataset.field || "";
|
||||
let address = parseInt(target.dataset.address || "-1");
|
||||
let address = target.dataset.address;
|
||||
let value = (target).value;
|
||||
alert(field + "/" + address + "=" + value);
|
||||
|
||||
xhr(`/rest/${address}/${field}`, "POST", value)
|
||||
.catch((xhr: XMLHttpRequest) => alert("Error: HTTP " + xhr.status));
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "amd",
|
||||
"target": "es2016",
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user