From 2f9d104f88314724bbf3b1ce8dac88e8ea0ee40c Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Tue, 28 Jan 2020 20:37:02 +0800
Subject: [PATCH] Jce Serialization optional elements support

---
 mirai-api-http/build.gradle.kts               |   2 +-
 mirai-core-qqandroid/build.gradle.kts         |   2 +-
 .../mirai/qqandroid/io/serialization/Jce.kt   | 167 +++++++++++-------
 .../network/protocol/jce/RequestPacket.kt     |   1 -
 .../network/protocol/jce/SvcReqRegister.kt    |   4 +-
 .../network/protocol/packet/PacketFactory.kt  |   2 +-
 .../packet/chat/data/PushNotifyPack.kt        |  16 +-
 .../chat/receive/MessageSvc.PushNotify.kt     |  42 +++++
 .../packet/chat/receive/MessageSvc.kt         |  34 ----
 .../network/protocol/packet/login/Register.kt |   7 +-
 .../packet/oidb/oidb0x769/Oidb0x769.kt        |  21 +--
 .../JceDecoderTest.kt                         |  26 +--
 .../TestRequesetPacket.kt                     |  17 ++
 mirai-core-timpc/build.gradle.kts             |   2 +-
 mirai-core/build.gradle.kts                   |   2 +-
 .../kotlin/net.mamoe.mirai/utils/map.kt       |   9 +
 mirai-debug/build.gradle.kts                  |   2 +-
 mirai-japt/build.gradle.kts                   |   2 +-
 mirai-plugins/image-sender/build.gradle.kts   |   2 +-
 19 files changed, 213 insertions(+), 147 deletions(-)
 create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt
 delete mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
 create mode 100644 mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/TestRequesetPacket.kt
 create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt

