2
0
Him188 6 жил өмнө
parent
commit
55e6c93419
21 өөрчлөгдсөн 881 нэмэгдсэн , 1979 устгасан
  1. 0 510
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceInput.kt
  2. 0 292
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceOutput.kt
  3. 1 3
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceStruct.kt
  4. 653 0
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt
  5. 0 376
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceEncoder.kt
  6. 19 1
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt
  7. 37 36
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt
  8. 0 12
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/uni.kt
  9. 2 2
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
  10. 4 1
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
  11. 5 1
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
  12. 69 67
      mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt
  13. 40 0
      mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt
  14. 0 80
      mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceEncoderTest.kt
  15. 1 1
      mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt
  16. 0 8
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverAndroid.kt
  17. 26 0
      mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt
  18. 1 1
      mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt
  19. 0 287
      mirai-debug/src/test/kotlin/jceTest/JceInputTest.kt
  20. 0 295
      mirai-debug/src/test/kotlin/jceTest/JceOutputTest.kt
  21. 23 6
      mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt

+ 0 - 510
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceInput.kt

@@ -1,510 +0,0 @@
-package net.mamoe.mirai.qqandroid.io
-
-import kotlinx.io.charsets.Charset
-import kotlinx.io.core.*
-import kotlinx.io.pool.ObjectPool
-import kotlinx.serialization.DeserializationStrategy
-import net.mamoe.mirai.qqandroid.io.serialization.Jce
-import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
-import net.mamoe.mirai.utils.io.DebugLogger
-import net.mamoe.mirai.utils.io.readIoBuffer
-import net.mamoe.mirai.utils.io.readString
-import net.mamoe.mirai.utils.io.toIoBuffer
-
-@UseExperimental(ExperimentalUnsignedTypes::class)
-inline class JceHead(private val value: Long) {
-    constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong())
-
-    val tag: Int get() = (value ushr 32).toInt()
-    val type: Byte get() = value.toUInt().toByte()
-
-    override fun toString(): String {
-        return "JceHead(tag=$tag, type=$type)"
-    }Z
-}
-
-fun <J : JceStruct> ByteArray.readJceStruct(
-    deserializer: DeserializationStrategy<J>,
-    tag: Int = 0,
-    charset: Charset = CharsetUTF8
-): J {
-    this.asJceInput(charset).use {
-        return Jce.byCharSet(charset).load(deserializer, this.)
-    }
-}
-
-fun <J : JceStruct> ByteReadPacket.readJceStruct(factory: JceStruct.Factory<J>, tag: Int = 0, charset: Charset = CharsetUTF8): J {
-    this.asJceInput(charset).use {
-        return it.readJceStruct(factory, tag)
-    }
-}
-
-fun ByteArray.asJceInput(charset: Charset = CharsetUTF8): JceInput =
-    JceInput(this.toIoBuffer(), charset)
-
-fun <J : JceStruct> ByteReadPacket.readJceRequestBufferMapVersion2ToJceStruct(factory: JceStruct.Factory<J>, charset: Charset = CharsetUTF8): J {
-    this.use {
-        val bytes =
-            readJceRequestBufferMapVersion2(charset).values.also { if (it.size != 1) DebugLogger.debug("读取 jce RequestPacket 时发现多个包在 map 中") }.firstOrNull()
-                ?: error("empty request map")
-        return bytes.readJceStruct(factory, 0)
-    }
-}
-
-fun <J : JceStruct> ByteReadPacket.readJceRequestBufferMapVersion3ToJceStruct(factory: JceStruct.Factory<J>, charset: Charset = CharsetUTF8): J {
-    this.use {
-        val bytes = readJceRequestBufferMapVersion3(charset).values.firstOrNull() ?: error("empty request map")
-        return bytes.readJceStruct(factory, 0, charset)
-    }
-}
-
-fun ByteReadPacket.readJceRequestBufferMapVersion2(charset: Charset = CharsetUTF8): Map<String, ByteArray> {
-    this.use {
-        discardExact(8)
-        val request = this.asJceInput(charset).use {
-            Jce
-            RequestPacket.serializer()
-        }
-        val map = request.sBuffer.asJceInput(charset).withUse {
-            readNestedMap<String, String, ByteArray>(0)
-        }
-        return map.mapValues { it.value.values.first() }
-    }
-}
-
-fun ByteReadPacket.readJceRequestBufferMapVersion3(charset: Charset = CharsetUTF8): Map<String, ByteArray> {
-    this.use {
-        discardExact(8)
-        val request = this.asJceInput(charset).use { RequestPacket.newInstanceFrom(it) }
-        return request.sBuffer.asJceInput(charset).withUse { readMap(0) }
-    }
-}
-
-fun ByteReadPacket.asJceInput(charset: Charset = CharsetUTF8): JceInput =
-    JceInput(this.readIoBuffer(), charset)
-
-inline fun <R> IoBuffer.useIoBuffer(block: IoBuffer.() -> R): R {
-    return try {
-        block(this)
-    } catch (first: Throwable) {
-        throw first
-    } finally {
-        release(IoBuffer.Pool)
-    }
-}
-
-inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R {
-    return try {
-        block(this)
-    } catch (first: Throwable) {
-        throw first
-    } finally {
-        close()
-    }
-}
-
-@Suppress("MemberVisibilityCanBePrivate")
-@UseExperimental(ExperimentalUnsignedTypes::class)
-class JceInput(
-    @PublishedApi
-    internal val input: IoBuffer,
-    private val charset: Charset = CharsetGBK,
-    private val pool: ObjectPool<IoBuffer> = IoBuffer.Pool
-) : Closeable {
-
-    constructor(input: Input) : this(IoBuffer.Pool.borrow().also { input.readAvailable(it) })
-
-    override fun close() {
-        input.release(pool)
-    }
-
-    @PublishedApi
-    internal fun readHead(): JceHead = input.readHead()
-
-    @PublishedApi
-    internal fun peakHead(): JceHead = input.makeView().readHead()
-
-    private fun IoBuffer.readHead(): JceHead {
-        val var2 = readUByte()
-        val type = var2 and 15u
-        var tag = var2.toUInt() shr 4
-        if (tag == 15u)
-            tag = readUByte().toUInt()
-        return JceHead(tag = tag.toInt(), type = type.toByte())
-    }
-
-    fun read(default: Byte, tag: Int): Byte = readByteOrNull(tag) ?: default
-    fun read(default: Short, tag: Int): Short = readShortOrNull(tag) ?: default
-    fun read(default: Int, tag: Int): Int = readIntOrNull(tag) ?: default
-    fun read(default: Long, tag: Int): Long = readLongOrNull(tag) ?: default
-    fun read(default: Float, tag: Int): Float = readFloatOrNull(tag) ?: default
-    fun read(default: Double, tag: Int): Double = readDoubleOrNull(tag) ?: default
-    fun read(default: Boolean, tag: Int): Boolean = readBooleanOrNull(tag) ?: default
-
-    fun read(default: ByteArray, tag: Int): ByteArray = readByteArrayOrNull(tag) ?: default
-    fun read(default: ShortArray, tag: Int): ShortArray = readShortArrayOrNull(tag) ?: default
-    fun read(default: IntArray, tag: Int): IntArray = readIntArrayOrNull(tag) ?: default
-    fun read(default: LongArray, tag: Int): LongArray = readLongArrayOrNull(tag) ?: default
-    fun read(default: FloatArray, tag: Int): FloatArray = readFloatArrayOrNull(tag) ?: default
-    fun read(default: DoubleArray, tag: Int): DoubleArray = readDoubleArrayOrNull(tag) ?: default
-    fun read(default: BooleanArray, tag: Int): BooleanArray = readBooleanArrayOrNull(tag) ?: default
-
-    fun readBoolean(tag: Int): Boolean = readBooleanOrNull(tag) ?: error("cannot find tag $tag")
-    fun readByte(tag: Int): Byte = readByteOrNull(tag) ?: error("cannot find tag $tag")
-    fun readShort(tag: Int): Short = readShortOrNull(tag) ?: error("cannot find tag $tag")
-    fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag")
-    fun readLong(tag: Int): Long = readLongOrNull(tag) ?: error("cannot find tag $tag")
-    fun readFloat(tag: Int): Float = readFloatOrNull(tag) ?: error("cannot find tag $tag")
-    fun readDouble(tag: Int): Double = readDoubleOrNull(tag) ?: error("cannot find tag $tag")
-
-    fun readString(tag: Int): String = readStringOrNull(tag) ?: error("cannot find tag $tag")
-
-    fun readByteArray(tag: Int): ByteArray = readByteArrayOrNull(tag) ?: error("cannot find tag $tag")
-    fun readShortArray(tag: Int): ShortArray = readShortArrayOrNull(tag) ?: error("cannot find tag $tag")
-    fun readLongArray(tag: Int): LongArray = readLongArrayOrNull(tag) ?: error("cannot find tag $tag")
-    fun readFloatArray(tag: Int): FloatArray = readFloatArrayOrNull(tag) ?: error("cannot find tag $tag")
-    fun readDoubleArray(tag: Int): DoubleArray = readDoubleArrayOrNull(tag) ?: error("cannot find tag $tag")
-    fun readIntArray(tag: Int): IntArray = readIntArrayOrNull(tag) ?: error("cannot find tag $tag")
-    fun readBooleanArray(tag: Int): BooleanArray = readBooleanArrayOrNull(tag) ?: error("cannot find tag $tag")
-    fun <K, V> readMap(defaultKey: K, defaultValue: V, tag: Int): Map<K, V> = readMapOrNull(defaultKey, defaultValue, tag) ?: error("cannot find tag $tag")
-    inline fun <reified K, reified V> readMap(tag: Int): Map<K, V> = readMapOrNull(tag) ?: error("cannot find tag $tag")
-    inline fun <reified K, reified InnerK, reified InnerV> readNestedMap(tag: Int): Map<K, Map<InnerK, InnerV>> =
-        readNestedMapOrNull(tag) ?: error("cannot find tag $tag")
-
-    inline fun <reified J : JceStruct> readStringToJceStructMap(factory: JceStruct.Factory<J>, tag: Int): Map<String, J> =
-        readStringToJceStructMapOrNull(factory, tag) ?: error("cannot find tag $tag")
-
-    fun <T> readList(defaultElement: T, tag: Int): List<T> = readListOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
-    inline fun <reified J: JceStruct> readJceStructList(factory: JceStruct.Factory<J>, tag: Int): List<J> = readJceStructListOrNull( factory, tag) ?: error("cannot find tag $tag")
-    inline fun <reified T> readList(tag: Int): List<T> = readListOrNull( tag) ?: error("cannot find tag $tag")
-    inline fun <reified T> readSimpleArray(defaultElement: T, tag: Int): Array<T> = readArrayOrNull(defaultElement, tag) ?: error("cannot find tag $tag")
-    fun <J : JceStruct> readJceStruct(factory: JceStruct.Factory<J>, tag: Int): J = readJceStructOrNull(factory, tag) ?: error("cannot find tag $tag")
-    fun readStringArray(tag: Int): Array<String> = readArrayOrNull("", tag) ?: error("cannot find tag $tag")
-
-    fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) {
-        return when (it.type.toInt()) {
-            12 -> 0
-            0 -> input.readByte().toLong()
-            1 -> input.readShort().toLong()
-            2 -> input.readInt().toLong()
-            3 -> input.readLong()
-            else -> error("type mismatch: ${it.type}")
-        }
-    }
-
-    fun readShortOrNull(tag: Int): Short? = skipToTagOrNull(tag) {
-        return when (it.type.toInt()) {
-            12 -> 0
-            0 -> input.readByte().toShort()
-            1 -> input.readShort()
-            else -> error("type mismatch: ${it.type}")
-        }
-    }
-
-    fun readIntOrNull(tag: Int): Int? = skipToTagOrNull(tag) {
-        return when (it.type.toInt()) {
-            12 -> 0
-            0 -> input.readByte().toInt()
-            1 -> input.readShort().toInt()
-            2 -> input.readInt()
-            else -> error("type mismatch: ${it.type}")
-        }
-    }
-
-    fun readByteOrNull(tag: Int): Byte? = skipToTagOrNull(tag) {
-        return when (it.type.toInt()) {
-            12 -> 0
-            0 -> input.readByte()
-            else -> error("type mismatch")
-        }
-    }
-
-    fun readFloatOrNull(tag: Int): Float? = skipToTagOrNull(tag) {
-        return when (it.type.toInt()) {
-            12 -> 0f
-            4 -> input.readFloat()
-            else -> error("type mismatch: ${it.type}")
-        }
-    }
-
-    fun readDoubleOrNull(tag: Int): Double? = skipToTagOrNull(tag) {
-        return when (it.type.toInt()) {
-            12 -> 0.0
-            4 -> input.readFloat().toDouble()
-            5 -> input.readDouble()
-            else -> error("type mismatch: ${it.type}")
-        }
-    }
-
-    fun readBooleanOrNull(tag: Int): Boolean? = this.readByteOrNull(tag)?.let { it.toInt() != 0 }
-
-    fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
-        when (it.type.toInt()) {
-            9 -> ByteArray(readInt(0)) { readByte(0) }
-            13 -> {
-                val head = readHead()
-                check(head.type.toInt() == 0) { "type mismatch" }
-                input.readBytes(readInt(0))
-            }
-            else -> error("type mismatch")
-        }
-    }
-
-    fun readShortArrayOrNull(tag: Int): ShortArray? = skipToTagOrNull(tag) {
-        require(it.type.toInt() == 9) { "type mismatch" }
-        ShortArray(readInt(0)) { readShort(0) }
-    }
-
-    fun readDoubleArrayOrNull(tag: Int): DoubleArray? = skipToTagOrNull(tag) {
-        require(it.type.toInt() == 9) { "type mismatch" }
-        DoubleArray(readInt(0)) { readDouble(0) }
-    }
-
-    fun readFloatArrayOrNull(tag: Int): FloatArray? = skipToTagOrNull(tag) {
-        require(it.type.toInt() == 9) { "type mismatch" }
-        FloatArray(readInt(0)) { readFloat(0) }
-    }
-
-    fun readIntArrayOrNull(tag: Int): IntArray? = skipToTagOrNull(tag) {
-        require(it.type.toInt() == 9) { "type mismatch" }
-        IntArray(readInt(0)) { readInt(0) }
-    }
-
-    fun readLongArrayOrNull(tag: Int): LongArray? = skipToTagOrNull(tag) {
-        require(it.type.toInt() == 9) { "type mismatch" }
-        LongArray(readInt(0)) { readLong(0) }
-    }
-
-    @Suppress("UNCHECKED_CAST")
-    inline fun <reified T> readArrayOrNull(tag: Int): Array<T>? = skipToTagOrNull(tag) {
-        require(it.type.toInt() == 9) { "type mismatch" }
-        Array(readInt(0)) { readSimpleObject<T>(0) }
-    }
-
-    @Suppress("UNCHECKED_CAST")
-    inline fun <reified T> readArrayOrNull(defaultElement: T, tag: Int): Array<T>? = skipToTagOrNull(tag) {
-        require(it.type.toInt() == 9) { "type mismatch" }
-        Array(readInt(0)) { readObject(defaultElement, 0) }
-    }
-
-    @Suppress("UNCHECKED_CAST")
-    inline fun <reified J : JceStruct> readJceStructArrayOrNull(factory: JceStruct.Factory<J>, tag: Int): Array<J>? = skipToTagOrNull(tag) {
-        require(it.type.toInt() == 9) { "type mismatch" }
-        Array(readInt(0)) { readJceStruct(factory, 0) }
-    }
-
-    fun readBooleanArrayOrNull(tag: Int): BooleanArray? = skipToTagOrNull(tag) {
-        require(it.type.toInt() == 9) { "type mismatch" }
-        BooleanArray(readInt(0)) { readBoolean(0) }
-    }
-
-    fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
-        return when (head.type.toInt()) {
-            6 -> input.readString(input.readUByte().toInt(), charset = charset)
-            7 -> input.readString(input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } }, charset = charset)
-            else -> error("type mismatch: ${head.type}")
-        }
-    }
-
-    inline fun <reified J : JceStruct> readStringToJceStructMapOrNull(factory: JceStruct.Factory<J>, tag: Int): Map<String, J>? = skipToTagOrNull(tag) {
-        check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
-        val size = readInt(0)
-        val map = HashMap<String, J>(size)
-        repeat(size) {
-            map[readString(0)] = readJceStruct(factory, 1)
-        }
-        return map
-    }
-
-
-    fun <K, V> readMapOrNull(defaultKey: K, defaultValue: V, tag: Int): Map<K, V>? = skipToTagOrNull(tag) {
-        check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
-        val size = readInt(0)
-        val map = HashMap<K, V>(size)
-        repeat(size) {
-            map[readObject(defaultKey, 0)] = readObject(defaultValue, 1)
-        }
-        return map
-    }
-
-    inline fun <reified K, reified InnerK, reified InnerV> readNestedMapOrNull(tag: Int): Map<K, Map<InnerK, InnerV>>? = skipToTagOrNull(tag) {
-        check(it.type.toInt() == 8) { "type mismatch" }
-        val size = readInt(0)
-        val map = HashMap<K, Map<InnerK, InnerV>>(size)
-        repeat(size) {
-            map[readSimpleObject(0)] = readMap(1)
-        }
-        return map
-    }
-
-    inline fun <reified K, reified V> readMapOrNull(tag: Int): Map<K, V>? = skipToTagOrNull(tag) {
-        check(it.type.toInt() == 8) { "type mismatch" }
-        val size = readInt(0)
-        val map = HashMap<K, V>(size)
-        repeat(size) {
-            map[readSimpleObject(0)] = readSimpleObject(1)
-        }
-        return map
-    }
-
-    inline fun <reified J : JceStruct> readJceStructListOrNull(factory: JceStruct.Factory<J>, tag: Int): List<J>? = skipToTagOrNull(tag) { head ->
-        check(head.type.toInt() == 9) { "type mismatch" }
-        val size = readInt(0)
-        val list = ArrayList<J>(size)
-        repeat(size) {
-            list.add(readJceStruct(factory, 0))
-        }
-        return list
-    }
-
-    inline fun <reified T> readListOrNull(tag: Int): List<T>? = skipToTagOrNull(tag) { head ->
-        check(head.type.toInt() == 9) { "type mismatch" }
-        val size = readInt(0)
-        val list = ArrayList<T>(size)
-        repeat(size) {
-            list.add(readSimpleObject(0))
-        }
-        return list
-    }
-
-    fun <T> readListOrNull(defaultElement: T, tag: Int): List<T>? = skipToTagOrNull(tag) { head ->
-        check(head.type.toInt() == 9) { "type mismatch" }
-        val size = readInt(0)
-        val list = ArrayList<T>(size)
-        repeat(size) {
-            list.add(readObject(defaultElement, 0))
-        }
-        return list
-    }
-
-    fun <J : JceStruct> readJceStructOrNull(factory: JceStruct.Factory<J>, tag: Int): J? = skipToTagOrNull(tag) { head ->
-        check(head.type.toInt() == 10) { "type mismatch" }
-        return factory.newInstanceFrom(this).also { skipToStructEnd() }
-    }
-
-    @Suppress("UNCHECKED_CAST")
-    fun <T> readArrayOrNull(default: Array<T>, tag: Int): Array<T>? = skipToTagOrNull(tag) { head ->
-        val defaultElement = default[0]
-        check(head.type.toInt() == 9) { "type mismatch" }
-        return Array(readInt(0)) { readObject(defaultElement, tag) as Any } as Array<T>
-    }
-
-    @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
-    fun <T> readObject(default: T, tag: Int): T = when (default) {
-        is Byte -> readByte(tag)
-        is Boolean -> readBoolean(tag)
-        is Short -> readShort(tag)
-        is Int -> readInt(tag)
-        is Long -> readLong(tag)
-        is Float -> readFloat(tag)
-        is Double -> readDouble(tag)
-        is String -> readString(tag)
-        is BooleanArray -> readBooleanArray(tag)
-        is ShortArray -> readShortArray(tag)
-        is IntArray -> readIntArray(tag)
-        is LongArray -> readLongArray(tag)
-        is ByteArray -> readByteArray(tag)
-        is FloatArray -> readByteArray(tag)
-        is DoubleArray -> readDoubleArrayOrNull(tag)
-        is List<*> -> {
-            readList(default, tag)
-        }
-        is Map<*, *> -> {
-            val entry = default.entries.first()
-            readMap(entry.key, entry.value, tag)
-        }
-        is Array<*> -> readSimpleArray(default, tag)
-        else -> error("unsupported type")
-    } as T
-
-    @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
-    inline fun <reified T> readSimpleObject(tag: Int): T = when (T::class) {
-        Byte::class -> readByte(tag)
-        Boolean::class -> readBoolean(tag)
-        Short::class -> readShort(tag)
-        Int::class -> readInt(tag)
-        Long::class -> readLong(tag)
-        Float::class -> readFloat(tag)
-        Double::class -> readDouble(tag)
-
-        String::class -> readString(tag)
-
-        BooleanArray::class -> readBooleanArray(tag)
-        ShortArray::class -> readShortArray(tag)
-        IntArray::class -> readIntArray(tag)
-        LongArray::class -> readLongArray(tag)
-        ByteArray::class -> readByteArray(tag)
-        FloatArray::class -> readByteArray(tag)
-        DoubleArray::class -> readDoubleArrayOrNull(tag)
-        else -> error("Type is not supported: ${T::class.simpleName}")
-    } as T
-
-    private fun skipField() {
-        skipField(readHead().type)
-    }
-
-    private fun skipToStructEnd() {
-        var head: JceHead
-        do {
-            head = readHead()
-            skipField(head.type)
-        } while (head.type.toInt() != 11)
-    }
-
-    @UseExperimental(ExperimentalUnsignedTypes::class)
-    @PublishedApi
-    internal fun skipField(type: Byte) = when (type.toInt()) {
-        0 -> this.input.discardExact(1)
-        1 -> this.input.discardExact(2)
-        2 -> this.input.discardExact(4)
-        3 -> this.input.discardExact(8)
-        4 -> this.input.discardExact(4)
-        5 -> this.input.discardExact(8)
-        6 -> this.input.discardExact(this.input.readUByte().toInt())
-        7 -> this.input.discardExact(this.input.readInt())
-        8 -> { // map
-            repeat(this.readInt(0) * 2) {
-                skipField()
-            }
-        }
-        9 -> { // list
-            repeat(this.readInt(0)) {
-                skipField()
-            }
-        }
-        10 -> this.skipToStructEnd()
-        11, 12 -> {
-
-        }
-        13 -> {
-            val head = readHead()
-            check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
-            this.input.discardExact(this.readInt(0))
-        }
-        else -> error("invalid type: $type")
-    }
-}
-
-private inline fun <R> JceInput.skipToTag(tag: Int, block: (JceHead) -> R): R {
-    return skipToTagOrNull(tag) { block(it) } ?: error("cannot find required tag $tag")
-}
-
-@PublishedApi
-internal inline fun <R> JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
-    while (true) {
-        if (this.input.endOfInput) {
-            println("endOfInput")
-            return null
-        }
-
-        val head = peakHead()
-        if (head.tag > tag) {
-            return null
-        }
-        readHead()
-        if (head.tag == tag) {
-            return block(head)
-        }
-        this.skipField(head.type)
-    }
-}

