diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceOutput.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceOutput.kt
new file mode 100644
index 000000000..a00696fbd
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceOutput.kt
@@ -0,0 +1,292 @@
+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)
\ No newline at end of file
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceStruct.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceStruct.kt
index adb5a6705..d58d65ca2 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceStruct.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/JceStruct.kt
@@ -1,3 +1,5 @@
 package net.mamoe.mirai.qqandroid.io
 
-interface JceStruct
\ No newline at end of file
+interface JceStruct {
+    fun writeTo(output: JceOutput) = Unit
+}
\ No newline at end of file
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt
index 9e54407a8..b644180d5 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt
@@ -13,7 +13,6 @@ 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")
@@ -24,7 +23,7 @@ fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset
     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)
+fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.GBK): ByteArray = Jce.byCharSet(c).dump(serializer, this)
 
 enum class JceCharset(val kotlinCharset: Charset) {
     GBK(Charset.forName("GBK")),
@@ -104,7 +103,6 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         @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)
@@ -115,24 +113,26 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
             }
             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
-                    )
-                }
+                //  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
+                            is ByteArray -> value.size
+                            is BooleanArray -> value.size
+                            else -> error("unknown array type: ${value.getClassName()}")
+                        }, popTag(), this
+                    ),
+                    value
+                )
+                //  }
             }
             is ArrayClassDesc -> {
                 serializer.serialize(
@@ -260,13 +260,13 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         }
 
         @PublishedApi
-        internal fun writeHead(type: Int, tag: Int) {
+        internal fun writeHead(type: Byte, tag: Int) {
             if (tag < 15) {
-                this.output.write((tag shl 4) or type)
+                this.output.write((tag shl 4) or type.toInt())
                 return
             }
             if (tag < 256) {
-                this.output.write(type or 0xF0)
+                this.output.write(type.toInt() or 0xF0)
                 this.output.write(tag)
                 return
             }
@@ -274,6 +274,45 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         }
     }
 
+    private open inner class JceMapReader(
+        val size: Int,
+        input: JceInput
+    ) : JceDecoder(input) {
+        override fun decodeCollectionSize(desc: SerialDescriptor): Int {
+            return size
+        }
+
+        override fun SerialDescriptor.getTag(index: Int): Int {
+            // 奇数 0, 即 key; 偶数 1, 即 value
+            return if (index % 2 == 0) 0 else 1
+        }
+    }
+
+    private open inner class JceListReader(
+        val size: Int,
+        input: JceInput
+    ) : JceDecoder(input) {
+        override fun decodeCollectionSize(desc: SerialDescriptor): Int {
+            return size
+        }
+
+        override fun SerialDescriptor.getTag(index: Int): Int {
+            return 0
+        }
+    }
+
+    private open inner class JceStructReader(
+        input: JceInput
+    ) : JceDecoder(input) {
+        override fun endStructure(desc: SerialDescriptor) {
+            input.readHead() // STRUCT_END
+        }
+    }
+
+    private open inner class NullReader(
+        input: JceInput
+    ) : JceDecoder(input)
+
     private open inner class JceDecoder(
         internal val input: JceInput
     ) : TaggedDecoder<Int>() {
@@ -291,73 +330,134 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         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()
+        /**
+         * 在 [KSerializer.serialize] 前
+         */
+        override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
+            println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}")
+            when (desc) {
+                // 由于 Byte 的数组有两种方式写入, 需特定读取器
+                ByteArraySerializer.descriptor -> {
+                    // ByteArray, 交给 decodeSerializableValue 进行处理
+                    return this
+                }
+                is ListLikeDescriptor -> {
+                    if (typeParams.isNotEmpty() && typeParams[0] is ByteSerializer) {
+                        // Array<Byte>
+                        return this // 交给 decodeSerializableValue
+                    }
+
+                    val tag = currentTagOrNull
+                    @Suppress("SENSELESS_COMPARISON") // 推断 bug
+                    if (tag != null && input.skipToTagOrNull(tag) {
+                            popTag()
+                            if (it.type == SIMPLE_LIST) {
+                                input.readHead() // list 里面元素类型, 没必要知道
+                            }
+                            return when (it.type) {
+                                SIMPLE_LIST, LIST -> JceListReader(input.readInt(0), this.input)
+                                MAP -> JceMapReader(input.readInt(0), this.input)
+                                else -> error("type mismatch")
+                            }
+                        } == null && desc.isNullable) {
+                        return NullReader(this.input)
+                    }
+                }
+
+                is MapLikeDescriptor -> {
+                    val tag = currentTagOrNull
+
+                    if (tag != null && input.skipToTagOrNull(tag) { popTag() } == null && desc.isNullable) {
+                        return NullReader(this.input)
+                    }
+
+                    if (tag!=null) {
+                        popTag()
+                    }
+                    return JceMapReader(input.readInt(0), this.input)
+                }
+            }
+
+            if (!input.input.endOfInput) {
+                val tag = currentTagOrNull
+                if (tag != null && input.peakHead().tag > tag) {
+                    return NullReader(this.input)
+                }
+            }
+
+            if (desc.kind == StructureKind.CLASS || desc.kind == UnionKind.OBJECT) {
+                val tag = currentTagOrNull
+                if (tag != null) {
+                    @Suppress("SENSELESS_COMPARISON") // 推断 bug
+                    if (input.skipToTagOrNull(tag) {
+                            popTag()
+                            return JceStructReader(input)
+                        } == null && desc.isNullable) {
+                        return NullReader(this.input)
+                    }
+                }
+
+                return this // top-level
+            }
+
+            return super.beginStructure(desc, *typeParams)
+        }
+
+        override fun decodeTaggedNull(tag: Int): Nothing? {
+            return null
+        }
+
+        override fun decodeTaggedNotNullMark(tag: Int): Boolean {
+            return !input.input.endOfInput && input.peakHead().tag <= tag
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
+            println("decodeNullableSerializableValue: ${deserializer.getClassName()}")
+            if (deserializer is NullReader) {
+                return null
+            }
+            when (deserializer.descriptor) {
+                ByteArraySerializer.descriptor -> {
+                    return input.readByteArray(popTag()) as T
+                }
+                is ListLikeDescriptor -> {
+                    if (deserializer is ReferenceArraySerializer<*, *>
+                        && (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams.isNotEmpty()
+                        && (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams[0] is ByteSerializer
+                    ) {
+                        return input.readByteArray(popTag()).toTypedArray() as T
+                    } else if (deserializer is ArrayListSerializer<*>
+                        && (deserializer as ArrayListSerializer<*>).typeParams.isNotEmpty()
+                        && (deserializer as ArrayListSerializer<*>).typeParams[0] is ByteSerializer
+                    ) {
+                        return input.readByteArray(popTag()).toMutableList() as T
+                    }
+                    return super.decodeSerializableValue(deserializer)
+                }
+                is MapLikeDescriptor -> {
+                    // 将 mapOf(k1 to v1, k2 to v2, ...) 转换为 listOf(k1, v1, k2, v2, ...) 以便于写入.
+                    val serializer = (deserializer as MapLikeSerializer<Any?, Any?, T, *>)
+                    val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
+                    val setOfEntries = HashSetSerializer(mapEntrySerial).deserialize(this)
+                    return setOfEntries.associateBy({ it.key }, { it.value }) as T
+                }
+            }
+            val tag = currentTagOrNull ?: return deserializer.deserialize(this)
+            return if (this.decodeTaggedNotNullMark(tag)){
+                deserializer.deserialize(this)
+            } else {
+                null
+            }
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
+            return decodeNullableSerializableValue(deserializer as DeserializationStrategy<Any?>) as? T ?: error("value is not optional but cannot find")
+        }
     }
 
 
@@ -606,23 +706,23 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
             }
         }
 