diff --git a/mirai-api-http/build.gradle.kts b/mirai-api-http/build.gradle.kts
index 5c5ad76ae..4f036e1d1 100644
--- a/mirai-api-http/build.gradle.kts
+++ b/mirai-api-http/build.gradle.kts
@@ -55,7 +55,7 @@ kotlin {
 
     sourceSets.all {
         languageSettings.enableLanguageFeature("InlineClasses")
-        languageSettings.enableLanguageFeature("NewInference")
+
         languageSettings.useExperimentalAnnotation("kotlin.Experimental")
 
         dependencies {
diff --git a/mirai-core-qqandroid/build.gradle.kts b/mirai-core-qqandroid/build.gradle.kts
index e4d8b96a3..7afca1a66 100644
--- a/mirai-core-qqandroid/build.gradle.kts
+++ b/mirai-core-qqandroid/build.gradle.kts
@@ -58,7 +58,7 @@ kotlin {
     sourceSets {
         all {
             languageSettings.enableLanguageFeature("InlineClasses")
-            languageSettings.enableLanguageFeature("NewInference")
+
             languageSettings.useExperimentalAnnotation("kotlin.Experimental")
 
             dependencies {
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 b644180d5..c4fa4f6fd 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
@@ -1,8 +1,5 @@
 package net.mamoe.mirai.qqandroid.io.serialization
 
-import kotlinx.io.ByteArrayOutputStream
-import kotlinx.io.ByteBuffer
-import kotlinx.io.ByteOrder
 import kotlinx.io.charsets.Charset
 import kotlinx.io.core.*
 import kotlinx.serialization.*
@@ -11,6 +8,8 @@ import kotlinx.serialization.modules.EmptyModule
 import kotlinx.serialization.modules.SerialModule
 import net.mamoe.mirai.qqandroid.io.JceStruct
 import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse
+import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
+import net.mamoe.mirai.utils.io.readIoBuffer
 import net.mamoe.mirai.utils.io.readString
 import net.mamoe.mirai.utils.io.toIoBuffer
 
@@ -23,6 +22,14 @@ fun <T> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset
     return Jce.byCharSet(c).load(deserializer, this)
 }
 
+fun <T> BytePacketBuilder.writeJceStruct(serializer: SerializationStrategy<T>, struct: T, charset: JceCharset = JceCharset.GBK) {
+    this.writePacket(Jce.byCharSet(charset).dumpAsPacket(serializer, struct))
+}
+
+fun <T> ByteReadPacket.readRemainingAsJceStruct(serializer: DeserializationStrategy<T>, charset: JceCharset = JceCharset.UTF8): T {
+    return Jce.byCharSet(charset).load(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) {
@@ -39,7 +46,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         private val count: Int,
         private val tag: Int,
         private val parentEncoder: JceEncoder
-    ) : JceEncoder(ByteArrayOutputStream()) {
+    ) : JceEncoder(BytePacketBuilder()) {
         override fun SerialDescriptor.getTag(index: Int): Int {
             return 0
         }
@@ -47,12 +54,12 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         override fun endEncode(desc: SerialDescriptor) {
             parentEncoder.writeHead(LIST, this.tag)
             parentEncoder.encodeTaggedInt(0, count)
-            parentEncoder.output.write(this.output.toByteArray())
+            parentEncoder.output.writePacket(this.output.build())
         }
     }
 
     private inner class JceMapWriter(
-        output: ByteArrayOutputStream
+        output: BytePacketBuilder
     ) : JceEncoder(output) {
         override fun SerialDescriptor.getTag(index: Int): Int {
             return if (index % 2 == 0) 0 else 1
@@ -81,7 +88,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
     @Suppress("unused", "MemberVisibilityCanBePrivate")
     @UseExperimental(ExperimentalIoApi::class)
     private open inner class JceEncoder(
-        internal val output: ByteArrayOutputStream
+        internal val output: BytePacketBuilder
     ) : TaggedEncoder<Int>() {
         override val context get() = this@Jce.context
 
@@ -100,6 +107,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
             else -> throw SerializationException("Primitives are not supported at top-level")
         }
 
+        @UseExperimental(ImplicitReflectionSerializer::class)
         @Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
         override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) {
             is MapLikeDescriptor -> {
@@ -113,9 +121,6 @@ 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) {
@@ -132,13 +137,16 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                     ),
                     value
                 )
-                //  }
             }
             is ArrayClassDesc -> {
-                serializer.serialize(
-                    ListWriter((value as Array<*>).size, popTag(), this),
-                    value
-                )
+                val descriptor = serializer.descriptor as ReferenceArraySerializer<Any, Any?>
+                if (descriptor.typeParams.isNotEmpty() && descriptor.typeParams[0] is ByteSerializer) {
+                    encodeTaggedByteArray(popTag(), (value as Array<Byte>).toByteArray())
+                } else
+                    serializer.serialize(
+                        ListWriter((value as Array<*>).size, popTag(), this),
+                        value
+                    )
             }
             is ListLikeDescriptor -> {
                 serializer.serialize(
@@ -151,11 +159,15 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                     if (currentTagOrNull == null) {
                         serializer.serialize(this, value)
                     } else {
-                        this.writeHead(STRUCT_BEGIN, currentTag)
+                        this.writeHead(STRUCT_BEGIN, popTag())
                         serializer.serialize(this, value)
                         this.writeHead(STRUCT_END, 0)
                     }
-                } else serializer.serialize(this, value)
+                } else if (value is ProtoBuf) {
+                    this.encodeTaggedByteArray(popTag(), kotlinx.serialization.protobuf.ProtoBuf.dump(value))
+                } else {
+                    serializer.serialize(this, value)
+                }
             }
         }
 
@@ -164,7 +176,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                 writeHead(ZERO_TYPE, tag)
             } else {
                 writeHead(BYTE, tag)
-                output.write(value.toInt())
+                output.writeByte(value)
             }
         }
 
@@ -173,7 +185,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                 encodeTaggedByte(tag, value.toByte())
             } else {
                 writeHead(SHORT, tag)
-                output.write(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(value).array())
+                output.writeShort(value)
             }
         }
 
@@ -182,18 +194,18 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                 encodeTaggedShort(tag, value.toShort())
             } else {
                 writeHead(INT, tag)
-                output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array())
+                output.writeInt(value)
             }
         }
 
         override fun encodeTaggedFloat(tag: Int, value: Float) {
             writeHead(FLOAT, tag)
-            output.write(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array())
+            output.writeFloat(value)
         }
 
         override fun encodeTaggedDouble(tag: Int, value: Double) {
             writeHead(DOUBLE, tag)
-            output.write(ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array())
+            output.writeDouble(value)
         }
 
         override fun encodeTaggedLong(tag: Int, value: Long) {
@@ -201,7 +213,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                 encodeTaggedInt(tag, value.toInt())
             } else {
                 writeHead(LONG, tag)
-                output.write(ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array())
+                output.writeLong(value)
             }
         }
 
@@ -228,19 +240,20 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
             writeHead(SIMPLE_LIST, tag)
             writeHead(BYTE, 0)
             encodeTaggedInt(0, bytes.size)