+ 0 - 292
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceOutput.kt

@@ -1,292 +0,0 @@
-package net.mamoe.mirai.qqandroid.io
-
-import kotlinx.io.charsets.Charset
-import kotlinx.io.core.*
-import kotlin.experimental.or
-import kotlin.reflect.KClass
-
-@PublishedApi
-internal val CharsetGBK = Charset.forName("GBK")
-@PublishedApi
-internal val CharsetUTF8 = Charset.forName("UTF8")
-
-inline fun buildJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit): ByteReadPacket {
-    return JceOutput(stringCharset).apply(block).build()
-}
-
-inline fun BytePacketBuilder.writeJcePacket(stringCharset: Charset = CharsetGBK, block: JceOutput.() -> Unit) {
-    return this.writePacket(buildJcePacket(stringCharset, block))
-}
-
-fun jceStruct(tag: Int, struct: JceStruct): ByteArray{
-    return buildJcePacket {
-        writeJceStruct(struct, tag)
-    }.readBytes()
-}
-
-fun <K, V> jceMap(tag: Int, vararg entries: Pair<K, V>): ByteArray {
-    return buildJcePacket {
-        writeMap(mapOf(*entries), tag)
-    }.readBytes()
-}
-
-/**
- *
- * From: com.qq.taf.jce.JceOutputStream
- */
-@Suppress("unused", "MemberVisibilityCanBePrivate")
-@UseExperimental(ExperimentalIoApi::class)
-class JceOutput(
-    private val stringCharset: Charset = CharsetGBK
-) {
-    private val output: BytePacketBuilder = BytePacketBuilder()
-
-    fun build(): ByteReadPacket = output.build()
-
-    fun close() = output.close()
-    fun flush() = output.flush()
-
-    fun writeByte(v: Byte, tag: Int) {
-        if (v.toInt() == 0) {
-            writeHead(ZERO_TYPE, tag)
-        } else {
-            writeHead(BYTE, tag)
-            output.writeByte(v)
-        }
-    }
-
-    fun writeDouble(v: Double, tag: Int) {
-        writeHead(DOUBLE, tag)
-        output.writeDouble(v)
-    }
-
-    fun writeFloat(v: Float, tag: Int) {
-        writeHead(FLOAT, tag)
-        output.writeFloat(v)
-    }
-
-    fun writeFully(src: ByteArray, tag: Int) {
-        writeHead(SIMPLE_LIST, tag)
-        writeHead(BYTE, 0)
-        writeInt(src.size, 0)
-        output.writeFully(src)
-    }
-
-    fun writeFully(src: DoubleArray, tag: Int) {
-        writeHead(LIST, tag)
-        writeInt(src.size, 0)
-        src.forEach {
-            writeDouble(it, 0)
-        }
-    }
-
-    fun writeFully(src: FloatArray, tag: Int) {
-        writeHead(LIST, tag)
-        writeInt(src.size, 0)
-        src.forEach {
-            writeFloat(it, 0)
-        }
-    }
-
-    fun writeFully(src: IntArray, tag: Int) {
-        writeHead(LIST, tag)
-        writeInt(src.size, 0)
-        src.forEach {
-            writeInt(it, 0)
-        }
-    }
-
-    fun writeFully(src: LongArray, tag: Int) {
-        writeHead(LIST, tag)
-        writeInt(src.size, 0)
-        src.forEach {
-            writeLong(it, 0)
-        }
-    }
-
-    fun writeFully(src: ShortArray, tag: Int) {
-        writeHead(LIST, tag)
-        writeInt(src.size, 0)
-        src.forEach {
-            writeShort(it, 0)
-        }
-    }
-
-    fun writeFully(src: BooleanArray, tag: Int) {
-        writeHead(LIST, tag)
-        writeInt(src.size, 0)
-        src.forEach {
-            writeBoolean(it, 0)
-        }
-    }
-
-    fun <T> writeFully(src: Array<T>, tag: Int) {
-        writeHead(LIST, tag)
-        writeInt(src.size, 0)
-        src.forEach {
-            writeObject(it, 0)
-        }
-    }
-
-    fun writeInt(v: Int, tag: Int) {
-        if (v in Short.MIN_VALUE..Short.MAX_VALUE) {
-            writeShort(v.toShort(), tag)
-        } else {
-            writeHead(INT, tag)
-            output.writeInt(v)
-        }
-    }
-
-    fun writeLong(v: Long, tag: Int) {
-        if (v in Int.MIN_VALUE..Int.MAX_VALUE) {
-            writeInt(v.toInt(), tag)
-        } else {
-            writeHead(LONG, tag)
-            output.writeLong(v)
-        }
-    }
-
-    fun writeShort(v: Short, tag: Int) {
-        if (v in Byte.MIN_VALUE..Byte.MAX_VALUE) {
-            writeByte(v.toByte(), tag)
-        } else {
-            writeHead(SHORT, tag)
-            output.writeShort(v)
-        }
-    }
-
-    fun writeBoolean(v: Boolean, tag: Int) {
-        this.writeByte(if (v) 1 else 0, tag)
-    }
-
-    fun writeString(v: String, tag: Int) {
-        val array = v.toByteArray(stringCharset)
-        if (array.size > 255) {
-            writeHead(STRING4, tag)
-            output.writeInt(array.size)
-            output.writeFully(array)
-        } else {
-            writeHead(STRING1, tag)
-            output.writeByte(array.size.toByte())
-            output.writeFully(array)
-        }
-    }
-
-    fun <K, V> writeMap(map: Map<K, V>, tag: Int) {
-        writeHead(MAP, tag)
-        if (map.isEmpty()) {
-            writeInt(0, 0)
-        } else {
-            writeInt(map.size, 0)
-            map.forEach { (key, value) ->
-                writeObject(key, 0)
-                writeObject(value, 1)
-            }
-        }
-    }
-
-    fun writeCollection(collection: Collection<*>?, tag: Int) {
-        writeHead(LIST, tag)
-        if (collection == null || collection.isEmpty()) {
-            writeInt(0, 0)
-        } else {
-            writeInt(collection.size, 0)
-            collection.forEach {
-                writeObject(it, 0)
-            }
-        }
-    }
-
-    fun writeJceStruct(v: JceStruct, tag: Int) {
-        writeHead(STRUCT_BEGIN, tag)
-        v.writeTo(this)
-        writeHead(STRUCT_END, 0)
-    }
-
-    fun <T> writeObject(v: T, tag: Int) {
-        when (v) {
-            is Byte -> writeByte(v, tag)
-            is Short -> writeShort(v, tag)
-            is Int -> writeInt(v, tag)
-            is Long -> writeLong(v, tag)
-            is Float -> writeFloat(v, tag)
-            is Double -> writeDouble(v, tag)
-            is JceStruct -> writeJceStruct(v, tag)
-            is ByteArray -> writeFully(v, tag)
-            is Collection<*> -> writeCollection(v, tag)
-            is Boolean -> writeBoolean(v, tag)
-            is Map<*, *> -> writeMap(v, tag)
-            is IntArray -> writeFully(v, tag)
-            is ShortArray -> writeFully(v, tag)
-            is BooleanArray -> writeFully(v, tag)
-            is LongArray -> writeFully(v, tag)
-            is FloatArray -> writeFully(v, tag)
-            is DoubleArray -> writeFully(v, tag)
-            is Array<*> -> writeFully(v, tag)
-            is String -> writeString(v, tag)
-
-//
-//            is ByteReadPacket -> ByteArrayPool.useInstance {
-//                v.readAvailable(it)
-//                writeFully(it, tag)
-//            }
-            else -> error("unsupported type: ${v.getClassName()}")
-        }
-    }
-
-    fun write(v: Int, tag: Int) = writeInt(v, tag)
-    fun write(v: Byte, tag: Int) = writeByte(v, tag)
-    fun write(v: Short, tag: Int) = writeShort(v, tag)
-    fun write(v: Long, tag: Int) = writeLong(v, tag)
-    fun write(v: Float, tag: Int) = writeFloat(v, tag)
-    fun write(v: Double, tag: Int) = writeDouble(v, tag)
-    fun write(v: String, tag: Int) = writeString(v, tag)
-    fun write(v: Boolean, tag: Int) = writeBoolean(v, tag)
-    fun write(v: Collection<*>, tag: Int) = writeCollection(v, tag)
-    fun write(v: Map<*, *>, tag: Int) = writeMap(v, tag)
-    fun write(v: ByteArray, tag: Int) = writeFully(v, tag)
-    fun write(v: IntArray, tag: Int) = writeFully(v, tag)
-    fun write(v: BooleanArray, tag: Int) = writeFully(v, tag)
-    fun write(v: LongArray, tag: Int) = writeFully(v, tag)
-    fun write(v: ShortArray, tag: Int) = writeFully(v, tag)
-    fun write(v: Array<*>, tag: Int) = writeFully(v, tag)
-    fun write(v: FloatArray, tag: Int) = writeFully(v, tag)
-    fun write(v: DoubleArray, tag: Int) = writeFully(v, tag)
-
-    @PublishedApi
-    internal companion object {
-        const val BYTE: Int = 0
-        const val DOUBLE: Int = 5
-        const val FLOAT: Int = 4
-        const val INT: Int = 2
-        const val JCE_MAX_STRING_LENGTH = 104857600
-        const val LIST: Int = 9
-        const val LONG: Int = 3
-        const val MAP: Int = 8
-        const val SHORT: Int = 1
-        const val SIMPLE_LIST: Int = 13
-        const val STRING1: Int = 6
-        const val STRING4: Int = 7
-        const val STRUCT_BEGIN: Int = 10
-        const val STRUCT_END: Int = 11
-        const val ZERO_TYPE: Int = 12
-
-        private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
-    }
-
-    @PublishedApi
-    internal fun writeHead(type: Int, tag: Int) {
-        if (tag < 15) {
-            this.output.writeByte(((tag shl 4) or type).toByte())
-            return
-        }
-        if (tag < 256) {
-            this.output.writeByte((type.toByte() or 0xF0.toByte()))
-            this.output.writeByte(tag.toByte())
-            return
-        }
-        throw JceEncodeException("tag is too large: $tag")
-    }
-}
-
-class JceEncodeException(message: String) : RuntimeException(message)

