Partial move of stuff to commonMain

This commit is contained in:
Smallhacker 2019-01-20 11:11:30 +01:00
parent f0bd8d5170
commit 852632e053
33 changed files with 337 additions and 285 deletions

View File

@ -14,6 +14,10 @@ kotlin {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
}
languageSettings {
enableLanguageFeature('InlineClasses')
useExperimentalAnnotation('kotlin.ExperimentalUnsignedTypes')
}
}
commonTest {
dependencies {
@ -47,6 +51,10 @@ kotlin {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
}
languageSettings {
enableLanguageFeature('InlineClasses')
useExperimentalAnnotation('kotlin.ExperimentalUnsignedTypes')
}
}
jsTest {
dependencies {

View File

@ -1,9 +1,9 @@
package com.smallhacker.disbrowser.asm
package com.smallhacker.disbrowser.memory
import com.smallhacker.disbrowser.util.UInt24
import com.smallhacker.disbrowser.util.joinBytes
import com.smallhacker.disbrowser.util.joinNullableBytes
import com.smallhacker.disbrowser.util.toUInt24
import com.smallhacker.util.UInt24
import com.smallhacker.util.joinBytes
import com.smallhacker.util.joinNullableBytes
import com.smallhacker.util.toUInt24
interface MemorySpace {
val size: UInt
@ -22,7 +22,8 @@ object EmptyMemorySpace : ValidMemorySpace {
fun MemorySpace.asSequence(): Sequence<UByte?> = (0u until size).asSequence().map { this[it] }
fun MemorySpace.getWord(address: UInt): UShort? = joinNullableBytes(this[address], this[address + 1u])?.toUShort()
fun MemorySpace.getLong(address: UInt): UInt24? = joinNullableBytes(this[address], this[address + 1u], this[address + 2u])?.toUInt24()
fun MemorySpace.range(start: UInt, length: UInt): MemorySpace = MemoryRange(this, start, length)
fun MemorySpace.range(start: UInt, length: UInt): MemorySpace =
MemoryRange(this, start, length)
fun MemorySpace.range(start: SnesAddress, length: UInt): MemorySpace = range(start.value.toUInt(), length)
fun MemorySpace.validate(): ValidMemorySpace? {
if (asSequence().any { it == null }) {
@ -34,7 +35,11 @@ fun MemorySpace.validate(): ValidMemorySpace? {
fun ValidMemorySpace.asSequence(): Sequence<UByte> = (0u until size).asSequence().map { this[it] }
fun ValidMemorySpace.getWord(address: UInt): UShort = joinBytes(this[address], this[address + 1u]).toUShort()
fun ValidMemorySpace.getLong(address: UInt): UInt24 = joinBytes(this[address], this[address + 1u], this[address + 2u]).toUInt24()
fun ValidMemorySpace.range(start: UInt, length: UInt): ValidMemorySpace = MemoryRange(this, start, length).validate()!!
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()!!
class ArrayMemorySpace(private val bytes: UByteArray) : MemorySpace {
@ -52,20 +57,22 @@ class UnreadableMemory(override val size: UInt) : MemorySpace {
override fun get(address: UInt): UByte? = null
}
private class MemoryRange(private val parent: MemorySpace, private val start: UInt, override val size: UInt) : MemorySpace {
private class MemoryRange(private val parent: MemorySpace, private val start: UInt, override val size: UInt) :
MemorySpace {
override fun get(address: UInt) = if (address < size) parent[start + address] else null
}
private class ValidatedMemorySpace(private val parent: MemorySpace) : ValidMemorySpace {
private class ValidatedMemorySpace(private val parent: MemorySpace) :
ValidMemorySpace {
override val size get() = parent.size
override fun get(address: UInt) = parent[address]!!
}
private class ReindexedMemorySpace(
private val parent: MemorySpace,
override val size: UInt,
private val mapper: (UInt) -> UInt
private val parent: MemorySpace,
override val size: UInt,
private val mapper: (UInt) -> UInt
) : MemorySpace {
override fun get(address: UInt): UByte? {
if (address >= size) {

View File

@ -0,0 +1,31 @@
package com.smallhacker.disbrowser.memory
import com.smallhacker.util.UInt24
import com.smallhacker.util.toHex
import com.smallhacker.util.toIntOrNull
import com.smallhacker.util.toUInt24
import kotlin.math.abs
data class SnesAddress(val value: UInt24) : Comparable<SnesAddress> {
operator fun plus(offset: Int) = SnesAddress(value + offset)
operator fun minus(offset: Int) = SnesAddress(value - offset)
operator fun inc() = plus(1)
operator fun dec() = minus(1)
override fun toString(): String = toFormattedString()
fun toFormattedString(): String = "$${(value shr 16).toUByte().toHex()}:${value.toUShort().toHex()}"
fun toSimpleString(): String = value.toHex()
fun withinBank(value: UShort): SnesAddress = SnesAddress((this.value and 0xFF_0000u) or value.toUInt24())
override fun compareTo(other: SnesAddress) = value.toUInt().compareTo(other.value.toUInt())
infix fun distanceTo(other: SnesAddress) = abs(value.toInt() - other.value.toInt()).toUInt()
companion object {
fun parse(address: String): SnesAddress? = toIntOrNull(address, 16)
?.let { SnesAddress(it.toUInt24()) }
}
}
fun address(snesAddress: Int) = SnesAddress(snesAddress.toUInt24())

View File

@ -1,16 +1,21 @@
package com.smallhacker.disbrowser.asm
package com.smallhacker.disbrowser.memory
import com.smallhacker.disbrowser.datatype.MutableRangeMap
import com.smallhacker.disbrowser.datatype.NaiveRangeMap
import com.smallhacker.disbrowser.util.toUInt24
import com.smallhacker.util.MutableRangeMap
import com.smallhacker.util.NaiveRangeMap
import com.smallhacker.util.toUInt24
abstract class SnesMemory: MemorySpace {
override val size = 0x100_0000u
private val areas: MutableRangeMap<UInt, UIntRange, MapperEntry> = NaiveRangeMap()
private val areas: MutableRangeMap<UInt, UIntRange, MapperEntry> =
NaiveRangeMap()
protected fun add(start: UInt, canonicalStart: UInt, memorySpace: MemorySpace) {
val range = start until (start + memorySpace.size)
areas[range] = MapperEntry(start, SnesAddress(canonicalStart.toUInt24()), memorySpace)
areas[range] = MapperEntry(
start,
SnesAddress(canonicalStart.toUInt24()),
memorySpace
)
}
override fun get(address: UInt): UByte? {

View File

@ -1,4 +1,4 @@
package com.smallhacker.disbrowser
package com.smallhacker.util
interface ImmStack<E>: Iterable<E> {
fun isEmpty(): Boolean

View File

@ -0,0 +1,7 @@
package com.smallhacker.util
fun toIntOrNull(s: String, radix: Int = 10) = try {
s.toInt(radix)
} catch (e: NumberFormatException) {
null
}

View File

@ -1,4 +1,4 @@
package com.smallhacker.disbrowser.util
package com.smallhacker.util
// The Kotlin standard library does not contain a counterpart to Java's ArrayDeque<E>, so let's implement a simplistic
// one ourselves for portability.

View File

@ -1,4 +1,4 @@
package com.smallhacker.disbrowser.util
package com.smallhacker.util
typealias MultiMap<K, V> = Map<K, List<V>>
typealias MutableMultiMap<K, V> = MutableMap<K, MutableList<V>>
@ -6,5 +6,5 @@ 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)
getOrPut(key) { ArrayList() }.add(value)
}

View File

@ -1,6 +1,4 @@
package com.smallhacker.disbrowser.datatype
import com.smallhacker.disbrowser.util.asReverseSequence
package com.smallhacker.util
interface RangeMap<K : Comparable<K>, R : ClosedRange<K>, V : Any> {
operator fun get(key: K): V?
@ -11,6 +9,7 @@ interface MutableRangeMap<K : Comparable<K>, R : ClosedRange<K>, V: Any> : Range
operator fun set(keyRange: R, value: V): RangeMap<K, R, V>
}
// Just an oversimplified implementation for now. Can be heavily optimized if needed.
class NaiveRangeMap<K : Comparable<K>, R : ClosedRange<K>, V: Any> : MutableRangeMap<K, R, V> {
private val entries = ArrayList<Pair<R, V>>()

View File

@ -0,0 +1,33 @@
package com.smallhacker.util
inline class UInt24(private val data: UInt) {
fun toUInt() = data and 0x00FF_FFFFu
fun toUShort() = toUInt().toUShort()
fun toUByte() = toUInt().toUByte()
fun toInt() = toUInt().toInt()
fun toShort() = toUShort().toShort()
fun toByte() = toUByte().toByte()
infix fun and(v: UInt24) = (data and v.data).toUInt24()
infix fun and(v: UInt) = (data and v).toUInt24()
infix fun or(v: UInt24) = (data or v.data).toUInt24()
infix fun or(v: UInt) = (data or v).toUInt24()
infix fun shl(v: Int) = (data shl v).toUInt24()
infix fun shr(v: Int) = (toUInt() shr v).toUInt24()
operator fun plus(v: UInt24) = (toUInt() + v.toUInt()).toUInt24()
operator fun plus(v: UInt) = (toUInt() + v).toUInt24()
operator fun plus(v: Int) = (toInt() + v).toUInt24()
operator fun minus(v: UInt24) = (toUInt() - v.toUInt()).toUInt24()
operator fun minus(v: UInt) = (toUInt() - v).toUInt24()
operator fun minus(v: Int) = (toInt() - v).toUInt24()
override fun toString() = data.toString()
}
fun UInt.toUInt24() = UInt24(this and 0x00FF_FFFFu)
fun UShort.toUInt24() = this.toUInt().toUInt24()
fun UByte.toUInt24() = this.toUInt().toUInt24()
fun Int.toUInt24() = this.toUInt().toUInt24()
fun Short.toUInt24() = this.toUInt().toUInt24()
fun Byte.toUInt24() = this.toUInt().toUInt24()

View File

@ -1,4 +1,4 @@
package com.smallhacker.disbrowser.asm
package com.smallhacker.util
inline class VagueNumber(private val valueAndCertainty: ULong) {
private constructor(value: UInt, certainty: UInt) : this(value.toULong() or (certainty.toULong() shl 32))

View File

@ -0,0 +1,52 @@
package com.smallhacker.util
fun toHex(value: UInt, digits: UInt): String {
if (digits == 0u) {
return ""
}
val hex = value.toLong().toString(16)
return hex.padStart(digits.toInt(), '0')
}
fun UInt.toHex() = toHex(this, 8u)
fun UInt24.toHex() = toHex(toUInt(), 6u)
fun UShort.toHex() = toHex(toUInt(), 4u)
fun UByte.toHex() = toHex(toUInt(), 2u)
fun joinBytes(vararg bytes: UByte) = bytes
.asSequence()
.mapIndexed { index, v -> v.toUInt() shl (index * 8) }
.reduce { a, b -> a or b }
fun joinNullableBytes(vararg bytes: UByte?): UInt? {
if (bytes.any { it == null }) {
return null
}
return bytes
.asSequence()
.mapIndexed { index, v -> v!!.toUInt() shl (index * 8) }
.reduce { a, b -> a or b }
}
fun <T> List<T>.asReverseSequence(): Sequence<T> =
((size - 1) downTo 0).asSequence().map { this[it] }
fun <K, V> MutableMap<K, V>.removeIf(condition: (K, V) -> Boolean) {
this.asSequence()
.filter { condition(it.key, it.value) }
.map { it.key }
.toList()
.forEach { remove(it) }
}
fun hashOf(vararg values: Any?): Int {
return if (values.isEmpty()) {
0
} else {
values.asSequence()
.map { it.hashCode() }
.reduce { acc, v -> (acc * 31) + v }
}
}

View File

@ -1,7 +1,13 @@
package com.smallhacker.disbrowser
import com.smallhacker.disbrowser.asm.*
import com.smallhacker.disbrowser.asm.CodeUnit
import com.smallhacker.disbrowser.asm.Disassembly
import com.smallhacker.disbrowser.asm.print
import com.smallhacker.disbrowser.game.Game
import com.smallhacker.disbrowser.memory.SnesAddress
import kotlin.collections.HashMap
import kotlin.collections.asSequence
import kotlin.collections.set
class Grid {
private val arrowCells = HashMap<Pair<Int, Int>, HtmlNode?>()

View File

@ -1,9 +1,13 @@
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.toUInt24
import com.smallhacker.disbrowser.game.Game
import com.smallhacker.disbrowser.memory.SnesAddress
import com.smallhacker.disbrowser.memory.address
import com.smallhacker.disbrowser.memory.getWord
import com.smallhacker.util.VagueNumber
import com.smallhacker.util.toUInt24
import kotlin.reflect.KMutableProperty1
private val RESET_VECTOR_LOCATION = address(0x00_FFFC)

View File

@ -1,5 +1,7 @@
package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.memory.SnesAddress
class Disassembly(lines: List<CodeUnit>) : Iterable<CodeUnit> {
override fun iterator() = lineList.iterator() as Iterator<CodeUnit>

View File

@ -1,7 +1,14 @@
package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.game.GameData
import com.smallhacker.disbrowser.util.*
import com.smallhacker.disbrowser.game.get
import com.smallhacker.disbrowser.memory.SnesAddress
import com.smallhacker.disbrowser.memory.SnesMemory
import com.smallhacker.disbrowser.memory.ValidMemorySpace
import com.smallhacker.disbrowser.memory.asSequence
import com.smallhacker.util.joinBytes
import com.smallhacker.util.toHex
import com.smallhacker.util.toUInt24
interface CodeUnit {
val address: SnesAddress?
@ -25,7 +32,7 @@ interface CodeUnit {
fun bytesToString(): String {
return bytes.asSequence()
.map { toHex(it.toUInt(), 1u) }
.map { it.toHex() }
.joinToString(" ")
.padEnd(11, ' ')
}
@ -55,35 +62,35 @@ 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 certainty: Certainty
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 certainty: Certainty
) : CodeUnit {
constructor(
opcode: Opcode,
bytes: ValidMemorySpace,
indicativeAddress: SnesAddress,
sortedAddress: SnesAddress,
relativeAddress: SnesAddress,
linkedState: State?,
memory: SnesMemory,
certainty: Certainty
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
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
@ -109,11 +116,11 @@ interface Instruction : CodeUnit {
}
class MutableInstruction(
override val bytes: ValidMemorySpace,
override val opcode: Opcode,
override val preState: State,
override var continuation: Continuation,
override var certainty: Certainty
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
@ -174,7 +181,9 @@ fun CodeUnit.print(gameData: GameData? = null): PrintedCodeUnit {
val secondaryMnemonic = mnemonic.alternativeName
var suffix = lengthSuffix
var operands = gameData?.let { opcode.mode.printWithLabel(this, it) }
val referencedAddress = opcode.mode.referencedAddress(this)
val operandLabel = gameData?.get(referencedAddress)?.label
var operands = opcode.mode.printWithLabel(this, operandLabel)
if (operands == null) {
operands = opcode.mode.printRaw(this)
suffix = null
@ -187,7 +196,7 @@ fun CodeUnit.print(gameData: GameData? = null): PrintedCodeUnit {
val bytes = bytesToString()
val labelAddress = if (opcode.mode.canHaveLabel) {
opcode.mode.referencedAddress(this)?.let {
referencedAddress?.let {
memory.toCanonical(it)
}
} else {

View File

@ -1,14 +1,16 @@
package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.game.GameData
import com.smallhacker.disbrowser.util.*
import com.smallhacker.disbrowser.memory.SnesAddress
import com.smallhacker.disbrowser.memory.SnesMemory
import com.smallhacker.util.UInt24
import com.smallhacker.util.toHex
interface Mode {
val dataMode get() = false
val showLengthSuffix get() = true
val canHaveLabel: Boolean
fun operandLength(state: State): UInt?
fun printWithLabel(ins: CodeUnit, gameData: GameData): String? = referencedAddress(ins)?.let { gameData[it]?.label }
fun printWithLabel(ins: CodeUnit, label: String?): String? = label
fun printRaw(ins: CodeUnit): String
fun referencedAddress(ins: CodeUnit): SnesAddress?
}
@ -31,7 +33,7 @@ private abstract class RawWrappedMode(
private val suffix: String = ""
) : Mode by parent {
override val canHaveLabel = false
override fun printWithLabel(ins: CodeUnit, gameData: GameData): String? = printRaw(ins)
override fun printWithLabel(ins: CodeUnit, label: String?): String? = null
override fun printRaw(ins: CodeUnit) = prefix + parent.printRaw(ins) + suffix
}
@ -40,7 +42,7 @@ private abstract class WrappedMode(
private val prefix: String = "",
private val suffix: String = ""
) : Mode by parent {
override fun printWithLabel(ins: CodeUnit, gameData: GameData): String? = parent.printWithLabel(ins, gameData)?.let { prefix + it + suffix }
override fun printWithLabel(ins: CodeUnit, label: String?): String? = parent.printWithLabel(ins, label)?.let { prefix + it + suffix }
override fun printRaw(ins: CodeUnit) = prefix + parent.printRaw(ins) + suffix
}
@ -51,7 +53,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, gameData: GameData): String? = get(ins.preState) { printWithLabel(ins, gameData) }
override fun printWithLabel(ins: CodeUnit, label: String?): String? = get(ins.preState) { printWithLabel(ins, label) }
override fun printRaw(ins: CodeUnit) = get(ins.preState) { printRaw(ins) }
@ -113,17 +115,17 @@ private abstract class DataValueMode(private val length: UInt, protected val typ
}
private class DataByteMode(type: DataValueType) : DataValueMode(1u, type) {
override fun printRaw(ins: CodeUnit) = "$" + toHex(ins.byte)
override fun printRaw(ins: CodeUnit) = "$" + ins.byte.toHex()
override fun referencedAddress(ins: CodeUnit): SnesAddress? = type.resolve(ins.byte, ins.preState, ins.memory)
}
private class DataWordMode(type: DataValueType) : DataValueMode(2u, type) {
override fun printRaw(ins: CodeUnit) = "$" + toHex(ins.word)
override fun printRaw(ins: CodeUnit) = "$" + ins.word.toHex()
override fun referencedAddress(ins: CodeUnit): SnesAddress? = type.resolve(ins.word, ins.preState, ins.memory)
}
private class DataLongMode(type: DataValueType) : DataValueMode(3u, type) {
override fun printRaw(ins: CodeUnit) = "$" + toHex(ins.long)
override fun printRaw(ins: CodeUnit) = "$" + ins.long.toHex()
override fun referencedAddress(ins: CodeUnit): SnesAddress? = type.resolve(ins.long, ins.preState, ins.memory)
}
@ -139,7 +141,7 @@ object Immediate8 : Mode {
override val canHaveLabel = false
override val showLengthSuffix = false
override fun operandLength(state: State) = 1u
override fun printRaw(ins: CodeUnit) = "#$" + toHex(ins.byte)
override fun printRaw(ins: CodeUnit) = "#$" + ins.byte.toHex()
override fun referencedAddress(ins: CodeUnit): SnesAddress? = null
}
@ -147,35 +149,35 @@ object Immediate16 : Mode {
override val canHaveLabel = false
override val showLengthSuffix = false
override fun operandLength(state: State) = 2u
override fun printRaw(ins: CodeUnit) = "#$" + toHex(ins.word)
override fun printRaw(ins: CodeUnit) = "#$" + ins.word.toHex()
override fun referencedAddress(ins: CodeUnit): SnesAddress? = null
}
object Direct : Mode {
override val canHaveLabel = true
override fun operandLength(state: State) = 1u
override fun printRaw(ins: CodeUnit) = "$" + toHex(ins.byte)
override fun printRaw(ins: CodeUnit) = "$" + ins.byte.toHex()
override fun referencedAddress(ins: CodeUnit) = ins.preState?.resolveDirectPage(ins.byte)
}
object Absolute : Mode {
override val canHaveLabel = true
override fun operandLength(state: State) = 2u
override fun printRaw(ins: CodeUnit) = "$" + toHex(ins.word)
override fun printRaw(ins: CodeUnit) = "$" + ins.word.toHex()
override fun referencedAddress(ins: CodeUnit) = ins.preState?.resolveAbsoluteData(ins.word)
}
object AbsoluteCode : Mode {
override val canHaveLabel = true
override fun operandLength(state: State) = 2u
override fun printRaw(ins: CodeUnit) = "$" + toHex(ins.word)
override fun printRaw(ins: CodeUnit) = "$" + ins.word.toHex()
override fun referencedAddress(ins: CodeUnit) = ins.preState?.resolveAbsoluteCode(ins.word)
}
object AbsoluteLong : Mode {
override val canHaveLabel = true
override fun operandLength(state: State) = 3u
override fun printRaw(ins: CodeUnit) = "$" + toHex(ins.long)
override fun printRaw(ins: CodeUnit) = "$" + ins.long.toHex()
override fun referencedAddress(ins: CodeUnit) = ins.memory.toCanonical(SnesAddress(ins.long))
}

View File

@ -1,5 +1,7 @@
package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.memory.SnesAddress
class Segment (val start: SnesAddress, val end: SegmentEnd, val instructions: List<Instruction>)
class SegmentEnd(val address: SnesAddress, val local: List<State> = emptyList(), val remote: List<State> = emptyList(), val returnAddress: SnesAddress? = null, val returning: Boolean = false)

View File

@ -1,34 +0,0 @@
package com.smallhacker.disbrowser.asm
import com.fasterxml.jackson.annotation.JsonCreator
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 SnesAddress(val value: UInt24) : Comparable<SnesAddress> {
operator fun plus(offset: Int) = SnesAddress(value + offset)
operator fun minus(offset: Int) = SnesAddress(value - offset)
operator fun inc() = plus(1)
operator fun dec() = minus(1)
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): SnesAddress = SnesAddress((this.value and 0xFF_0000u) or value.toUInt24())
override fun compareTo(other: SnesAddress) = value.toUInt().compareTo(other.value.toUInt())
infix fun distanceTo(other: SnesAddress) = Math.abs(value.toInt() - other.value.toInt()).toUInt()
companion object {
@JvmStatic
@JsonCreator
fun parse(address: String): SnesAddress? = tryParseInt(address, 16)
?.let { SnesAddress(it.toUInt24()) }
}
}
fun address(snesAddress: Int) = SnesAddress(snesAddress.toUInt24())

View File

@ -1,9 +1,12 @@
package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.ImmStack
import com.smallhacker.util.ImmStack
import com.smallhacker.disbrowser.game.GameData
import com.smallhacker.disbrowser.immStack
import com.smallhacker.disbrowser.util.toUInt24
import com.smallhacker.util.immStack
import com.smallhacker.disbrowser.memory.SnesAddress
import com.smallhacker.disbrowser.memory.SnesMemory
import com.smallhacker.util.VagueNumber
import com.smallhacker.util.toUInt24
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)

View File

@ -2,12 +2,14 @@ package com.smallhacker.disbrowser.disassembler
import com.smallhacker.disbrowser.asm.*
import com.smallhacker.disbrowser.game.*
import com.smallhacker.disbrowser.util.LifoQueue
import com.smallhacker.disbrowser.util.mutableMultiMap
import com.smallhacker.disbrowser.util.putSingle
import com.smallhacker.disbrowser.memory.*
import com.smallhacker.util.LifoQueue
import com.smallhacker.util.mutableMultiMap
import com.smallhacker.util.putSingle
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
object Disassembler {
fun disassemble(initialState: State, gameData: GameData, global: Boolean): Disassembly {
val seen = HashSet<SnesAddress>()

View File

@ -1,13 +1,13 @@
package com.smallhacker.disbrowser.game
import com.smallhacker.disbrowser.asm.SnesMemory
import com.smallhacker.disbrowser.memory.SnesMemory
import com.smallhacker.disbrowser.util.JsonFile
class Game(
val id: String,
val memory: SnesMemory,
val gameData: GameData,
private val gameDataFile: JsonFile<GameData>
val id: String,
val memory: SnesMemory,
val gameData: GameData,
private val gameDataFile: JsonFile<GameData>
) {
fun saveGameData() {
gameData.cleanUp()

View File

@ -6,9 +6,10 @@ 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 com.smallhacker.disbrowser.memory.*
import com.smallhacker.util.joinNullableBytes
import com.smallhacker.util.removeIf
import com.smallhacker.util.toUInt24
import java.util.*
class GameData {

View File

@ -1,6 +1,6 @@
package com.smallhacker.disbrowser.game
import com.smallhacker.disbrowser.asm.SnesMemory
import com.smallhacker.disbrowser.memory.SnesMemory
import com.smallhacker.disbrowser.util.jsonFile
import org.glassfish.jersey.server.ResourceConfig
import java.nio.file.Files

View File

@ -1,9 +1,9 @@
package com.smallhacker.disbrowser.resource
import com.smallhacker.disbrowser.*
import com.smallhacker.disbrowser.asm.SnesAddress
import com.smallhacker.disbrowser.asm.VagueNumber
import com.smallhacker.util.VagueNumber
import com.smallhacker.disbrowser.game.getGameSource
import com.smallhacker.disbrowser.memory.SnesAddress
import java.nio.charset.StandardCharsets
import javax.ws.rs.GET
import javax.ws.rs.Path

View File

@ -1,9 +1,9 @@
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.getGameSource
import com.smallhacker.disbrowser.memory.SnesAddress
import javax.ws.rs.Consumes
import javax.ws.rs.POST
import javax.ws.rs.Path

View File

@ -0,0 +1,64 @@
package com.smallhacker.disbrowser.util
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.module.SimpleModule
typealias KSerializer<T> = T.(gen: JsonGenerator, serializers: SerializerProvider) -> Unit
typealias KDeserializer<T> = JsonParser.(ctxt: DeserializationContext) -> T
typealias KKeyDeserializer<T> = String.(ctxt: DeserializationContext) -> T
interface JsonTypeMapper<T> {
val type: Class<T>
fun serialize(value: T, gen: JsonGenerator, serializers: SerializerProvider)
fun deserialize(p: JsonParser, ctxt: DeserializationContext): T
fun keyDeserialize(value: String, ctxt: DeserializationContext): T
}
inline fun <reified T> jsonTypeMapper(
noinline serializer: KSerializer<T>,
noinline deserializer: KDeserializer<T>,
noinline keyDeserializer: KKeyDeserializer<T>
) =
object : JsonTypeMapper<T> {
override val type: Class<T> = T::class.java
override fun serialize(value: T, gen: JsonGenerator, serializers: SerializerProvider) =
serializer(value, gen, serializers)
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) =
deserializer(p, ctxt)
override fun keyDeserialize(value: String, ctxt: DeserializationContext) =
keyDeserializer(value, ctxt)
}
inline fun <reified T : Any> ObjectMapper.addMapper(
noinline serializer: KSerializer<T>,
noinline deserializer: KDeserializer<T>,
noinline keyDeserializer: KKeyDeserializer<T>
) = addMapper(jsonTypeMapper(serializer, deserializer, keyDeserializer))
fun <T : Any> ObjectMapper.addMapper(mapper: JsonTypeMapper<T>) =
registerModule(object : SimpleModule() {
init {
val serializer = object : JsonSerializer<T>() {
override fun serialize(value: T, gen: JsonGenerator, serializers: SerializerProvider) =
mapper.serialize(value, gen, serializers)
}
val deserializer = object : JsonDeserializer<T>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) =
mapper.deserialize(p, ctxt)
}
val keyDeserializer = object : KeyDeserializer() {
override fun deserializeKey(key: String, ctxt: DeserializationContext) =
mapper.keyDeserialize(key, ctxt)
}
addSerializer(mapper.type, serializer)
addDeserializer(mapper.type, deserializer)
addKeySerializer(mapper.type, serializer)
addKeyDeserializer(mapper.type, keyDeserializer)
}
})!!

View File

@ -1,57 +0,0 @@
package com.smallhacker.disbrowser.util
abstract class UType(val bytes: Int, val name: String) {
val mask = (1 shl (bytes * 8)) - 1
}
class UVal<U : UType>(value: Int, val type: U) : Comparable<UVal<U>> {
val value = value and type.mask
override fun equals(other: Any?) = equalsBy(other, { value }, { type })
override fun toString() = "${type.name}(${toHex(true)})"
fun toHex(prefix: Boolean = false): String {
val digits = 2 * type.bytes
val start = if (prefix) "0x" else ""
val pattern = "%0${digits}x"
return start + String.format(pattern, value)
}
override fun compareTo(other: UVal<U>): Int = Integer.compare(value, other.value)
override fun hashCode() = hashOf(value, type)
//fun value(value: Int) = UVal(value, type)
//fun mutate(mutator: (Int) -> Int) = UVal(mutator(value), type)
object U1 : UType(1, "OldUByte")
object U2 : UType(2, "OldUWord")
object U3 : UType(3, "OldULong")
}
inline fun <reified U: UVal<*>> U.value(value: Int): U = UVal(value, type) as U
inline infix fun <reified U: UVal<*>> U.mutate(mutator: (Int) -> Int): U = value(mutator(value))
typealias OldUByte = UVal<UVal.U1>
typealias OldUWord = UVal<UVal.U2>
typealias OldULong = UVal<UVal.U3>
fun UVal<*>.toByte() = uByte(value)
fun UVal<*>.toWord() = uWord(value)
fun UVal<*>.toLong() = uLong(value)
private val UBYTE_CACHE = Array(256) { OldUByte(it, UVal.U1) }
fun uByte(value: Int): OldUByte = UBYTE_CACHE[value and 0xFF]
fun uWord(value: Int): OldUWord = UVal(value, UVal.U2)
fun uLong(value: Int): OldULong = UVal(value, UVal.U3)
inline infix fun <reified U: UVal<V>, V: UType> U.left(count: Int): U = mutate { it shl count }
inline infix fun <reified U: UVal<V>, V: UType> U.right(count: Int): U = mutate { it ushr count }
inline infix fun <reified U: UVal<V>, V: UType> U.and(other: U): U = mutate { it and other.value }
inline infix fun <reified U: UVal<V>, V: UType> U.or(other: U): U = mutate { it or other.value }
inline operator fun <reified U: UVal<*>> U.not(): U = mutate { it.inv() }
inline infix operator fun <reified U: UVal<V>, V: UType> U.plus(other: U): U = mutate { it + other.value }
inline infix operator fun <reified U: UVal<V>, V: UType> U.minus(other: U): U = mutate { it - other.value }

View File

@ -3,10 +3,15 @@ package com.smallhacker.disbrowser.util
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.smallhacker.disbrowser.memory.SnesAddress
import java.nio.file.Path
val jsonMapper: ObjectMapper by lazy {
jacksonObjectMapper()
.addMapper(
{ gen, _ -> gen.writeString(this.toSimpleString()) },
{ SnesAddress.parse(text)!! },
{ SnesAddress.parse(this)!! })
}
interface JsonFile<T> {

View File

@ -1,99 +0,0 @@
package com.smallhacker.disbrowser.util
inline fun <reified T> T.equalsBy(other: Any?, vararg values: T.() -> Any?) = when (other) {
!is T -> false
else -> values.asSequence()
.map { it(this) to it(other) }
.all { it.first == it.second }
}
fun tryParseInt(s: String, radix: Int = 10): Int? {
return try {
Integer.parseInt(s, radix)
} catch (e: NumberFormatException) {
null
}
}
fun toHex(value: UInt, bytes: UInt): String {
if (bytes == 0u) {
return ""
}
val digits = 2u * bytes
val pattern = "%0${digits}x"
return String.format(pattern, value.toInt())
}
fun toHex(value: UInt24) = toHex(value.toUInt(), 3u)
fun toHex(value: UShort) = toHex(value.toUInt(), 2u)
fun toHex(value: UByte) = toHex(value.toUInt(), 1u)
fun joinBytes(vararg bytes: UByte) = bytes
.asSequence()
.mapIndexed { index, v -> v.toUInt() shl (index * 8) }
.reduce { a, b -> a or b }
fun joinNullableBytes(vararg bytes: UByte?): UInt? {
if (bytes.any { it == null }) {
return null
}
return bytes
.asSequence()
.mapIndexed { index, v -> v!!.toUInt() shl (index * 8) }
.reduce { a, b -> a or b }
}
inline class UInt24(private val data: UInt) {
fun toUInt() = data and 0x00FF_FFFFu
fun toUShort() = toUInt().toUShort()
fun toUByte() = toUInt().toUByte()
fun toInt() = toUInt().toInt()
fun toShort() = toUShort().toShort()
fun toByte() = toUByte().toByte()
infix fun and(v: UInt24) = (data and v.data).toUInt24()
infix fun and(v: UInt) = (data and v).toUInt24()
infix fun or(v: UInt24) = (data or v.data).toUInt24()
infix fun or(v: UInt) = (data or v).toUInt24()
infix fun shl(v: Int) = (data shl v).toUInt24()
infix fun shr(v: Int) = (toUInt() shr v).toUInt24()
operator fun plus(v: UInt24) = (toUInt() + v.toUInt()).toUInt24()
operator fun plus(v: UInt) = (toUInt() + v).toUInt24()
operator fun plus(v: Int) = (toInt() + v).toUInt24()
operator fun minus(v: UInt24) = (toUInt() - v.toUInt()).toUInt24()
operator fun minus(v: UInt) = (toUInt() - v).toUInt24()
operator fun minus(v: Int) = (toInt() - v).toUInt24()
override fun toString() = data.toString()
}
fun UInt.toUInt24() = UInt24(this and 0x00FF_FFFFu)
fun UShort.toUInt24() = this.toUInt().toUInt24()
fun UByte.toUInt24() = this.toUInt().toUInt24()
fun Int.toUInt24() = this.toUInt().toUInt24()
fun Short.toUInt24() = this.toUInt().toUInt24()
fun Byte.toUInt24() = this.toUInt().toUInt24()
fun <T> List<T>.asReverseSequence(): Sequence<T> =
((size - 1) downTo 0).asSequence().map { this[it] }
fun <K, V> MutableMap<K, V>.removeIf(condition: (K, V) -> Boolean) {
this.asSequence()
.filter { condition(it.key, it.value) }
.map { it.key }
.toList()
.forEach { remove(it) }
}
fun hashOf(vararg values: Any?): Int {
return if (values.isEmpty()) {
0
} else {
values.asSequence()
.map { it.hashCode() }
.reduce { acc, v -> (acc * 31) + v }
}
}