mirror of
https://github.com/Smallhacker/disbrowser.git
synced 2025-04-09 07:39:20 +00:00
Multi-game support, externalized config files
This commit is contained in:
parent
4c3cfea18a
commit
32b19c2c47
@ -1,196 +0,0 @@
|
||||
{
|
||||
"code" : {
|
||||
"002111" : {
|
||||
"label" : "layer3X"
|
||||
},
|
||||
"002112" : {
|
||||
"label" : "layer3Y"
|
||||
},
|
||||
"002140" : {
|
||||
"label" : "apuIo0"
|
||||
},
|
||||
"002141" : {
|
||||
"label" : "apuIo1"
|
||||
},
|
||||
"002142" : {
|
||||
"label" : "apuIo2"
|
||||
},
|
||||
"002143" : {
|
||||
"label" : "apuIo3"
|
||||
},
|
||||
"00420c" : {
|
||||
"label" : "hdmaEnable"
|
||||
},
|
||||
"008000" : {
|
||||
"label" : "ResetVector"
|
||||
},
|
||||
"00801b" : {
|
||||
"comment" : "\\ Turn off emulation mode"
|
||||
},
|
||||
"00801c" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"00801f" : {
|
||||
"comment" : "\\ Set direct page"
|
||||
},
|
||||
"008022" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"008023" : {
|
||||
"comment" : "\\ Set stack position"
|
||||
},
|
||||
"008026" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"008034" : {
|
||||
"label" : "MainGameLoop",
|
||||
"comment" : "Wait for NMI"
|
||||
},
|
||||
"00805d" : {
|
||||
"comment" : "Clear NMI flag"
|
||||
},
|
||||
"0080b5" : {
|
||||
"label" : "JumpToGameMode",
|
||||
"comment" : "Y = Current game mode"
|
||||
},
|
||||
"0080b7" : {
|
||||
"comment" : "\\ Load routine low byte"
|
||||
},
|
||||
"0080ba" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"0080bc" : {
|
||||
"comment" : "\\ Load routine mid byte"
|
||||
},
|
||||
"0080bf" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"0080c1" : {
|
||||
"comment" : "\\ Load routine high byte"
|
||||
},
|
||||
"0080c4" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"0080c6" : {
|
||||
"flags" : [ {
|
||||
"flagType" : "JmpIndirectLongInterleavedTable",
|
||||
"start" : "008061",
|
||||
"entries" : 28
|
||||
} ]
|
||||
},
|
||||
"0080c9" : {
|
||||
"label" : "NmiVector"
|
||||
},
|
||||
"00822c" : {
|
||||
"label" : "UnusedVector"
|
||||
},
|
||||
"0082d8" : {
|
||||
"label" : "IrqVector"
|
||||
},
|
||||
"00841e" : {
|
||||
"label" : "ClearOam"
|
||||
},
|
||||
"00879c" : {
|
||||
"comment" : "Preserve Y value for later",
|
||||
"flags" : [ {
|
||||
"flagType" : "NonReturningRoutine"
|
||||
} ]
|
||||
},
|
||||
"00879e" : {
|
||||
"comment" : "Y = Ret.Bank"
|
||||
},
|
||||
"00879f" : {
|
||||
"comment" : "$02 = Ret.Bank"
|
||||
},
|
||||
"0087a3" : {
|
||||
"comment" : "\\"
|
||||
},
|
||||
"0087a6" : {
|
||||
"comment" : "|"
|
||||
},
|
||||
"0087a8" : {
|
||||
"comment" : "| Y = In.A * 3"
|
||||
},
|
||||
"0087a9" : {
|
||||
"comment" : "|"
|
||||
},
|
||||
"0087ab" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"0087ac" : {
|
||||
"comment" : "\\ $03-$04 = Ret.Offset"
|
||||
},
|
||||
"0087ad" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"0087af" : {
|
||||
"comment" : "Increase Y to compensate for Ret being off by one"
|
||||
},
|
||||
"0087b0" : {
|
||||
"comment" : "\\"
|
||||
},
|
||||
"0087b2" : {
|
||||
"comment" : "|"
|
||||
},
|
||||
"0087b4" : {
|
||||
"comment" : "| Load target pointer into $00-03 (last byte unused)"
|
||||
},
|
||||
"0087b5" : {
|
||||
"comment" : "|"
|
||||
},
|
||||
"0087b7" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"0087bb" : {
|
||||
"comment" : "Restore initial Y value"
|
||||
},
|
||||
"0087bd" : {
|
||||
"comment" : "Jump to pointer"
|
||||
},
|
||||
"008901" : {
|
||||
"comment" : "\\"
|
||||
},
|
||||
"008903" : {
|
||||
"comment" : "|"
|
||||
},
|
||||
"008905" : {
|
||||
"comment" : "| Write #$19:8000 to $00"
|
||||
},
|
||||
"008907" : {
|
||||
"comment" : "|"
|
||||
},
|
||||
"008909" : {
|
||||
"comment" : "|"
|
||||
},
|
||||
"00890b" : {
|
||||
"comment" : "/"
|
||||
},
|
||||
"00ffff" : {
|
||||
"label" : "CrashVector"
|
||||
},
|
||||
"0287d0" : {
|
||||
"flags" : [ {
|
||||
"flagType" : "JslTableRoutine",
|
||||
"entries" : 4
|
||||
} ]
|
||||
},
|
||||
"029ee3" : {
|
||||
"label" : "GM_TriforceRoom"
|
||||
},
|
||||
"0cc115" : {
|
||||
"flags" : [ {
|
||||
"flagType" : "JslTableRoutine",
|
||||
"entries" : 12
|
||||
} ]
|
||||
},
|
||||
"7e0010" : {
|
||||
"label" : "gameMode"
|
||||
},
|
||||
"7e0011" : {
|
||||
"label" : "subGameMode"
|
||||
},
|
||||
"7e0012" : {
|
||||
"label" : "nmiExecuted"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package com.smallhacker.disbrowser
|
||||
|
||||
import com.smallhacker.disbrowser.asm.*
|
||||
import com.smallhacker.disbrowser.game.Game
|
||||
import com.smallhacker.disbrowser.game.GameData
|
||||
|
||||
class Grid {
|
||||
private val arrowCells = HashMap<Pair<Int, Int>, HtmlNode?>()
|
||||
@ -45,7 +47,6 @@ class Grid {
|
||||
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")
|
||||
}
|
||||
|
||||
@ -59,8 +60,9 @@ class Grid {
|
||||
.first()
|
||||
}
|
||||
|
||||
fun add(ins: CodeUnit, metadata: Metadata, disassembly: Disassembly) {
|
||||
fun add(ins: CodeUnit, game: Game, disassembly: Disassembly) {
|
||||
val presentedAddress = ins.presentedAddress
|
||||
val gameData = game.gameData
|
||||
|
||||
if (nextAddress != null) {
|
||||
if (presentedAddress != nextAddress) {
|
||||
@ -73,12 +75,12 @@ class Grid {
|
||||
addresses[presentedAddress] = y
|
||||
|
||||
val (address, bytes, label, primaryMnemonic, secondaryMnemonic, suffix, operands, state, comment, labelAddress)
|
||||
= ins.print(metadata)
|
||||
= ins.print(gameData)
|
||||
|
||||
add(y, ins.address,
|
||||
text(address ?: ""),
|
||||
text(bytes),
|
||||
editableField(presentedAddress, "label", label),
|
||||
editableField(game, presentedAddress, "label", label),
|
||||
fragment {
|
||||
if (secondaryMnemonic == null) {
|
||||
text(primaryMnemonic)
|
||||
@ -94,26 +96,24 @@ class Grid {
|
||||
if (labelAddress == null) {
|
||||
text(operands ?: "")
|
||||
} else {
|
||||
val currentLabel = metadata[labelAddress]?.label
|
||||
editablePopupField(labelAddress, "label", operands, currentLabel)
|
||||
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 -> "/${link.address.toSimpleString()}/${link.urlString}"
|
||||
else -> "/${game.id}/${link.address.toSimpleString()}/${link.urlString}"
|
||||
}
|
||||
|
||||
//operands = metadata[link.address]?.label ?: operands
|
||||
|
||||
a {
|
||||
text(operands ?: "")
|
||||
}.attr("href", url)
|
||||
}
|
||||
},
|
||||
text(state ?: ""),
|
||||
editableField(presentedAddress, "comment", comment)
|
||||
editableField(game, presentedAddress, "comment", comment)
|
||||
)
|
||||
|
||||
if (ins.opcode.continuation == Continuation.NO) {
|
||||
@ -121,15 +121,16 @@ class Grid {
|
||||
}
|
||||
}
|
||||
|
||||
private fun editableField(address: SnesAddress, type: String, value: String?): HtmlNode {
|
||||
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(address: SnesAddress, type: String, displayValue: String?, editValue: String?) {
|
||||
private fun HtmlArea.editablePopupField(game: Game, address: SnesAddress, type: String, displayValue: String?, editValue: String?) {
|
||||
span {
|
||||
text(displayValue ?: "")
|
||||
span {}.addClass("field-editable-popup-icon")
|
||||
@ -138,6 +139,7 @@ class Grid {
|
||||
.addClass("field-editable-popup")
|
||||
.attr("data-field", type)
|
||||
.attr("data-value", editValue ?: "")
|
||||
.attr("data-game", game.id)
|
||||
.attr("data-address", address.toSimpleString())
|
||||
}
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
//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\"")
|
@ -1,45 +0,0 @@
|
||||
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.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
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
36
src/main/java/com/smallhacker/disbrowser/Main.kt
Normal file
36
src/main/java/com/smallhacker/disbrowser/Main.kt
Normal file
@ -0,0 +1,36 @@
|
||||
package com.smallhacker.disbrowser
|
||||
|
||||
import com.smallhacker.disbrowser.game.addGameSource
|
||||
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.lang.IllegalArgumentException
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
object Main {
|
||||
private const val BASE_URI = "http://localhost:8080/"
|
||||
|
||||
private fun startServer(path: Path): HttpServer {
|
||||
val rc = ResourceConfig()
|
||||
.packages("com.smallhacker.disbrowser.resource")
|
||||
.addGameSource(path)
|
||||
return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
if (args.size != 1) {
|
||||
throw IllegalArgumentException("Game data directory needed")
|
||||
}
|
||||
val server = startServer(Paths.get(args[0]))
|
||||
println("Server started. Press any key to stop.")
|
||||
System.`in`.read()
|
||||
server.stop()
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package com.smallhacker.disbrowser
|
||||
|
||||
import com.smallhacker.disbrowser.asm.*
|
||||
import com.smallhacker.disbrowser.game.Game
|
||||
import com.smallhacker.disbrowser.disassembler.Disassembler
|
||||
import com.smallhacker.disbrowser.util.jsonFile
|
||||
import com.smallhacker.disbrowser.game.GameData
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
import java.nio.file.Paths
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
|
||||
private val RESET_VECTOR_LOCATION = address(0x00_FFFC)
|
||||
@ -25,37 +25,26 @@ private val VECTORS = listOf(
|
||||
)
|
||||
|
||||
object Service {
|
||||
private const 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\disbrowser""")
|
||||
private val metaFile = jsonFile<Metadata>(metaDir.resolve("$romName.json"), true)
|
||||
private val metadata by lazy { metaFile.load() }
|
||||
|
||||
private val snesMemory by lazy {
|
||||
val path = romDir.resolve("$romName.sfc")
|
||||
SnesLoRom(loadRomData(path))
|
||||
}
|
||||
|
||||
fun showDisassemblyFromReset(): HtmlNode? {
|
||||
val resetVector = snesMemory.getWord(RESET_VECTOR_LOCATION)
|
||||
fun showDisassemblyFromReset(game: Game): HtmlNode? {
|
||||
val resetVector = game.memory.getWord(RESET_VECTOR_LOCATION)
|
||||
val fullResetVector = resetVector!!.toUInt24()
|
||||
val initialAddress = SnesAddress(fullResetVector)
|
||||
val flags = VagueNumber(0x30u)
|
||||
return showDisassembly(initialAddress, flags)
|
||||
return showDisassembly(game, initialAddress, flags)
|
||||
}
|
||||
|
||||
fun showDisassembly(initialAddress: SnesAddress, flags: VagueNumber): HtmlNode? {
|
||||
val initialState = State(memory = snesMemory, address = initialAddress, flags = flags, metadata = metadata)
|
||||
val disassembly = Disassembler.disassemble(initialState, metadata, false)
|
||||
fun showDisassembly(game: Game, initialAddress: SnesAddress, flags: VagueNumber): HtmlNode? {
|
||||
val initialState = State(memory = game.memory, address = initialAddress, flags = flags, gameData = game.gameData)
|
||||
val disassembly = Disassembler.disassemble(initialState, game.gameData, false)
|
||||
|
||||
return print(disassembly, metadata)
|
||||
return print(disassembly, game)
|
||||
}
|
||||
|
||||
|
||||
private fun print(disassembly: Disassembly, metadata: Metadata): HtmlNode {
|
||||
private fun print(disassembly: Disassembly, game: Game): HtmlNode {
|
||||
val grid = Grid()
|
||||
disassembly.forEach {
|
||||
grid.add(it, metadata, disassembly)
|
||||
grid.add(it, game, disassembly)
|
||||
}
|
||||
disassembly.asSequence()
|
||||
.mapNotNull {
|
||||
@ -70,28 +59,27 @@ object Service {
|
||||
return grid.output()
|
||||
}
|
||||
|
||||
fun updateMetadata(address: SnesAddress, field: KMutableProperty1<MetadataLine, String?>, value: String) {
|
||||
fun updateMetadata(game: Game, address: SnesAddress, field: KMutableProperty1<MetadataLine, String?>, value: String) {
|
||||
if (value.isEmpty()) {
|
||||
if (address in metadata) {
|
||||
doUpdateMetadata(address, field, null)
|
||||
if (address in game.gameData) {
|
||||
doUpdateMetadata(game, address, field, null)
|
||||
}
|
||||
} else {
|
||||
doUpdateMetadata(address, field, value)
|
||||
doUpdateMetadata(game, address, field, value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doUpdateMetadata(address: SnesAddress, field: KMutableProperty1<MetadataLine, String?>, value: String?) {
|
||||
val line = metadata.getOrCreate(address)
|
||||
private fun doUpdateMetadata(game: Game, address: SnesAddress, field: KMutableProperty1<MetadataLine, String?>, value: String?) {
|
||||
val line = game.gameData.getOrCreate(address)
|
||||
field.set(line, value)
|
||||
|
||||
metadata.cleanUp()
|
||||
metaFile.save(metadata)
|
||||
game.saveGameData()
|
||||
}
|
||||
|
||||
fun getVectors() = VECTORS.asSequence()
|
||||
fun getVectors(game: Game) = VECTORS.asSequence()
|
||||
.map { (vectorLocation: SnesAddress, name: String ) ->
|
||||
val codeLocation = SnesAddress(snesMemory.getWord(vectorLocation)!!.toUInt24())
|
||||
val label = metadata[codeLocation]?.label
|
||||
val codeLocation = SnesAddress(game.memory.getWord(vectorLocation)!!.toUInt24())
|
||||
val label = game.gameData[codeLocation]?.label
|
||||
?: codeLocation.toFormattedString()
|
||||
Vector(vectorLocation, codeLocation, name, label)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.smallhacker.disbrowser.game.GameData
|
||||
import com.smallhacker.disbrowser.util.*
|
||||
|
||||
interface CodeUnit {
|
||||
@ -16,7 +17,7 @@ interface CodeUnit {
|
||||
val opcode: Opcode
|
||||
val lengthSuffix: String?
|
||||
|
||||
val memory: SnesMapper
|
||||
val memory: SnesMemory
|
||||
|
||||
fun operandByte(index: UInt): UByte = bytes[opcode.operandIndex + index]
|
||||
|
||||
@ -57,7 +58,7 @@ class DataBlock(
|
||||
override val presentedAddress: SnesAddress,
|
||||
override val relativeAddress: SnesAddress,
|
||||
override val linkedState: State?,
|
||||
override val memory: SnesMapper
|
||||
override val memory: SnesMemory
|
||||
) : CodeUnit {
|
||||
override val nextPresentedAddress: SnesAddress
|
||||
get() = presentedAddress + operandLength.toInt()
|
||||
@ -122,21 +123,21 @@ class Instruction(override val bytes: ValidMemorySpace, override val opcode: Opc
|
||||
}
|
||||
}
|
||||
|
||||
fun CodeUnit.print(metadata: Metadata? = null): PrintedCodeUnit {
|
||||
fun CodeUnit.print(gameData: GameData? = null): PrintedCodeUnit {
|
||||
val mnemonic = opcode.mnemonic
|
||||
val primaryMnemonic = mnemonic.displayName
|
||||
val secondaryMnemonic = mnemonic.alternativeName
|
||||
|
||||
var suffix = lengthSuffix
|
||||
var operands = metadata?.let { opcode.mode.printWithLabel(this, it) }
|
||||
var operands = gameData?.let { opcode.mode.printWithLabel(this, it) }
|
||||
if (operands == null) {
|
||||
operands = opcode.mode.printRaw(this)
|
||||
suffix = null
|
||||
}
|
||||
|
||||
val state = postState?.toString()
|
||||
val label = address?.let { metadata?.get(it)?.label }
|
||||
val comment = address?.let { metadata?.get(it)?.comment }
|
||||
val label = address?.let { gameData?.get(it)?.label }
|
||||
val comment = address?.let { gameData?.get(it)?.comment }
|
||||
val formattedAddress = address?.toFormattedString()
|
||||
val bytes = bytesToString()
|
||||
|
||||
|
@ -39,11 +39,6 @@ fun ValidMemorySpace.getLong(address: UInt): UInt24 = joinBytes(this[address], t
|
||||
fun ValidMemorySpace.range(start: UInt, length: UInt): ValidMemorySpace = MemoryRange(this, start, length).validate()!!
|
||||
fun ValidMemorySpace.range(start: SnesAddress, length: UInt): ValidMemorySpace = range(start.value.toUInt(), length).validate()!!
|
||||
|
||||
fun loadRomData(path: Path): MemorySpace {
|
||||
val bytes = Files.readAllBytes(path).toUByteArray()
|
||||
return ArrayMemorySpace(bytes)
|
||||
}
|
||||
|
||||
class ArrayMemorySpace(private val bytes: UByteArray) : MemorySpace {
|
||||
override val size = bytes.size.toUInt()
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.smallhacker.disbrowser.game.InstructionFlag
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||
data class MetadataLine(
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.smallhacker.disbrowser.game.GameData
|
||||
import com.smallhacker.disbrowser.util.*
|
||||
|
||||
interface Mode {
|
||||
@ -7,7 +8,7 @@ interface Mode {
|
||||
val showLengthSuffix get() = true
|
||||
val canHaveLabel: Boolean
|
||||
fun operandLength(state: State): UInt?
|
||||
fun printWithLabel(ins: CodeUnit, metadata: Metadata): String? = referencedAddress(ins)?.let { metadata[it]?.label }
|
||||
fun printWithLabel(ins: CodeUnit, gameData: GameData): String? = referencedAddress(ins)?.let { gameData[it]?.label }
|
||||
fun printRaw(ins: CodeUnit): String
|
||||
fun referencedAddress(ins: CodeUnit): SnesAddress?
|
||||
}
|
||||
@ -30,7 +31,7 @@ private abstract class RawWrappedMode(
|
||||
private val suffix: String = ""
|
||||
) : Mode by parent {
|
||||
override val canHaveLabel = false
|
||||
override fun printWithLabel(ins: CodeUnit, metadata: Metadata): String? = printRaw(ins)
|
||||
override fun printWithLabel(ins: CodeUnit, gameData: GameData): String? = printRaw(ins)
|
||||
override fun printRaw(ins: CodeUnit) = prefix + parent.printRaw(ins) + suffix
|
||||
}
|
||||
|
||||
@ -39,7 +40,7 @@ private abstract class WrappedMode(
|
||||
private val prefix: String = "",
|
||||
private val suffix: String = ""
|
||||
) : Mode by parent {
|
||||
override fun printWithLabel(ins: CodeUnit, metadata: Metadata): String? = parent.printWithLabel(ins, metadata)?.let { prefix + it + suffix }
|
||||
override fun printWithLabel(ins: CodeUnit, gameData: GameData): String? = parent.printWithLabel(ins, gameData)?.let { prefix + it + suffix }
|
||||
override fun printRaw(ins: CodeUnit) = prefix + parent.printRaw(ins) + suffix
|
||||
}
|
||||
|
||||
@ -50,7 +51,7 @@ abstract class MultiMode(private val fallback: Mode, private vararg val options:
|
||||
|
||||
override fun operandLength(state: State) = get(state) { operandLength(state) }
|
||||
|
||||
override fun printWithLabel(ins: CodeUnit, metadata: Metadata): String? = get(ins.preState) { printWithLabel(ins, metadata) }
|
||||
override fun printWithLabel(ins: CodeUnit, gameData: GameData): String? = get(ins.preState) { printWithLabel(ins, gameData) }
|
||||
|
||||
override fun printRaw(ins: CodeUnit) = get(ins.preState) { printRaw(ins) }
|
||||
|
||||
@ -73,9 +74,9 @@ abstract class MultiMode(private val fallback: Mode, private vararg val options:
|
||||
}
|
||||
|
||||
private interface DataValueType {
|
||||
fun resolve(byte: UByte, state: State?, memory: SnesMapper): SnesAddress?
|
||||
fun resolve(word: UShort, state: State?, memory: SnesMapper): SnesAddress?
|
||||
fun resolve(long: UInt24, state: State?, memory: SnesMapper): SnesAddress?
|
||||
fun resolve(byte: UByte, state: State?, memory: SnesMemory): SnesAddress?
|
||||
fun resolve(word: UShort, state: State?, memory: SnesMemory): SnesAddress?
|
||||
fun resolve(long: UInt24, state: State?, memory: SnesMemory): SnesAddress?
|
||||
val canHaveLabel: Boolean
|
||||
|
||||
val byte: Mode get() = DataByteMode(this)
|
||||
@ -85,23 +86,23 @@ private interface DataValueType {
|
||||
|
||||
object DirectData : DataValueType {
|
||||
override val canHaveLabel = false
|
||||
override fun resolve(byte: UByte, state: State?, memory: SnesMapper): SnesAddress? = null
|
||||
override fun resolve(word: UShort, state: State?, memory: SnesMapper): SnesAddress? = null
|
||||
override fun resolve(long: UInt24, state: State?, memory: SnesMapper): SnesAddress? = null
|
||||
override fun resolve(byte: UByte, state: State?, memory: SnesMemory): SnesAddress? = null
|
||||
override fun resolve(word: UShort, state: State?, memory: SnesMemory): SnesAddress? = null
|
||||
override fun resolve(long: UInt24, state: State?, memory: SnesMemory): SnesAddress? = null
|
||||
}
|
||||
|
||||
object DataPointer : DataValueType {
|
||||
override val canHaveLabel = true
|
||||
override fun resolve(byte: UByte, state: State?, memory: SnesMapper) = state?.resolveDirectPage(byte)
|
||||
override fun resolve(word: UShort, state: State?, memory: SnesMapper) = state?.resolveAbsoluteData(word)
|
||||
override fun resolve(long: UInt24, state: State?, memory: SnesMapper) = memory.toCanonical(SnesAddress(long))
|
||||
override fun resolve(byte: UByte, state: State?, memory: SnesMemory) = state?.resolveDirectPage(byte)
|
||||
override fun resolve(word: UShort, state: State?, memory: SnesMemory) = state?.resolveAbsoluteData(word)
|
||||
override fun resolve(long: UInt24, state: State?, memory: SnesMemory) = memory.toCanonical(SnesAddress(long))
|
||||
}
|
||||
|
||||
object CodePointer : DataValueType {
|
||||
override val canHaveLabel = true
|
||||
override fun resolve(byte: UByte, state: State?, memory: SnesMapper) = state?.resolveDirectPage(byte)
|
||||
override fun resolve(word: UShort, state: State?, memory: SnesMapper) = state?.resolveAbsoluteCode(word)
|
||||
override fun resolve(long: UInt24, state: State?, memory: SnesMapper) = memory.toCanonical(SnesAddress(long))
|
||||
override fun resolve(byte: UByte, state: State?, memory: SnesMemory) = state?.resolveDirectPage(byte)
|
||||
override fun resolve(word: UShort, state: State?, memory: SnesMemory) = state?.resolveAbsoluteCode(word)
|
||||
override fun resolve(long: UInt24, state: State?, memory: SnesMemory) = memory.toCanonical(SnesAddress(long))
|
||||
}
|
||||
|
||||
private abstract class DataValueMode(private val length: UInt, protected val type: DataValueType) : Mode {
|
||||
|
@ -3,8 +3,10 @@ package com.smallhacker.disbrowser.asm
|
||||
import com.smallhacker.disbrowser.datatype.MutableRangeMap
|
||||
import com.smallhacker.disbrowser.datatype.NaiveRangeMap
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
abstract class SnesMapper: MemorySpace {
|
||||
abstract class SnesMemory: MemorySpace {
|
||||
override val size = 0x100_0000u
|
||||
private val areas: MutableRangeMap<UInt, UIntRange, MapperEntry> = NaiveRangeMap()
|
||||
|
||||
@ -24,6 +26,15 @@ abstract class SnesMapper: MemorySpace {
|
||||
val offset = address.value - entry.start
|
||||
return entry.canonicalStart + offset.toInt()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun loadRom(path: Path): SnesMemory {
|
||||
val bytes = Files.readAllBytes(path).toUByteArray()
|
||||
val romSpace = ArrayMemorySpace(bytes)
|
||||
// TODO: Auto-detect ROM type
|
||||
return SnesLoRom(romSpace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun MemorySpace.get(address: SnesAddress) = get(address.value.toUInt())
|
||||
@ -32,7 +43,7 @@ fun MemorySpace.getLong(address: SnesAddress) = getLong(address.value.toUInt())
|
||||
|
||||
data class MapperEntry(val start: UInt, val canonicalStart: SnesAddress, val space: MemorySpace)
|
||||
|
||||
class SnesLoRom(romData: MemorySpace): SnesMapper() {
|
||||
class SnesLoRom(romData: MemorySpace): SnesMemory() {
|
||||
init {
|
||||
val ram = UnreadableMemory(0x2_0000u)
|
||||
val ramMirror = ram.range(0x00_0000u, 0x00_2000u)
|
||||
@ -58,10 +69,18 @@ class SnesLoRom(romData: MemorySpace): SnesMapper() {
|
||||
}
|
||||
|
||||
for (bank in 0x40u..0x6Fu) {
|
||||
val romArea = (bank shl 16) or 0x00_8000u
|
||||
// Lower half unmapped
|
||||
add(romArea, romArea, romData.range(pc, 0x00_8000u))
|
||||
add(romArea + high, romArea, romData.range(pc, 0x00_8000u))
|
||||
val lowerRomArea = (bank shl 16)
|
||||
val upperRomArea = (bank shl 16) or 0x00_8000u
|
||||
// Mirror upper and lower banks to the same ROM space.
|
||||
// Some games map it this way, some leave the lower half completely unmapped
|
||||
// While not 100% correct, we do the former to support as many games as possible
|
||||
|
||||
// Of note, we choose to explicitly define the upper half to be the canonical half.
|
||||
|
||||
add(lowerRomArea, upperRomArea, romData.range(pc, 0x00_8000u))
|
||||
add(lowerRomArea + high, upperRomArea, romData.range(pc, 0x00_8000u))
|
||||
add(upperRomArea, upperRomArea, romData.range(pc, 0x00_8000u))
|
||||
add(upperRomArea + high, upperRomArea, romData.range(pc, 0x00_8000u))
|
||||
pc += 0x00_8000u
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
import com.smallhacker.disbrowser.ImmStack
|
||||
import com.smallhacker.disbrowser.game.GameData
|
||||
import com.smallhacker.disbrowser.immStack
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
|
||||
data class State(val origin: Instruction? = null, val memory: SnesMapper, val address: SnesAddress, val flags: VagueNumber = VagueNumber(), val stack: ImmStack<VagueNumber> = immStack(), val metadata: Metadata) {
|
||||
data class State(val origin: Instruction? = null, val memory: SnesMemory, val address: SnesAddress, val flags: VagueNumber = VagueNumber(), val stack: ImmStack<VagueNumber> = immStack(), val gameData: GameData) {
|
||||
val m: Boolean? get() = flags.getBoolean(0x20u)
|
||||
val x: Boolean? get() = flags.getBoolean(0x10u)
|
||||
val db: UByte? get() = pb // TODO
|
||||
|
@ -1,11 +1,15 @@
|
||||
package com.smallhacker.disbrowser.disassembler
|
||||
|
||||
import com.smallhacker.disbrowser.asm.*
|
||||
import com.smallhacker.disbrowser.game.GameData
|
||||
import com.smallhacker.disbrowser.game.JmpIndirectLongInterleavedTable
|
||||
import com.smallhacker.disbrowser.game.JslTableRoutine
|
||||
import com.smallhacker.disbrowser.game.NonReturningRoutine
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
object Disassembler {
|
||||
fun disassemble(initialState: State, metadata: Metadata, global: Boolean): Disassembly {
|
||||
fun disassemble(initialState: State, gameData: GameData, global: Boolean): Disassembly {
|
||||
val seen = HashSet<SnesAddress>()
|
||||
val queue = ArrayDeque<State>()
|
||||
|
||||
@ -26,7 +30,7 @@ object Disassembler {
|
||||
var stop = (ins.opcode.continuation == Continuation.NO) or
|
||||
(ins.opcode.mode.instructionLength(state) == null)
|
||||
|
||||
metadata[ins.address]?.flags?.forEach { flag ->
|
||||
gameData[ins.address]?.flags?.forEach { flag ->
|
||||
if (flag is JmpIndirectLongInterleavedTable) {
|
||||
if (global) {
|
||||
flag.readTable(state.memory)
|
||||
@ -53,7 +57,7 @@ object Disassembler {
|
||||
val linkedState = ins.linkedState
|
||||
|
||||
if (linkedState != null) {
|
||||
metadata[linkedState.address]?.flags?.forEach {
|
||||
gameData[linkedState.address]?.flags?.forEach {
|
||||
if (it === NonReturningRoutine) {
|
||||
stop = true
|
||||
println(ins.address.toFormattedString())
|
||||
|
16
src/main/java/com/smallhacker/disbrowser/game/Game.kt
Normal file
16
src/main/java/com/smallhacker/disbrowser/game/Game.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package com.smallhacker.disbrowser.game
|
||||
|
||||
import com.smallhacker.disbrowser.asm.SnesMemory
|
||||
import com.smallhacker.disbrowser.util.JsonFile
|
||||
|
||||
class Game(
|
||||
val id: String,
|
||||
val memory: SnesMemory,
|
||||
val gameData: GameData,
|
||||
private val gameDataFile: JsonFile<GameData>
|
||||
) {
|
||||
fun saveGameData() {
|
||||
gameData.cleanUp()
|
||||
gameDataFile.save(gameData)
|
||||
}
|
||||
}
|
@ -1,42 +1,51 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
package com.smallhacker.disbrowser.game
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo
|
||||
import com.smallhacker.disbrowser.asm.*
|
||||
import com.smallhacker.disbrowser.util.joinNullableBytes
|
||||
import com.smallhacker.disbrowser.util.removeIf
|
||||
import com.smallhacker.disbrowser.util.toUInt24
|
||||
import java.util.*
|
||||
|
||||
class Metadata {
|
||||
class GameData {
|
||||
@JsonProperty
|
||||
private val code: MutableMap<SnesAddress, MetadataLine>
|
||||
val name: String
|
||||
@JsonProperty
|
||||
val path: String
|
||||
@JsonProperty
|
||||
private val metadata: MutableMap<SnesAddress, MetadataLine>
|
||||
|
||||
constructor() {
|
||||
this.code = TreeMap()
|
||||
constructor(name: String, path: String) {
|
||||
this.name = name
|
||||
this.path = path
|
||||
this.metadata = TreeMap()
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
private constructor(@JsonProperty code: Map<SnesAddress, MetadataLine>) {
|
||||
this.code = TreeMap(code)
|
||||
private constructor(@JsonProperty name: String, @JsonProperty path: String, @JsonProperty metadata: Map<SnesAddress, MetadataLine>) {
|
||||
this.name = name
|
||||
this.path = path
|
||||
this.metadata = TreeMap(metadata)
|
||||
}
|
||||
|
||||
operator fun set(address: SnesAddress, line: MetadataLine?): Metadata {
|
||||
operator fun set(address: SnesAddress, line: MetadataLine?): GameData {
|
||||
if (line == null) {
|
||||
code.remove(address)
|
||||
metadata.remove(address)
|
||||
} else {
|
||||
code[address] = line
|
||||
metadata[address] = line
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun get(address: SnesAddress): MetadataLine? {
|
||||
return code[address]
|
||||
return metadata[address]
|
||||
}
|
||||
|
||||
operator fun contains(address: SnesAddress) = code[address] != null
|
||||
operator fun contains(address: SnesAddress) = metadata[address] != null
|
||||
|
||||
fun getOrCreate(address: SnesAddress): MetadataLine {
|
||||
val line = this[address]
|
||||
@ -49,7 +58,7 @@ class Metadata {
|
||||
}
|
||||
|
||||
fun cleanUp() {
|
||||
code.removeIf { _, v -> v.isEmpty() }
|
||||
metadata.removeIf { _, v -> v.isEmpty() }
|
||||
}
|
||||
}
|
||||
|
51
src/main/java/com/smallhacker/disbrowser/game/GameSource.kt
Normal file
51
src/main/java/com/smallhacker/disbrowser/game/GameSource.kt
Normal file
@ -0,0 +1,51 @@
|
||||
package com.smallhacker.disbrowser.game
|
||||
|
||||
import com.smallhacker.disbrowser.asm.SnesMemory
|
||||
import com.smallhacker.disbrowser.util.jsonFile
|
||||
import org.glassfish.jersey.server.ResourceConfig
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.ws.rs.core.Configuration
|
||||
|
||||
private val VALID_FILE_NAME = Regex("""^[a-zA-Z0-9_ ]*$""")
|
||||
|
||||
class GameSource(private val gameDataDir: Path) {
|
||||
private val gameCache = ConcurrentHashMap<String, Game?>()
|
||||
|
||||
fun getGame(name: String): Game? {
|
||||
return gameCache.computeIfAbsent(name) {
|
||||
loadGame(name, it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadGame(name: String, it: String): Game? {
|
||||
if (!validFileName(name)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val gameDataFile = gameDataDir.resolve("$it.json").toFile()
|
||||
if (!gameDataFile.exists()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
val gameDataJsonFile = jsonFile<GameData>(gameDataFile.toPath(), true)
|
||||
|
||||
val gameData = gameDataJsonFile.load()
|
||||
val gamePath = Paths.get(gameData.path)
|
||||
val rom = SnesMemory.loadRom(gamePath)
|
||||
Game(name, rom, gameData, gameDataJsonFile)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun validFileName(name: String) = VALID_FILE_NAME.matches(name)
|
||||
}
|
||||
|
||||
private const val GAME_SOURCE_PROPERTY = "gameSource"
|
||||
|
||||
fun ResourceConfig.addGameSource(path: Path) = this.property(GAME_SOURCE_PROPERTY, GameSource(path))!!
|
||||
fun Configuration.getGameSource() = getProperty(GAME_SOURCE_PROPERTY)!! as GameSource
|
@ -3,49 +3,67 @@ package com.smallhacker.disbrowser.resource
|
||||
import com.smallhacker.disbrowser.*
|
||||
import com.smallhacker.disbrowser.asm.SnesAddress
|
||||
import com.smallhacker.disbrowser.asm.VagueNumber
|
||||
import com.smallhacker.disbrowser.game.GameSource
|
||||
import com.smallhacker.disbrowser.game.getGameSource
|
||||
import java.nio.charset.StandardCharsets
|
||||
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.Configuration
|
||||
import javax.ws.rs.core.Context
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
@Path("/")
|
||||
@Path("/{game}")
|
||||
class DisassemblyResource {
|
||||
@Context
|
||||
private lateinit var config: Configuration
|
||||
private val games by lazy { config.getGameSource() }
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt() = handle {
|
||||
//Service.showDisassemblyFromReset()
|
||||
table {
|
||||
Service.getVectors().forEach {
|
||||
tr {
|
||||
td { text(it.name) }
|
||||
td { text("(" + it.vectorLocation.toFormattedString() + ")") }
|
||||
td {
|
||||
a {
|
||||
text(it.label)
|
||||
}.attr("href", "/${it.codeLocation.toSimpleString()}/MX")
|
||||
fun getIt(@PathParam("game") gameName: String) = handle {
|
||||
games.getGame(gameName)?.let { game ->
|
||||
table {
|
||||
Service.getVectors(game).forEach {
|
||||
tr {
|
||||
td { text(it.name) }
|
||||
td { text("(" + it.vectorLocation.toFormattedString() + ")") }
|
||||
td {
|
||||
a {
|
||||
text(it.label)
|
||||
}.attr("href", "/${game.id}/${it.codeLocation.toSimpleString()}/MX")
|
||||
}
|
||||
td { text("(" + it.codeLocation.toFormattedString() + ")") }
|
||||
}
|
||||
td { text("(" + it.codeLocation.toFormattedString() + ")") }
|
||||
}
|
||||
}
|
||||
}.addClass("vector-table")
|
||||
}.addClass("vector-table")
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{address}")
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt(@PathParam("address") address: String) = getIt(address, "")
|
||||
fun getIt(
|
||||
@PathParam("game") gameName: String,
|
||||
@PathParam("address") address: String
|
||||
) = getIt(gameName, address, "")
|
||||
|
||||
@GET
|
||||
@Path("{address}/{state}")
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt(@PathParam("address") address: String, @PathParam("state") state: String): Response {
|
||||
fun getIt(
|
||||
@PathParam("game") gameName: String,
|
||||
@PathParam("address") address: String,
|
||||
@PathParam("state") state: String
|
||||
): Response {
|
||||
return handle {
|
||||
SnesAddress.parse(address)?.let {
|
||||
val flags = parseState(state)
|
||||
Service.showDisassembly(it, flags)
|
||||
games.getGame(gameName)?.let { game ->
|
||||
SnesAddress.parse(address)?.let {
|
||||
val flags = parseState(state)
|
||||
Service.showDisassembly(game, it, flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,29 +3,46 @@ package com.smallhacker.disbrowser.resource
|
||||
import com.smallhacker.disbrowser.Service
|
||||
import com.smallhacker.disbrowser.asm.SnesAddress
|
||||
import com.smallhacker.disbrowser.asm.MetadataLine
|
||||
import com.smallhacker.disbrowser.game.GameSource
|
||||
import com.smallhacker.disbrowser.game.getGameSource
|
||||
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.Configuration
|
||||
import javax.ws.rs.core.Context
|
||||
import javax.ws.rs.core.MediaType
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
@Path("/rest")
|
||||
@Path("/rest/{game}")
|
||||
class RestResource {
|
||||
@Context
|
||||
private lateinit var config: Configuration
|
||||
private val games by lazy { config.getGameSource() }
|
||||
|
||||
@POST
|
||||
@Path("{address}/{field}")
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
fun getIt(@PathParam("address") address: String, @PathParam("field") fieldName: String, body: String): Response {
|
||||
fun getIt(
|
||||
@PathParam("game") gameName: String,
|
||||
@PathParam("address") address: String,
|
||||
@PathParam("field") fieldName: String,
|
||||
body: String
|
||||
): Response {
|
||||
val parsedAddress = SnesAddress.parse(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()
|
||||
else -> return http404()
|
||||
}
|
||||
|
||||
Service.updateMetadata(parsedAddress, field, body)
|
||||
val game = games.getGame(gameName)
|
||||
?: return http404()
|
||||
Service.updateMetadata(game, parsedAddress, field, body)
|
||||
|
||||
return Response.noContent().build()
|
||||
}
|
||||
|
||||
private fun http404(): Response = Response.status(404).build()
|
||||
}
|
||||
|
@ -5,10 +5,10 @@ import javax.ws.rs.Path
|
||||
import javax.ws.rs.PathParam
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
@Path("/")
|
||||
@Path("/resources")
|
||||
class StaticResource {
|
||||
@GET
|
||||
@Path("resources/{file}.{ext}")
|
||||
@Path("{file}.{ext}")
|
||||
fun getStatic(@PathParam("file") file: String, @PathParam("ext") ext: String): Response {
|
||||
val mime = when (ext) {
|
||||
"js" -> "application/javascript"
|
||||
|
@ -74,9 +74,10 @@ for (let i = 0; i < editables.length; i++) {
|
||||
let target = <HTMLInputElement>(e.target);
|
||||
let field = target.dataset.field || "";
|
||||
let address = target.dataset.address;
|
||||
let game = target.dataset.game;
|
||||
let value = (target).value;
|
||||
|
||||
xhr(`/rest/${address}/${field}`, "POST", value)
|
||||
xhr(`/rest/${game}/${address}/${field}`, "POST", value)
|
||||
.catch((xhr: XMLHttpRequest) => alert("Error: HTTP " + xhr.status));
|
||||
|
||||
return false;
|
||||
@ -94,12 +95,13 @@ for (let i = 0; i < popupEditables.length; i++) {
|
||||
let field = editable.dataset.field || "";
|
||||
let address = editable.dataset.address;
|
||||
let value = editable.dataset.value;
|
||||
let game = editable.dataset.game;
|
||||
let newValue = prompt("Label for $" + address, value);
|
||||
if (newValue === null || newValue == value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xhr(`/rest/${address}/${field}`, "POST", newValue)
|
||||
xhr(`/rest/${game}/${address}/${field}`, "POST", newValue)
|
||||
.then(() => location.reload())
|
||||
.catch((xhr: XMLHttpRequest) => alert("Error: HTTP " + xhr.status));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user