+ 1 - 3
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceStruct.kt

@@ -1,5 +1,3 @@
 package net.mamoe.mirai.qqandroid.io
 
-interface JceStruct {
-
-}
+interface JceStruct

+ 653 - 0
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt

@@ -0,0 +1,653 @@
+package net.mamoe.mirai.qqandroid.io.serialization
+
+import kotlinx.io.ByteArrayOutputStream
+import kotlinx.io.ByteBuffer
+import kotlinx.io.ByteOrder
+import kotlinx.io.charsets.Charset
+import kotlinx.io.core.*
+import kotlinx.serialization.*
+import kotlinx.serialization.internal.*
+import kotlinx.serialization.modules.EmptyModule
+import kotlinx.serialization.modules.SerialModule
+import net.mamoe.mirai.qqandroid.io.JceStruct
+import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
+import net.mamoe.mirai.utils.io.readString
+import net.mamoe.mirai.utils.io.toIoBuffer
+import kotlin.reflect.KClass
+
+@PublishedApi
+internal val CharsetGBK = Charset.forName("GBK")
+@PublishedApi
+internal val CharsetUTF8 = Charset.forName("UTF8")
+
+fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T {
+    return Jce.byCharSet(c).load(deserializer, this)
+}
+
+fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.UTF8): ByteArray = Jce.byCharSet(c).dump(serializer, this)
+
+enum class JceCharset(val kotlinCharset: Charset) {
+    GBK(Charset.forName("GBK")),
+    UTF8(Charset.forName("UTF8"))
+}
+
+
+internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<SerialId>(index)?.id
+
+class Jce private constructor(private val charset: JceCharset, context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
+
+    private inner class ListWriter(
+        private val count: Int,
+        private val tag: Int,
+        private val parentEncoder: JceEncoder
+    ) : JceEncoder(ByteArrayOutputStream()) {
+        override fun SerialDescriptor.getTag(index: Int): Int {
+            return 0
+        }
+
+        override fun endEncode(desc: SerialDescriptor) {
+            parentEncoder.writeHead(LIST, this.tag)
+            parentEncoder.encodeTaggedInt(0, count)
+            parentEncoder.output.write(this.output.toByteArray())
+        }
+    }
+
+    private inner class JceMapWriter(
+        output: ByteArrayOutputStream
+    ) : JceEncoder(output) {
+        override fun SerialDescriptor.getTag(index: Int): Int {
+            return if (index % 2 == 0) 0 else 1
+        }
+
+        /*
+        override fun endEncode(desc: SerialDescriptor) {
+            parentEncoder.writeHead(MAP, this.tag)
+            parentEncoder.encodeTaggedInt(Int.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count)
+            println(this.output.toByteArray().toUHexString())
+            parentEncoder.output.write(this.output.toByteArray())
+        }*/
+
+        override fun beginCollection(desc: SerialDescriptor, collectionSize: Int, vararg typeParams: KSerializer<*>): CompositeEncoder {
+            return this
+        }
+
+        override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder {
+            return this
+        }
+    }
+
+    /**
+     * From: com.qq.taf.jce.JceOutputStream
+     */
+    @Suppress("unused", "MemberVisibilityCanBePrivate")
+    @UseExperimental(ExperimentalIoApi::class)
+    private open inner class JceEncoder(
+        internal val output: ByteArrayOutputStream
+    ) : TaggedEncoder<Int>() {
+        override val context get() = [email protected]
+
+        override fun SerialDescriptor.getTag(index: Int): Int {
+            return getSerialId(this, index) ?: error("cannot find @SerialId")
+        }
+
+        /**
+         * 序列化最开始的时候的
+         */
+        override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder = when (desc.kind) {
+            StructureKind.LIST -> this
+            StructureKind.MAP -> this
+            StructureKind.CLASS, UnionKind.OBJECT -> this
+            is PolymorphicKind -> this
+            else -> throw SerializationException("Primitives are not supported at top-level")
+        }
+
+        @Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
+        override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) {
+            is MapLikeDescriptor -> {
+                println("hello")
+                val entries = (value as Map<*, *>).entries
+                val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
+                val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
+
+                this.writeHead(MAP, currentTag)
+                this.encodeTaggedInt(0, entries.count())
+                HashSetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries)
+            }
+            ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray)
+            is PrimitiveArrayDescriptor -> {
+                if (value is ByteArray) {
+                    this.encodeTaggedByteArray(popTag(), value)
+                } else {
+                    serializer.serialize(
+                        ListWriter(
+                            when (value) {
+                                is ShortArray -> value.size
+                                is IntArray -> value.size
+                                is LongArray -> value.size
+                                is FloatArray -> value.size
+                                is DoubleArray -> value.size
+                                is CharArray -> value.size
+                                else -> error("unknown array type: ${value.getClassName()}")
+                            }, popTag(), this
+                        ),
+                        value
+                    )
+                }
+            }
+            is ArrayClassDesc -> {
+                serializer.serialize(
+                    ListWriter((value as Array<*>).size, popTag(), this),
+                    value
+                )
+            }
+            is ListLikeDescriptor -> {
+                serializer.serialize(
+                    ListWriter((value as Collection<*>).size, popTag(), this),
+                    value
+                )
+            }
+            else -> {
+                if (value is JceStruct) {
+                    if (currentTagOrNull == null) {
+                        serializer.serialize(this, value)
+                    } else {
+                        this.writeHead(STRUCT_BEGIN, currentTag)
+                        serializer.serialize(this, value)
+                        this.writeHead(STRUCT_END, 0)
+                    }
+                } else serializer.serialize(this, value)
+            }
+        }
+
+        override fun encodeTaggedByte(tag: Int, value: Byte) {
+            if (value.toInt() == 0) {
+                writeHead(ZERO_TYPE, tag)
+            } else {
+                writeHead(BYTE, tag)
+                output.write(value.toInt())
+            }
+        }
+
+        override fun encodeTaggedShort(tag: Int, value: Short) {
+            if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) {
+                encodeTaggedByte(tag, value.toByte())
+            } else {
+                writeHead(SHORT, tag)
+                output.write(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value).array())
+            }
+        }
+
+        override fun encodeTaggedInt(tag: Int, value: Int) {
+            if (value in Short.MIN_VALUE..Short.MAX_VALUE) {
+                encodeTaggedShort(tag, value.toShort())
+            } else {
+                writeHead(INT, tag)
+                output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array())
+            }
+        }
+
+        override fun encodeTaggedFloat(tag: Int, value: Float) {
+            writeHead(FLOAT, tag)
+            output.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array())
+        }
+
+        override fun encodeTaggedDouble(tag: Int, value: Double) {
+            writeHead(DOUBLE, tag)
+            output.write(ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array())
+        }
+
+        override fun encodeTaggedLong(tag: Int, value: Long) {
+            if (value in Int.MIN_VALUE..Int.MAX_VALUE) {
+                encodeTaggedInt(tag, value.toInt())
+            } else {
+                writeHead(LONG, tag)
+                output.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array())
+            }
+        }
+
+        override fun encodeTaggedBoolean(tag: Int, value: Boolean) {
+            encodeTaggedByte(tag, if (value) 1 else 0)
+        }
+
+        override fun encodeTaggedChar(tag: Int, value: Char) {
+            encodeTaggedByte(tag, value.toByte())
+        }
+
+        override fun encodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor, ordinal: Int) {
+            TODO()
+        }
+
+        override fun encodeTaggedNull(tag: Int) {
+        }
+
+        override fun encodeTaggedUnit(tag: Int) {
+            encodeTaggedNull(tag)
+        }
+
+        fun encodeTaggedByteArray(tag: Int, bytes: ByteArray) {
+            writeHead(SIMPLE_LIST, tag)
+            writeHead(BYTE, 0)
+            encodeTaggedInt(0, bytes.size)
+            output.write(bytes)
+        }
+
+        override fun encodeTaggedString(tag: Int, value: String) {
+            val array = value.toByteArray(charset.kotlinCharset)
+            if (array.size > 255) {
+                writeHead(STRING4, tag)
+                output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(array.size).array())
+                output.write(array)
+            } else {
+                writeHead(STRING1, tag)
+                output.write(ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN).put(array.size.toByte()).array())
+                output.write(array)
+            }
+        }
+
+        override fun encodeTaggedValue(tag: Int, value: Any) {
+            when (value) {
+                is Byte -> encodeTaggedByte(tag, value)
+                is Short -> encodeTaggedShort(tag, value)
+                is Int -> encodeTaggedInt(tag, value)
+                is Long -> encodeTaggedLong(tag, value)
+                is Float -> encodeTaggedFloat(tag, value)
+                is Double -> encodeTaggedDouble(tag, value)
+                is Boolean -> encodeTaggedBoolean(tag, value)
+                is String -> encodeTaggedString(tag, value)
+                is Unit -> encodeTaggedUnit(tag)
+                else -> error("unsupported type: ${value.getClassName()}")
+            }
+        }
+
+        @PublishedApi
+        internal fun writeHead(type: Int, tag: Int) {
+            if (tag < 15) {
+                this.output.write((tag shl 4) or type)
+                return
+            }
+            if (tag < 256) {
+                this.output.write(type or 0xF0)
+                this.output.write(tag)
+                return
+            }
+            error("tag is too large: $tag")
+        }
+    }
+
+    private open inner class JceDecoder(
+        internal val input: JceInput
+    ) : TaggedDecoder<Int>() {
+        override fun SerialDescriptor.getTag(index: Int): Int {
+            return getSerialId(this, index) ?: error("cannot find tag")
+        }
+
+        override fun decodeTaggedByte(tag: Int): Byte = input.readByte(tag)
+        override fun decodeTaggedShort(tag: Int): Short = input.readShort(tag)
+        override fun decodeTaggedInt(tag: Int): Int = input.readInt(tag)
+        override fun decodeTaggedLong(tag: Int): Long = input.readLong(tag)
+        override fun decodeTaggedFloat(tag: Int): Float = input.readFloat(tag)
+        override fun decodeTaggedDouble(tag: Int): Double = input.readDouble(tag)
+        override fun decodeTaggedChar(tag: Int): Char = input.readByte(tag).toChar()
+        override fun decodeTaggedString(tag: Int): String = input.readString(tag)
+        override fun decodeTaggedBoolean(tag: Int): Boolean = input.readBoolean(tag)
+
+        @Suppress("UNCHECKED_CAST")
+        override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when (deserializer.descriptor) {
+            is MapLikeDescriptor -> {
+                deserializer as MapLikeSerializer<Any?, Any?, T, *>
+
+                val tag = popTag()
+                this.input.skipToTagOrNull(tag) {
+                    check(it.type.toInt() == 8) { "type mismatch: ${it.type}" }
+                    val size = this.input.readInt(0)
+                    val map = HashMap<Any?, Any?>(size)
+                    repeat(size) {
+                        pushTag(0)
+                        val key = deserializer.keySerializer.deserialize(this)
+                        pushTag(1)
+                        val value = deserializer.valueSerializer.deserialize(this)
+                        map[key] = value
+                    }
+                    return map as T
+                } ?: error("cannot find tag $tag")
+            }
+            ByteArraySerializer.descriptor -> input.readByteArray(popTag()) as T
+            ShortArraySerializer.descriptor -> input.readShortArray(popTag()) as T
+            IntArraySerializer.descriptor -> input.readIntArray(popTag()) as T
+            LongArraySerializer.descriptor -> input.readLongArray(popTag()) as T
+            FloatArraySerializer.descriptor -> input.readFloatArray(popTag()) as T
+            DoubleArraySerializer.descriptor -> input.readDoubleArray(popTag()) as T
+            CharArraySerializer.descriptor -> input.readByteArray(popTag()).map { it.toChar() }.toCharArray() as T
+            BooleanArraySerializer.descriptor -> input.readBooleanArray(popTag()) as T
+
+            is ArrayClassDesc -> {
+                deserializer as ArrayListSerializer<Any?>
+
+                val tag = popTag()
+                input.skipToTagOrNull(tag) { head ->
+                    return Array(input.readInt(0)) {
+                        input.readHead()
+                        deserializer.deserialize(this)
+                    } as T
+                } ?: error("cannot find tag $tag")
+            }
+            is ListLikeDescriptor -> {
+                deserializer as ListLikeSerializer<Any?, T, *>
+
+                val tag = currentTag
+                input.skipToTagOrNull(tag) { head ->
+                    val size = input.readInt(0)
+                    val list = ArrayList<Any?>(size)
+
+                    repeat(size) {
+                        //input.readHead()
+                        this.pushTag( 0)
+                        list.add(deserializer.typeParams[0].also { println(it.getClassName()) }.deserialize(this))
+                    }
+
+                    return list as T
+                } ?: error("cannot find tag $tag")
+            }
+            else -> {
+                if (input.peakHead().type.toInt() == STRUCT_BEGIN) {
+                    input.readHead()
+                    deserializer.deserialize(this).also { input.readHead() }
+                } else deserializer.deserialize(this)
+            }
+        }
+
+        override fun decodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor): Int =
+            TODO()
+    }
+
+
+    @UseExperimental(ExperimentalUnsignedTypes::class)
+    private inner class JceInput(
+        @PublishedApi
+        internal val input: IoBuffer
+    ) : Closeable {
+        override fun close() {
+            input.close()
+        }
+
+        @PublishedApi
+        internal fun readHead(): JceHead = input.readHead()
+
+        @PublishedApi
+        internal fun peakHead(): JceHead = input.makeView().readHead()
+
+        private fun IoBuffer.readHead(): JceHead {
+            val var2 = readUByte()
+            val type = var2 and 15u
+            var tag = var2.toUInt() shr 4
+            if (tag == 15u)
+                tag = readUByte().toUInt()
+            return JceHead(tag = tag.toInt(), type = type.toByte())
+        }
+
+        fun readBoolean(tag: Int): Boolean = readBooleanOrNull(tag) ?: error("cannot find tag $tag")
+        fun readByte(tag: Int): Byte = readByteOrNull(tag) ?: error("cannot find tag $tag")
+        fun readShort(tag: Int): Short = readShortOrNull(tag) ?: error("cannot find tag $tag")
+        fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag")
+        fun readLong(tag: Int): Long = readLongOrNull(tag) ?: error("cannot find tag $tag")
+        fun readFloat(tag: Int): Float = readFloatOrNull(tag) ?: error("cannot find tag $tag")
+        fun readDouble(tag: Int): Double = readDoubleOrNull(tag) ?: error("cannot find tag $tag")
+
+        fun readString(tag: Int): String = readStringOrNull(tag) ?: error("cannot find tag $tag")
+
+        fun readByteArray(tag: Int): ByteArray = readByteArrayOrNull(tag) ?: error("cannot find tag $tag")
+        fun readShortArray(tag: Int): ShortArray = readShortArrayOrNull(tag) ?: error("cannot find tag $tag")
+        fun readLongArray(tag: Int): LongArray = readLongArrayOrNull(tag) ?: error("cannot find tag $tag")
+        fun readFloatArray(tag: Int): FloatArray = readFloatArrayOrNull(tag) ?: error("cannot find tag $tag")
+        fun readDoubleArray(tag: Int): DoubleArray = readDoubleArrayOrNull(tag) ?: error("cannot find tag $tag")
+        fun readIntArray(tag: Int): IntArray = readIntArrayOrNull(tag) ?: error("cannot find tag $tag")
+        fun readBooleanArray(tag: Int): BooleanArray = readBooleanArrayOrNull(tag) ?: error("cannot find tag $tag")
+
+
+        fun readShortArrayOrNull(tag: Int): ShortArray? = skipToTagOrNull(tag) {
+            require(it.type.toInt() == 9) { "type mismatch" }
+            ShortArray(readInt(0)) { readShort(0) }
+        }
+
+        fun readDoubleArrayOrNull(tag: Int): DoubleArray? = skipToTagOrNull(tag) {
+            require(it.type.toInt() == 9) { "type mismatch" }
+            DoubleArray(readInt(0)) { readDouble(0) }
+        }
+
+        fun readFloatArrayOrNull(tag: Int): FloatArray? = skipToTagOrNull(tag) {
+            require(it.type.toInt() == 9) { "type mismatch" }
+            FloatArray(readInt(0)) { readFloat(0) }
+        }
+
+        fun readIntArrayOrNull(tag: Int): IntArray? = skipToTagOrNull(tag) {
+            require(it.type.toInt() == 9) { "type mismatch" }
+            IntArray(readInt(0)) { readInt(0) }
+        }
+
+        fun readLongArrayOrNull(tag: Int): LongArray? = skipToTagOrNull(tag) {
+            require(it.type.toInt() == 9) { "type mismatch" }
+            LongArray(readInt(0)) { readLong(0) }
+        }
+
+        fun readBooleanArrayOrNull(tag: Int): BooleanArray? = skipToTagOrNull(tag) {
+            require(it.type.toInt() == 9) { "type mismatch" }
+            BooleanArray(readInt(0)) { readBoolean(0) }
+        }
+
+        fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
+            when (it.type.toInt()) {
+                9 -> ByteArray(readInt(0)) { readByte(0) }
+                13 -> {
+                    val head = readHead()
+                    check(head.type.toInt() == 0) { "type mismatch" }
+                    input.readBytes(readInt(0))
+                }
+                else -> error("type mismatch")
+            }
+        }
+
+        @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
+        fun <T> readObject(default: T, tag: Int): T = when (default) {
+            is Byte -> readByte(tag)
+            is Boolean -> readBoolean(tag)
+            is Short -> readShort(tag)
+            is Int -> readInt(tag)
+            is Long -> readLong(tag)
+            is Float -> readFloat(tag)
+            is Double -> readDouble(tag)
+            is String -> readString(tag)
+            else -> error("unsupported type")
+        } as T
+
+        fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
+            return when (head.type.toInt()) {
+                6 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset)
+                7 -> input.readString(
+                    input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } },
+                    charset = charset.kotlinCharset
+                )
+                else -> error("type mismatch: ${head.type}")
+            }
+        }
+
+        fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) {
+            return when (it.type.toInt()) {
+                12 -> 0
+                0 -> input.readByte().toLong()
+                1 -> input.readShort().toLong()
+                2 -> input.readInt().toLong()
+                3 -> input.readLong()
+                else -> error("type mismatch: ${it.type}")
+            }
+        }
+
+        fun readShortOrNull(tag: Int): Short? = skipToTagOrNull(tag) {
+            return when (it.type.toInt()) {
+                12 -> 0
+                0 -> input.readByte().toShort()
+                1 -> input.readShort()
+                else -> error("type mismatch: ${it.type}")
+            }
+        }
+
+        fun readIntOrNull(tag: Int): Int? = skipToTagOrNull(tag) {
+            return when (it.type.toInt()) {
+                12 -> 0
+                0 -> input.readByte().toInt()
+                1 -> input.readShort().toInt()
+                2 -> input.readInt()
+                else -> error("type mismatch: ${it.type}")
+            }
+        }
+
+        fun readByteOrNull(tag: Int): Byte? = skipToTagOrNull(tag) {
+            return when (it.type.toInt()) {
+                12 -> 0
+                0 -> input.readByte()
+                else -> error("type mismatch")
+            }
+        }
+
+        fun readFloatOrNull(tag: Int): Float? = skipToTagOrNull(tag) {
+            return when (it.type.toInt()) {
+                12 -> 0f
+                4 -> input.readFloat()
+                else -> error("type mismatch: ${it.type}")
+            }
+        }
+
+        fun readDoubleOrNull(tag: Int): Double? = skipToTagOrNull(tag) {
+            return when (it.type.toInt()) {
+                12 -> 0.0
+                4 -> input.readFloat().toDouble()
+                5 -> input.readDouble()
+                else -> error("type mismatch: ${it.type}")
+            }
+        }
+
+        fun readBooleanOrNull(tag: Int): Boolean? = this.readByteOrNull(tag)?.let { it.toInt() != 0 }
+
+
+        private fun skipField() {
+            skipField(readHead().type)
+        }
+
+        private fun skipToStructEnd() {
+            var head: JceHead
+            do {
+                head = readHead()
+                skipField(head.type)
+            } while (head.type.toInt() != 11)
+        }
+
+        @UseExperimental(ExperimentalUnsignedTypes::class)
+        @PublishedApi
+        internal fun skipField(type: Byte) = when (type.toInt()) {
+            0 -> this.input.discardExact(1)
+            1 -> this.input.discardExact(2)
+            2 -> this.input.discardExact(4)
+            3 -> this.input.discardExact(8)
+            4 -> this.input.discardExact(4)
+            5 -> this.input.discardExact(8)
+            6 -> this.input.discardExact(this.input.readUByte().toInt())
+            7 -> this.input.discardExact(this.input.readInt())
+            8 -> { // map
+                repeat(this.readInt(0) * 2) {
+                    skipField()
+                }
+            }
+            9 -> { // list
+                repeat(this.readInt(0)) {
+                    skipField()
+                }
+            }
+            10 -> this.skipToStructEnd()
+            11, 12 -> {
+
+            }
+            13 -> {
+                val head = readHead()
+                check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
+                this.input.discardExact(this.readInt(0))
+            }
+            else -> error("invalid type: $type")
+        }
+
+        internal inline fun <R> skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
+            while (true) {
+                if (this.input.endOfInput) {
+                    println("endOfInput")
+                    return null
+                }
+
+                val head = peakHead()
+                if (head.tag > tag) {
+                    return null
+                }
+                readHead()
+                if (head.tag == tag) {
+                    return block(head)
+                }
+                this.skipField(head.type)
+            }
+        }
+
+    }
+
+    companion object {
+        val UTF8 = Jce(JceCharset.UTF8)
+        val GBK = Jce(JceCharset.GBK)
+
+        fun byCharSet(c: JceCharset): Jce {
+            return if (c === JceCharset.UTF8) {
+                UTF8
+            } else {
+                GBK
+            }
+        }
+
+        internal const val BYTE: Int = 0
+        internal const val DOUBLE: Int = 5
+        internal const val FLOAT: Int = 4
+        internal const val INT: Int = 2
+        internal const val JCE_MAX_STRING_LENGTH = 104857600
+        internal const val LIST: Int = 9
+        internal const val LONG: Int = 3
+        internal const val MAP: Int = 8
+        internal const val SHORT: Int = 1
+        internal const val SIMPLE_LIST: Int = 13
+        internal const val STRING1: Int = 6
+        internal const val STRING4: Int = 7
+        internal const val STRUCT_BEGIN: Int = 10
+        internal const val STRUCT_END: Int = 11
+        internal const val ZERO_TYPE: Int = 12
+
+        private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
+    }
+
+    override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
+        val encoder = ByteArrayOutputStream()
+        val dumper = JceEncoder(encoder)
+        dumper.encode(serializer, obj)
+        return encoder.toByteArray()
+    }
+
+    override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
+        return bytes.toIoBuffer().withUse {
+            val decoder = JceDecoder(JceInput(this))
+            decoder.decode(deserializer)
+        }
+    }
+}
+
+@UseExperimental(ExperimentalUnsignedTypes::class)
+inline class JceHead(private val value: Long) {
+    constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong())
+
+    val tag: Int get() = (value ushr 32).toInt()
+    val type: Byte get() = value.toUInt().toByte()
+
+    override fun toString(): String {
+        return "JceHead(tag=$tag, type=$type)"
+    }
+}