-            output.write(bytes)
+            output.writeFully(bytes)
         }
 
         override fun encodeTaggedString(tag: Int, value: String) {
+            require(value.length <= JCE_MAX_STRING_LENGTH) { "string is too long for tag $tag" }
             val array = value.toByteArray(charset.kotlinCharset)
             if (array.size > 255) {
                 writeHead(STRING4, tag)
-                output.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(array.size).array())
-                output.write(array)
+                output.writeInt(array.size)
+                output.writeFully(array)
             } else {
                 writeHead(STRING1, tag)
-                output.write(ByteBuffer.allocate(1).order(ByteOrder.LITTLE_ENDIAN).put(array.size.toByte()).array())
-                output.write(array)
+                output.writeByte(array.size.toByte()) // one byte
+                output.writeFully(array)
             }
         }
 
@@ -262,12 +275,12 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         @PublishedApi
         internal fun writeHead(type: Byte, tag: Int) {
             if (tag < 15) {
-                this.output.write((tag shl 4) or type.toInt())
+                this.output.writeByte(((tag shl 4) or type.toInt()).toByte())
                 return
             }
             if (tag < 256) {
-                this.output.write(type.toInt() or 0xF0)
-                this.output.write(tag)
+                this.output.writeByte((type.toInt() or 0xF0).toByte())
+                this.output.writeByte(tag.toByte())
                 return
             }
             error("tag is too large: $tag")
@@ -305,7 +318,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         input: JceInput
     ) : JceDecoder(input) {
         override fun endStructure(desc: SerialDescriptor) {
-            input.readHead() // STRUCT_END
+            input.readHeadOrNull() // STRUCT_END
         }
     }
 
@@ -333,11 +346,12 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
 
         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()}")
+            //println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}")
             when (desc) {
                 // 由于 Byte 的数组有两种方式写入, 需特定读取器
                 ByteArraySerializer.descriptor -> {
@@ -374,7 +388,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                         return NullReader(this.input)
                     }
 
-                    if (tag!=null) {
+                    if (tag != null) {
                         popTag()
                     }
                     return JceMapReader(input.readInt(0), this.input)
@@ -411,30 +425,40 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         }
 
         override fun decodeTaggedNotNullMark(tag: Int): Boolean {
-            return !input.input.endOfInput && input.peakHead().tag <= tag
+            return !isTagOptional(tag)
+        }
+
+        fun isTagOptional(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()}")
+            //println("decodeNullableSerializableValue: ${deserializer.getClassName()}")
             if (deserializer is NullReader) {
                 return null
             }
             when (deserializer.descriptor) {
                 ByteArraySerializer.descriptor -> {
-                    return input.readByteArray(popTag()) as T
+                    val tag = popTag()
+                    return if (isTagOptional(tag)) input.readByteArrayOrNull(tag) as? T
+                    else input.readByteArray(tag) 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
+                        val tag = popTag()
+                        return if (isTagOptional(tag)) input.readByteArrayOrNull(tag)?.toTypedArray() as? T
+                        else input.readByteArray(tag).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
+                        val tag = popTag()
+                        return if (isTagOptional(tag)) input.readByteArrayOrNull(tag)?.toMutableList() as? T
+                        else input.readByteArray(tag).toMutableList() as T
                     }
                     return super.decodeSerializableValue(deserializer)
                 }
@@ -447,7 +471,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
                 }
             }
             val tag = currentTagOrNull ?: return deserializer.deserialize(this)
-            return if (this.decodeTaggedNotNullMark(tag)){
+            return if (this.decodeTaggedNotNullMark(tag)) {
                 deserializer.deserialize(this)
             } else {
                 null
@@ -456,7 +480,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
 
         @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")
+            return decodeNullableSerializableValue(deserializer as DeserializationStrategy<Any?>) as? T ?: error("value with tag $currentTagOrNull is not optional but cannot find")
         }
     }
 
@@ -471,17 +495,24 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         }
 
         @PublishedApi
-        internal fun readHead(): JceHead = input.readHead()
+        internal fun readHead(): JceHead = input.readHead() ?: error("no enough data to read head")
 
         @PublishedApi
-        internal fun peakHead(): JceHead = input.makeView().readHead()
+        internal fun readHeadOrNull(): JceHead? = input.readHead()
 
