mirror of
https://github.com/Smallhacker/disbrowser.git
synced 2024-12-27 05:33:12 +00:00
Partial move of stuff to commonMain
This commit is contained in:
parent
f0bd8d5170
commit
852632e053
@ -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 {
|
||||
|
@ -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) {
|
@ -1,10 +1,10 @@
|
||||
package com.smallhacker.disbrowser.asm
|
||||
package com.smallhacker.disbrowser.memory
|
||||
|
||||
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
|
||||
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)
|
||||
@ -13,20 +13,17 @@ data class SnesAddress(val value: UInt24) : Comparable<SnesAddress> {
|
||||
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 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) = Math.abs(value.toInt() - other.value.toInt()).toUInt()
|
||||
infix fun distanceTo(other: SnesAddress) = abs(value.toInt() - other.value.toInt()).toUInt()
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@JsonCreator
|
||||
fun parse(address: String): SnesAddress? = tryParseInt(address, 16)
|
||||
fun parse(address: String): SnesAddress? = toIntOrNull(address, 16)
|
||||
?.let { SnesAddress(it.toUInt24()) }
|
||||
}
|
||||
}
|
@ -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? {
|
@ -1,4 +1,4 @@
|
||||
package com.smallhacker.disbrowser
|
||||
package com.smallhacker.util
|
||||
|
||||
interface ImmStack<E>: Iterable<E> {
|
||||
fun isEmpty(): Boolean
|
7
src/commonMain/kotlin/com/smallhacker/util/Ints.kt
Normal file
7
src/commonMain/kotlin/com/smallhacker/util/Ints.kt
Normal file
@ -0,0 +1,7 @@
|
||||
package com.smallhacker.util
|
||||
|
||||
fun toIntOrNull(s: String, radix: Int = 10) = try {
|
||||
s.toInt(radix)
|
||||
} catch (e: NumberFormatException) {
|
||||
null
|
||||
}
|
@ -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.
|
@ -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)
|
||||
}
|
@ -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>>()
|
||||
|
33
src/commonMain/kotlin/com/smallhacker/util/UInt24.kt
Normal file
33
src/commonMain/kotlin/com/smallhacker/util/UInt24.kt
Normal 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()
|
@ -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))
|
52
src/commonMain/kotlin/com/smallhacker/util/misc.kt
Normal file
52
src/commonMain/kotlin/com/smallhacker/util/misc.kt
Normal 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 }
|
||||
}
|
||||
}
|
@ -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?>()
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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>()
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
})!!
|
@ -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 }
|
@ -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> {
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user