+ 0 - 376
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceEncoder.kt

@@ -1,376 +0,0 @@
-package net.mamoe.mirai.qqandroid.io.serialization
-
-import kotlinx.io.ByteArrayOutputStream
-import kotlinx.io.ByteBuffer
-import kotlinx.io.ByteOrder
-import kotlinx.io.charsets.Charset
-import kotlinx.io.core.ExperimentalIoApi
-import kotlinx.io.core.toByteArray
-import kotlinx.serialization.*
-import kotlinx.serialization.internal.*
-import kotlinx.serialization.modules.EmptyModule
-import kotlinx.serialization.modules.SerialModule
-import net.mamoe.mirai.qqandroid.io.CharsetUTF8
-import net.mamoe.mirai.qqandroid.io.JceEncodeException
-import net.mamoe.mirai.qqandroid.io.JceStruct
-import kotlin.reflect.KClass
-
-fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: Charset): T {
-    return Jce.byCharSet(c).load(deserializer, this)
-}
-
-
-enum class JceCharset(val kotlinCharset: Charset) {
-    GBK(Charset.forName("GBK")),
-    UTF8(Charset.forName("UTF8"))
-}
-
-@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD)
-@Retention(AnnotationRetention.RUNTIME)
-annotation class SerialCharset(val charset: JceCharset)
-
-internal object JceType {
-    const val BYTE: Int = 0
-    const val DOUBLE: Int = 5
-    const val FLOAT: Int = 4
-    const val INT: Int = 2
-    const val JCE_MAX_STRING_LENGTH = 104857600
-    const val LIST: Int = 9
-    const val LONG: Int = 3
-    const val MAP: Int = 8
-    const val SHORT: Int = 1
-    const val SIMPLE_LIST: Int = 13
-    const val STRING1: Int = 6
-    const val STRING4: Int = 7
-    const val STRUCT_BEGIN: Int = 10
-    const val STRUCT_END: Int = 11
-    const val ZERO_TYPE: Int = 12
-
-    private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
-}
-
-
-internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<SerialId>(index)?.id
-
-internal data class JceDesc(
-    val id: Int,
-    val charset: JceCharset
-) {
-    companion object {
-        val STUB_FOR_PRIMITIVE_NUMBERS_GBK = JceDesc(0, JceCharset.GBK)
-    }
-}
-
-class Jce private constructor(private val charset: JceCharset, context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
-
-    private inner class ListWriter(
-        defaultStringCharset: JceCharset,
-        private val count: Int,
-        private val tag: JceDesc,
-        private val parentEncoder: JceEncoder
-    ) : JceEncoder(defaultStringCharset, ByteArrayOutputStream()) {
-        override fun SerialDescriptor.getTag(index: Int): JceDesc {
-            return JceDesc(0, getCharset(index))
-        }
-
-        override fun endEncode(desc: SerialDescriptor) {
-            parentEncoder.writeHead(LIST, this.tag.id)
-            parentEncoder.encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count)
-            parentEncoder.output.write(this.output.toByteArray())
-        }
-    }
-
-    private inner class JceStructWriter(
-        defaultStringCharset: JceCharset,
-        private val tag: JceDesc,
-        private val parentEncoder: JceEncoder,
-        private val stream: ByteArrayOutputStream = ByteArrayOutputStream()
-    ) : JceEncoder(defaultStringCharset, stream) {
-        override fun endEncode(desc: SerialDescriptor) {
-            parentEncoder.writeHead(STRUCT_BEGIN, this.tag.id)
-            parentEncoder.output.write(stream.toByteArray())
-            parentEncoder.writeHead(STRUCT_END, 0)
-        }
-    }
-
-    private inner class JceMapWriter(
-        defaultStringCharset: JceCharset,
-        output: ByteArrayOutputStream
-    ) : JceEncoder(defaultStringCharset, output) {
-        override fun SerialDescriptor.getTag(index: Int): JceDesc {
-            return if (index % 2 == 0) JceDesc(0, getCharset(index))
-            else JceDesc(1, getCharset(index))
-        }
-
-        /*
-        override fun endEncode(desc: SerialDescriptor) {
-            parentEncoder.writeHead(MAP, this.tag.id)
-            parentEncoder.encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count)
-            println(this.output.toByteArray().toUHexString())
-            parentEncoder.output.write(this.output.toByteArray())
-        }*/
-
-        override fun beginCollection(desc: SerialDescriptor, collectionSize: Int, vararg typeParams: KSerializer<*>): CompositeEncoder {
-            return this
-        }
-
-        override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder {
-            return this
-        }
-    }
-
-    /**
-     * From: com.qq.taf.jce.JceOutputStream
-     */
-    @Suppress("unused", "MemberVisibilityCanBePrivate")
-    @UseExperimental(ExperimentalIoApi::class)
-    private open inner class JceEncoder(
-        /**
-         * 标注在 class 上的 charset
-         */
-        private val defaultStringCharset: JceCharset,
-        internal val output: ByteArrayOutputStream
-    ) : TaggedEncoder<JceDesc>() {
-        override val context get() = [email protected]
-
-        protected fun SerialDescriptor.getCharset(index: Int): JceCharset {
-            return findAnnotation<SerialCharset>(index)?.charset ?: defaultStringCharset
-        }
-
-        override fun SerialDescriptor.getTag(index: Int): JceDesc {
-            return JceDesc(getSerialId(this, index) ?: error("cannot find @SerialId"), getCharset(index))
-        }
-
-        /**
-         * 序列化最开始的时候的
-         */
-        override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder = when (desc.kind) {
-            StructureKind.LIST -> this
-            StructureKind.MAP -> this
-            StructureKind.CLASS, UnionKind.OBJECT -> this
-            is PolymorphicKind -> this
-            else -> throw SerializationException("Primitives are not supported at top-level")
-        }
-
-        @Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
-        override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) {
-            is MapLikeDescriptor -> {
-                println("hello")
-                val entries = (value as Map<*, *>).entries
-                val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
-                val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
-
-                this.writeHead(MAP, currentTag.id)
-                this.encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, entries.count())
-                HashSetSerializer(mapEntrySerial).serialize(JceMapWriter(charset, this.output), entries)
-            }
-            ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray)
-            is PrimitiveArrayDescriptor -> {
-                if (value is ByteArray) {
-                    this.encodeTaggedByteArray( popTag(), value)
-                } else{
-                    serializer.serialize(
-                        ListWriter(charset, when(value){
-                            is ShortArray -> value.size
-                            is IntArray -> value.size
-                            is LongArray -> value.size
-                            is FloatArray -> value.size
-                            is DoubleArray -> value.size
-                            is CharArray -> value.size
-                            else -> error("unknown array type: ${value.getClassName()}")
-                        },  popTag(), this),
-                        value
-                    )
-                }
-            }
-            is ArrayClassDesc-> {
-                serializer.serialize(
-                    ListWriter(charset, (value as Array<*>).size,  popTag(), this),
-                    value
-                )
-            }
-            is ListLikeDescriptor -> {
-                serializer.serialize(
-                    ListWriter(charset, (value as Collection<*>).size,  popTag(), this),
-                    value
-                )
-            }
-            else -> {
-                if (value is JceStruct) {
-                    if (currentTagOrNull == null) {
-                        serializer.serialize(this, value)
-                    } else {
-                        this.writeHead(STRUCT_BEGIN, currentTag.id)
-                        serializer.serialize(this, value)
-                        this.writeHead(STRUCT_END, 0)
-                    }
-                } else serializer.serialize(this, value)
-            }
-        }
-
-        override fun encodeTaggedByte(tag: JceDesc, value: Byte) {
-            if (value.toInt() == 0) {
-                writeHead(ZERO_TYPE, tag.id)
-            } else {
-                writeHead(BYTE, tag.id)
-                output.write(value.toInt())
-            }
-        }
-
-        override fun encodeTaggedShort(tag: JceDesc, value: Short) {
-            if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) {
-                encodeTaggedByte(tag, value.toByte())
-            } else {
-                writeHead(SHORT, tag.id)
-                output.write(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value).array())
-            }
-        }
-
-        override fun encodeTaggedInt(tag: JceDesc, value: Int) {
-            if (value in Short.MIN_VALUE..Short.MAX_VALUE) {
-                encodeTaggedShort(tag, value.toShort())
-            } else {
-                writeHead(INT, tag.id)
-                output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array())
-            }
-        }
-
-        override fun encodeTaggedFloat(tag: JceDesc, value: Float) {
-            writeHead(FLOAT, tag.id)
-            output.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array())
-        }
-
-        override fun encodeTaggedDouble(tag: JceDesc, value: Double) {
-            writeHead(DOUBLE, tag.id)
-            output.write(ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array())
-        }
-
-        override fun encodeTaggedLong(tag: JceDesc, value: Long) {
-            if (value in Int.MIN_VALUE..Int.MAX_VALUE) {
-                encodeTaggedInt(tag, value.toInt())
-            } else {
-                writeHead(LONG, tag.id)
-                output.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array())
-            }
-        }
-
-        override fun encodeTaggedBoolean(tag: JceDesc, value: Boolean) {
-            encodeTaggedByte(tag, if (value) 1 else 0)
-        }
-
-        override fun encodeTaggedChar(tag: JceDesc, value: Char) {
-            encodeTaggedByte(tag, value.toByte())
-        }
-
-        override fun encodeTaggedEnum(tag: JceDesc, enumDescription: SerialDescriptor, ordinal: Int) {
-            TODO()
-        }
-
-        override fun encodeTaggedNull(tag: JceDesc) {
-        }
-
-        override fun encodeTaggedUnit(tag: JceDesc) {
-            encodeTaggedNull(tag)
-        }
-
-        fun encodeTaggedByteArray(tag: JceDesc, bytes: ByteArray) {
-            writeHead(SIMPLE_LIST, tag.id)
-            writeHead(BYTE, 0)
-            encodeTaggedInt(JceDesc.STUB_FOR_PRIMITIVE_NUMBERS_GBK, bytes.size)
-            output.write(bytes)
-        }
-
-        override fun encodeTaggedString(tag: JceDesc, value: String) {
-            val array = value.toByteArray(defaultStringCharset.kotlinCharset)
-            if (array.size > 255) {
-                writeHead(STRING4, tag.id)
-                output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(array.size).array())
-                output.write(array)
-            } else {
-                writeHead(STRING1, tag.id)
-                output.write(ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN).put(array.size.toByte()).array())
-                output.write(array)
-            }
-        }
-
-        override fun encodeTaggedValue(tag: JceDesc, value: Any) {
-            when (value) {
-                is Byte -> encodeTaggedByte(tag, value)
-                is Short -> encodeTaggedShort(tag, value)
-                is Int -> encodeTaggedInt(tag, value)
-                is Long -> encodeTaggedLong(tag, value)
-                is Float -> encodeTaggedFloat(tag, value)
-                is Double -> encodeTaggedDouble(tag, value)
-                is Boolean -> encodeTaggedBoolean(tag, value)
-                is String -> encodeTaggedString(tag, value)
-                is Unit -> encodeTaggedUnit(tag)
-                else -> error("unsupported type: ${value.getClassName()}")
-            }
-        }
-
-        @PublishedApi
-        internal fun writeHead(type: Int, tag: Int) {
-            if (tag < 15) {
-                this.output.write((tag shl 4) or type)
-                return
-            }
-            if (tag < 256) {
-                this.output.write(type or 0xF0)
-                this.output.write(tag)
-                return
-            }
-            throw JceEncodeException("tag is too large: $tag")
-        }
-    }
-
-    companion object {
-        val UTF8 = Jce(JceCharset.UTF8)
-        val GBK = Jce(JceCharset.GBK)
-
-        public fun byCharSet(c: Charset): Jce {
-            return if (c === CharsetUTF8) {
-                UTF8
-            } else {
-                GBK
-            }
-        }
-
-        internal const val BYTE: Int = 0
-        internal const val DOUBLE: Int = 5
-        internal const val FLOAT: Int = 4
-        internal const val INT: Int = 2
-        internal const val JCE_MAX_STRING_LENGTH = 104857600
-        internal const val LIST: Int = 9
-        internal const val LONG: Int = 3
-        internal const val MAP: Int = 8
-        internal const val SHORT: Int = 1
-        internal const val SIMPLE_LIST: Int = 13
-        internal const val STRING1: Int = 6
-        internal const val STRING4: Int = 7
-        internal const val STRUCT_BEGIN: Int = 10
-        internal const val STRUCT_END: Int = 11
-        internal const val ZERO_TYPE: Int = 12
-
-        private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
-
-        internal const val VARINT = 0
-        internal const val i64 = 1
-        internal const val SIZE_DELIMITED = 2
-        internal const val i32 = 5
-    }
-
-    override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
-        val encoder = ByteArrayOutputStream()
-
-        val dumper = JceEncoder(charset, encoder)
-        dumper.encode(serializer, obj)
-        return encoder.toByteArray()
-    }
-
-    override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
-        TODO()
-    }
-
-    override fun <T>
-
-}

