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 cb76190d3..f709304d3 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
@@ -25,9 +25,11 @@ enum class JceCharset(val kotlinCharset: Charset) {
     UTF8(Charset.forName("UTF8"))
 }
 
-
 internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<SerialId>(index)?.id
 
+/**
+ * Jce 数据结构序列化和反序列化工具, 能将 kotlinx.serialization 通用的注解标记格式的 `class` 序列化为 [ByteArray]
+ */
 class Jce private constructor(private val charset: JceCharset, context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
 
     private inner class ListWriter(
@@ -152,7 +154,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                         this.writeHead(STRUCT_END, 0)
                     }
                 } else if (value is ProtoBuf) {
-                    this.encodeTaggedByteArray(popTag(), net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.dump(value))
+                    this.encodeTaggedByteArray(popTag(), ProtoBufWithNullableSupport.dump(value))
                 } else {
                     serializer.serialize(this, value)
                 }
@@ -417,7 +419,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
 
         @Suppress("UNCHECKED_CAST")
         override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
-            println("decodeNullableSerializableValue: ${deserializer.getClassName()}")
+            // println("decodeNullableSerializableValue: ${deserializer.getClassName()}")
             if (deserializer is NullReader) {
                 return null
             }
@@ -444,7 +446,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                         else input.readByteArray(tag).toMutableList() as T
                     }
                     val tag = popTag()