-        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 BYTE: Byte = 0
+        internal const val DOUBLE: Byte = 5
+        internal const val FLOAT: Byte = 4
+        internal const val INT: Byte = 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
+        internal const val LIST: Byte = 9
+        internal const val LONG: Byte = 3
+        internal const val MAP: Byte = 8
+        internal const val SHORT: Byte = 1
+        internal const val SIMPLE_LIST: Byte = 13
+        internal const val STRING1: Byte = 6
+        internal const val STRING4: Byte = 7
+        internal const val STRUCT_BEGIN: Byte = 10
+        internal const val STRUCT_END: Byte = 11
+        internal const val ZERO_TYPE: Byte = 12
 
-        private fun Any?.getClassName(): KClass<out Any> = if (this == null) Unit::class else this::class
+        private fun Any?.getClassName(): String = (if (this == null) Unit::class else this::class).simpleName ?: "<unnamed class>"
     }
 
     override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt
index 8f6b3a06f..875453673 100644
--- a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt
+++ b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt
@@ -1,11 +1,19 @@
 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.JceOutput
 import net.mamoe.mirai.qqandroid.io.JceStruct
+import net.mamoe.mirai.qqandroid.io.buildJcePacket
 import net.mamoe.mirai.utils.cryptor.contentToString
+import net.mamoe.mirai.utils.io.toUHexString
 import kotlin.test.Test
+import kotlin.test.assertEquals
 
+fun main() {
+    JceDecoderTest().testSimpleMap()
+}
 
 class JceDecoderTest {
 
@@ -18,23 +26,113 @@ class JceDecoderTest {
         @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())
+    ) : JceStruct {
+        override fun writeTo(output: JceOutput) = output.run {
+            writeString(string, 0)
+            writeByte(byte, 1)
+            writeShort(short, 2)
+            writeInt(int, 3)
+            writeLong(long, 4)
+            writeFloat(float, 5)
+            writeDouble(double, 6)
+        }
     }
 