+ 19 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt

@@ -1,8 +1,11 @@
 package net.mamoe.mirai.qqandroid.network.protocol.jce
 
+import kotlinx.serialization.Polymorphic
 import kotlinx.serialization.SerialId
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.UseSerializers
 import net.mamoe.mirai.qqandroid.io.JceStruct
+import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
 
 private val EMPTY_MAP = mapOf<String, String>()
 private val EMPTY_SBUFFER_MAP = mapOf<Int, ByteArray>()
@@ -15,8 +18,23 @@ class RequestPacket(
     @SerialId(4) val iRequestId: Int = 0,
     @SerialId(5) val sServantName: String = "",
     @SerialId(6) val sFuncName: String = "",
-    @SerialId(7) val sBuffer: Map<Int, ByteArray> = EMPTY_SBUFFER_MAP,
+    @SerialId(7) val sBuffer: ByteArray = EMPTY_BYTE_ARRAY,
     @SerialId(8) val iTimeout: Int = 0,
     @SerialId(9) val context: Map<String, String> = EMPTY_MAP,
     @SerialId(10) val status: Map<String, String> = EMPTY_MAP
+) : JceStruct
+
+@Serializable
+class RequestDataVersion3(
+    @SerialId(0) val map: Map<String, ByteArray>
+) : JceStruct
+
+@Serializable
+class RequestDataVersion2(
+    @SerialId(0) val map: Map<String, Map<String, ByteArray>>
+) : JceStruct
+
+@Serializable
+class RequestDataStructSvcReqRegister(
+    @SerialId(0) val struct: SvcReqRegister
 ) : JceStruct

