package com.smallhacker.disbrowser import com.smallhacker.disbrowser.asm.* import com.smallhacker.disbrowser.game.Game class Grid { private val arrowCells = HashMap, HtmlNode?>() private val arrowClasses = HashMap, String>() private var arrowWidth = 0 private val content = HashMap, HtmlNode?>() private val cellClasses = HashMap, String>() private val addresses = HashMap() private val rowClasses = HashMap() private val rowId = HashMap() private var height = 0 private var nextAddress: SnesAddress? = null fun arrow(from: SnesAddress, to: SnesAddress) { 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] = 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: CodeUnit, game: Game, disassembly: Disassembly) { val sortedAddress = ins.sortedAddress val indicativeAddress = ins.indicativeAddress val gameData = game.gameData if (nextAddress != null) { if (sortedAddress != nextAddress) { addDummy() } } nextAddress = ins.nextSortedAddress val y = (height++) addresses[sortedAddress] = y val (address, bytes, label, primaryMnemonic, secondaryMnemonic, suffix, operands, state, comment, labelAddress) = ins.print(gameData) add(y, ins.address, text(address ?: ""), text(bytes), editableField(game, indicativeAddress, "label", label), fragment { if (secondaryMnemonic == null) { text(primaryMnemonic) } else { span { text(primaryMnemonic) }.attr("title", secondaryMnemonic).addClass("opcode-info") } text(suffix ?: "") text(" ") val link = ins.linkedState if (link == null) { if (labelAddress == null) { text(operands ?: "") } else { val currentLabel = gameData[labelAddress]?.label editablePopupField(game, labelAddress, "label", operands, currentLabel) } } else { val local = link.address in disassembly val url = when { local -> "#${link.address.toSimpleString()}" else -> "/${game.id}/${link.address.toSimpleString()}/${link.urlString}" } a { text(operands ?: "") }.attr("href", url) } }, text(state ?: ""), editableField(game, indicativeAddress, "comment", comment) ) if (ins.opcode.continuation == Continuation.NO) { rowClasses[y] = "routine-end" } } private fun editableField(game: Game, address: SnesAddress, type: String, value: String?): HtmlNode { return input(value = value ?: "") .addClass("field-$type") .addClass("field-editable") .attr("data-field", type) .attr("data-game", game.id) .attr("data-address", address.toSimpleString()) } private fun HtmlArea.editablePopupField(game: Game, address: SnesAddress, type: String, displayValue: String?, editValue: String?) { 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) .attr("data-address", address.toSimpleString()) } private fun addDummy() { val y = (height++) add(y, null, null, null, null, text("..."), null, null) } private fun add(y: Int, address: SnesAddress?, cAddress: HtmlNode?, cBytes: HtmlNode?, cLabel: HtmlNode?, cCode: HtmlNode?, cState: HtmlNode?, cComment: 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 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 { 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]) } } } }