-        private fun IoBuffer.readHead(): JceHead {
+        @PublishedApi
+        internal fun peakHead(): JceHead = input.makeView().readHead() ?: error("no enough data to read head")
+
+        @Suppress("NOTHING_TO_INLINE") // 避免 stacktrace 出现两个 readHead
+        private inline fun IoBuffer.readHead(): JceHead? {
+            if (endOfInput) return null
             val var2 = readUByte()
             val type = var2 and 15u
             var tag = var2.toUInt() shr 4
-            if (tag == 15u)
+            if (tag == 15u) {
+                if (endOfInput) return null
                 tag = readUByte().toUInt()
+            }
             return JceHead(tag = tag.toInt(), type = type.toByte())
         }
 
@@ -535,9 +566,9 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         }
 
         fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
-            when (it.type.toInt()) {
-                9 -> ByteArray(readInt(0)) { readByte(0) }
-                13 -> {
+            when (it.type) {
+                LIST -> ByteArray(readInt(0)) { readByte(0) }
+                SIMPLE_LIST -> {
                     val head = readHead()
                     check(head.type.toInt() == 0) { "type mismatch" }
                     input.readBytes(readInt(0))
@@ -560,9 +591,9 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         } as T
 
         fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
-            return when (head.type.toInt()) {
-                6 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset)
-                7 -> input.readString(
+            return when (head.type) {
+                STRING1 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset)
+                STRING4 -> input.readString(
                     input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } },
                     charset = charset.kotlinCharset
                 )
@@ -571,13 +602,13 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         }
 
         fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) {
-            return when (it.type.toInt()) {
-                12 -> 0
-                0 -> input.readByte().toLong()
-                1 -> input.readShort().toLong()
-                2 -> input.readInt().toLong()
-                3 -> input.readLong()
-                else -> error("type mismatch: ${it.type}")
+            return when (it.type) {
+                ZERO_TYPE -> 0
+                BYTE -> input.readByte().toLong()
+                SHORT -> input.readShort().toLong()
+                INT -> input.readInt().toLong()
+                LONG -> input.readLong()
+                else -> error("type mismatch ${it.type} when reading tag $tag")
             }
         }
 
@@ -676,7 +707,6 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         internal inline fun <R> skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
             while (true) {
                 if (this.input.endOfInput) {
-                    println("endOfInput")
                     return null
                 }
 
@@ -699,7 +729,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         val GBK = Jce(JceCharset.GBK)
 
         fun byCharSet(c: JceCharset): Jce {
-            return if (c === JceCharset.UTF8) {
+            return if (c == JceCharset.UTF8) {
                 UTF8
             } else {
                 GBK
@@ -725,11 +755,22 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
         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 {
-        val encoder = ByteArrayOutputStream()
+    fun <T> dumpAsPacket(serializer: SerializationStrategy<T>, obj: T): ByteReadPacket {
+        val encoder = BytePacketBuilder()
         val dumper = JceEncoder(encoder)
         dumper.encode(serializer, obj)
-        return encoder.toByteArray()
+        return encoder.build()
+    }
+
+    override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
+        return dumpAsPacket(serializer, obj).readBytes()
+    }
+
+    fun <T> load(deserializer: DeserializationStrategy<T>, packet: ByteReadPacket): T {
+        packet.readIoBuffer().withUse {
+            val decoder = JceDecoder(JceInput(this))
+            return decoder.decode(deserializer)
+        }
     }
 
     override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt
index ed46f5cdd..562c2b7c4 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt
@@ -8,7 +8,6 @@ import net.mamoe.mirai.qqandroid.io.JceStruct
 import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
 
 private val EMPTY_MAP = mapOf<String, String>()
-private val EMPTY_SBUFFER_MAP = mapOf<Int, ByteArray>()
 
 @Serializable
 class RequestPacket(
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt
index 8b37ed0e4..013f7e0b3 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt
@@ -34,11 +34,11 @@ class SvcReqRegister(
     @SerialId(24) val iLastWatchStartTime: Long = 0L,
     @SerialId(26) val uOldSSOIp: Long = 0L,
     @SerialId(27) val uNewSSOIp: Long = 0L,
-    @SerialId(28) val sChannelNo: String? = "",
+    @SerialId(28) val sChannelNo: String? = null,
     @SerialId(29) val lCpId: Long = 0L,
     @SerialId(30) val strVendorName: String? = null,
     @SerialId(31) val strVendorOSName: String? = null,
-    @SerialId(32) val strIOSIdfa: String? = "",
+    @SerialId(32) val strIOSIdfa: String? = null,
     @SerialId(33) val bytes_0x769_reqbody: ByteArray? = null,
     @SerialId(34) val bIsSetStatus: Byte = 0,
     @SerialId(35) val vecServerBuf: ByteArray? = null,
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 c33272337..019ad2ee6 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
@@ -250,7 +250,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
     private suspend fun ByteReadPacket.parseUniResponse(bot: QQAndroidBot, packetFactory: PacketFactory<*>, ssoSequenceId: Int, consumer: PacketConsumer) {
         val uni = readBytes(readInt() - 4).loadAs(RequestPacket.serializer())
         PacketLogger.verbose(uni.toString())
-        consumer(packetFactory.decode(bot, uni.sBuffer.toReadPacket()), uni.sServantName + "." + uni.sFuncName, ssoSequenceId)
+       /// consumer(packetFactory.decode(bot, uni.sBuffer.toReadPacket()), uni.sServantName + "." + uni.sFuncName, ssoSequenceId)
     }
 }
 
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt
index 88c6a5d03..234623085 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/PushNotifyPack.kt
@@ -12,7 +12,7 @@ internal class RequestPushNotify(
     @SerialId(1) val ctype: Byte = 0,
     @SerialId(2) val strService: String?,
     @SerialId(3) val strCmd: String?,
-    @SerialId(4) val vNotifyCookie: ByteArray = EMPTY_BYTE_ARRAY,
+    @SerialId(4) val vNotifyCookie: ByteArray? = EMPTY_BYTE_ARRAY,
     @SerialId(5) val usMsgType: Int?,
     @SerialId(6) val wUserActive: Int?,
     @SerialId(7) val wGeneralFlag: Int?,
@@ -28,14 +28,14 @@ internal class RequestPushNotify(
 internal class MsgInfo(
     @SerialId(0) val lFromUin: Long = 0L,
     @SerialId(1) val uMsgTime: Long = 0L,
-    @SerialId(2) val shMsgType: Short?,
-    @SerialId(3) val shMsgSeq: Short?,
+    @SerialId(2) val shMsgType: Short,
+    @SerialId(3) val shMsgSeq: Short,
     @SerialId(4) val strMsg: String?,
     @SerialId(5) val uRealMsgTime: Int?,
     @SerialId(6) val vMsg: ByteArray?,
     @SerialId(7) val uAppShareID: Long?,
-    @SerialId(8) val vMsgCookies: ByteArray = EMPTY_BYTE_ARRAY,
-    @SerialId(9) val vAppShareCookie: ByteArray = EMPTY_BYTE_ARRAY,
+    @SerialId(8) val vMsgCookies: ByteArray? = EMPTY_BYTE_ARRAY,
+    @SerialId(9) val vAppShareCookie: ByteArray? = EMPTY_BYTE_ARRAY,
     @SerialId(10) val lMsgUid: Long?,
     @SerialId(11) val lLastChangeTime: Long?,
     @SerialId(12) val vCPicInfo: List<CPicInfo>?,
@@ -59,12 +59,12 @@ class ShareData(
 
 @Serializable
 class TempMsgHead(
-    @SerialId(0) val c2c_type: Int = 0,
-    @SerialId(1) val serviceType: Int = 0
+    @SerialId(0) val c2c_type: Int? = 0,
+    @SerialId(1) val serviceType: Int? = 0
 ) : JceStruct
 
 @Serializable
 class CPicInfo(
     @SerialId(0) val vPath: ByteArray = EMPTY_BYTE_ARRAY,
-    @SerialId(1) val vHost: ByteArray = EMPTY_BYTE_ARRAY
+    @SerialId(1) val vHost: ByteArray? = EMPTY_BYTE_ARRAY
 ) : JceStruct
\ No newline at end of file
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt
new file mode 100644
index 000000000..42c2687b5
--- /dev/null
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt
@@ -0,0 +1,42 @@
+package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
+
+import kotlinx.io.core.ByteReadPacket
+import kotlinx.io.core.discardExact
+import kotlinx.serialization.SerialId
+import kotlinx.serialization.Serializable
+import net.mamoe.mirai.qqandroid.QQAndroidBot
+import net.mamoe.mirai.qqandroid.io.JceStruct
+import net.mamoe.mirai.qqandroid.io.serialization.loadAs
+import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsJceStruct
+import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2
+import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
+import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
+import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify
+import net.mamoe.mirai.utils.cryptor.contentToString
+import net.mamoe.mirai.utils.firstValue
+import net.mamoe.mirai.utils.io.debugPrint
+import net.mamoe.mirai.utils.io.toReadPacket
+import net.mamoe.mirai.utils.io.toUHexString
+
+class MessageSvc {
+    internal object PushNotify : PacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
+        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
+            discardExact(8)
+
+            @Serializable
+            class ResponseDataRequestPushNotify(
+                @SerialId(0) val notify: RequestPushNotify
+            ) : JceStruct
+
+            val requestPushNotify = readRemainingAsJceStruct(RequestPacket.serializer()).sBuffer
+                .loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue()
+                .toReadPacket().apply { discardExact(1) }
+                .debugPrint()
+                .readRemainingAsJceStruct(RequestPushNotify.serializer())
+
+            println(requestPushNotify.contentToString())
+            TODO()
+        }
+    }
+}
+
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
deleted file mode 100644
index 2ddd56973..000000000
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
-
-import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.discardExact
-import kotlinx.serialization.protobuf.ProtoBuf
-import net.mamoe.mirai.qqandroid.QQAndroidBot
-import net.mamoe.mirai.qqandroid.io.serialization.Jce
-import net.mamoe.mirai.qqandroid.io.serialization.loadAs
-import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2
-import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
-import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
-import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
-import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify
-import net.mamoe.mirai.utils.cryptor.contentToString
-import net.mamoe.mirai.utils.io.readRemainingBytes
-
-class MessageSvc {
-    internal object PushNotify : PacketFactory<RequestPushNotify>("MessageSvc.PushNotify") {
-        override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
-            val req = Jce.UTF8.load(
-                RequestPacket.serializer(),
-                this.apply { discardExact(8) }.readRemainingBytes()
-            )
-            val messageNotification = Jce.UTF8.load(
-                RequestPushNotify.serializer(),
-                req.sBuffer.loadAs(RequestDataVersion2.serializer()).map.entries.first().value.entries.first().value
-            )
-            println(messageNotification.contentToString())
-            ProtoBuf
-            TODO()
-        }
-    }
-}
-
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt
index 0930e2928..7ac9839b5 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Register.kt
@@ -1,11 +1,11 @@
 package net.mamoe.mirai.qqandroid.network.protocol.packet.login
 
 import kotlinx.io.core.ByteReadPacket
-import kotlinx.io.core.writeFully
 import kotlinx.serialization.protobuf.ProtoBuf
 import net.mamoe.mirai.data.Packet
 import net.mamoe.mirai.qqandroid.QQAndroidBot
 import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
+import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct
 import net.mamoe.mirai.qqandroid.network.QQAndroidClient
 import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataStructSvcReqRegister
 import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
@@ -56,7 +56,8 @@ class StatSvc {
                 client, subAppId = subAppId, commandName = commandName,
                 extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
             ) {
-                writeFully(
+                writeJceStruct(
+                    RequestPacket.serializer(),
                     RequestPacket(
                         sServantName = "PushService",
                         sFuncName = "SvcReqRegister",
@@ -121,7 +122,7 @@ class StatSvc {
                                 ).toByteArray(RequestDataStructSvcReqRegister.serializer())
                             )
                         ).toByteArray(RequestDataVersion3.serializer())
-                    ).toByteArray(RequestPacket.serializer())
+                    )
                 )
             }
         }
diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/oidb0x769/Oidb0x769.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/oidb0x769/Oidb0x769.kt
index da7aed64b..5f82bd34d 100644
--- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/oidb0x769/Oidb0x769.kt
+++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/oidb0x769/Oidb0x769.kt
@@ -2,6 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769
 
 import kotlinx.serialization.SerialId
 import kotlinx.serialization.Serializable
+import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
 
 class Oidb0x769 {
     @Serializable
@@ -13,19 +14,19 @@ class Oidb0x769 {
        // @SerialId(5) val city: String,
        // @SerialId(6) val req_debug_msg: Int = 0,
        // @SerialId(101) val query_uin_package_usage_req: QueryUinPackageUsageReq
-    )
+    ) : ProtoBuf
 
     @Serializable
     class QueryUinPackageUsageReq(
         @SerialId(1) val type: Int,
         @SerialId(2) val uinFileSize: Long = 0
-    )
+    ): ProtoBuf
 
     @Serializable
     class ConfigSeq(
         @SerialId(1) val type: Int, // uint
         @SerialId(2) val version: Int // uint
-    )
+    ): ProtoBuf
 
     @Serializable
     class DeviceInfo(
@@ -37,7 +38,7 @@ class Oidb0x769 {
         //@SerialId(6) val storage: Storage,
         //@SerialId(7) val screen: Screen,
         //@SerialId(8) val camera: Camera
-    )
+    ): ProtoBuf
 
     @Serializable
     class OS(
@@ -46,27 +47,27 @@ class Oidb0x769 {
         @SerialId(3) val sdk: String,
         @SerialId(4) val kernel: String,
         @SerialId(5) val rom: String
-    )
+    ): ProtoBuf
 
     @Serializable
     class Camera(
         @SerialId(1) val primary: Long,
         @SerialId(2) val secondary: Long,
         @SerialId(3) val flag: Boolean
-    )
+    ): ProtoBuf
 
     @Serializable
     class CPU(
         @SerialId(1) val model: String,
         @SerialId(2) val frequency: Int,
         @SerialId(3) val cores: Int
-    )
+    ): ProtoBuf
 
     @Serializable
     class Memory(
         @SerialId(1) val total: Int,
         @SerialId(2) val process: Int
-    )
+    ): ProtoBuf
 
     @Serializable
     class Screen(
@@ -75,11 +76,11 @@ class Oidb0x769 {
         @SerialId(3) val height: Int,
         @SerialId(4) val dpi: Int,
         @SerialId(5) val multiTouch: Boolean
-    )
+    ): ProtoBuf
 
     @Serializable
     class Storage(
         @SerialId(1) val builtin: Int,
         @SerialId(2) val external: Int
-    )
+    ): ProtoBuf
 }