+ 37 - 36
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt

@@ -1,46 +1,47 @@
 package net.mamoe.mirai.qqandroid.network.protocol.jce
 
+import kotlinx.serialization.Polymorphic
 import kotlinx.serialization.SerialId
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.qqandroid.io.JceStruct
 
 @Serializable
 class SvcReqRegister(
-    @SerialId(6) var bIsOnline: Byte = 0,
-    @SerialId(34) var bIsSetStatus: Byte = 0,
-    @SerialId(7) var bIsShowOnline: Byte = 0,
-    @SerialId(8) var bKikPC: Byte = 0,
-    @SerialId(9) var bKikWeak: Byte = 0,
-    @SerialId(5) var bOnlinePush: Byte = 0,
-    @SerialId(22) var bOpenPush: Byte = 1,
-    @SerialId(14) var bRegType: Byte = 0,
-    @SerialId(36) var bSetMute: Byte = 0,
-    @SerialId(18) var bSlientPush: Byte = 0,
-    @SerialId(33) var bytes_0x769_reqbody: ByteArray? = null,
-    @SerialId(2) var cConnType: Byte = 0,
-    @SerialId(12) var cNetType: Byte = 0,
-    @SerialId(23) var iLargeSeq: Long = 0L,
-    @SerialId(24) var iLastWatchStartTime: Long = 0L,
-    @SerialId(17) var iLocaleID: Int = 2052,
-    @SerialId(11) var iOSVersion: Long = 0L,
-    @SerialId(4) var iStatus: Int = 11,
-    @SerialId(1) var lBid: Long = 0L,
-    @SerialId(29) var lCpId: Long = 0L,
-    @SerialId(0) var lUin: Long = 0L,
-    @SerialId(13) var sBuildVer: String? = "",
-    @SerialId(28) var sChannelNo: String? = "",
-    @SerialId(3) var sOther: String = "",
-    @SerialId(19) var strDevName: String? = null,
-    @SerialId(20) var strDevType: String? = null,
-    @SerialId(32) var strIOSIdfa: String? = "",
-    @SerialId(21) var strOSVer: String? = null,
-    @SerialId(30) var strVendorName: String? = null,
-    @SerialId(31) var strVendorOSName: String? = null,
-    @SerialId(10) var timeStamp: Long = 0L,
-    @SerialId(27) var uNewSSOIp: Long = 0L,
-    @SerialId(26) var uOldSSOIp: Long = 0L,
-    @SerialId(15) var vecDevParam: ByteArray? = null,
-    @SerialId(16) var vecGuid: ByteArray? = null,
-    @SerialId(35) var vecServerBuf: ByteArray? = null
+    @SerialId(0) val lUin: Long = 0L,
+    @SerialId(1) val lBid: Long = 0L,
+    @SerialId(2) val cConnType: Byte = 0,
+    @SerialId(3) val sOther: String = "",
+    @SerialId(4) val iStatus: Int = 11,
+    @SerialId(5) val bOnlinePush: Byte = 0,
+    @SerialId(6) val bIsOnline: Byte = 0,
+    @SerialId(7) val bIsShowOnline: Byte = 0,
+    @SerialId(8) val bKikPC: Byte = 0,
+    @SerialId(9) val bKikWeak: Byte = 0,
+    @SerialId(10) val timeStamp: Long = 0L,
+    @SerialId(11) val iOSVersion: Long = 0L,
+    @SerialId(12) val cNetType: Byte = 0,
+    @SerialId(13) val sBuildVer: String? = "",
+    @SerialId(14) val bRegType: Byte = 0,
+    @SerialId(15) val vecDevParam: ByteArray? = null,
+    @SerialId(16) val vecGuid: ByteArray? = null,
+    @SerialId(17) val iLocaleID: Int = 2052,
+    @SerialId(18) val bSlientPush: Byte = 0,
+    @SerialId(19) val strDevName: String? = null,
+    @SerialId(20) val strDevType: String? = null,
+    @SerialId(21) val strOSVer: String? = null,
+    @SerialId(22) val bOpenPush: Byte = 1,
+    @SerialId(23) val iLargeSeq: Long = 0L,
+    @SerialId(24) val iLastWatchStartTime: Long = 0L,
+    @SerialId(26) val uOldSSOIp: Long = 0L,
+    @SerialId(27) val uNewSSOIp: Long = 0L,
+    @SerialId(28) val sChannelNo: String? = "",
+    @SerialId(29) val lCpId: Long = 0L,
+    @SerialId(30) val strVendorName: String? = null,
+    @SerialId(31) val strVendorOSName: String? = null,
+    @SerialId(32) val strIOSIdfa: String? = "",
+    @SerialId(33) val bytes_0x769_reqbody: ByteArray? = null,
+    @SerialId(34) val bIsSetStatus: Byte = 0,
+    @SerialId(35) val vecServerBuf: ByteArray? = null,
+    @SerialId(36) val bSetMute: Byte = 0
     // @SerialId(25) var vecBindUin: ArrayList<*>? = null // ?? 未知泛型
 ) : JceStruct

+ 0 - 12
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/uni.kt

@@ -1,12 +0,0 @@
-package net.mamoe.mirai.qqandroid.network.protocol.jce
-
-import kotlinx.io.core.BytePacketBuilder
-import net.mamoe.mirai.qqandroid.io.JceOutput
-import net.mamoe.mirai.qqandroid.io.buildJcePacket
-import net.mamoe.mirai.qqandroid.io.writeJcePacket
-
-inline fun BytePacketBuilder.writeUniRequestPacket(requestPacket: RequestPacket.() -> Unit) {
-    writeJcePacket {
-        RequestPacket().apply(requestPacket).writeTo(this)
-    }
-}

+ 2 - 2
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt

@@ -4,7 +4,7 @@ import kotlinx.io.core.*
 import kotlinx.io.pool.useInstance
 import net.mamoe.mirai.data.Packet
 import net.mamoe.mirai.qqandroid.QQAndroidBot
-import net.mamoe.mirai.qqandroid.io.JceInput
+import net.mamoe.mirai.qqandroid.io.serialization.loadAs
 import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
@@ -248,7 +248,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
     }
 
     private suspend fun ByteReadPacket.parseUniResponse(bot: QQAndroidBot, packetFactory: PacketFactory<*>, ssoSequenceId: Int, consumer: PacketConsumer) {
-        val uni = RequestPacket.newInstanceFrom(JceInput(readIoBuffer(readInt() - 4)))
+        val uni = readBytes(readInt() - 4).loadAs(RequestPacket.serializer())
         PacketLogger.verbose(uni.toString())
         consumer(packetFactory.decode(bot, uni.sBuffer.toReadPacket()), uni.sServantName + "." + uni.sFuncName, ssoSequenceId)
     }

+ 4 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt

@@ -4,6 +4,9 @@ import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
 import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.io.serialization.Jce
+import net.mamoe.mirai.qqandroid.io.serialization.loadAs
+import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2
+import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
 import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify
@@ -19,7 +22,7 @@ class MessageSvc {
             )
             val messageNotification = Jce.UTF8.load(
                 RequestPushNotify.serializer(),
-                req.sBuffer[0]!!
+                req.sBuffer.loadAs(RequestDataVersion2.serializer()).map.entries.first().value.entries.first().value
             )
             println(messageNotification.contentToString())
             TODO()

+ 5 - 1
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt

@@ -219,7 +219,11 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
 
 
     sealed class LoginPacketResponse : Packet {
-        object Success : LoginPacketResponse()
+        object Success : LoginPacketResponse(){
+            override fun toString(): String {
+                return "LoginPacketResponse.Success"
+            }
+        }
         data class Error(
             val title: String,
             val message: String,

+ 69 - 67
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt

@@ -1,21 +1,22 @@
 package net.mamoe.mirai.qqandroid.network.protocol.packet.login
 
 import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.writeFully
 import kotlinx.serialization.protobuf.ProtoBuf
 import net.mamoe.mirai.data.Packet
 import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
 import net.mamoe.mirai.qqandroid.network.QQAndroidClient
-import net.mamoe.mirai.qqandroid.io.jceMap
-import net.mamoe.mirai.qqandroid.io.jceStruct
+import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataStructSvcReqRegister
+import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
+import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
 import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister
-import net.mamoe.mirai.qqandroid.network.protocol.jce.writeUniRequestPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
 import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769.Oidb0x769
 import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket
 import net.mamoe.mirai.qqandroid.utils.NetworkType
-import net.mamoe.mirai.utils.io.debugPrint
 import net.mamoe.mirai.utils.io.encodeToString
 import net.mamoe.mirai.utils.io.toReadPacket
 import net.mamoe.mirai.utils.localIpAddress
@@ -55,72 +56,73 @@ class StatSvc {
                 client, subAppId = subAppId, commandName = commandName,
                 extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
             ) {
-                writeUniRequestPacket {
-                    sServantName = "PushService"
-                    sFuncName = "SvcReqRegister"
-                    sBuffer = jceMap(
-                        0,
-                        "SvcReqRegister" to jceStruct(
-                            0,
-                            SvcReqRegister().apply {
-                                cConnType = 0
-                                lBid = 1 or 2 or 4
-                                lUin = client.uin
-                                iStatus = client.onlineStatus.id
-                                bKikPC = 0 // 是否把 PC 踢下线
-                                bKikWeak = 0
-                                timeStamp = 0
-                                // timeStamp = currentTimeSeconds // millis or seconds??
-                                iLargeSeq = 1551 // ?
-                                bOpenPush = 1
-                                iLocaleID = 2052
-                                bRegType =
-                                    (if (regPushReason == RegPushReason.appRegister ||
-                                        regPushReason == RegPushReason.fillRegProxy ||
-                                        regPushReason == RegPushReason.createDefaultRegInfo ||
-                                        regPushReason == RegPushReason.setOnlineStatus
-                                    ) 0 else 1).toByte()
-                                bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0
-                                iOSVersion = client.device.version.sdk.toLong()
-                                cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0
-                                vecGuid = client.device.guid
-                                strDevName = client.device.model.encodeToString()
-                                strDevType = client.device.model.encodeToString()
-                                strOSVer = client.device.version.release.encodeToString()
+                writeFully(
+                    RequestPacket(
+                        sServantName = "PushService",
+                        sFuncName = "SvcReqRegister",
+                        sBuffer = RequestDataVersion3(
+                            mapOf(
+                                "SvcReqRegister" to RequestDataStructSvcReqRegister(
+                                    SvcReqRegister(
+                                        cConnType = 0,
+                                        lBid = 1 or 2 or 4,
+                                        lUin = client.uin,
+                                        iStatus = client.onlineStatus.id,
+                                        bKikPC = 0, // 是否把 PC 踢下线
+                                        bKikWeak = 0,
+                                        timeStamp = 0,
+                                        // timeStamp = currentTimeSeconds // millis or seconds??
+                                        iLargeSeq = 1551, // ?
+                                        bOpenPush = 1,
+                                        iLocaleID = 2052,
+                                        bRegType =
+                                        (if (regPushReason == RegPushReason.appRegister ||
+                                            regPushReason == RegPushReason.fillRegProxy ||
+                                            regPushReason == RegPushReason.createDefaultRegInfo ||
+                                            regPushReason == RegPushReason.setOnlineStatus
+                                        ) 0 else 1).toByte(),
+                                        bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0,
+                                        iOSVersion = client.device.version.sdk.toLong(),
+                                        cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0,
+                                        vecGuid = client.device.guid,
+                                        strDevName = client.device.model.encodeToString(),
+                                        strDevType = client.device.model.encodeToString(),
+                                        strOSVer = client.device.version.release.encodeToString(),
 
-                                uOldSSOIp = 0
-                                uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String ->
-                                    acc or ((s.toLong() shl (index * 16)))
-                                }
-                                strVendorName = "MIUI"
-                                strVendorOSName = "?ONEPLUS A5000_23_17"
-                                // register 时还需要
-                                /*
-                                var44.uNewSSOIp = field_127445;
-                                var44.uOldSSOIp = field_127444;
-                                var44.strVendorName = ROMUtil.getRomName();
-                                var44.strVendorOSName = ROMUtil.getRomVersion(20);
-                                */
-                                bytes_0x769_reqbody = ProtoBuf.dump(
-                                    Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
-                                        rpt_config_list = listOf(
-                                            Oidb0x769.ConfigSeq(
-                                                type = 46,
-                                                version = 0
-                                            ),
-                                            Oidb0x769.ConfigSeq(
-                                                type = 283,
-                                                version = 0
+                                        uOldSSOIp = 0,
+                                        uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String ->
+                                            acc or ((s.toLong() shl (index * 16)))
+                                        },
+                                        strVendorName = "MIUI",
+                                        strVendorOSName = "?ONEPLUS A5000_23_17",
+                                        // register 时还需要
+                                        /*
+                                        var44.uNewSSOIp = field_127445;
+                                        var44.uOldSSOIp = field_127444;
+                                        var44.strVendorName = ROMUtil.getRomName();
+                                        var44.strVendorOSName = ROMUtil.getRomVersion(20);
+                                        */
+                                        bytes_0x769_reqbody = ProtoBuf.dump(
+                                            Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
+                                                rpt_config_list = listOf(
+                                                    Oidb0x769.ConfigSeq(
+                                                        type = 46,
+                                                        version = 0
+                                                    ),
+                                                    Oidb0x769.ConfigSeq(
+                                                        type = 283,
+                                                        version = 0
+                                                    )
+                                                )
                                             )
-                                        )
+                                        ),
+                                        bSetMute = 0
                                     )
-                                )
-                                bSetMute = 0
-                            }
-                        )
-                    )
-                }
-                this.writePacket(this.build().debugPrint("sso body"))
+                                ).toByteArray(RequestDataStructSvcReqRegister.serializer())
+                            )
+                        ).toByteArray(RequestDataVersion3.serializer())
+                    ).toByteArray(RequestPacket.serializer())
+                )
             }
         }
 

