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
new file mode 100644
index 000000000..09e042a5f
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt
@@ -0,0 +1,94 @@
+package net.mamoe.mirai.qqandroid.io.serialization
+
+import kotlinx.io.core.*
+import kotlinx.serialization.DeserializationStrategy
+import kotlinx.serialization.SerializationStrategy
+import net.mamoe.mirai.qqandroid.io.JceStruct
+import net.mamoe.mirai.qqandroid.io.ProtoBuf
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion2
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3
+import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
+import net.mamoe.mirai.utils.firstValue
+import net.mamoe.mirai.utils.io.read
+import net.mamoe.mirai.utils.io.toUHexString
+
+
+fun <T : JceStruct> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T {
+    return Jce.byCharSet(c).load(deserializer, this)
+}
+
+fun <T : JceStruct> BytePacketBuilder.writeJceStruct(serializer: SerializationStrategy<T>, struct: T, charset: JceCharset = JceCharset.GBK) {
+    this.writePacket(Jce.byCharSet(charset).dumpAsPacket(serializer, struct))
+}
+
+fun <T : JceStruct> ByteReadPacket.readRemainingAsJceStruct(serializer: DeserializationStrategy<T>, charset: JceCharset = JceCharset.UTF8): T {
+    return Jce.byCharSet(charset).load(serializer, this)
+}
+
+/**
+ * 先解析为 [RequestPacket], 即 `UniRequest`, 再按版本解析 map, 再找出指定数据并反序列化
+ */
+fun <T : JceStruct> ByteReadPacket.decodeUniPacket(deserializer: DeserializationStrategy<T>, name: String? = null): T {
+    return decodeUniRequestPacketAndDeserialize(name) {
+        it.read {
+            discardExact(1)
+            this.readRemainingAsJceStruct(deserializer)
+        }
+    }
+}
+
+/**
+ * 先解析为 [RequestPacket], 即 `UniRequest`, 再按版本解析 map, 再找出指定数据并反序列化
+ */
+fun <T : ProtoBuf> ByteReadPacket.decodeUniPacket(deserializer: DeserializationStrategy<T>, name: String? = null): T {
+    return decodeUniRequestPacketAndDeserialize(name) {
+        it.read {
+            discardExact(1)
+            this.readRemainingAsProtoBuf(deserializer)
+        }
+    }
+}
+
+
+private fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R {
+    val request = this.readRemainingAsJceStruct(RequestPacket.serializer())
+
+    return block(if (name == null) when (request.iVersion.toInt()) {
+        2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue()
+        3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.firstValue()
+        else -> error("unsupported version ${request.iVersion}")
+    } else when (request.iVersion.toInt()) {
+        2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.getOrElse(name) { error("cannot find $name") }.firstValue()
+        3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.getOrElse(name) { error("cannot find $name") }
+        else -> error("unsupported version ${request.iVersion}")
+    })
+}
+
+fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.GBK): ByteArray = Jce.byCharSet(c).dump(serializer, this)
+
+fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
+    this.writeFully(v.toByteArray(serializer).also {
+        println("发送 protobuf: ${it.toUHexString()}")
+    })
+}
+
+/**
+ * dump
+ */
+fun <T : ProtoBuf> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray {
+    return ProtoBufWithNullableSupport.dump(serializer, this)
+}
+
+/**
+ * load
+ */
+fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T {
+    return ProtoBufWithNullableSupport.load(deserializer, this)
+}
+
+/**
+ * load
+ */
+fun <T : ProtoBuf> Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy<T>): T {
+    return ProtoBufWithNullableSupport.load(serializer, this.readBytes())
+}
\ No newline at end of file