\ No newline at end of file
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 875453673..1d6f56942 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
@@ -7,9 +7,7 @@ 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()
@@ -41,7 +39,8 @@ class JceDecoderTest {
 
     @Serializable
     class TestComplexJceStruct(
-        @SerialId(7) val byteArray: ByteArray = byteArrayOf(1, 2, 3),
+        @SerialId(6) val string: String = "haha",
+        @SerialId(7) val byteArray: ByteArray = ByteArray(2000),
         @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(),
@@ -50,10 +49,11 @@ class JceDecoderTest {
 
     @Serializable
     class TestComplexNullableJceStruct(
-        @SerialId(7) val byteArray: ByteArray? = byteArrayOf(1, 2, 3),
+        @SerialId(6) val string: String = "haha",
+        @SerialId(7) val byteArray: ByteArray = ByteArray(2000),
         @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(10) val nestedJceStruct: TestComplexJceStruct? = TestComplexJceStruct(),
         @SerialId(11) val byteList2: List<List<Int>>? = listOf(listOf(1, 2, 3), listOf(1, 2, 3))
     ) : JceStruct
 
@@ -63,20 +63,10 @@ class JceDecoderTest {
     }
 
     @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()
-        )
+    fun testEncoder3() {
+        println(TestComplexNullableJceStruct().toByteArray(TestComplexNullableJceStruct.serializer()).loadAs(TestComplexNullableJceStruct.serializer()).contentToString())
     }
 
-
     @Test
     fun testNestedList() {
         @Serializable
@@ -133,6 +123,6 @@ class JceDecoderTest {
         )
         println(buildJcePacket {
             writeMap(mapOf(byteArrayOf(1) to mapOf(byteArrayOf(1) to shortArrayOf(2))), 7)
-        }.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value!!.contentToString())
+        }.readBytes().loadAs(TestNestedMap.serializer()).map.entries.first().value.contentToString())
     }
 }