-    @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()
+        @SerialId(8) val byteList: List<Byte> = listOf(1, 2, 3), // error here
+        @SerialId(9) val map: Map<String, Map<String, ByteArray>> = mapOf("哈哈" to mapOf("哈哈" to byteArrayOf(1, 2, 3))),
+        //   @SerialId(10) val nestedJceStruct: TestSimpleJceStruct = TestSimpleJceStruct(),
+        @SerialId(11) val byteList2: List<List<Int>> = listOf(listOf(1, 2, 3), listOf(1, 2, 3))
     ) : JceStruct
+
+    @Serializable
+    class TestComplexNullableJceStruct(
+        @SerialId(7) val byteArray: ByteArray? = byteArrayOf(1, 2, 3),
+        @SerialId(8) val byteList: List<Byte>? = listOf(1, 2, 3), // error here
+        @SerialId(9) val map: Map<String, Map<String, ByteArray>>? = mapOf("哈哈" to mapOf("哈哈" to byteArrayOf(1, 2, 3))),
+        @SerialId(10) val nestedJceStruct: TestSimpleJceStruct? = TestSimpleJceStruct(),
+        @SerialId(11) val byteList2: List<List<Int>>? = listOf(listOf(1, 2, 3), listOf(1, 2, 3))
+    ) : JceStruct
+
+    @Test
+    fun testEncoder() {
+        println(TestComplexJceStruct().toByteArray(TestComplexJceStruct.serializer()).loadAs(TestComplexNullableJceStruct.serializer()).contentToString())
+    }
+
+    @Test
+    fun testEncoder2() {
+        assertEquals(
+            buildJcePacket {
+                writeFully(byteArrayOf(1, 2, 3), 7)
+                writeCollection(listOf(1, 2, 3), 8)
+                writeMap(mapOf("哈哈" to mapOf("哈哈" to byteArrayOf(1, 2, 3))), 9)
+                writeJceStruct(TestSimpleJceStruct(), 10)
+                writeCollection(listOf(listOf(1, 2, 3), listOf(1, 2, 3)), 11)
+            }.readBytes().toUHexString(),
+            TestComplexNullableJceStruct().toByteArray(TestComplexNullableJceStruct.serializer()).toUHexString()
+        )
+    }
+
+
+    @Test
+    fun testNestedList() {
+        @Serializable
+        class TestNestedList(
+            @SerialId(7) val array: List<List<Int>> = listOf(listOf(1, 2, 3), listOf(1, 2, 3), listOf(1, 2, 3))
+        )
+
+        println(buildJcePacket {
+            writeCollection(listOf(listOf(1, 2, 3), listOf(1, 2, 3), listOf(1, 2, 3)), 7)
+        }.readBytes().loadAs(TestNestedList.serializer()).contentToString())
+    }
+
+    @Test
+    fun testNestedArray() {
+        @Serializable
+        class TestNestedArray(
+            @SerialId(7) val array: Array<Array<Int>> = arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3))
+        )
+
+        println(buildJcePacket {
+            writeFully(arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3)), 7)
+        }.readBytes().loadAs(TestNestedArray.serializer()).contentToString())
+    }
+
+    @Test
+    fun testSimpleMap() {
+
+        @Serializable
+        class TestSimpleMap(
+            @SerialId(7) val map: Map<String, Long> = mapOf("byteArrayOf(1)" to 2222L)
+        )
+        println(buildJcePacket {
+            writeMap(mapOf("byteArrayOf(1)" to 2222), 7)
+        }.readBytes().loadAs(TestSimpleMap.serializer()).contentToString())
+    }
+
+    @Test
+    fun testSimpleList() {
+
+        @Serializable
+        class TestSimpleList(
+            @SerialId(7) val list: List<String> = listOf("asd", "asdasdasd")
+        )
+        println(buildJcePacket {
+            writeCollection(listOf("asd", "asdasdasd"), 7)
+        }.readBytes().loadAs(TestSimpleList.serializer()).contentToString())
+    }
+
+    @Test
+    fun testNestedMap() {
+        @Serializable
+        class TestNestedMap(
+            @SerialId(7) val map: Map<ByteArray, Map<ByteArray, ShortArray>> = mapOf(byteArrayOf(1) to mapOf(byteArrayOf(1) to shortArrayOf(2)))
+        )
+        println(buildJcePacket {
+            writeMap(mapOf(byteArrayOf(1) to mapOf(byteArrayOf(1) to shortArrayOf(2))), 7)
+        }.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value!!.contentToString())
+    }
 }
\ No newline at end of file