-                    println(tag)
+//                    println(tag)
                     @Suppress("SENSELESS_COMPARISON") // false positive
                     if (input.skipToTagOrNull(tag) {
                             return deserializer.deserialize(JceListReader(input.readInt(0), input))
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt
index 1c06ac7f0..76167692e 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt
@@ -6,16 +6,12 @@ package net.mamoe.mirai.qqandroid.io.serialization
 
 import kotlinx.io.*
 import kotlinx.serialization.*
-import kotlinx.serialization.CompositeDecoder.Companion.READ_DONE
 import kotlinx.serialization.internal.*
 import kotlinx.serialization.modules.EmptyModule
 import kotlinx.serialization.modules.SerialModule
+import kotlinx.serialization.protobuf.ProtoBuf
 import kotlinx.serialization.protobuf.ProtoNumberType
 import kotlinx.serialization.protobuf.ProtoType
-import kotlinx.serialization.protobuf.ProtobufDecodingException
-import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.decodeSignedVarintInt
-import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.decodeSignedVarintLong
-import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.decodeVarint
 import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport.Varint.encodeVarint
 
 internal typealias ProtoDesc = Pair<Int, ProtoNumberType>
@@ -28,6 +24,12 @@ internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefa
 }
 
 
+/**
+ * 带有 null (optional) support 的 Protocol buffers 序列化器.
+ * 所有的为 null 的属性都将不会被序列化. 以此实现可选属性.
+ *
+ * 代码复制自 kotlinx.serialization. 修改部分已进行标注 (详见 "MIRAI MODIFY START")
+ */
 class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat {
 
     internal open inner class ProtobufWriter(val encoder: ProtobufEncoder) : TaggedEncoder<ProtoDesc>() {
@@ -171,178 +173,6 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
             }
     }
 
-    private open inner class ProtobufReader(val decoder: ProtobufDecoder) : TaggedDecoder<ProtoDesc>() {
-        override val context: SerialModule
-            get() = this@ProtoBufWithNullableSupport.context
-
-        private val indexByTag: MutableMap<Int, Int> = mutableMapOf()
-        private fun findIndexByTag(desc: SerialDescriptor, serialId: Int, zeroBasedDefault: Boolean = false): Int =
-            (0 until desc.elementsCount).firstOrNull {
-                extractParameters(
-                    desc,
-                    it,
-                    zeroBasedDefault
-                ).first == serialId
-            } ?: -1
-
-        override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder = when (desc.kind) {
-            StructureKind.LIST -> RepeatedReader(decoder, currentTag)
-            StructureKind.CLASS, UnionKind.OBJECT, is PolymorphicKind ->
-                ProtobufReader(makeDelimited(decoder, currentTagOrNull))
-            StructureKind.MAP -> MapEntryReader(makeDelimited(decoder, currentTagOrNull), currentTagOrNull)
-            else -> throw SerializationException("Primitives are not supported at top-level")
-        }
-
-        override fun decodeTaggedBoolean(tag: ProtoDesc): Boolean = when (val i = decoder.nextInt(ProtoNumberType.DEFAULT)) {
-            0 -> false
-            1 -> true
-            else -> throw ProtobufDecodingException("Expected boolean value (0 or 1), found $i")
-        }
-
-        override fun decodeTaggedByte(tag: ProtoDesc): Byte = decoder.nextInt(tag.second).toByte()
-        override fun decodeTaggedShort(tag: ProtoDesc): Short = decoder.nextInt(tag.second).toShort()
-        override fun decodeTaggedInt(tag: ProtoDesc): Int = decoder.nextInt(tag.second)
-        override fun decodeTaggedLong(tag: ProtoDesc): Long = decoder.nextLong(tag.second)
-        override fun decodeTaggedFloat(tag: ProtoDesc): Float = decoder.nextFloat()
-        override fun decodeTaggedDouble(tag: ProtoDesc): Double = decoder.nextDouble()
-        override fun decodeTaggedChar(tag: ProtoDesc): Char = decoder.nextInt(tag.second).toChar()
-        override fun decodeTaggedString(tag: ProtoDesc): String = decoder.nextString()
-        override fun decodeTaggedEnum(tag: ProtoDesc, enumDescription: SerialDescriptor): Int =
-            findIndexByTag(enumDescription, decoder.nextInt(ProtoNumberType.DEFAULT), zeroBasedDefault = true)
-
-        @Suppress("UNCHECKED_CAST")
-        override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when {
-            // encode maps as collection of map entries, not merged collection of key-values
-            deserializer.descriptor is MapLikeDescriptor -> {
-                val serializer = (deserializer as MapLikeSerializer<Any?, Any?, T, *>)
-                val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
-                val setOfEntries = HashSetSerializer(mapEntrySerial).deserialize(this)
-                setOfEntries.associateBy({ it.key }, { it.value }) as T
-            }
-            deserializer.descriptor == ByteArraySerializer.descriptor -> decoder.nextObject() as T
-            else -> deserializer.deserialize(this)
-        }
-
-        override fun SerialDescriptor.getTag(index: Int) = this.getProtoDesc(index)
-
-        override fun decodeElementIndex(desc: SerialDescriptor): Int {
-            while (true) {
-                if (decoder.curId == -1) // EOF
-                    return READ_DONE
-                val ind = indexByTag.getOrPut(decoder.curId) { findIndexByTag(desc, decoder.curId) }
-                if (ind == -1) // not found
-                    decoder.skipElement()
-                else return ind
-            }
-        }
-    }
-
-    private inner class RepeatedReader(decoder: ProtobufDecoder, val targetTag: ProtoDesc) : ProtobufReader(decoder) {
-        private var ind = -1
-
-        override fun decodeElementIndex(desc: SerialDescriptor) = if (decoder.curId == targetTag.first) ++ind else READ_DONE
-        override fun SerialDescriptor.getTag(index: Int): ProtoDesc = targetTag
-    }
-
-    private inner class MapEntryReader(decoder: ProtobufDecoder, val parentTag: ProtoDesc?) : ProtobufReader(decoder) {
-        override fun SerialDescriptor.getTag(index: Int): ProtoDesc =
-            if (index % 2 == 0) 1 to (parentTag?.second ?: ProtoNumberType.DEFAULT)
-            else 2 to (parentTag?.second ?: ProtoNumberType.DEFAULT)
-    }
-
-    internal class ProtobufDecoder(val inp: ByteArrayInputStream) {
-        val curId
-            get() = curTag.first
-        private var curTag: Pair<Int, Int> = -1 to -1
-
-        init {
-            readTag()
-        }
-
-        private fun readTag(): Pair<Int, Int> {
-            val header = decode32(eofAllowed = true)
-            curTag = if (header == -1) {
-                -1 to -1
-            } else {
-                val wireType = header and 0b111
-                val fieldId = header ushr 3
-                fieldId to wireType
-            }
-            return curTag
-        }
-
-        fun skipElement() {
-            when (curTag.second) {
-                VARINT -> nextInt(ProtoNumberType.DEFAULT)
-                i64 -> nextLong(ProtoNumberType.FIXED)
-                SIZE_DELIMITED -> nextObject()
-                i32 -> nextInt(ProtoNumberType.FIXED)
-                else -> throw ProtobufDecodingException("Unsupported start group or end group wire type")
-            }
-        }
-
-        @Suppress("NOTHING_TO_INLINE")
-        private inline fun assertWireType(expected: Int) {
-            if (curTag.second != expected) throw ProtobufDecodingException("Expected wire type $expected, but found ${curTag.second}")
-        }
-
-        fun nextObject(): ByteArray {
-            assertWireType(SIZE_DELIMITED)
-            val len = decode32()
-            check(len >= 0)
-            val ans = inp.readExactNBytes(len)
-            readTag()
-            return ans
-        }
-
-        fun nextInt(format: ProtoNumberType): Int {
-            val wireType = if (format == ProtoNumberType.FIXED) i32 else VARINT
-            assertWireType(wireType)
-            val ans = decode32(format)
-            readTag()
-            return ans
-        }
-
-        fun nextLong(format: ProtoNumberType): Long {
-            val wireType = if (format == ProtoNumberType.FIXED) i64 else VARINT
-            assertWireType(wireType)
-            val ans = decode64(format)
-            readTag()
-            return ans
-        }
-
-        fun nextFloat(): Float {
-            assertWireType(i32)
-            val ans = inp.readToByteBuffer(4).order(ByteOrder.LITTLE_ENDIAN).getFloat()
-            readTag()
-            return ans
-        }
-
-        fun nextDouble(): Double {
-            assertWireType(i64)
-            val ans = inp.readToByteBuffer(8).order(ByteOrder.LITTLE_ENDIAN).getDouble()
-            readTag()
-            return ans
-        }
-
-        fun nextString(): String {
-            val bytes = this.nextObject()
-            return stringFromUtf8Bytes(bytes)
-        }
-
-        private fun decode32(format: ProtoNumberType = ProtoNumberType.DEFAULT, eofAllowed: Boolean = false): Int = when (format) {
-            ProtoNumberType.DEFAULT -> decodeVarint(inp, 64, eofAllowed).toInt()
-            ProtoNumberType.SIGNED -> decodeSignedVarintInt(inp)
-            ProtoNumberType.FIXED -> inp.readToByteBuffer(4).order(ByteOrder.LITTLE_ENDIAN).getInt()
-        }
-
-        private fun decode64(format: ProtoNumberType = ProtoNumberType.DEFAULT): Long = when (format) {
-            ProtoNumberType.DEFAULT -> decodeVarint(inp, 64)
-            ProtoNumberType.SIGNED -> decodeSignedVarintLong(inp)
-            ProtoNumberType.FIXED -> inp.readToByteBuffer(8).order(ByteOrder.LITTLE_ENDIAN).getLong()
-        }
-    }
-
     /**
      *  Source for all varint operations:
      *  https://github.com/addthis/stream-lib/blob/master/src/main/java/com/clearspring/analytics/util/Varint.java
@@ -381,57 +211,10 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
             }
             return out
         }
-
-        internal fun decodeVarint(inp: InputStream, bitLimit: Int = 32, eofOnStartAllowed: Boolean = false): Long {
-            var result = 0L
-            var shift = 0
-            var b: Int
-            do {
-                if (shift >= bitLimit) {
-                    // Out of range
-                    throw ProtobufDecodingException("Varint too long: exceeded $bitLimit bits")
-                }
-                // Get 7 bits from next byte
-                b = inp.read()
-                if (b == -1) {
-                    if (eofOnStartAllowed && shift == 0) return -1
-                    else throw IOException("Unexpected EOF")
-                }
-                result = result or (b.toLong() and 0x7FL shl shift)
-                shift += 7
-            } while (b and 0x80 != 0)
-            return result
-        }
-
-        internal fun decodeSignedVarintInt(inp: InputStream): Int {
-            val raw = decodeVarint(inp, 32).toInt()
-            val temp = raw shl 31 shr 31 xor raw shr 1
-            // This extra step lets us deal with the largest signed values by treating
-            // negative results from read unsigned methods as like unsigned values.
-            // Must re-flip the top bit if the original read value had it set.
-            return temp xor (raw and (1 shl 31))
-        }
-
-        internal fun decodeSignedVarintLong(inp: InputStream): Long {
-            val raw = decodeVarint(inp, 64)
-            val temp = raw shl 63 shr 63 xor raw shr 1
-            // This extra step lets us deal with the largest signed values by treating
-            // negative results from read unsigned methods as like unsigned values
-            // Must re-flip the top bit if the original read value had it set.
-            return temp xor (raw and (1L shl 63))
-
-        }
     }
 
     companion object : BinaryFormat {
-        public override val context: SerialModule get() = plain.context
-
-        // todo: make more memory-efficient
-        private fun makeDelimited(decoder: ProtobufDecoder, parentTag: ProtoDesc?): ProtobufDecoder {
-            if (parentTag == null) return decoder
-            val bytes = decoder.nextObject()
-            return ProtobufDecoder(ByteArrayInputStream(bytes))
-        }
+        override val context: SerialModule get() = plain.context
 
         private fun SerialDescriptor.getProtoDesc(index: Int): ProtoDesc {
             return extractParameters(this, index)
@@ -457,9 +240,7 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
     }
 
     override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
-        val stream = ByteArrayInputStream(bytes)
-        val reader = ProtobufReader(ProtobufDecoder(stream))
-        return reader.decode(deserializer)
+        return ProtoBuf.load(deserializer, bytes)
     }
 
 }
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationHelper.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationHelper.kt
deleted file mode 100644
index e75bde933..000000000
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationHelper.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package net.mamoe.mirai.qqandroid.io.serialization
-
-import kotlinx.serialization.SerialDescriptor
-
-/*
- * Helper for kotlinx.serialization
- */
-
-internal inline fun <reified A: Annotation> SerialDescriptor.findAnnotation(elementIndex: Int): A? {
-    val candidates = getElementAnnotations(elementIndex).filterIsInstance<A>()
-    return when (candidates.size) {
-        0 -> null
-        1 -> candidates[0]
-        else -> throw IllegalStateException("There are duplicate annotations of type ${A::class} in the descriptor $this")
-    }
-}
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt
index 09e042a5f..bf3daae17 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt
@@ -2,6 +2,7 @@ package net.mamoe.mirai.qqandroid.io.serialization
 
 import kotlinx.io.core.*
 import kotlinx.serialization.DeserializationStrategy
+import kotlinx.serialization.SerialDescriptor
 import kotlinx.serialization.SerializationStrategy
 import net.mamoe.mirai.qqandroid.io.JceStruct
 import net.mamoe.mirai.qqandroid.io.ProtoBuf
@@ -91,4 +92,27 @@ fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T
  */
 fun <T : ProtoBuf> Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy<T>): T {
     return ProtoBufWithNullableSupport.load(serializer, this.readBytes())
-}
\ No newline at end of file
+}
+
+/**
+ * 构造 [RequestPacket] 的 [RequestPacket.sBuffer]
+ */
+fun <T : JceStruct> jceRequestSBuffer(name: String, serializer: SerializationStrategy<T>, jceStruct: T): ByteArray {
+    return RequestDataVersion3(
+        mapOf(
+            name to JCE_STRUCT_HEAD_OF_TAG_0 + jceStruct.toByteArray(serializer) + JCE_STRUCT_TAIL_OF_TAG_0
+        )
+    ).toByteArray(RequestDataVersion3.serializer())
+}
+
+private val JCE_STRUCT_HEAD_OF_TAG_0 = byteArrayOf(0x0A)
+private val JCE_STRUCT_TAIL_OF_TAG_0 = byteArrayOf(0x0B)
+
+internal inline fun <reified A : Annotation> SerialDescriptor.findAnnotation(elementIndex: Int): A? {
+    val candidates = getElementAnnotations(elementIndex).filterIsInstance<A>()
+    return when (candidates.size) {
+        0 -> null
+        1 -> candidates[0]
+        else -> throw IllegalStateException("There are duplicate annotations of type ${A::class} in the descriptor $this")
+    }
+}
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
index ec7a03344..f3eb44953 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt
@@ -18,7 +18,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.KnownPacketFactories
 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.PacketLogger
