disbrowser/src/main/java/com/smallhacker/disbrowser/Grid.kt

230 lines
7.6 KiB
Kotlin
Raw Normal View History

2019-01-07 18:19:37 +00:00
package com.smallhacker.disbrowser
import com.smallhacker.disbrowser.asm.*
import com.smallhacker.disbrowser.game.Game
2019-01-07 18:19:37 +00:00
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>()
2019-01-11 16:35:35 +00:00
private val addresses = HashMap<SnesAddress, Int>()
2019-01-07 18:19:37 +00:00
private val rowClasses = HashMap<Int, String>()
2019-01-15 05:54:54 +00:00
private val rowCertainties = HashMap<Int, String>()
2019-01-07 18:19:37 +00:00
private val rowId = HashMap<Int, String>()
private var height = 0
2019-01-11 16:35:35 +00:00
private var nextAddress: SnesAddress? = null
2019-01-07 18:19:37 +00:00
2019-01-11 16:35:35 +00:00
fun arrow(from: SnesAddress, to: SnesAddress) {
2019-01-07 18:19:37 +00:00
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 yEnd] = htmlFragment {
div.addClass("arrow-head")
}
2019-01-07 18:19:37 +00:00
}
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: CodeUnit, game: Game, disassembly: Disassembly) {
2019-01-14 05:23:19 +00:00
val sortedAddress = ins.sortedAddress
val indicativeAddress = ins.indicativeAddress
val gameData = game.gameData
2019-01-07 18:19:37 +00:00
if (nextAddress != null) {
2019-01-14 05:23:19 +00:00
if (sortedAddress != nextAddress) {
2019-01-07 18:19:37 +00:00
addDummy()
}
}
2019-01-14 05:23:19 +00:00
nextAddress = ins.nextSortedAddress
2019-01-07 18:19:37 +00:00
val y = (height++)
2019-01-14 05:23:19 +00:00
addresses[sortedAddress] = y
2019-01-07 18:19:37 +00:00
2019-01-13 03:38:59 +00:00
val (address, bytes, label, primaryMnemonic, secondaryMnemonic, suffix, operands, state, comment, labelAddress)
= ins.print(gameData)
2019-01-13 03:38:59 +00:00
2019-01-07 18:19:37 +00:00
add(y, ins.address,
htmlFragment {
text(address ?: "")
},
htmlFragment {
text(bytes)
},
2019-01-14 05:23:19 +00:00
editableField(game, indicativeAddress, "label", label),
htmlFragment {
2019-01-13 03:38:59 +00:00
if (secondaryMnemonic == null) {
text(primaryMnemonic)
} else {
span {
text(primaryMnemonic)
}.attr("title", secondaryMnemonic).addClass("opcode-info")
}
text(suffix ?: "")
2019-01-07 18:19:37 +00:00
text(" ")
val link = ins.linkedState
if (link == null) {
2019-01-13 03:38:59 +00:00
if (labelAddress == null) {
text(operands ?: "")
} else {
val currentLabel = gameData[labelAddress]?.label
editablePopupField(game, labelAddress, "label", operands, currentLabel)
2019-01-13 03:38:59 +00:00
}
2019-01-07 18:19:37 +00:00
} else {
val local = link.address in disassembly
val url = when {
local -> "#${link.address.toSimpleString()}"
else -> "/${game.id}/${link.address.toSimpleString()}/${link.urlString}"
2019-01-07 18:19:37 +00:00
}
a {
2019-01-13 03:38:59 +00:00
text(operands ?: "")
2019-01-07 18:19:37 +00:00
}.attr("href", url)
}
},
htmlFragment {
text(state ?: "")
},
2019-01-14 05:23:19 +00:00
editableField(game, indicativeAddress, "comment", comment)
2019-01-07 18:19:37 +00:00
)
2019-01-15 05:54:54 +00:00
if (ins.opcode.continuation.shouldStop) {
2019-01-07 18:19:37 +00:00
rowClasses[y] = "routine-end"
}
2019-01-15 05:54:54 +00:00
rowCertainties[y] = ins.certainty.value.toString()
2019-01-07 18:19:37 +00:00
}
private fun editableField(game: Game, address: SnesAddress, type: String, value: String?): HtmlNode {
return htmlFragment {
input.attr("value", value ?: "")
.attr("type", "text")
.addClass("field-$type")
.addClass("field-editable")
.attr("data-field", type)
.attr("data-game", game.id)
.attr("data-address", address.toSimpleString())
}
2019-01-11 05:09:12 +00:00
}
private fun HtmlArea.editablePopupField(game: Game, address: SnesAddress, type: String, displayValue: String?, editValue: String?) {
2019-01-13 03:38:59 +00:00
span {
text(displayValue ?: "")
span {}.addClass("field-editable-popup-icon")
}
.addClass("field-$type")
.addClass("field-editable-popup")
.attr("data-field", type)
.attr("data-value", editValue ?: "")
.attr("data-game", game.id)
2019-01-13 03:38:59 +00:00
.attr("data-address", address.toSimpleString())
}
2019-01-07 18:19:37 +00:00
private fun addDummy() {
val y = (height++)
add(y,
null,
null,
null,
null,
htmlFragment {
text("...")
},
null,
null
)
2019-01-07 18:19:37 +00:00
}
2019-01-11 16:35:35 +00:00
private fun add(y: Int, address: SnesAddress?,
2019-01-07 18:19:37 +00:00
cAddress: HtmlNode?,
cBytes: HtmlNode?,
cLabel: HtmlNode?,
cCode: HtmlNode?,
2019-01-11 03:19:08 +00:00
cState: HtmlNode?,
cComment: HtmlNode?
2019-01-07 18:19:37 +00:00
) {
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
2019-01-11 03:19:08 +00:00
content[5 to y] = cComment
2019-01-07 18:19:37 +00:00
}
fun output(): HtmlNode {
2019-01-11 03:19:08 +00:00
val contentMaxX = content.keys.asSequence()
.map { it.first }
.max()
?: -1
return htmlFragment {
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..contentMaxX) {
val cssClass = cellClasses[x to y]
td {
content[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
}.addClass(rowClasses[y])
.attr("id", rowId[y])
.attr("row-certainty", rowCertainties[y])
}
2019-01-07 18:19:37 +00:00
}
}
}
}