\ No newline at end of file
diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/TestRequesetPacket.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/TestRequesetPacket.kt
new file mode 100644
index 000000000..29ec6edb3
--- /dev/null
+++ b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/TestRequesetPacket.kt
@@ -0,0 +1,17 @@
+package net.mamoe.mirai.qqandroid.io.serialization
+
+import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
+import net.mamoe.mirai.utils.io.hexToBytes
+
+class TestRequesetPacket {
+    companion object {
+        @JvmStatic
+        fun main(args: Array<String>) {
+
+            val data =
+                "10 03 2C 3C 4C 56 0B 50 75 73 68 53 65 72 76 69 63 65 66 0E 53 76 63 52 65 71 52 65 67 69 73 74 65 72 7D 00 01 D6 00 08 00 01 06 0E 53 76 63 52 65 71 52 65 67 69 73 74 65 72 1D 00 01 BE 00 0A 02 DD B8 E4 76 10 07 2C 36 00 40 0B 5C 6C 7C 8C 9C AC B0 1D C0 01 D6 00 EC FD 10 00 00 10 07 EA 76 78 FD EB 06 C3 33 B2 18 80 32 15 09 BA F1 11 04 08 FC 12 F6 13 19 41 6E 64 72 6F 69 64 20 53 44 4B 20 62 75 69 6C 74 20 66 6F 72 20 78 38 36 F6 14 19 41 6E 64 72 6F 69 64 20 53 44 4B 20 62 75 69 6C 74 20 66 6F 72 20 78 38 36 F6 15 02 31 30 F0 16 01 F1 17 0F 06 FC 18 FC 1A F3 1B A9 00 FE 00 66 00 82 00 FC 1D F6 1E 04 4D 49 55 49 F6 1F 14 3F 4F 4E 45 50 4C 55 53 20 41 35 30 30 30 5F 32 33 5F 31 37 FD 21 00 00 0D 0A 04 08 2E 10 00 0A 05 08 9B 02 10 00 FC 22 FC 24 0B 8C 98 0C A8 0C".hexToBytes()
+
+            println(data.loadAs(RequestPacket.serializer(), JceCharset.UTF8))
+        }
+    }
+}
\ No newline at end of file
diff --git a/mirai-core-timpc/build.gradle.kts b/mirai-core-timpc/build.gradle.kts
index e93a2041c..820adf222 100644
--- a/mirai-core-timpc/build.gradle.kts
+++ b/mirai-core-timpc/build.gradle.kts
@@ -58,7 +58,7 @@ kotlin {
     sourceSets {
         all {
             languageSettings.enableLanguageFeature("InlineClasses")
-            languageSettings.enableLanguageFeature("NewInference")
+
             languageSettings.useExperimentalAnnotation("kotlin.Experimental")
 
             dependencies {
diff --git a/mirai-core/build.gradle.kts b/mirai-core/build.gradle.kts
index aba19e519..22b20102d 100644
--- a/mirai-core/build.gradle.kts
+++ b/mirai-core/build.gradle.kts
@@ -57,7 +57,7 @@ kotlin {
     sourceSets {
         all {
             languageSettings.enableLanguageFeature("InlineClasses")
-            languageSettings.enableLanguageFeature("NewInference")
+
             languageSettings.useExperimentalAnnotation("kotlin.Experimental")
 
             dependencies {
diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt
new file mode 100644
index 000000000..0e9710358
--- /dev/null
+++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt
@@ -0,0 +1,9 @@
+package net.mamoe.mirai.utils
+
+fun <K, V> Map<K, V>.firstValue(): V = this.entries.first().value
+
+fun <K, V> Map<K, V>.firstValueOrNull(): V? = this.entries.firstOrNull()?.value
+
+fun <K, V> Map<K, V>.firstKey(): K = this.entries.first().key
+
+fun <K, V> Map<K, V>.firstKeyOrNull(): K? = this.entries.firstOrNull()?.key
\ No newline at end of file
diff --git a/mirai-debug/build.gradle.kts b/mirai-debug/build.gradle.kts
index 322ebd5a0..9c686358c 100644
--- a/mirai-debug/build.gradle.kts
+++ b/mirai-debug/build.gradle.kts
@@ -30,7 +30,7 @@ kotlin {
     sourceSets {
         all {
             languageSettings.enableLanguageFeature("InlineClasses")
-            languageSettings.enableLanguageFeature("NewInference")
+
             languageSettings.useExperimentalAnnotation("kotlin.Experimental")
         }
     }
diff --git a/mirai-japt/build.gradle.kts b/mirai-japt/build.gradle.kts
index 6a0bb70af..eee895d1d 100644
--- a/mirai-japt/build.gradle.kts
+++ b/mirai-japt/build.gradle.kts
@@ -17,7 +17,7 @@ kotlin {
     sourceSets {
         all {
             languageSettings.enableLanguageFeature("InlineClasses")
-            languageSettings.enableLanguageFeature("NewInference")
+
             languageSettings.useExperimentalAnnotation("kotlin.Experimental")
         }
     }
diff --git a/mirai-plugins/image-sender/build.gradle.kts b/mirai-plugins/image-sender/build.gradle.kts
index 53f40635c..e519033a2 100644
--- a/mirai-plugins/image-sender/build.gradle.kts
+++ b/mirai-plugins/image-sender/build.gradle.kts
@@ -20,7 +20,7 @@ kotlin {
     sourceSets {
         all {
             languageSettings.enableLanguageFeature("InlineClasses")
-            languageSettings.enableLanguageFeature("NewInference")
+
             languageSettings.useExperimentalAnnotation("kotlin.Experimental")
         }
     }