-import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendListPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.*
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
@@ -32,6 +31,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
     override val bot: QQAndroidBot by bot.unsafeWeakRef()
     override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
 
+    override val coroutineContext: CoroutineContext = bot.coroutineContext + CoroutineExceptionHandler { _, throwable ->
+        throwable.logStacktrace("Exception in NetworkHandler")
+    }
+
     private lateinit var channel: PlatformSocket
 
     override suspend fun login() {
@@ -88,19 +91,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
 
         println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}")
         StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>()
-        println("登陆完成 开始尝试获取friendList")
-        println("登陆完成 开始尝试获取friendList")
-        println("登陆完成 开始尝试获取friendList")
-        println("登陆完成 开始尝试获取friendList")
-        println("登陆完成 开始尝试获取friendList")
-        FriendListPacket(
-            bot.client,
-            0,
-            20,
-            0,
-            0
-        ).sendAndExpect<FriendListPacket.GetFriendListResponse>()
-
     }
 
     /**
@@ -168,18 +158,13 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
     }
 
     private suspend inline fun <P : Packet> generifiedParsePacket(input: Input) {
-        try {
-            KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int ->
-                handlePacket(packetFactory, packet, commandName, sequenceId)
-                if (packet is MultiPacket<*>) {
-                    packet.forEach {
-                        handlePacket(null, it, commandName, sequenceId)
-                    }
+        KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int ->
+            handlePacket(packetFactory, packet, commandName, sequenceId)
+            if (packet is MultiPacket<*>) {
+                packet.forEach {
+                    handlePacket(null, it, commandName, sequenceId)
                 }
             }
-        } finally {
-            println()
-            println() // separate for debugging
         }
     }
 
@@ -211,7 +196,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
             if (packet is Cancellable && packet.cancelled) return
         }
 
-        bot.logger.info(packet)
+        bot.logger.info("Received packet: $packet")
 
         packetFactory?.run {
             bot.handle(packet)
@@ -325,17 +310,19 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
     /**
      * 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
      */