+ 40 - 0
mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt

@@ -0,0 +1,40 @@
+package net.mamoe.mirai.qqandroid.io.serialization
+
+import kotlinx.serialization.SerialId
+import kotlinx.serialization.Serializable
+import net.mamoe.mirai.qqandroid.io.JceStruct
+import net.mamoe.mirai.utils.cryptor.contentToString
+import kotlin.test.Test
+
+
+class JceDecoderTest {
+
+    @Serializable
+    class TestSimpleJceStruct(
+        @SerialId(0) val string: String = "123",
+        @SerialId(1) val byte: Byte = 123,
+        @SerialId(2) val short: Short = 123,
+        @SerialId(3) val int: Int = 123,
+        @SerialId(4) val long: Long = 123,
+        @SerialId(5) val float: Float = 123f,
+        @SerialId(6) val double: Double = 123.0
+    ) : JceStruct
+
+    @Test
+    fun testEncoder() {
+        println(TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).loadAs(TestComplexJceStruct.serializer()).contentToString())
+    }
+
+    @Test
+    fun testEncoder2() {
+
+    }
+
+    @Serializable
+    class TestComplexJceStruct(
+        @SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3),
+        @SerialId(8) val byteList: List<Byte> = listOf(1, 2, 3),
+        @SerialId(9) val map: Map<String, String> = mapOf("哈哈" to "嘿嘿"),
+        @SerialId(10) val nestedJceStruct: TestSimpleJceStruct = TestSimpleJceStruct()
+    ) : JceStruct
+}

+ 0 - 80
mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceEncoderTest.kt

@@ -1,80 +0,0 @@
-package net.mamoe.mirai.qqandroid.io.serialization
-
-import kotlinx.io.core.readBytes
-import kotlinx.serialization.SerialId
-import kotlinx.serialization.Serializable
-import net.mamoe.mirai.qqandroid.io.CharsetUTF8
-import net.mamoe.mirai.qqandroid.io.JceOutput
-import net.mamoe.mirai.qqandroid.io.JceStruct
-import net.mamoe.mirai.qqandroid.io.buildJcePacket
-import net.mamoe.mirai.utils.io.toUHexString
-import kotlin.test.Test
-import kotlin.test.assertEquals
-
-
-class JceEncoderTest {
-
-    @Serializable
-    class TestSimpleJceStruct(
-        @SerialId(0) val string: String = "123",
-        @SerialId(1) val byte: Byte = 123,
-        @SerialId(2) val short: Short = 123,
-        @SerialId(3) val int: Int = 123,
-        @SerialId(4) val long: Long = 123,
-        @SerialId(5) val float: Float = 123f,
-        @SerialId(6) val double: Double = 123.0
-    ) : JceStruct() {
-        override fun writeTo(builder: JceOutput) = builder.run {
-            writeString("123", 0)
-            writeByte(123, 1)
-            writeShort(123, 2)
-            writeInt(123, 3)
-            writeLong(123, 4)
-            writeFloat(123f, 5)
-            writeDouble(123.0, 6)
-        }
-    }
-
-    @Test
-    fun testEncoder() {
-        assertEquals(
-            buildJcePacket {
-                writeString("123", 0)
-                writeByte(123, 1)
-                writeShort(123, 2)
-                writeInt(123, 3)
-                writeLong(123, 4)
-                writeFloat(123f, 5)
-                writeDouble(123.0, 6)
-            }.readBytes().toUHexString(),
-            Jce.GBK.dump(
-                TestSimpleJceStruct.serializer(),
-                TestSimpleJceStruct()
-            ).toUHexString()
-        )
-    }
-
-    @Test
-    fun testEncoder2() {
-        assertEquals(
-            buildJcePacket(stringCharset = CharsetUTF8) {
-                writeFully(byteArrayOf(1, 2, 3), 7)
-                writeCollection(listOf(1, 2, 3), 8)
-                writeMap(mapOf("哈哈" to "嘿嘿"), 9)
-                writeJceStruct(TestSimpleJceStruct(), 10)
-            }.readBytes().toUHexString(),
-            Jce.UTF8.dump(
-                TestComplexJceStruct.serializer(),
-                TestComplexJceStruct()
-            ).toUHexString()
-        )
-    }
-
-    @Serializable
-    class TestComplexJceStruct(
-        @SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3),
-        @SerialId(8) val byteList: List<Byte> = listOf(1, 2, 3),
-        @SerialId(9) val map: Map<String, String> = mapOf("哈哈" to "嘿嘿"),
-        @SerialId(10) val nestedJceStruct: TestSimpleJceStruct = TestSimpleJceStruct()
-    )
-}

+ 1 - 1
mirai-core-timpc/src/commonMain/kotlin/net.mamoe.mirai.timpc/network/TIMPCBotNetworkHandler.kt

@@ -389,7 +389,7 @@ internal class TIMPCBotNetworkHandler internal constructor(coroutineContext: Cor
                             close()
                             return
                         }
-                        val code = configuration.loginSolver(bot, captchaCache!!)
+                        val code = configuration.loginSolver.onSolvePicCaptcha(bot, captchaCache!!)
 
                         this.captchaCache = null
                         if (code == null || code.length != 4) {

+ 0 - 8
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverAndroid.kt

@@ -1,8 +0,0 @@
-package net.mamoe.mirai.utils
-
-/**
- * 直接抛出异常. 需自行处理验证码, 在 [BotConfiguration.captchaSolver] 中调整
- */
-actual var DefaultCaptchaSolver: CaptchaSolver = {
-    error("No CaptchaSolver found. BotConfiguration.captchaSolver should be assigned manually")
-}

+ 26 - 0
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/defaultLoginSolver.kt

@@ -0,0 +1,26 @@
+package net.mamoe.mirai.utils
+
+import kotlinx.io.core.IoBuffer
+import net.mamoe.mirai.Bot
+
+/**
+ * 在各平台实现的默认的验证码处理器.
+ */
+actual var defaultLoginSolver: LoginSolver = object : LoginSolver() {
+    override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
+        error("should be implemented manually by you")
+    }
+
+    override suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? {
+        error("should be implemented manually by you")
+    }
+
+    override suspend fun onGetPhoneNumber(): String {
+        error("should be implemented manually by you")
+    }
+
+    override suspend fun onGetSMSVerifyCode(): String {
+        error("should be implemented manually by you")
+    }
+
+}

+ 1 - 1
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/DefaultCaptchaSolverJvm.kt

@@ -28,7 +28,7 @@ import kotlin.coroutines.CoroutineContext
 actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver()
 
 
