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 { dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-common' implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
} }
languageSettings {
enableLanguageFeature('InlineClasses')
useExperimentalAnnotation('kotlin.ExperimentalUnsignedTypes')
}
} }
commonTest { commonTest {
dependencies { dependencies {
@ -47,6 +51,10 @@ kotlin {
dependencies { dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-js' implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
} }
languageSettings {
enableLanguageFeature('InlineClasses')
useExperimentalAnnotation('kotlin.ExperimentalUnsignedTypes')
}
} }
jsTest { jsTest {
dependencies { 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.util.UInt24
import com.smallhacker.disbrowser.util.joinBytes import com.smallhacker.util.joinBytes
import com.smallhacker.disbrowser.util.joinNullableBytes import com.smallhacker.util.joinNullableBytes
import com.smallhacker.disbrowser.util.toUInt24 import com.smallhacker.util.toUInt24
interface MemorySpace { interface MemorySpace {
val size: UInt val size: UInt
@ -22,7 +22,8 @@ object EmptyMemorySpace : ValidMemorySpace {
fun MemorySpace.asSequence(): Sequence<UByte?> = (0u until size).asSequence().map { this[it] } 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.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.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.range(start: SnesAddress, length: UInt): MemorySpace = range(start.value.toUInt(), length)
fun MemorySpace.validate(): ValidMemorySpace? { fun MemorySpace.validate(): ValidMemorySpace? {
if (asSequence().any { it == null }) { 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.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.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.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()!! fun ValidMemorySpace.range(start: SnesAddress, length: UInt): ValidMemorySpace = range(start.value.toUInt(), length).validate()!!
class ArrayMemorySpace(private val bytes: UByteArray) : MemorySpace { 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 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 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 val size get() = parent.size
override fun get(address: UInt) = parent[address]!! override fun get(address: UInt) = parent[address]!!
} }
private class ReindexedMemorySpace( private class ReindexedMemorySpace(
private val parent: MemorySpace, private val parent: MemorySpace,
override val size: UInt, override val size: UInt,
private val mapper: (UInt) -> UInt private val mapper: (UInt) -> UInt
) : MemorySpace { ) : MemorySpace {
override fun get(address: UInt): UByte? { override fun get(address: UInt): UByte? {
if (address >= size) { 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.util.MutableRangeMap
import com.smallhacker.disbrowser.datatype.NaiveRangeMap import com.smallhacker.util.NaiveRangeMap
import com.smallhacker.disbrowser.util.toUInt24 import com.smallhacker.util.toUInt24
abstract class SnesMemory: MemorySpace { abstract class SnesMemory: MemorySpace {
override val size = 0x100_0000u 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) { protected fun add(start: UInt, canonicalStart: UInt, memorySpace: MemorySpace) {
val range = start until (start + memorySpace.size) 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? { 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> { interface ImmStack<E>: Iterable<E> {
fun isEmpty(): Boolean 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 // The Kotlin standard library does not contain a counterpart to Java's ArrayDeque<E>, so let's implement a simplistic
// one ourselves for portability. // 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 MultiMap<K, V> = Map<K, List<V>>
typealias MutableMultiMap<K, V> = MutableMap<K, MutableList<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(): MutableMultiMap<K, V> = HashMap()
fun <K, V> MutableMultiMap<K, V>.putSingle(key: K, value: V) { 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 package com.smallhacker.util
import com.smallhacker.disbrowser.util.asReverseSequence
interface RangeMap<K : Comparable<K>, R : ClosedRange<K>, V : Any> { interface RangeMap<K : Comparable<K>, R : ClosedRange<K>, V : Any> {
operator fun get(key: K): V? 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> 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> { class NaiveRangeMap<K : Comparable<K>, R : ClosedRange<K>, V: Any> : MutableRangeMap<K, R, V> {
private val entries = ArrayList<Pair<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) { inline class VagueNumber(private val valueAndCertainty: ULong) {
private constructor(value: UInt, certainty: UInt) : this(value.toULong() or (certainty.toULong() shl 32)) 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 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.game.Game
import com.smallhacker.disbrowser.memory.SnesAddress
import kotlin.collections.HashMap
import kotlin.collections.asSequence
import kotlin.collections.set
class Grid { class Grid {
private val arrowCells = HashMap<Pair<Int, Int>, HtmlNode?>() private val arrowCells = HashMap<Pair<Int, Int>, HtmlNode?>()

View File

@ -1,9 +1,13 @@
package com.smallhacker.disbrowser package com.smallhacker.disbrowser
import com.smallhacker.disbrowser.asm.* import com.smallhacker.disbrowser.asm.*
import com.smallhacker.disbrowser.game.Game
import com.smallhacker.disbrowser.disassembler.Disassembler 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 import kotlin.reflect.KMutableProperty1
private val RESET_VECTOR_LOCATION = address(0x00_FFFC) private val RESET_VECTOR_LOCATION = address(0x00_FFFC)

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
package com.smallhacker.disbrowser.asm package com.smallhacker.disbrowser.asm
import com.smallhacker.disbrowser.memory.SnesAddress
class Segment (val start: SnesAddress, val end: SegmentEnd, val instructions: List<Instruction>) 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) 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 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.game.GameData
import com.smallhacker.disbrowser.immStack import com.smallhacker.util.immStack
import com.smallhacker.disbrowser.util.toUInt24 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) { 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 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.asm.*
import com.smallhacker.disbrowser.game.* import com.smallhacker.disbrowser.game.*
import com.smallhacker.disbrowser.util.LifoQueue import com.smallhacker.disbrowser.memory.*
import com.smallhacker.disbrowser.util.mutableMultiMap import com.smallhacker.util.LifoQueue
import com.smallhacker.disbrowser.util.putSingle import com.smallhacker.util.mutableMultiMap
import com.smallhacker.util.putSingle
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap
object Disassembler { object Disassembler {
fun disassemble(initialState: State, gameData: GameData, global: Boolean): Disassembly { fun disassemble(initialState: State, gameData: GameData, global: Boolean): Disassembly {
val seen = HashSet<SnesAddress>() val seen = HashSet<SnesAddress>()

View File

@ -1,13 +1,13 @@
package com.smallhacker.disbrowser.game package com.smallhacker.disbrowser.game
import com.smallhacker.disbrowser.asm.SnesMemory import com.smallhacker.disbrowser.memory.SnesMemory
import com.smallhacker.disbrowser.util.JsonFile import com.smallhacker.disbrowser.util.JsonFile
class Game( class Game(
val id: String, val id: String,
val memory: SnesMemory, val memory: SnesMemory,
val gameData: GameData, val gameData: GameData,
private val gameDataFile: JsonFile<GameData> private val gameDataFile: JsonFile<GameData>
) { ) {
fun saveGameData() { fun saveGameData() {
gameData.cleanUp() 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.JsonSubTypes.Type
import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.smallhacker.disbrowser.asm.* import com.smallhacker.disbrowser.asm.*
import com.smallhacker.disbrowser.util.joinNullableBytes import com.smallhacker.disbrowser.memory.*
import com.smallhacker.disbrowser.util.removeIf import com.smallhacker.util.joinNullableBytes
import com.smallhacker.disbrowser.util.toUInt24 import com.smallhacker.util.removeIf
import com.smallhacker.util.toUInt24
import java.util.* import java.util.*
class GameData { class GameData {

View File

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

View File

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

View File

@ -1,9 +1,9 @@
package com.smallhacker.disbrowser.resource package com.smallhacker.disbrowser.resource
import com.smallhacker.disbrowser.Service import com.smallhacker.disbrowser.Service
import com.smallhacker.disbrowser.asm.SnesAddress
import com.smallhacker.disbrowser.asm.MetadataLine import com.smallhacker.disbrowser.asm.MetadataLine
import com.smallhacker.disbrowser.game.getGameSource import com.smallhacker.disbrowser.game.getGameSource
import com.smallhacker.disbrowser.memory.SnesAddress
import javax.ws.rs.Consumes import javax.ws.rs.Consumes
import javax.ws.rs.POST import javax.ws.rs.POST
import javax.ws.rs.Path 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.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import com.smallhacker.disbrowser.memory.SnesAddress
import java.nio.file.Path import java.nio.file.Path
val jsonMapper: ObjectMapper by lazy { val jsonMapper: ObjectMapper by lazy {
jacksonObjectMapper() jacksonObjectMapper()
.addMapper(
{ gen, _ -> gen.writeString(this.toSimpleString()) },
{ SnesAddress.parse(text)!! },
{ SnesAddress.parse(this)!! })
} }
interface JsonFile<T> { 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 }
}
}