-    suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
+    suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timoutMillis: Long = 3000): E {
         val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
         packetListeners.addLast(handler)
         bot.logger.info("Send: ${this.commandName}")
         channel.send(delegate)
-        return withTimeoutOrNull(3000) {
+        return withTimeoutOrNull(timoutMillis) {
             @Suppress("UNCHECKED_CAST")
             handler.await() as E
+
+            // 不要 `withTimeout`. timeout 的异常会不知道去哪了.
         } ?: net.mamoe.mirai.qqandroid.utils.inline {
             packetListeners.remove(handler)
-            error("timeout when sending ${this.commandName}")
+            error("timeout when sending ${commandName}")
         }
     }
 
@@ -351,6 +338,4 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
     }
 
     override suspend fun awaitDisconnection() = supervisor.join()
-
-    override val coroutineContext: CoroutineContext = bot.coroutineContext
 }
\ No newline at end of file
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
index a238f8734..41c6dc6af 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt
@@ -124,6 +124,7 @@ internal open class QQAndroidClient(
     @PublishedApi
     internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray()
 
+    var outgoingPacketUnknownValue: ByteArray = 0x02B05B8B.toByteArray()
     var loginState = 0
 
     var t150: Tlv? = null
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendListRequest.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendListRequest.kt
index 24a325239..3966bf581 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendListRequest.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendListRequest.kt
@@ -3,8 +3,6 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce
 import kotlinx.serialization.SerialId
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.qqandroid.io.JceStruct
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Vec0xd50
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Vec0xd6b
 
 @Serializable
 internal class GetFriendListReq(
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt
index 14773d7e5..36adb78a6 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt
@@ -23,7 +23,7 @@ class RequestPacket(
 
 @Serializable
 class RequestDataVersion3(
-    @SerialId(0) val map: Map<String, ByteArray>
+    @SerialId(0) val map: Map<String, ByteArray> // 注意: ByteArray 不能直接放序列化的 JceStruct!! 要放类似 RequestDataStructSvcReqRegister 的
 ) : JceStruct
 
 @Serializable
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
index 8c8a6d77d..798a30710 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt
@@ -9,6 +9,7 @@ import net.mamoe.mirai.qqandroid.io.serialization.loadAs
 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
 import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
+import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket
 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc
 import net.mamoe.mirai.utils.DefaultLogger
@@ -17,6 +18,7 @@ import net.mamoe.mirai.utils.cryptor.adjustToPublicKey
 import net.mamoe.mirai.utils.cryptor.decryptBy
 import net.mamoe.mirai.utils.io.*
 import net.mamoe.mirai.utils.unzip
+import net.mamoe.mirai.utils.withSwitch
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 import kotlin.jvm.JvmName
@@ -53,7 +55,7 @@ internal val DECRYPTER_16_ZERO = ByteArray(16)
 internal typealias PacketConsumer<T> = suspend (packetFactory: PacketFactory<T>, packet: T, commandName: String, ssoSequenceId: Int) -> Unit
 
 @PublishedApi
-internal val PacketLogger: MiraiLogger = DefaultLogger("Packet")
+internal val PacketLogger: MiraiLogger = DefaultLogger("Packet").withSwitch(false)
 
 @UseExperimental(ExperimentalUnsignedTypes::class)
 internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
@@ -63,7 +65,8 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
     MessageSvc.PushNotify,
     MessageSvc.PbGetMsg,
     MessageSvc.PushForceOffline,
-    MessageSvc.PbSendMsg
+    MessageSvc.PbSendMsg,
+    FriendList.GetFriendGroupList
 ) {
     // SvcReqMSFLoginNotify 自己的其他设备上限
     // MessageSvc.PushReaded 电脑阅读了别人的消息, 告知手机
@@ -193,8 +196,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
             PacketLogger.verbose("sso(inner)extraData = ${extraData.toUHexString()}")
 
             commandName = readString(readInt() - 4)
-            val unknown = readBytes(readInt() - 4)
-            if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
+            bot.client.outgoingPacketUnknownValue = readBytes(readInt() - 4)
 
             dataCompressed = readInt()
         }
@@ -213,7 +215,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
         // body
         val packetFactory = findPacketFactory(commandName)
 
-        bot.logger.info("Received: $commandName")
+        bot.logger.debug("Received commandName: $commandName")
         return IncomingPacket(packetFactory, ssoSequenceId, packet)
     }
 
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
index 557ff7a71..e8cb52115 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
@@ -2,7 +2,6 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
 
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.io.core.discardExact
-import kotlinx.io.core.writeFully
 import net.mamoe.mirai.data.MultiPacket
 import net.mamoe.mirai.data.Packet
 import net.mamoe.mirai.message.FriendMessage
@@ -26,8 +25,6 @@ import net.mamoe.mirai.qqandroid.utils.toRichTextElems
 import net.mamoe.mirai.utils.cryptor.contentToString
 import net.mamoe.mirai.utils.io.hexToBytes
 import net.mamoe.mirai.utils.io.toReadPacket
-import kotlin.math.absoluteValue
-import kotlin.random.Random
 
 class MessageSvc {
     /**
@@ -145,11 +142,11 @@ class MessageSvc {
                         richText = ImMsgBody.RichText(
                             elems = message.toRichTextElems()
                         )
-                    ),
-                    msgSeq = 17041,
-                    msgRand = Random.nextInt().absoluteValue,
-                    syncCookie = client.c2cMessageSync.syncCookie.takeIf { it.isNotEmpty() } ?: "08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00".hexToBytes(),
-                    msgVia = 1
+                    )
+                    //  msgSeq = 17041,
+                    //  msgRand = Random.nextInt().absoluteValue,
+                    //  syncCookie = client.c2cMessageSync.syncCookie.takeIf { it.isNotEmpty() } ?: "08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00".hexToBytes(),
+                    //  msgVia = 1
                 )
             )
         }
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
index f7ad93dfc..a1efd444a 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendListPacket.kt
@@ -3,56 +3,57 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.list
 import kotlinx.io.core.ByteReadPacket
 import net.mamoe.mirai.data.Packet
 import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.io.serialization.jceRequestSBuffer
 import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
 import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct
-import net.mamoe.mirai.qqandroid.io.writeJcePacket
 import net.mamoe.mirai.qqandroid.network.QQAndroidClient
 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.GetFriendListReq
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataStructSvcReqRegister
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3
 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.GetImgUrlReq
 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Vec0xd50
-import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Vec0xd6b
-import net.mamoe.mirai.qqandroid.network.protocol.packet.*
+import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
 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.chat.image.ImageDownPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
 import net.mamoe.mirai.utils.io.debugPrint
-import net.mamoe.mirai.utils.io.debugPrintln
 
 
-internal object FriendListPacket :
-    PacketFactory<FriendListPacket.GetFriendListResponse>("friendlist.getFriendGroupList") {
+internal class FriendList {
 
-    class GetFriendListResponse() : Packet
+    internal object GetFriendGroupList : PacketFactory<GetFriendGroupList.Response>("friendlist.getFriendGroupList") {
 
+        class Response : Packet {
+            override fun toString(): String = "FriendList.GetFriendGroupList.Response"
+        }
 
-    override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GetFriendListResponse {
-        println("aaaa")
-        return GetFriendListResponse()
-    }
+        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
+            // 00 00 0A D6 10 03 2C 3C 42 72 85 3C F2 56 29 6D 71 71 2E 49 4D 53 65 72 76 69 63 65 2E 46 72 69 65 6E 64 4C 69 73 74 53 65 72 76 69 63 65 53 65 72 76 61 6E 74 4F 62 6A 66 10 47 65 74 46 72 69 65 6E 64 4C 69 73 74 52 65 71 7D 00 01 0A 82 08 00 01 06 06 46 4C 52 45 53 50 1D 00 01 0A 72 0A 00 03 1C 22 76 E4 B8 DD 3C 41 86 9F 50 0A 60 0A 79 00 0A 0A 02 1A F7 2F 11 1C 21 01 2C 36 09 65 27 74 65 72 6E 69 74 79 4C 50 14 6C 7C 8C 9C A0 14 BC C6 00 DC E6 09 65 27 74 65 72 6E 69 74 79 FC 0F FD 10 00 0C FD 11 00 0C FC 12 FA 13 08 00 04 00 01 1A 0C 1C 2C 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 2C 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B FC 14 FD 15 00 00 0A 08 91 DE DC D7 01 88 19 B8 17 FC 16 FC 17 FC 18 F1 19 27 1E FC 1A F6 1B 00 FC 1C F0 1D 01 F2 1E 5C 4F 2E 39 F0 1F 02 F2 20 00 01 1E 27 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 FC 26 FC 27 FC 28 FD 29 00 0C FC 2A FC 2B FC 2C F6 2D 00 F2 2E 5E 0E BE 48 FC 2F FC 30 F6 31 00 FC 32 F0 33 02 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 0A 02 2D 5C 53 A6 1C 21 01 F2 36 06 E6 A2 A8 E5 A4 B4 4C 50 14 6C 7C 8C 9C A0 0A BC C6 00 DC E6 06 E6 A2 A8 E5 A4 B4 FC 0F FD 10 00 0C FD 11 00 0C F2 12 00 01 2F 02 FA 13 08 00 04 00 01 1A 0C 1C 20 03 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 20 03 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B FC 14 FD 15 00 00 0A 08 A6 A7 F1 EA 02 88 19 B8 17 FC 16 FC 17 FC 18 FC 19 F0 1A 0A F6 1B 0F 54 49 4D E7 A7 BB E5 8A A8 E5 9C A8 E7 BA BF FC 1C FC 1D FC 1E F0 1F FF F2 20 00 02 00 00 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 F2 26 5B 74 16 A2 FC 27 FC 28 FD 29 00 0C F0 2A 02 FC 2B FC 2C F6 2D 00 F2 2E 58 89 FB 37 FC 2F FC 30 F6 31 00 FC 32 FC 33 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 0A 02 3E 03 3F A2 1C 21 01 1D 36 09 48 69 6D 31 38 38 6D 6F 65 4C 50 14 6C 7C 8C 9C A0 0A BC C6 00 DC E6 09 48 69 6D 31 38 38 6D 6F 65 FC 0F FD 10 00 0C FD 11 00 0C F2 12 00 01 2E 01 FA 13 08 00 04 00 01 1A 0C 1C 20 07 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 20 07 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B FC 14 FD 15 00 00 0A 08 A2 FF 8C F0 03 88 19 B8 17 FC 16 FC 17 FC 18 FC 19 F0 1A 0A F6 1B 0F 54 49 4D E7 94 B5 E8 84 91 E5 9C A8 E7 BA BF FC 1C F0 1D 02 F2 1E 58 8C 5F D3 F0 1F 02 FC 20 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 F2 26 59 A6 FC BD FC 27 FC 28 FD 29 00 0C F0 2A 02 FC 2B FC 2C F6 2D 00 F2 2E 5A 9A A6 F9 FC 2F FC 30 F6 31 00 FC 32 FC 33 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 0A 02 59 17 3E 05 1C 21 02 1C 36 09 E3 82 A2 E3 82 A4 E3 83 A9 4C 50 0B 6C 7C 8C 9C A0 0A BC C6 00 DC E6 09 E3 82 A2 E3 82 A4 E3 83 A9 FC 0F FD 10 00 0C FD 11 00 0C F2 12 00 01 01 07 FA 13 08 00 04 00 01 1A 0C 1C 2C 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 2C 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B F0 14 01 FD 15 00 00 0A 08 85 FC DC C8 05 88 19 B8 17 F0 16 01 F1 17 08 F9 F0 18 01 F1 19 27 20 F0 1A 01 F6 1B 0C E6 89 8B E6 9C BA E5 9C A8 E7 BA BF FC 1C F0 1D 02 F2 1E 59 4A 8C EA F0 1F 02 F2 20 00 01 20 27 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 F2 26 5D 4F 9D 77 F2 27 59 84 39 2C FC 28 FD 29 00 0C F0 2A 02 FC 2B F0 2C 06 F6 2D 00 F2 2E 5C 4A B7 A2 FC 2F FC 30 F6 31 00 FC 32 FC 33 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 0A 02 76 E4 B8 DD 1C 21 02 5B 36 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 4C 50 0B 6C 7C 8C 9C A0 0A BC C6 00 DC E6 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E FC 0F FD 10 00 0C FD 11 00 0C F2 12 00 01 01 07 FA 13 08 00 04 00 01 1A 0C 1C 2C 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 2C 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B F0 14 01 FD 15 00 00 0A 08 DD F1 92 B7 07 88 19 B8 17 F0 16 01 FC 17 F0 18 01 FC 19 F0 1A 01 F6 1B 0C E6 89 8B E6 9C BA E5 9C A8 E7 BA BF FC 1C FC 1D FC 1E F0 1F 01 FC 20 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 FC 26 FC 27 FC 28 FD 29 00 0C F0 2A 02 FC 2B FC 2C F6 2D 00 F2 2E 5D B4 12 03 FC 2F FC 30 F6 31 00 FC 32 FC 33 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 0A 02 7C BC D3 C1 1C 21 00 ED 36 09 F0 9F 90 B8 6C 69 74 6F 75 4C 50 14 6C 7C 8C 9C A0 0A BC C6 00 DC E6 09 F0 9F 90 B8 6C 69 74 6F 75 FC 0F FD 10 00 0C FD 11 00 0C FC 12 FA 13 08 00 04 00 01 1A 0C 1C 20 07 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 20 07 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B F0 14 01 FD 15 00 00 0A 08 C1 A7 F3 E5 07 88 19 B8 17 F0 16 01 FC 17 FC 18 FC 19 F0 1A 0A F6 1B 0F 54 49 4D E7 A7 BB E5 8A A8 E5 9C A8 E7 BA BF FC 1C F0 1D 01 F2 1E 5D AA 93 F1 F0 1F 01 F2 20 00 02 00 00 F6 21 00 F6 22 00 FC 23 F2 24 5B C3 68 00 FC 25 F2 26 5C 66 22 C5 F2 27 59 93 B9 9D FC 28 FD 29 00 0C F0 2A 02 FC 2B F0 2C 14 F6 2D 00 F2 2E 5C A7 87 30 FC 2F FC 30 F6 31 00 FC 32 F0 33 02 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 0A 03 00 00 00 00 88 C8 FE 49 1C 21 02 3D 36 0A 32 32 39 34 38 37 33 36 37 33 4C 50 14 6C 7C 8C 9C A0 14 BC C6 00 DC E6 0A 32 32 39 34 38 37 33 36 37 33 FC 0F FD 10 00 0C FD 11 00 0C FC 12 FA 13 08 00 04 00 01 1A 0C 1C 2C 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 2C 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B FC 14 FD 15 00 00 0A 08 C9 FC A3 C6 08 88 19 B8 17 FC 16 FC 17 FC 18 FC 19 FC 1A F6 1B 00 FC 1C FC 1D FC 1E F0 1F 01 FC 20 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 FC 26 FC 27 FC 28 FD 29 00 0C FC 2A FC 2B FC 2C F6 2D 00 FC 2E FC 2F FC 30 F6 31 00 FC 32 FC 33 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 0A 03 00 00 00 00 A8 32 51 A1 1C 2C 36 01 4E 4C 50 0B 6C 7C 8C 9C A0 0A BC C6 00 D0 01 E6 0F E3 82 AE E3 83 A9 E3 83 86 E3 82 A3 E3 83 8A FC 0F FD 10 00 0C FD 11 00 0C F2 12 00 01 01 07 FA 13 08 00 04 00 01 1A 0C 1C 2C 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 2C 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B FC 14 FD 15 00 00 0A 08 A1 A3 C9 C1 0A 88 19 B8 17 FC 16 FC 17 FC 18 F1 19 27 1E F0 1A 65 F6 1B 0C E6 89 8B E6 9C BA E5 9C A8 E7 BA BF F1 1C 27 78 FC 1D FC 1E F0 1F 01 F2 20 00 01 1E 27 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 FC 26 FC 27 FC 28 FD 29 00 0C FC 2A FC 2B FC 2C F6 2D 00 F2 2E 59 62 C5 18 FC 2F FC 30 F6 31 00 FC 32 FC 33 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 0A 03 00 00 00 00 B1 89 BE 09 1C 21 02 58 36 01 4E 4C 50 14 6C 7C 8C 9C A0 0A BC C6 17 6C 69 75 6A 69 61 68 75 61 31 32 33 31 32 33 40 31 32 36 2E 63 6F 6D D0 01 E6 09 4E 61 74 75 72 61 6C 48 47 FC 0F FD 10 00 0C FD 11 00 0C F2 12 00 01 35 02 FA 13 08 00 04 00 01 1A 0C 1C 2C 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 2C 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B F0 14 71 FD 15 00 00 0A 08 89 FC A6 8C 0B 88 19 B8 17 FC 16 FC 17 F0 18 04 FC 19 F0 1A 04 F6 1B 0E 69 50 68 6F 6E 65 20 58 E5 9C A8 E7 BA BF FC 1C FC 1D FC 1E F0 1F 01 FC 20 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 F2 26 5D BB 7C 19 FC 27 FC 28 FD 29 00 0C FC 2A FC 2B FC 2C F6 2D 00 F2 2E 5D B5 3E F2 FC 2F FC 30 F6 31 00 FC 32 FC 33 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 0A 03 00 00 00 00 BC 41 BA A7 1C 21 02 3A 36 06 32 33 33 33 33 33 4C 50 14 6C 7C 8C 9C A0 14 BC C6 00 DC E6 06 32 33 33 33 33 33 FC 0F FD 10 00 0C FD 11 00 0C FC 12 FA 13 08 00 04 00 01 1A 0C 1C 2C 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 2C 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B FC 14 FD 15 00 00 0A 08 A7 F5 86 E2 0B 88 19 B8 17 FC 16 FC 17 FC 18 FC 19 FC 1A F6 1B 00 FC 1C FC 1D FC 1E F0 1F 01 FC 20 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 FC 26 FC 27 FC 28 FD 29 00 0C FC 2A FC 2B FC 2C F6 2D 00 F2 2E 5A 76 BE 66 FC 2F FC 30 F6 31 00 FC 32 FC 33 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B 8C 9C AC B0 9F C0 04 DC E9 0C FC 0F FC 10 F0 11 07 F2 12 5E 32 AF F9 FC 13 F9 14 0C FC 15 FC 16 FA 17 02 76 E4 B8 DD 1C 21 02 5B 36 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 4C 50 0B 6C 7C 8C 9C A0 0A BC C6 00 DC E6 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E FC 0F FD 10 00 0C FD 11 00 0C F2 12 00 01 01 07 FA 13 08 00 04 00 01 1A 0C 1C 2C 3C 4C 0B 00 02 1A 0C 1C 2C 3C 4C 0B 00 03 1A 0C 1C 2C 3C 4C 0B 00 07 1A 0C 1C 2C 3C 4C 0B 1C 2C 0B F0 14 01 FD 15 00 00 0A 08 DD F1 92 B7 07 88 19 B8 17 F0 16 01 FC 17 F0 18 01 FC 19 F0 1A 01 F6 1B 0C E6 89 8B E6 9C BA E5 9C A8 E7 BA BF FC 1C FC 1D FC 1E F0 1F 01 FC 20 F6 21 00 F6 22 00 FC 23 FC 24 FC 25 FC 26 FC 27 FC 28 FD 29 00 0C F0 2A 02 FC 2B FC 2C F6 2D 00 F2 2E 5D B4 12 03 FC 2F FC 30 F6 31 00 FC 32 FC 33 FD 34 00 0C FC 35 FC 36 FD 37 00 0C FD 38 00 0C FC 39 FC 3A 0B F0 18 01 FC 19 FA 1A 0C 1C 0B 0B 8C 98 0C A8 0C
 
-    operator fun invoke(
-        client: QQAndroidClient,
-        friendListStartIndex: Int,
-        friendListCount: Int,
-        groupListStartIndex: Int,
-        groupListCount: Int
-    ): OutgoingPacket {
-        return buildOutgoingUniPacket(client, key = client.wLoginSigInfo.d2Key) {
-            writeJceStruct(
-                RequestPacket.serializer(),
-                RequestPacket(
-                    sFuncName = "GetFriendListReq",
-                    sServantName = "mqq.IMService.FriendListServiceServantObj",
-                    iVersion = 3,
-                    cPacketType = 0x003,
-                    iMessageType = 0x00000,
-                    iRequestId = 1921334514,
-                    sBuffer = RequestDataVersion3(
-                        mapOf(
-                            "FL" to GetFriendListReq(
+            println("aaaa")
+            this.debugPrint()
+            return Response()
+        }
+
+        operator fun invoke(
+            client: QQAndroidClient,
+            friendListStartIndex: Int,
+            friendListCount: Int,
+            groupListStartIndex: Int,
+            groupListCount: Int
+        ): OutgoingPacket {
+            return buildOutgoingUniPacket(client, bodyType = 1, key = client.wLoginSigInfo.d2Key) {
+                writeJceStruct(
+                    RequestPacket.serializer(),
+                    RequestPacket(
+                        sFuncName = "GetFriendListReq",
+                        sServantName = "mqq.IMService.FriendListServiceServantObj",
+                        iVersion = 3,
+                        cPacketType = 0x003,
+                        iMessageType = 0x00000,
+                        iRequestId = 1921334514,
+                        sBuffer = jceRequestSBuffer(
+                            "FL",
+                            GetFriendListReq.serializer(),
+                            GetFriendListReq(
                                 reqtype = 3,
                                 ifReflush = if (friendListStartIndex <= 0) {
                                     0
@@ -86,14 +87,11 @@ internal object FriendListPacket :
                                     reqMutualmarkAlienation = 1
                                 ).toByteArray(Vec0xd50.ReqBody.serializer()),
                                 vecSnsTypelist = listOf(13580L, 13581L, 13582L)
-                            ).toByteArray(GetFriendListReq.serializer())
+                            )
                         )
-                    ).toByteArray(RequestDataVersion3.serializer())
+                    )
                 )
-            )
-            this.build().debugPrint()
+            }
         }
     }
-
-}
-
+}
\ No newline at end of file
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt
index ca62166bf..766f26ba7 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt
@@ -4,11 +4,9 @@ import kotlinx.io.core.ByteReadPacket
 import net.mamoe.mirai.data.Packet
 import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.io.serialization.ProtoBufWithNullableSupport
-import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
+import net.mamoe.mirai.qqandroid.io.serialization.jceRequestSBuffer
 import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct
 import net.mamoe.mirai.qqandroid.network.QQAndroidClient
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataStructSvcReqRegister
-import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3
 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.SvcReqRegister
 import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
@@ -61,67 +59,65 @@ class StatSvc {
                     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(),
+                        sBuffer = jceRequestSBuffer(
+                            "SvcReqRegister",
+                            SvcReqRegister.serializer(),
+                            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 = ProtoBufWithNullableSupport.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 = ProtoBufWithNullableSupport.dump(
+                                    Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
+                                        rpt_config_list = listOf(
+                                            Oidb0x769.ConfigSeq(
+                                                type = 46,
+                                                version = 0
+                                            ),
+                                            Oidb0x769.ConfigSeq(
+                                                type = 283,
+                                                version = 0
                                             )
-                                        ),
-                                        bSetMute = 0
+                                        )
                                     )
-                                ).toByteArray(RequestDataStructSvcReqRegister.serializer())
+                                ),
+                                bSetMute = 0
                             )
-                        ).toByteArray(RequestDataVersion3.serializer())
+                        )
                     )
                 )
             }
diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt
index 68c1b5cea..b9da65d88 100644
--- a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt
+++ b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/serverToClient.kt
@@ -219,7 +219,7 @@ private fun parseSsoFrame(input: ByteReadPacket): KnownPacketFactories.IncomingP
         commandName = readString(readInt() - 4)
         DebugLogger.warning("commandName=$commandName")
         val unknown = readBytes(readInt() - 4)
-        if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
+        //if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
 
         check(readInt() == 0)
     }
@@ -261,7 +261,7 @@ private fun parseUniFrame(input: ByteReadPacket): KnownPacketFactories.IncomingP
         commandName = readString(readInt() - 4)
         DebugLogger.warning("commandName=$commandName")
         val unknown = readBytes(readInt() - 4)
-        if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
+        //if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}")
 
         check(readInt() == 0)
     }
diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/test/JceDataClassGenerator.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/test/JceDataClassGenerator.kt
index dd533ea25..978638517 100644
--- a/mirai-core-qqandroid/src/jvmTest/kotlin/test/JceDataClassGenerator.kt
+++ b/mirai-core-qqandroid/src/jvmTest/kotlin/test/JceDataClassGenerator.kt
@@ -3,19 +3,23 @@ package test;
 import java.io.File
 
 fun main(){
-    val var9 = toJCEInfo(
-        File(
-            """
-        E:\Projects\QQAndroidFF\app\src\main\java\PushNotifyPack\RequestPushForceOffline.java
-    """.trimIndent()
-        ).readText()
-    )
     println(
         "import kotlinx.serialization.SerialId\n" +
                 "import kotlinx.serialization.Serializable\n" +
                 "import net.mamoe.mirai.qqandroid.io.JceStruct\n"
     )
-    println(var9.toString())
+    File(
+        """
+        E:\Projects\QQAndroidFF\app\src\main\java\friendlist\
+    """.trimIndent()
+    ).listFiles()!!.forEach {
+        try {
+            println(toJCEInfo(it.readText()).toString())
+        } catch (e: Exception) {
+            println("when processing ${it.path}")
+            throw e
+        }
+    }
 }
 
 
@@ -91,7 +95,7 @@ class Property(
     }
 
     fun toStringWithSpacing(maxIDLength:Int): String {
-        val space = " ".repeat(maxIDLength - (jceID.toString().length))
+        val space = " ".repeat((maxIDLength - (jceID.toString().length)).coerceAtLeast(0))
         var base = "    @SerialId(" + jceID + ") " + space + "val " + name + ":" + type + ""
         if(!isRequired){
             if(defaultValue == null) {
@@ -114,7 +118,7 @@ fun toJCEInfo(source:String):JCEInfo{
     val info = JCEInfo()
     val allProperties = mutableMapOf<String,Property>()
     var inputStreamVariableRegix:String? = null
-    println(source)
+    // println(source)
     source.split("\n").forEach{
         when{
             it.contains("class") -> {
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
index 27d55cf58..889564387 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt
@@ -92,7 +92,7 @@ abstract class Bot : CoroutineScope {
     abstract val network: BotNetworkHandler
 
     /**
-     * 登录.
+     * 登录, 或重新登录.
      *
      * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.login]
      *
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt
index 5d5f072aa..4341880a9 100644
--- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt
@@ -7,6 +7,7 @@ import kotlinx.coroutines.CompletableJob
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import net.mamoe.mirai.Bot
+import net.mamoe.mirai.utils.MiraiInternalAPI
 import net.mamoe.mirai.utils.io.PlatformDatagramChannel
 
 /**
@@ -40,7 +41,10 @@ abstract class BotNetworkHandler : CoroutineScope {
     /**
      * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回.
      * 本函数将挂起直到登录成功.
+     *
+     * 不要使用这个 API. 请使用 [Bot.login]
      */
+    @MiraiInternalAPI
     abstract suspend fun login()
 
     /**