diff --git a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt
index 97fd94331..c7af51525 100644
--- a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt
+++ b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2020 Mamoe Technologies and contributors.
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
  *
  *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@@ -24,6 +24,8 @@ import net.mamoe.mirai.internal.utils.io.ProtoBuf
 import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars
 import net.mamoe.mirai.utils.read
 import net.mamoe.mirai.utils.readPacketExact
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 
 internal fun <T : JceStruct> ByteArray.loadWithUniPacket(
     deserializer: DeserializationStrategy<T>,
@@ -158,6 +160,23 @@ internal fun <T : JceStruct> jceRequestSBuffer(
     ).toByteArray(RequestDataVersion3.serializer())
 }
 
+internal inline fun jceRequestSBuffer(block: JceRequestSBufferBuilder.() -> Unit): ByteArray {
+    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
+    return JceRequestSBufferBuilder().apply(block).complete()
+}
+
+internal class JceRequestSBufferBuilder {
+    val map: MutableMap<String, ByteArray> = LinkedHashMap()
+    operator fun <T : JceStruct> String.invoke(
+        serializer: SerializationStrategy<T>,
+        jceStruct: T
+    ) {
+        map[this] = JCE_STRUCT_HEAD_OF_TAG_0 + jceStruct.toByteArray(serializer) + JCE_STRUCT_TAIL_OF_TAG_0
+    }
+
+    fun complete(): ByteArray = RequestDataVersion3(map).toByteArray(RequestDataVersion3.serializer())
+}
+
 private val JCE_STRUCT_HEAD_OF_TAG_0 = byteArrayOf(0x0A)
 private val JCE_STRUCT_TAIL_OF_TAG_0 = byteArrayOf(0x0B)