mirror of
https://github.com/Smallhacker/disbrowser.git
synced 2025-01-14 01:29:58 +00:00
Added simplistic certainty analysis
This commit is contained in:
parent
3322f2460f
commit
1dd32a2de8
@ -12,6 +12,7 @@ class Grid {
|
||||
private val cellClasses = HashMap<Pair<Int, Int>, String>()
|
||||
private val addresses = HashMap<SnesAddress, Int>()
|
||||
private val rowClasses = HashMap<Int, String>()
|
||||
private val rowCertainties = HashMap<Int, String>()
|
||||
private val rowId = HashMap<Int, String>()
|
||||
private var height = 0
|
||||
private var nextAddress: SnesAddress? = null
|
||||
@ -116,9 +117,11 @@ class Grid {
|
||||
editableField(game, indicativeAddress, "comment", comment)
|
||||
)
|
||||
|
||||
if (ins.opcode.continuation == Continuation.NO) {
|
||||
if (ins.opcode.continuation.shouldStop) {
|
||||
rowClasses[y] = "routine-end"
|
||||
}
|
||||
|
||||
rowCertainties[y] = ins.certainty.value.toString()
|
||||
}
|
||||
|
||||
private fun editableField(game: Game, address: SnesAddress, type: String, value: String?): HtmlNode {
|
||||
@ -194,7 +197,9 @@ class Grid {
|
||||
content[x to y]?.appendTo(parent)
|
||||
}.addClass(cssClass)
|
||||
}
|
||||
}.addClass(rowClasses[y]).attr("id", rowId[y])
|
||||
}.addClass(rowClasses[y])
|
||||
.attr("id", rowId[y])
|
||||
.attr("row-certainty", rowCertainties[y])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
src/main/java/com/smallhacker/disbrowser/asm/Certainty.kt
Normal file
17
src/main/java/com/smallhacker/disbrowser/asm/Certainty.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
inline class Certainty(val value: UInt) {
|
||||
operator fun minus(value: Int): Certainty {
|
||||
val signed = this.value.toInt() - value
|
||||
return if (signed < 0) {
|
||||
PROBABLY_WRONG
|
||||
} else Certainty(signed.toUInt())
|
||||
}
|
||||
|
||||
companion object {
|
||||
val PROBABLY_CORRECT = Certainty(100u)
|
||||
val UNCERTAIN = Certainty(50u)
|
||||
val PROBABLY_WRONG = Certainty(0u)
|
||||
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
|
||||
enum class Continuation {
|
||||
NO, YES, MAYBE
|
||||
enum class Continuation(val shouldStop: Boolean) {
|
||||
CONTINUE(false),
|
||||
MAY_STOP(false),
|
||||
STOP(true),
|
||||
FATAL_ERROR(true),
|
||||
INSUFFICIENT_DATA(true),
|
||||
}
|
@ -19,6 +19,7 @@ interface CodeUnit {
|
||||
val lengthSuffix: String?
|
||||
|
||||
val memory: SnesMemory
|
||||
val certainty: Certainty
|
||||
|
||||
fun operandByte(index: UInt): UByte = bytes[opcode.operandIndex + index]
|
||||
|
||||
@ -56,23 +57,64 @@ interface CodeUnit {
|
||||
class DataBlock(
|
||||
override val opcode: Opcode,
|
||||
override val bytes: ValidMemorySpace,
|
||||
override val address: SnesAddress?,
|
||||
override val indicativeAddress: SnesAddress,
|
||||
override val sortedAddress: SnesAddress,
|
||||
override val relativeAddress: SnesAddress,
|
||||
override val linkedState: State?,
|
||||
override val memory: SnesMemory
|
||||
override val memory: SnesMemory,
|
||||
override val certainty: Certainty
|
||||
) : CodeUnit {
|
||||
constructor(
|
||||
opcode: Opcode,
|
||||
bytes: ValidMemorySpace,
|
||||
indicativeAddress: SnesAddress,
|
||||
sortedAddress: SnesAddress,
|
||||
relativeAddress: SnesAddress,
|
||||
linkedState: State?,
|
||||
memory: SnesMemory,
|
||||
certainty: Certainty
|
||||
) : this(opcode, bytes, null, indicativeAddress, sortedAddress, relativeAddress, linkedState, memory, certainty)
|
||||
|
||||
constructor(
|
||||
opcode: Opcode,
|
||||
bytes: ValidMemorySpace,
|
||||
address: SnesAddress,
|
||||
relativeAddress: SnesAddress,
|
||||
linkedState: State?,
|
||||
memory: SnesMemory,
|
||||
certainty: Certainty
|
||||
) : this(opcode, bytes, address, address, address, relativeAddress, linkedState, memory, certainty)
|
||||
|
||||
override val nextSortedAddress: SnesAddress
|
||||
get() = sortedAddress + operandLength.toInt()
|
||||
override val operandLength get() = bytes.size
|
||||
|
||||
override val address: SnesAddress? = null
|
||||
override val preState: State? = null
|
||||
override val postState: State? = null
|
||||
override val lengthSuffix: String? = null
|
||||
}
|
||||
|
||||
class Instruction(override val bytes: ValidMemorySpace, override val opcode: Opcode, override val preState: State) : CodeUnit {
|
||||
interface Instruction : CodeUnit {
|
||||
override val preState: State
|
||||
override val address: SnesAddress
|
||||
override val postState: State
|
||||
|
||||
val continuation: Continuation
|
||||
val showLengthSuffix: Boolean
|
||||
fun link(): SnesAddress?
|
||||
fun referencedAddress(): SnesAddress?
|
||||
|
||||
override fun toString(): String
|
||||
}
|
||||
|
||||
class MutableInstruction(
|
||||
override val bytes: ValidMemorySpace,
|
||||
override val opcode: Opcode,
|
||||
override val preState: State,
|
||||
override var continuation: Continuation,
|
||||
override var certainty: Certainty
|
||||
) : Instruction {
|
||||
override val memory = preState.memory
|
||||
override val address: SnesAddress get() = preState.address
|
||||
override val indicativeAddress get() = address
|
||||
@ -89,7 +131,7 @@ class Instruction(override val bytes: ValidMemorySpace, override val opcode: Opc
|
||||
.withOrigin(this)
|
||||
}
|
||||
|
||||
private val showLengthSuffix get() = opcode.mode.showLengthSuffix and opcode.mnemonic.showLengthSuffix
|
||||
override val showLengthSuffix get() = opcode.mode.showLengthSuffix and opcode.mnemonic.showLengthSuffix
|
||||
|
||||
override val lengthSuffix: String?
|
||||
get() {
|
||||
@ -109,7 +151,7 @@ class Instruction(override val bytes: ValidMemorySpace, override val opcode: Opc
|
||||
override val operandLength
|
||||
get() = opcode.mode.operandLength(preState)
|
||||
|
||||
private fun link(): SnesAddress? {
|
||||
override fun link(): SnesAddress? {
|
||||
if (!opcode.link) {
|
||||
return null
|
||||
}
|
||||
@ -117,7 +159,7 @@ class Instruction(override val bytes: ValidMemorySpace, override val opcode: Opc
|
||||
return referencedAddress()
|
||||
}
|
||||
|
||||
private fun referencedAddress() = opcode.mode.referencedAddress(this)
|
||||
override fun referencedAddress() = opcode.mode.referencedAddress(this)
|
||||
|
||||
override fun toString(): String {
|
||||
val (address, bytes, _, primaryMnemonic, _, suffix, operands, _, _) = print()
|
||||
|
@ -7,15 +7,13 @@ import com.smallhacker.disbrowser.asm.Mnemonic.*
|
||||
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) 0u else 1u
|
||||
|
||||
val continuation: Continuation
|
||||
get() = _continuation
|
||||
var continuation: Continuation = Continuation.CONTINUE
|
||||
|
||||
val link: Boolean
|
||||
get() = _link
|
||||
@ -23,15 +21,13 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end
|
||||
val branch: Boolean
|
||||
get() = _branch
|
||||
|
||||
private fun stop(): Opcode {
|
||||
this._continuation = Continuation.NO
|
||||
return this
|
||||
}
|
||||
private fun insufficientData() = also { this.continuation = Continuation.INSUFFICIENT_DATA }
|
||||
|
||||
private fun mayStop(): Opcode {
|
||||
this._continuation = Continuation.MAYBE
|
||||
return this
|
||||
}
|
||||
private fun fatal() = also { this.continuation = Continuation.FATAL_ERROR }
|
||||
|
||||
private fun stop() = also { this.continuation = Continuation.STOP }
|
||||
|
||||
private fun mayStop() = also { this.continuation = Continuation.MAY_STOP }
|
||||
|
||||
private fun linking(): Opcode {
|
||||
this._link = true
|
||||
@ -80,15 +76,14 @@ class Opcode private constructor(val mnemonic: Mnemonic, val mode: Mode, val end
|
||||
val dynamicSubJumping: SegmentEnder = { stoppingSegmentEnd(address) }
|
||||
val returning: SegmentEnder = { returnSegmentEnd(address) }
|
||||
|
||||
UNKNOWN_OPCODE = Opcode(UNKNOWN, Implied, alwaysStop, Instruction::preState).stop()
|
||||
UNKNOWN_OPCODE = Opcode(UNKNOWN, Implied, alwaysStop, Instruction::preState).insufficientData()
|
||||
|
||||
add(0x00, BRK, Immediate8, alwaysStop).stop()
|
||||
add(0x02, COP, Immediate8, alwaysStop).stop()
|
||||
add(0x42, WDM, Immediate8, alwaysStop).stop()
|
||||
add(0x00, BRK, Immediate8, alwaysStop).fatal()
|
||||
add(0x02, COP, Immediate8, alwaysStop).fatal()
|
||||
add(0x42, WDM, Immediate8, alwaysStop).fatal()
|
||||
add(0xDB, STP, Implied, alwaysStop).fatal()
|
||||
|
||||
add(0xEA, NOP, Implied, alwaysContinue)
|
||||
|
||||
add(0xDB, STP, Implied, alwaysStop).stop()
|
||||
add(0xCB, WAI, Implied, alwaysContinue)
|
||||
|
||||
add(0x10, BPL, Relative, branching).branching()
|
||||
|
@ -1,24 +1,29 @@
|
||||
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 com.smallhacker.disbrowser.game.*
|
||||
import com.smallhacker.disbrowser.util.mutableMultiMap
|
||||
import com.smallhacker.disbrowser.util.putSingle
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
object Disassembler {
|
||||
fun disassemble(initialState: State, gameData: GameData, global: Boolean): Disassembly {
|
||||
val seen = HashSet<SnesAddress>()
|
||||
val queue = ArrayDeque<State>()
|
||||
val origins = mutableMultiMap<SnesAddress, SnesAddress>()
|
||||
val instructionMap = HashMap<SnesAddress, MutableInstruction>()
|
||||
|
||||
fun tryAdd(state: State) {
|
||||
fun tryAdd(state: State, origin: SnesAddress?) {
|
||||
if (origin != null) {
|
||||
origins.putSingle(state.address, origin)
|
||||
}
|
||||
if (seen.add(state.address)) {
|
||||
queue.add(state)
|
||||
}
|
||||
}
|
||||
tryAdd(initialState)
|
||||
tryAdd(initialState, null)
|
||||
|
||||
val instructions = ArrayList<CodeUnit>()
|
||||
while (queue.isNotEmpty()) {
|
||||
@ -26,57 +31,94 @@ object Disassembler {
|
||||
|
||||
val ins = disassembleInstruction(state)
|
||||
instructions.add(ins)
|
||||
instructionMap[ins.address] = ins
|
||||
|
||||
var stop = (ins.opcode.continuation == Continuation.NO) or
|
||||
(ins.opcode.mode.instructionLength(state) == null)
|
||||
|
||||
gameData[ins.address]?.flags?.forEach { flag ->
|
||||
if (flag is JmpIndirectLongInterleavedTable) {
|
||||
if (global) {
|
||||
flag.readTable(state.memory)
|
||||
.filterNotNull()
|
||||
.map { ins.postState.copy(address = it) }
|
||||
.forEach { tryAdd(it) }
|
||||
}
|
||||
|
||||
flag.generateCode(ins)
|
||||
.forEach { instructions.add(it) }
|
||||
|
||||
stop = true
|
||||
} else if (flag is JslTableRoutine) {
|
||||
if (global) {
|
||||
flag.readTable(ins.postState)
|
||||
.filterNotNull()
|
||||
.map { ins.postState.copy(address = it) }
|
||||
.forEach { tryAdd(it) }
|
||||
}
|
||||
stop = true
|
||||
}
|
||||
if (ins.opcode.mode.instructionLength(state) == null) {
|
||||
ins.continuation = Continuation.INSUFFICIENT_DATA
|
||||
}
|
||||
|
||||
val linkedState = ins.linkedState
|
||||
|
||||
if (linkedState != null) {
|
||||
gameData[linkedState.address]?.flags?.forEach {
|
||||
if (it === NonReturningRoutine) {
|
||||
stop = true
|
||||
println(ins.address.toFormattedString())
|
||||
}
|
||||
val localAddress = ins.address
|
||||
val remoteAddress = linkedState?.address
|
||||
|
||||
val localFlags = gameData.flagsAt(localAddress)
|
||||
val remoteFlags = gameData.flagsAt(remoteAddress)
|
||||
|
||||
val pointerTableEntries = localFlags.findFlag<PointerTableLength>()?.entries
|
||||
|
||||
localFlags.forFlag<JmpIndirectLongInterleavedTable> {
|
||||
ins.continuation = Continuation.STOP
|
||||
|
||||
if (global) {
|
||||
readTable(state.memory)
|
||||
.filterNotNull()
|
||||
.map { ins.postState.copy(address = it) }
|
||||
.forEach { tryAdd(it, ins.address) }
|
||||
}
|
||||
|
||||
generatePointerTable(ins).forEach {
|
||||
instructions.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (!stop) {
|
||||
tryAdd(ins.postState)
|
||||
remoteFlags.forFlag<JslTableRoutine> {
|
||||
ins.continuation = Continuation.STOP
|
||||
|
||||
if (pointerTableEntries != null) {
|
||||
if (global) {
|
||||
readTable(ins.postState, pointerTableEntries)
|
||||
.filterNotNull()
|
||||
.map { ins.postState.copy(address = it) }
|
||||
.forEach { tryAdd(it, ins.address) }
|
||||
}
|
||||
}
|
||||
|
||||
generatePointerTable(ins, pointerTableEntries?.toUInt()).forEach {
|
||||
instructions.add(it)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
remoteFlags.forFlag<NonReturningRoutine> {
|
||||
ins.continuation = Continuation.STOP
|
||||
}
|
||||
|
||||
|
||||
if (!ins.continuation.shouldStop) {
|
||||
tryAdd(ins.postState, ins.address)
|
||||
}
|
||||
|
||||
|
||||
if (linkedState != null) {
|
||||
if (ins.opcode.branch || global) {
|
||||
tryAdd(linkedState)
|
||||
tryAdd(linkedState, ins.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val fatalSeen = HashSet<SnesAddress>()
|
||||
val fatalQueue = ArrayDeque<SnesAddress>()
|
||||
fun tryAddFatal(snesAddress: SnesAddress) {
|
||||
if (fatalSeen.add(snesAddress)) {
|
||||
fatalQueue.addLast(snesAddress)
|
||||
}
|
||||
}
|
||||
|
||||
instructions.asSequence()
|
||||
.filterIsInstance<Instruction>()
|
||||
.filter { it.continuation == Continuation.FATAL_ERROR }
|
||||
.forEach { tryAddFatal(it.address) }
|
||||
|
||||
while (fatalQueue.isNotEmpty()) {
|
||||
val badAddress = fatalQueue.removeFirst()!!
|
||||
val instruction = instructionMap[badAddress] ?: continue
|
||||
val mnemonic = instruction.opcode.mnemonic
|
||||
if (mnemonic == Mnemonic.JSL || mnemonic == Mnemonic.JSR) continue
|
||||
instruction.certainty = Certainty.PROBABLY_WRONG
|
||||
origins[badAddress]?.forEach{tryAddFatal(it)}
|
||||
}
|
||||
|
||||
val instructionList = instructions
|
||||
.sortedBy { it.sortedAddress }
|
||||
.toList()
|
||||
@ -118,8 +160,6 @@ object Disassembler {
|
||||
end.remote.forEach {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return segments
|
||||
@ -167,15 +207,17 @@ object Disassembler {
|
||||
return finalize(Segment(initialState.address, continuationSegmentEnd(lastState), instructions))
|
||||
}
|
||||
|
||||
private fun disassembleInstruction(state: State): Instruction {
|
||||
private fun disassembleInstruction(state: State): MutableInstruction {
|
||||
val opcodeValue = state.memory[state.address] ?: return unreadableInstruction(state)
|
||||
val opcode = Opcode.opcode(opcodeValue)
|
||||
val length = opcode.mode.instructionLength(state) ?: 1u
|
||||
val bytes = state.memory.range(state.address.value.toUInt(), length).validate()
|
||||
?: return unreadableInstruction(state)
|
||||
return Instruction(bytes, opcode, state)
|
||||
?: return unreadableInstruction(state)
|
||||
val continuation = opcode.continuation
|
||||
val certainty = Certainty.PROBABLY_CORRECT
|
||||
return MutableInstruction(bytes, opcode, state, continuation, certainty)
|
||||
}
|
||||
|
||||
private fun unreadableInstruction(state: State) =
|
||||
Instruction(EmptyMemorySpace, Opcode.UNKNOWN_OPCODE, state)
|
||||
MutableInstruction(EmptyMemorySpace, Opcode.UNKNOWN_OPCODE, state, Continuation.INSUFFICIENT_DATA, Certainty.PROBABLY_WRONG)
|
||||
}
|
||||
|
@ -62,11 +62,22 @@ class GameData {
|
||||
}
|
||||
}
|
||||
|
||||
fun GameData.flagsAt(snesAddress: SnesAddress?): MetadataLineFlags =
|
||||
MetadataLineFlags(get(snesAddress)?.flags ?: emptyList())
|
||||
|
||||
inline class MetadataLineFlags(val flags: List<InstructionFlag>) {
|
||||
inline fun <reified F> findFlag() = flags.asSequence().filterIsInstance<F>().firstOrNull()
|
||||
inline fun <reified F> forFlag(action: F.() -> Unit) = findFlag<F>()?.run(action)
|
||||
}
|
||||
|
||||
operator fun GameData.get(address: SnesAddress?) = if (address == null) null else this[address]
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "flagType")
|
||||
@JsonSubTypes(
|
||||
Type(value = NonReturningRoutine::class, name = "NonReturningRoutine"),
|
||||
Type(value = JmpIndirectLongInterleavedTable::class, name = "JmpIndirectLongInterleavedTable"),
|
||||
Type(value = JslTableRoutine::class, name = "JslTableRoutine")
|
||||
Type(value = JslTableRoutine::class, name = "JslTableRoutine"),
|
||||
Type(value = PointerTableLength::class, name = "PointerTableLength")
|
||||
)
|
||||
interface InstructionFlag
|
||||
|
||||
@ -88,7 +99,7 @@ class JmpIndirectLongInterleavedTable @JsonCreator constructor(
|
||||
.map { pointer -> pointer?.let { SnesAddress(it) } }
|
||||
}
|
||||
|
||||
fun generateCode(jumpInstruction: Instruction): Sequence<DataBlock> {
|
||||
fun generatePointerTable(jumpInstruction: Instruction): Sequence<DataBlock> {
|
||||
val table = jumpInstruction.preState.memory.deinterleave(uEntries,
|
||||
start.value.toUInt(),
|
||||
(start + entries).value.toUInt(),
|
||||
@ -110,7 +121,8 @@ class JmpIndirectLongInterleavedTable @JsonCreator constructor(
|
||||
jumpInstruction.opcode.mutate(jumpInstruction)
|
||||
.mutateAddress { SnesAddress(target) }
|
||||
.withOrigin(jumpInstruction),
|
||||
jumpInstruction.memory
|
||||
jumpInstruction.memory,
|
||||
Certainty.PROBABLY_CORRECT
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -118,18 +130,68 @@ class JmpIndirectLongInterleavedTable @JsonCreator constructor(
|
||||
override fun toString() = "JmpIndirectLongInterleavedTable($start, $entries)"
|
||||
}
|
||||
|
||||
class JslTableRoutine @JsonCreator constructor(
|
||||
@field:JsonProperty @JsonProperty private val entries: Int
|
||||
) : InstructionFlag {
|
||||
|
||||
fun readTable(postJsr: State): Sequence<SnesAddress?> {
|
||||
class JslTableRoutine : InstructionFlag {
|
||||
fun readTable(postJsr: State, entryCount: Int): Sequence<SnesAddress?> {
|
||||
val data = postJsr.memory
|
||||
return (0 until entries)
|
||||
return (0 until entryCount)
|
||||
.asSequence()
|
||||
.map { postJsr.address + (it * 3) }
|
||||
.map { address -> joinNullableBytes(data[address], data[address + 1], data[address + 2])?.toUInt24() }
|
||||
.map { pointer -> pointer?.let { SnesAddress(it) } }
|
||||
}
|
||||
|
||||
override fun toString() = "JslTableRoutine($entries)"
|
||||
fun generatePointerTable(jumpInstruction: Instruction, entryCount: UInt?): Sequence<DataBlock> {
|
||||
val count: UInt
|
||||
var certainty: Certainty
|
||||
val certaintyDecrease: Int
|
||||
|
||||
if (entryCount == null) {
|
||||
count = 30u
|
||||
certainty = Certainty.UNCERTAIN
|
||||
certaintyDecrease = 5
|
||||
} else {
|
||||
count = entryCount
|
||||
certainty = Certainty.PROBABLY_CORRECT
|
||||
certaintyDecrease = 0
|
||||
}
|
||||
|
||||
val start = jumpInstruction.postState.address
|
||||
val memory = jumpInstruction.memory
|
||||
|
||||
return (0u until count.toUInt())
|
||||
.asSequence()
|
||||
.map { index -> index * 3u }
|
||||
.mapNotNull { offset ->
|
||||
val pointerLoc= start + offset.toInt()
|
||||
val addressRange = memory.range(pointerLoc, 3u).validate()
|
||||
|
||||
if (addressRange == null) {
|
||||
null
|
||||
} else {
|
||||
val target = addressRange.getLong(0u)
|
||||
|
||||
val block = DataBlock(
|
||||
Opcode.CODE_POINTER_LONG,
|
||||
addressRange,
|
||||
pointerLoc,
|
||||
jumpInstruction.relativeAddress,
|
||||
jumpInstruction.opcode.mutate(jumpInstruction)
|
||||
.mutateAddress { SnesAddress(target) }
|
||||
.withOrigin(jumpInstruction),
|
||||
memory,
|
||||
certainty
|
||||
)
|
||||
certainty -= certaintyDecrease
|
||||
block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = "JslTableRoutine"
|
||||
}
|
||||
|
||||
class PointerTableLength @JsonCreator constructor(
|
||||
@field:JsonProperty @JsonProperty val entries: Int
|
||||
) : InstructionFlag {
|
||||
override fun toString() = "PointerTableLength($entries)"
|
||||
}
|
@ -88,7 +88,7 @@ class DisassemblyResource {
|
||||
return Response.ok(html.toString().toByteArray(StandardCharsets.UTF_8))
|
||||
.encoding("UTF-8")
|
||||
.build()
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
throw e
|
||||
}
|
||||
|
10
src/main/java/com/smallhacker/disbrowser/util/multiMap.kt
Normal file
10
src/main/java/com/smallhacker/disbrowser/util/multiMap.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package com.smallhacker.disbrowser.util
|
||||
|
||||
typealias MultiMap<K, V> = Map<K, List<V>>
|
||||
typealias MutableMultiMap<K, V> = MutableMap<K, MutableList<V>>
|
||||
|
||||
fun <K, V> mutableMultiMap(): MutableMultiMap<K, V> = HashMap()
|
||||
|
||||
fun <K, V> MutableMultiMap<K, V>.putSingle(key: K, value: V) {
|
||||
computeIfAbsent(key) { ArrayList() }.add(value)
|
||||
}
|
@ -127,4 +127,54 @@ tr.line-active {
|
||||
}
|
||||
.field-editable-popup-icon::before {
|
||||
content: "[e]"
|
||||
}
|
||||
|
||||
[row-certainty="0"],[row-certainty="1"],[row-certainty="2"],[row-certainty="3"],[row-certainty="4"],
|
||||
[row-certainty="5"],[row-certainty="6"],[row-certainty="7"],[row-certainty="8"],[row-certainty="9"] {
|
||||
color: rgb(200,200,200);
|
||||
}
|
||||
|
||||
[row-certainty="10"],[row-certainty="11"],[row-certainty="12"],[row-certainty="13"],[row-certainty="14"],
|
||||
[row-certainty="15"],[row-certainty="16"],[row-certainty="17"],[row-certainty="18"],[row-certainty="19"] {
|
||||
color: rgb(180,180,180);
|
||||
}
|
||||
|
||||
[row-certainty="20"],[row-certainty="21"],[row-certainty="22"],[row-certainty="23"],[row-certainty="24"],
|
||||
[row-certainty="25"],[row-certainty="26"],[row-certainty="27"],[row-certainty="28"],[row-certainty="29"] {
|
||||
color: rgb(160,160,160);
|
||||
}
|
||||
|
||||
[row-certainty="30"],[row-certainty="31"],[row-certainty="32"],[row-certainty="33"],[row-certainty="34"],
|
||||
[row-certainty="35"],[row-certainty="36"],[row-certainty="37"],[row-certainty="38"],[row-certainty="39"] {
|
||||
color: rgb(140,140,140);
|
||||
}
|
||||
|
||||
[row-certainty="40"],[row-certainty="41"],[row-certainty="42"],[row-certainty="43"],[row-certainty="44"],
|
||||
[row-certainty="45"],[row-certainty="46"],[row-certainty="47"],[row-certainty="48"],[row-certainty="49"] {
|
||||
color: rgb(120,120,120);
|
||||
}
|
||||
|
||||
[row-certainty="50"],[row-certainty="51"],[row-certainty="52"],[row-certainty="53"],[row-certainty="54"],
|
||||
[row-certainty="55"],[row-certainty="56"],[row-certainty="57"],[row-certainty="58"],[row-certainty="59"] {
|
||||
color: rgb(100,100,100);
|
||||
}
|
||||
|
||||
[row-certainty="60"],[row-certainty="61"],[row-certainty="62"],[row-certainty="63"],[row-certainty="64"],
|
||||
[row-certainty="65"],[row-certainty="66"],[row-certainty="67"],[row-certainty="68"],[row-certainty="69"] {
|
||||
color: rgb(80,80,80);
|
||||
}
|
||||
|
||||
[row-certainty="70"],[row-certainty="71"],[row-certainty="72"],[row-certainty="73"],[row-certainty="74"],
|
||||
[row-certainty="75"],[row-certainty="76"],[row-certainty="77"],[row-certainty="78"],[row-certainty="79"] {
|
||||
color: rgb(60,60,60);
|
||||
}
|
||||
|
||||
[row-certainty="80"],[row-certainty="81"],[row-certainty="82"],[row-certainty="83"],[row-certainty="84"],
|
||||
[row-certainty="85"],[row-certainty="86"],[row-certainty="87"],[row-certainty="88"],[row-certainty="89"] {
|
||||
color: rgb(40,40,40);
|
||||
}
|
||||
|
||||
[row-certainty="90"],[row-certainty="91"],[row-certainty="92"],[row-certainty="93"],[row-certainty="94"],
|
||||
[row-certainty="95"],[row-certainty="96"],[row-certainty="97"],[row-certainty="98"],[row-certainty="99"] {
|
||||
color: rgb(20,20,20);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user