-class DefaultLoginSolver(): LoginSolver(){
+class DefaultLoginSolver : LoginSolver() {
     override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
         loginSolverLock.withLock {
             val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }

+ 0 - 287
mirai-debug/src/test/kotlin/jceTest/JceInputTest.kt

@@ -1,287 +0,0 @@
-package jceTest
-
-import io.ktor.util.InternalAPI
-import jce.jce.JceInputStream
-import jceTest.JceOutputTest.TestMiraiStruct
-import jceTest.JceOutputTest.TestQQStruct
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.readBytes
-import net.mamoe.mirai.qqandroid.network.io.JceInput
-import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
-import net.mamoe.mirai.utils.cryptor.contentToString
-import net.mamoe.mirai.utils.io.toIoBuffer
-import org.junit.Test
-
-private infix fun <T> T.shouldEqualTo(another: T) {
-    if (this is Array<*>) {
-        this.contentEquals(another as Array<*>)
-    } else
-        check(this.contentToString() == another.contentToString()) {
-            """actual:   ${this.contentToString()}
-              |required: ${another.contentToString()} 
-        """.trimMargin()
-        }
-}
-
-@UseExperimental(InternalAPI::class)
-private fun <R> ByteArray.qqJce(block: JceInputStream.() -> R): R {
-    return JceInputStream(this).run(block)
-}
-
-private fun <R> ByteArray.read(block: JceInput.() -> R): R {
-    return JceInput(this.toIoBuffer()).run(block)
-}
-
-private fun ByteReadPacket.check(block: ByteArray.() -> Unit) {
-    this.readBytes().apply(block)
-}
-
-internal class JceInputTest {
-
-    @Test
-    fun readByte() = buildJcePacket {
-        writeByte(1, 1)
-    }.check {
-        read {
-            readByte(1)
-        } shouldEqualTo qqJce {
-            read(0.toByte(), 1, true)
-        }
-    }
-
-    @Test
-    fun readDouble() = buildJcePacket {
-        writeDouble(1.0, 1)
-    }.check {
-        read {
-            readDouble(1)
-        } shouldEqualTo qqJce {
-            read(0.toDouble(), 1, true)
-        }
-    }
-
-    @Test
-    fun readFloat() = buildJcePacket {
-        writeFloat(1.0f, 1)
-    }.check {
-        read {
-            readFloat(1)
-        } shouldEqualTo qqJce {
-            read(0.toFloat(), 1, true)
-        }
-    }
-
-    @Test
-    fun readFully() = buildJcePacket {
-        writeFully(byteArrayOf(1, 2, 3), 1)
-    }.check {
-        read {
-            readByteArray(1)
-        } shouldEqualTo qqJce {
-            read(byteArrayOf(), 1, true)
-        }
-    }
-
-    @Test
-    fun testWriteFully() = buildJcePacket {
-        writeFully(shortArrayOf(1, 2, 3), 1)
-    }.check {
-        read {
-            readShortArray(1)
-        } shouldEqualTo qqJce {
-            read(shortArrayOf(), 1, true)
-        }
-    }
-
-    @Test
-    fun testWriteFully1() = buildJcePacket {
-        writeFully(intArrayOf(1, 2, 3), 1)
-    }.check {
-        read {
-            readIntArray(1)
-        } shouldEqualTo qqJce {
-            read(intArrayOf(), 1, true)
-        }
-    }
-
-    @Test
-    fun testWriteFully2() = buildJcePacket {
-        writeFully(longArrayOf(1, 2, 3), 1)
-    }.check {
-        read {
-            readLongArray(1)
-        } shouldEqualTo qqJce {
-            read(longArrayOf(), 1, true)
-        }
-    }
-
-    @Test
-    fun testWriteFully3() = buildJcePacket {
-        writeFully(booleanArrayOf(true, false, true), 1)
-    }.check {
-        read {
-            readBooleanArray(1)
-        } shouldEqualTo qqJce {
-            read(booleanArrayOf(), 1, true)
-        }
-    }
-
-    @Test
-    fun testWriteFully4() = buildJcePacket {
-        writeFully(floatArrayOf(1f, 2f, 3f), 1)
-    }.check {
-        read {
-            readFloatArray(1)
-        } shouldEqualTo qqJce {
-            read(floatArrayOf(), 1, true)
-        }
-    }
-
-    @Test
-    fun testWriteFully5() = buildJcePacket {
-        writeFully(doubleArrayOf(1.0, 2.0, 3.0), 1)
-    }.check {
-        read {
-            readDoubleArray(1)
-        } shouldEqualTo qqJce {
-            read(doubleArrayOf(), 1, true)
-        }
-    }
-
-    @Test
-    fun testWriteFully6() = buildJcePacket {
-        writeFully(arrayOf("sss", "哈哈"), 1)
-    }.check {
-        read {
-            readSimpleArray("", 1)
-        } shouldEqualTo qqJce {
-            read(arrayOf(""), 1, true)
-        }
-    }
-
-    @Test
-    fun testWriteFully7() = buildJcePacket {
-        writeFully(arrayOf("sss", "哈哈"), 1)
-    }.check {
-        read {
-            readArrayOrNull("", 1)!!
-        } shouldEqualTo qqJce {
-            read(arrayOf(""), 1, true)
-        }
-    }
-
-    @Test
-    fun testWriteFully8() = buildJcePacket {
-        writeFully(arrayOf(TestMiraiStruct("Haha")), 1)
-    }.check {
-        read {
-            readJceStructArrayOrNull(TestMiraiStruct, 1)!!
-        } shouldEqualTo qqJce {
-            read(arrayOf(TestQQStruct("stub")), 1, true)
-        }
-    }
-
-    @Test
-    fun readInt() = buildJcePacket {
-        writeInt(1, 2)
-    }.check {
-        read {
-            readInt(2)
-        } shouldEqualTo qqJce {
-            read(0, 2, true)
-        }
-    }
-
-    @Test
-    fun readLong() = buildJcePacket {
-        writeLong(1, 2)
-    }.check {
-        read {
-            readLong(2)
-        } shouldEqualTo qqJce {
-            read(0L, 2, true)
-        }
-    }
-
-    @Test
-    fun readShort() = buildJcePacket {
-        writeShort(1, 2)
-    }.check {
-        read {
-            readShort(2)
-        } shouldEqualTo qqJce {
-            read(0.toShort(), 2, true)
-        }
-    }
-
-    @Test
-    fun readBoolean() = buildJcePacket {
-        writeBoolean(true, 2)
-    }.check {
-        read {
-            readBoolean(2)
-        } shouldEqualTo qqJce {
-            read(false, 2, true)
-        }
-    }
-
-    @Test
-    fun readString() = buildJcePacket {
-        writeString("嗨", 2)
-    }.check {
-        read {
-            readString(2)
-        } shouldEqualTo qqJce {
-            read("", 2, true)
-        }
-    }
-
-    @Test
-    fun readMap() = buildJcePacket {
-        writeMap(mapOf(123.0 to "Hello"), 3)
-    }.check {
-        read {
-            readMap(0.0, "", 3)
-        } shouldEqualTo qqJce {
-            read(mapOf(0.0 to ""), 3, true)
-        }
-    }
-
-    @Test
-    fun readCollection() = buildJcePacket {
-        writeCollection(listOf("1", "还"), 3)
-    }.check {
-        repeat(0) {
-            error("fuck kotlin")
-        }
-        read {
-            readList("", 3)
-        } shouldEqualTo qqJce {
-            read(listOf(""), 3, true)
-        }
-    }
-
-
-    @Test
-    fun readJceStruct() = buildJcePacket {
-        writeJceStruct(TestMiraiStruct("123"), 3)
-    }.check {
-        read {
-            readJceStruct(TestMiraiStruct, 3)
-        } shouldEqualTo qqJce {
-            read(TestQQStruct("stub"), 3, true)!!
-        }
-    }
-
-    @Test
-    fun readObject() = buildJcePacket {
-        writeObject(123, 3)
-    }.check {
-        read {
-            readObject(123, 3)
-        } shouldEqualTo qqJce {
-            read(123 as Any, 3, true)
-        }
-    }
-
-}

+ 0 - 295
mirai-debug/src/test/kotlin/jceTest/JceOutputTest.kt

@@ -1,295 +0,0 @@
-package jceTest
-
-import io.ktor.util.InternalAPI
-import jce.jce.JceInputStream
-import jce.jce.JceOutputStream
-import jce.jce.JceStruct
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.readBytes
-import net.mamoe.mirai.qqandroid.network.io.JceInput
-import net.mamoe.mirai.qqandroid.network.io.JceOutput
-import net.mamoe.mirai.qqandroid.network.io.buildJcePacket
-import net.mamoe.mirai.utils.io.toUHexString
-import org.junit.Test
-
-private infix fun ByteReadPacket.shouldEqualTo(another: ByteArray) {
-    this.readBytes().let {
-        check(it.contentEquals(another)) {
-            """actual:   ${it.toUHexString()}
-              |required: ${another.toUHexString()} 
-        """.trimMargin()
-        }
-    }
-}
-
-@UseExperimental(InternalAPI::class)
-private fun qqJce(block: JceOutputStream.() -> Unit): ByteArray {
-    return JceOutputStream().apply(block).toByteArray()
-}
-
-internal class JceOutputTest {
-
-    @Test
-    fun writeByte() {
-        buildJcePacket {
-            writeByte(1, 1)
-            writeByte(-128, 2)
-        } shouldEqualTo qqJce {
-            write(1.toByte(), 1)
-            write((-128).toByte(), 2)
-        }
-    }
-
-    @Test
-    fun writeDouble() {
-        buildJcePacket {
-            writeDouble(1.0, 1)
-            writeDouble(-128.0, 2)
-        } shouldEqualTo qqJce {
-            write(1.toDouble(), 1)
-            write((-128).toDouble(), 2)
-        }
-    }
-
-    @Test
-    fun writeFloat() {
-        buildJcePacket {
-            writeFloat(1.0f, 1)
-            writeFloat(-128.0f, 2)
-        } shouldEqualTo qqJce {
-            write(1.toFloat(), 1)
-            write((-128).toFloat(), 2)
-        }
-    }
-
-    @Test
-    fun writeFully() {
-        buildJcePacket {
-            writeFully(byteArrayOf(1, 2), 1)
-            writeFully(byteArrayOf(1, 2), 2)
-        } shouldEqualTo qqJce {
-            write(byteArrayOf(1, 2), 1)
-            write(byteArrayOf(1, 2), 2)
-        }
-    }
-
-    @Test
-    fun testWriteFully() {
-        buildJcePacket {
-            writeFully(intArrayOf(1, 2), 1)
-            writeFully(intArrayOf(1, 2), 2)
-        } shouldEqualTo qqJce {
-            write(intArrayOf(1, 2), 1)
-            write(intArrayOf(1, 2), 2)
-        }
-    }
-
-    @Test
-    fun testWriteFully1() {
-        buildJcePacket {
-            writeFully(shortArrayOf(1, 2), 1)
-            writeFully(shortArrayOf(1, 2), 2)
-        } shouldEqualTo qqJce {
-            write(shortArrayOf(1, 2), 1)
-            write(shortArrayOf(1, 2), 2)
-        }
-    }
-
-    @Test
-    fun testWriteFully2() {
-        buildJcePacket {
-            writeFully(booleanArrayOf(true, false), 1)
-            writeFully(booleanArrayOf(true, false), 2)
-        } shouldEqualTo qqJce {
-            write(booleanArrayOf(true, false), 1)
-            write(booleanArrayOf(true, false), 2)
-        }
-    }
-
-    @Test
-    fun testWriteFully3() {
-        buildJcePacket {
-            writeFully(longArrayOf(1, 2), 1)
-            writeFully(longArrayOf(1, 2), 2)
-        } shouldEqualTo qqJce {
-            write(longArrayOf(1, 2), 1)
-            write(longArrayOf(1, 2), 2)
-        }
-    }
-
-    @Test
-    fun testWriteFully4() {
-        buildJcePacket {
-            writeFully(floatArrayOf(1f, 2f), 1)
-            writeFully(floatArrayOf(1f, 2f), 2)
-        } shouldEqualTo qqJce {
-            write(floatArrayOf(1f, 2f), 1)
-            write(floatArrayOf(1f, 2f), 2)
-        }
-    }
-
-    @Test
-    fun testWriteFully5() {
-        buildJcePacket {
-            writeFully(doubleArrayOf(1.0, 2.0), 1)
-            writeFully(doubleArrayOf(1.0, 2.0), 2)
-        } shouldEqualTo qqJce {
-            write(doubleArrayOf(1.0, 2.0), 1)
-            write(doubleArrayOf(1.0, 2.0), 2)
-        }
-    }
-
-    @Test
-    fun testWriteFully6() {
-        buildJcePacket {
-            writeFully(arrayOf("123", "哈哈"), 1)
-            writeFully(arrayOf("123", "哈哈"), 2)
-        } shouldEqualTo qqJce {
-            write(arrayOf("123", "哈哈"), 1)
-            write(arrayOf("123", "哈哈"), 2)
-        }
-    }
-
-    @Test
-    fun writeInt() {
-        buildJcePacket {
-            writeInt(1, 1)
-            writeInt(-128, 2)
-        } shouldEqualTo qqJce {
-            write(1, 1)
-            write(-128, 2)
-        }
-    }
-
-    @Test
-    fun writeLong() {
-        buildJcePacket {
-            writeLong(1, 1)
-            writeLong(-128, 2)
-        } shouldEqualTo qqJce {
-            write(1L, 1)
-            write(-128L, 2)
-        }
-    }
-
-    @Test
-    fun writeShort() {
-        buildJcePacket {
-            writeShort(1, 1)
-            writeShort(-128, 2)
-        } shouldEqualTo qqJce {
-            write(1.toShort(), 1)
-            write((-128).toShort(), 2)
-        }
-    }
-
-    @Test
-    fun writeBoolean() {
-        buildJcePacket {
-            writeBoolean(true, 1)
-            writeBoolean(false, 2)
-        } shouldEqualTo qqJce {
-            write(true, 1)
-            write(false, 2)
-        }
-    }
-
-    @Test
-    fun writeString() {
-        buildJcePacket {
-            writeString("1", 1)
-            writeString("哈啊", 2)
-        } shouldEqualTo qqJce {
-            write("1", 1)
-            write("哈啊", 2)
-        }
-    }
-
-    @Test
-    fun writeMap() {
-        buildJcePacket {
-            writeMap(mapOf("" to ""), 1)
-            writeMap(mapOf("" to 123), 2)
-            writeMap(mapOf(123.0 to "Hello"), 3)
-        } shouldEqualTo qqJce {
-            write(mapOf("" to ""), 1)
-            write(mapOf("" to 123), 2)
-            write(mapOf(123.0 to "Hello"), 3)
-        }
-    }
-
-    @Test
-    fun writeCollection() {
-        buildJcePacket {
-            writeCollection(listOf("啊", "333", "1"), 1)
-        } shouldEqualTo qqJce {
-            write(listOf("啊", "333", "1"), 1)
-        }
-    }
-
-    data class TestMiraiStruct(
-        val message: String
-    ) : net.mamoe.mirai.qqandroid.network.io.JceStruct() {
-        override fun writeTo(builder: JceOutput) {
-            builder.writeString(message, 0)
-        }
-
-        companion object : Factory<TestMiraiStruct> {
-            override fun newInstanceFrom(input: JceInput): TestMiraiStruct {
-                return TestMiraiStruct(input.readString(0))
-            }
-        }
-    }
-
-    class TestQQStruct(
-        private var message: String
-    ) : JceStruct() {
-        constructor() : this("")
-
-        override fun readFrom(var1: JceInputStream) {
-            message = var1.read("", 0, true)
-        }
-
-        override fun writeTo(var1: JceOutputStream) {
-            var1.write(message, 0)
-        }
-
-        override fun toString(): String {
-            return "TestMiraiStruct(message=$message)"
-        }
-    }
-
-    @Test
-    fun writeJceStruct() {
-        buildJcePacket {
-            writeJceStruct(TestMiraiStruct("Hello"), 0)
-            writeJceStruct(TestMiraiStruct("嗨"), 1)
-        } shouldEqualTo qqJce {
-            write(TestQQStruct("Hello"), 0)
-            write(TestQQStruct("嗨"), 1)
-        }
-    }
-
-    @Test
-    fun writeObject() {
-        buildJcePacket {
-            writeObject(0.toByte(), 1)
-            writeObject(0.toShort(), 2)
-            writeObject(0, 3)
-            writeObject(0L, 4)
-            writeObject(0f, 5)
-            writeObject(0.0, 6)
-            writeObject("hello", 7)
-            writeObject(TestMiraiStruct("Hello"), 8)
-        } shouldEqualTo qqJce {
-            write(0.toByte(), 1)
-            write(0.toShort(), 2)
-            write(0, 3)
-            write(0L, 4)
-            write(0f, 5)
-            write(0.0, 6)
-            write("hello", 7)
-            write(TestQQStruct("Hello"), 8)
-        }
-    }
-}

+ 23 - 6
mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt

@@ -10,11 +10,13 @@ import android.os.IBinder
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
+import kotlinx.io.core.IoBuffer
 import kotlinx.io.core.readBytes
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.event.subscribeMessages
 import net.mamoe.mirai.timpc.TIMPC
 import net.mamoe.mirai.utils.LoginFailedException
+import net.mamoe.mirai.utils.LoginSolver
 import java.lang.ref.WeakReference
 
 class MiraiService : Service() {
@@ -42,12 +44,27 @@ class MiraiService : Service() {
     private fun login(qq: Long, pwd: String) {
         GlobalScope.launch {
             mBot = TIMPC.Bot(qq, pwd) {
-                captchaSolver = {
-                    val bytes = it.readBytes()
-                    val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
-                    mCaptchaDeferred = CompletableDeferred()
-                    mCallback?.get()?.onCaptcha(bitmap)
-                    mCaptchaDeferred.await()
+                loginSolver = object : LoginSolver() {
+                    override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
+                        val bytes = data.readBytes()
+                        val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
+                        mCaptchaDeferred = CompletableDeferred()
+                        mCallback?.get()?.onCaptcha(bitmap)
+                        return mCaptchaDeferred.await()
+                    }
+
+                    override suspend fun onSolveSliderCaptcha(bot: Bot, data: IoBuffer): String? {
+                        TODO("not implemented")
+                    }
+
+                    override suspend fun onGetPhoneNumber(): String {
+                        TODO("not implemented")
+                    }
+
+                    override suspend fun onGetSMSVerifyCode(): String {
+                        TODO("not implemented")
+                    }
+
                 }
             }.apply {
                 try {