diff --git a/build.gradle b/build.gradle index 0c36512..93569bc 100644 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Certainty.kt b/src/commonMain/kotlin/com/smallhacker/disbrowser/asm/Certainty.kt similarity index 100% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Certainty.kt rename to src/commonMain/kotlin/com/smallhacker/disbrowser/asm/Certainty.kt diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Continuation.kt b/src/commonMain/kotlin/com/smallhacker/disbrowser/asm/Continuation.kt similarity index 100% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Continuation.kt rename to src/commonMain/kotlin/com/smallhacker/disbrowser/asm/Continuation.kt diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Mnemonic.kt b/src/commonMain/kotlin/com/smallhacker/disbrowser/asm/Mnemonic.kt similarity index 100% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Mnemonic.kt rename to src/commonMain/kotlin/com/smallhacker/disbrowser/asm/Mnemonic.kt diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/MemorySpace.kt b/src/commonMain/kotlin/com/smallhacker/disbrowser/memory/MemorySpace.kt similarity index 82% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/MemorySpace.kt rename to src/commonMain/kotlin/com/smallhacker/disbrowser/memory/MemorySpace.kt index b33ae74..b81f3ea 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/MemorySpace.kt +++ b/src/commonMain/kotlin/com/smallhacker/disbrowser/memory/MemorySpace.kt @@ -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 = (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 = (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) { diff --git a/src/commonMain/kotlin/com/smallhacker/disbrowser/memory/SnesAddress.kt b/src/commonMain/kotlin/com/smallhacker/disbrowser/memory/SnesAddress.kt new file mode 100644 index 0000000..de3ddb4 --- /dev/null +++ b/src/commonMain/kotlin/com/smallhacker/disbrowser/memory/SnesAddress.kt @@ -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 { + 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()) \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/SnesMemory.kt b/src/commonMain/kotlin/com/smallhacker/disbrowser/memory/SnesMemory.kt similarity index 91% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/SnesMemory.kt rename to src/commonMain/kotlin/com/smallhacker/disbrowser/memory/SnesMemory.kt index a3cdc95..1060b5d 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/SnesMemory.kt +++ b/src/commonMain/kotlin/com/smallhacker/disbrowser/memory/SnesMemory.kt @@ -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 = NaiveRangeMap() + private val areas: MutableRangeMap = + 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? { diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/ImmStack.kt b/src/commonMain/kotlin/com/smallhacker/util/ImmStack.kt similarity index 95% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/ImmStack.kt rename to src/commonMain/kotlin/com/smallhacker/util/ImmStack.kt index 4a7aad5..2fd26f5 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/ImmStack.kt +++ b/src/commonMain/kotlin/com/smallhacker/util/ImmStack.kt @@ -1,4 +1,4 @@ -package com.smallhacker.disbrowser +package com.smallhacker.util interface ImmStack: Iterable { fun isEmpty(): Boolean diff --git a/src/commonMain/kotlin/com/smallhacker/util/Ints.kt b/src/commonMain/kotlin/com/smallhacker/util/Ints.kt new file mode 100644 index 0000000..1e8b505 --- /dev/null +++ b/src/commonMain/kotlin/com/smallhacker/util/Ints.kt @@ -0,0 +1,7 @@ +package com.smallhacker.util + +fun toIntOrNull(s: String, radix: Int = 10) = try { + s.toInt(radix) +} catch (e: NumberFormatException) { + null +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/ItemQueue.kt b/src/commonMain/kotlin/com/smallhacker/util/ItemQueue.kt similarity index 91% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/util/ItemQueue.kt rename to src/commonMain/kotlin/com/smallhacker/util/ItemQueue.kt index f629640..eaac75c 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/ItemQueue.kt +++ b/src/commonMain/kotlin/com/smallhacker/util/ItemQueue.kt @@ -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, so let's implement a simplistic // one ourselves for portability. diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/multiMap.kt b/src/commonMain/kotlin/com/smallhacker/util/MultiMap.kt similarity index 71% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/util/multiMap.kt rename to src/commonMain/kotlin/com/smallhacker/util/MultiMap.kt index 3b19d01..0c4d2d5 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/multiMap.kt +++ b/src/commonMain/kotlin/com/smallhacker/util/MultiMap.kt @@ -1,4 +1,4 @@ -package com.smallhacker.disbrowser.util +package com.smallhacker.util typealias MultiMap = Map> typealias MutableMultiMap = MutableMap> @@ -6,5 +6,5 @@ typealias MutableMultiMap = MutableMap> fun mutableMultiMap(): MutableMultiMap = HashMap() fun MutableMultiMap.putSingle(key: K, value: V) { - computeIfAbsent(key) { ArrayList() }.add(value) + getOrPut(key) { ArrayList() }.add(value) } \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/datatype/RangeMap.kt b/src/commonMain/kotlin/com/smallhacker/util/RangeMap.kt similarity index 86% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/datatype/RangeMap.kt rename to src/commonMain/kotlin/com/smallhacker/util/RangeMap.kt index 52f0459..f449300 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/datatype/RangeMap.kt +++ b/src/commonMain/kotlin/com/smallhacker/util/RangeMap.kt @@ -1,6 +1,4 @@ -package com.smallhacker.disbrowser.datatype - -import com.smallhacker.disbrowser.util.asReverseSequence +package com.smallhacker.util interface RangeMap, R : ClosedRange, V : Any> { operator fun get(key: K): V? @@ -11,6 +9,7 @@ interface MutableRangeMap, R : ClosedRange, V: Any> : Range operator fun set(keyRange: R, value: V): RangeMap } +// Just an oversimplified implementation for now. Can be heavily optimized if needed. class NaiveRangeMap, R : ClosedRange, V: Any> : MutableRangeMap { private val entries = ArrayList>() diff --git a/src/commonMain/kotlin/com/smallhacker/util/UInt24.kt b/src/commonMain/kotlin/com/smallhacker/util/UInt24.kt new file mode 100644 index 0000000..c13c5e4 --- /dev/null +++ b/src/commonMain/kotlin/com/smallhacker/util/UInt24.kt @@ -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() \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/VagueNumber.kt b/src/commonMain/kotlin/com/smallhacker/util/VagueNumber.kt similarity index 97% rename from src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/VagueNumber.kt rename to src/commonMain/kotlin/com/smallhacker/util/VagueNumber.kt index b1539cb..43ddd48 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/VagueNumber.kt +++ b/src/commonMain/kotlin/com/smallhacker/util/VagueNumber.kt @@ -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)) diff --git a/src/commonMain/kotlin/com/smallhacker/util/misc.kt b/src/commonMain/kotlin/com/smallhacker/util/misc.kt new file mode 100644 index 0000000..a5ec5ba --- /dev/null +++ b/src/commonMain/kotlin/com/smallhacker/util/misc.kt @@ -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 List.asReverseSequence(): Sequence = + ((size - 1) downTo 0).asSequence().map { this[it] } + +fun MutableMap.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 } + } +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/Grid.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/Grid.kt index 9023037..170c609 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/Grid.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/Grid.kt @@ -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, HtmlNode?>() diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/Service.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/Service.kt index 62a7904..5e37ba4 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/Service.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/Service.kt @@ -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) diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Disassembly.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Disassembly.kt index 1d8c53d..e22dc2a 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Disassembly.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Disassembly.kt @@ -1,5 +1,7 @@ package com.smallhacker.disbrowser.asm +import com.smallhacker.disbrowser.memory.SnesAddress + class Disassembly(lines: List) : Iterable { override fun iterator() = lineList.iterator() as Iterator diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Instruction.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Instruction.kt index 6cea94d..cfbb21b 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Instruction.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Instruction.kt @@ -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 { diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Mode.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Mode.kt index 46b2971..eb622a5 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Mode.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Mode.kt @@ -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)) } diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Segment.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Segment.kt index 005809a..7480483 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Segment.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/Segment.kt @@ -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) class SegmentEnd(val address: SnesAddress, val local: List = emptyList(), val remote: List = emptyList(), val returnAddress: SnesAddress? = null, val returning: Boolean = false) diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/SnesAddress.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/SnesAddress.kt deleted file mode 100644 index 30e06ae..0000000 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/SnesAddress.kt +++ /dev/null @@ -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 { - 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()) \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/State.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/State.kt index 38b6cfe..897db90 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/State.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/asm/State.kt @@ -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 = immStack(), val gameData: GameData) { val m: Boolean? get() = flags.getBoolean(0x20u) diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/disassembler/Disassembler.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/disassembler/Disassembler.kt index 26cf88a..6e89dda 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/disassembler/Disassembler.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/disassembler/Disassembler.kt @@ -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() diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/Game.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/Game.kt index ad3f663..edb4b68 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/Game.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/Game.kt @@ -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 + val id: String, + val memory: SnesMemory, + val gameData: GameData, + private val gameDataFile: JsonFile ) { fun saveGameData() { gameData.cleanUp() diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/GameData.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/GameData.kt index bf38c0b..f1eb213 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/GameData.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/GameData.kt @@ -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 { diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/GameSource.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/GameSource.kt index 93348b6..5c2226e 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/GameSource.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/game/GameSource.kt @@ -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 diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/resource/DisassemblyResource.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/resource/DisassemblyResource.kt index 0aa300b..f380a86 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/resource/DisassemblyResource.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/resource/DisassemblyResource.kt @@ -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 diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/resource/RestResource.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/resource/RestResource.kt index 5c0f5d7..0453904 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/resource/RestResource.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/resource/RestResource.kt @@ -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 diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/JsonTypeMapper.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/JsonTypeMapper.kt new file mode 100644 index 0000000..c88c2bc --- /dev/null +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/JsonTypeMapper.kt @@ -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.(gen: JsonGenerator, serializers: SerializerProvider) -> Unit +typealias KDeserializer = JsonParser.(ctxt: DeserializationContext) -> T +typealias KKeyDeserializer = String.(ctxt: DeserializationContext) -> T + +interface JsonTypeMapper { + val type: Class + fun serialize(value: T, gen: JsonGenerator, serializers: SerializerProvider) + fun deserialize(p: JsonParser, ctxt: DeserializationContext): T + fun keyDeserialize(value: String, ctxt: DeserializationContext): T +} + +inline fun jsonTypeMapper( + noinline serializer: KSerializer, + noinline deserializer: KDeserializer, + noinline keyDeserializer: KKeyDeserializer +) = + object : JsonTypeMapper { + override val type: Class = 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 ObjectMapper.addMapper( + noinline serializer: KSerializer, + noinline deserializer: KDeserializer, + noinline keyDeserializer: KKeyDeserializer +) = addMapper(jsonTypeMapper(serializer, deserializer, keyDeserializer)) + +fun ObjectMapper.addMapper(mapper: JsonTypeMapper) = + registerModule(object : SimpleModule() { + init { + val serializer = object : JsonSerializer() { + override fun serialize(value: T, gen: JsonGenerator, serializers: SerializerProvider) = + mapper.serialize(value, gen, serializers) + } + val deserializer = object : JsonDeserializer() { + 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) + } + })!! \ No newline at end of file diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/UVal.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/UVal.kt deleted file mode 100644 index c7b7d8c..0000000 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/UVal.kt +++ /dev/null @@ -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(value: Int, val type: U) : Comparable> { - 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): 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 > U.value(value: Int): U = UVal(value, type) as U -inline infix fun > U.mutate(mutator: (Int) -> Int): U = value(mutator(value)) - -typealias OldUByte = UVal -typealias OldUWord = UVal -typealias OldULong = UVal - -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 , V: UType> U.left(count: Int): U = mutate { it shl count } -inline infix fun , V: UType> U.right(count: Int): U = mutate { it ushr count } -inline infix fun , V: UType> U.and(other: U): U = mutate { it and other.value } -inline infix fun , V: UType> U.or(other: U): U = mutate { it or other.value } -inline operator fun > U.not(): U = mutate { it.inv() } - -inline infix operator fun , V: UType> U.plus(other: U): U = mutate { it + other.value } -inline infix operator fun , V: UType> U.minus(other: U): U = mutate { it - other.value } diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/json.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/json.kt index 0bf26e0..3d09437 100644 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/json.kt +++ b/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/json.kt @@ -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 { diff --git a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/misc.kt b/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/misc.kt deleted file mode 100644 index 2288bf9..0000000 --- a/src/jvmMain/kotlin/com/smallhacker/disbrowser/util/misc.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.smallhacker.disbrowser.util - -inline fun 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 List.asReverseSequence(): Sequence = - ((size - 1) downTo 0).asSequence().map { this[it] } - -fun MutableMap.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 } - } -} \ No newline at end of file