Prechádzať zdrojové kódy

Tlv writer (#2569)

* [core] TlvMapWriter

* [core] bind tlv writing with writer

* [core] Add checking to avoid wrong nest writing

* [core] Drop Int.invoke

* [core] Merge with dev

* [core] Update style
微莹·纤绫 3 rokov pred
rodič
commit
434ef0cc39

+ 0 - 76
mirai-core-utils/src/commonMain/kotlin/IO.kt

@@ -75,82 +75,6 @@ public inline fun ByteReadPacket.readPacketExact(
 ): ByteReadPacket = this.readBytes(n).toReadPacket()
 
 
-public typealias TlvMap = MutableMap<Int, ByteArray>
-
-public inline fun TlvMap.getOrFail(tag: Int): ByteArray {
-    return this[tag] ?: error("cannot find tlv 0x${tag.toUHexString("")}($tag)")
-}
-
-public inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteArray {
-    return this[tag] ?: error(lazyMessage(tag))
-}
-
-@Suppress("FunctionName")
-public inline fun Input._readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap =
-    _readTLVMap(true, tagSize, suppressDuplication)
-
-@Suppress("DuplicatedCode", "FunctionName")
-public fun Input._readTLVMap(
-    expectingEOF: Boolean = true,
-    tagSize: Int,
-    suppressDuplication: Boolean = true
-): TlvMap {
-    val map = mutableMapOf<Int, ByteArray>()
-    var key = 0
-
-    while (kotlin.run {
-            try {
-                key = when (tagSize) {
-                    1 -> readUByte().toInt()
-                    2 -> readUShort().toInt()
-                    4 -> readUInt().toInt()
-                    else -> error("Unsupported tag size: $tagSize")
-                }
-            } catch (e: Exception) { // java.nio.BufferUnderflowException is not a EOFException...
-                if (expectingEOF) {
-                    return map
-                }
-                throw e
-            }
-            key
-        }.toUByte() != UByte.MAX_VALUE) {
-
-        if (map.containsKey(key)) {
-            @Suppress("ControlFlowWithEmptyBody")
-            if (!suppressDuplication) {
-                /*
-                @Suppress("DEPRECATION")
-                MiraiLogger.error(
-                    @Suppress("IMPLICIT_CAST_TO_ANY")
-                    """
-                Error readTLVMap:
-                duplicated key ${when (tagSize) {
-                        1 -> key.toByte()
-                        2 -> key.toShort()
-                        4 -> key
-                        else -> error("unreachable")
-                    }.contentToString()}
-                map=${map.contentToString()}
-                duplicating value=${this.readUShortLVByteArray().toUHexString()}
-                """.trimIndent()
-                )*/
-            } else {
-                this.discardExact(this.readShort().toInt() and 0xffff)
-            }
-        } else {
-            try {
-                map[key] = this.readBytes(readUShort().toInt())
-            } catch (e: Exception) { // BufferUnderflowException, java.io.EOFException
-                // if (expectingEOF) {
-                //     return map
-                // }
-                throw e
-            }
-        }
-    }
-    return map
-}
-
 public fun Input.readAllText(): String = Charsets.UTF_8.newDecoder().decode(this)
 
 public inline fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String =

+ 225 - 0
mirai-core-utils/src/commonMain/kotlin/TlvMap.kt

@@ -0,0 +1,225 @@
+/*
+ * Copyright 2019-2023 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.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.utils
+
+import io.ktor.utils.io.core.*
+import kotlin.jvm.JvmField
+
+public typealias TlvMap = MutableMap<Int, ByteArray>
+
+public fun TlvMap(): TlvMap = linkedMapOf()
+
+@Suppress("FunctionName")
+public fun Output._writeTlvMap(
+    tagSize: Int,
+    includeCount: Boolean = true,
+    map: TlvMap,
+) {
+    if (includeCount) {
+        when (tagSize) {
+            1 -> writeByte(map.size.toByte())
+            2 -> writeShort(map.size.toShort())
+            4 -> writeInt(map.size)
+            else -> error("Unsupported tag size: $tagSize")
+        }
+    }
+
+    map.forEach { (key, value) ->
+        when (tagSize) {
+            1 -> writeByte(key.toByte())
+            2 -> writeShort(key.toShort())
+            4 -> writeInt(key)
+            else -> error("Unsupported tag size: $tagSize")
+        }
+
+        writeShort(value.size.toShort())
+        writeFully(value)
+    }
+}
+
+@Suppress("MemberVisibilityCanBePrivate")
+public class TlvMapWriter
+internal constructor(
+    private val tagSize: Int,
+) {
+    @JvmField
+    internal val buffer = BytePacketBuilder()
+
+    @JvmField
+    internal var counter: Int = 0
+
+    @PublishedApi
+    @JvmField
+    internal var isWriting: Boolean = false
+
+    private fun writeKey(key: Int) {
+        when (tagSize) {
+            1 -> buffer.writeByte(key.toByte())
+            2 -> buffer.writeShort(key.toShort())
+            4 -> buffer.writeInt(key)
+            else -> error("Unsupported tag size: $tagSize")
+        }
+        counter++
+    }
+
+    @PublishedApi
+    internal fun ensureNotWriting() {
+        if (isWriting) error("Cannot write a new Tlv when writing Tlv")
+    }
+
+    public fun tlv(key: Int, data: ByteArray) {
+        ensureNotWriting()
+        tlv0(key, data)
+    }
+
+
+    private fun tlv0(key: Int, data: ByteArray) {
+        writeKey(key)
+        buffer.writeShort(data.size.toShort())
+        buffer.writeFully(data)
+//        println("Writing [${key.toUHexString()}](${data.size}) => " + data.toUHexString())
+    }
+
+    public fun tlv(key: Int, data: ByteReadPacket) {
+        ensureNotWriting()
+        tlv0(key, data)
+    }
+
+
+    @PublishedApi
+    internal fun tlv0(key: Int, data: ByteReadPacket) {
+        writeKey(key)
+        buffer.writeShort(data.remaining.toShort())
+
+//        println("Writing [${key.toUHexString()}](${data.remaining}) => " + data.copy()
+//            .use { d1 -> d1.readBytes().toUHexString() })
+
+        buffer.writePacket(data)
+    }
+
+
+    public inline fun tlv(
+        key: Int,
+        crossinline builder: BytePacketBuilder.() -> Unit,
+    ) {
+        ensureNotWriting()
+        try {
+            isWriting = true
+            buildPacket(builder).use { tlv0(key, it) }
+        } finally {
+            isWriting = false
+        }
+    }
+
+}
+
+public fun Output._writeTlvMap(
+    tagSize: Int = 2,
+    includeCount: Boolean = true,
+    block: TlvMapWriter.() -> Unit
+) {
+    val writer = TlvMapWriter(tagSize)
+    try {
+        block(writer)
+        if (includeCount) {
+            when (tagSize) {
+                1 -> writeByte(writer.counter.toByte())
+                2 -> writeShort(writer.counter.toShort())
+                4 -> writeInt(writer.counter)
+                else -> error("Unsupported tag size: $tagSize")
+            }
+        }
+        writer.buffer.build().use {
+//            println(it.copy().use { it.readBytes().toUHexString() })
+
+            writePacket(it)
+        }
+    } finally {
+        writer.buffer.release()
+    }
+}
+
+public fun TlvMap.getOrFail(tag: Int): ByteArray {
+    return this[tag] ?: error("cannot find tlv 0x${tag.toUHexString("")}($tag)")
+}
+
+public fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteArray {
+    return this[tag] ?: error(lazyMessage(tag))
+}
+
+@Suppress("FunctionName")
+public fun Input._readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap =
+    _readTLVMap(true, tagSize, suppressDuplication)
+
+@Suppress("DuplicatedCode", "FunctionName")
+public fun Input._readTLVMap(
+    expectingEOF: Boolean = true,
+    tagSize: Int,
+    suppressDuplication: Boolean = true
+): TlvMap {
+    val map = linkedMapOf<Int, ByteArray>()
+    var key = 0
+
+    while (kotlin.run {
+            try {
+                key = when (tagSize) {
+                    1 -> readUByte().toInt()
+                    2 -> readUShort().toInt()
+                    4 -> readUInt().toInt()
+                    else -> error("Unsupported tag size: $tagSize")
+                }
+            } catch (e: Exception) { // java.nio.BufferUnderflowException is not a EOFException...
+                if (expectingEOF) {
+                    return map
+                }
+                throw e
+            }
+            key
+        }.toUByte() != UByte.MAX_VALUE) {
+
+        if (map.containsKey(key)) {
+//            println("reading ${key.toUHexString()}")
+
+            if (!suppressDuplication) {
+                /*
+                @Suppress("DEPRECATION")
+                MiraiLogger.error(
+                    @Suppress("IMPLICIT_CAST_TO_ANY")
+                    """
+                Error readTLVMap:
+                duplicated key ${when (tagSize) {
+                        1 -> key.toByte()
+                        2 -> key.toShort()
+                        4 -> key
+                        else -> error("unreachable")
+                    }.contentToString()}
+                map=${map.contentToString()}
+                duplicating value=${this.readUShortLVByteArray().toUHexString()}
+                """.trimIndent()
+                )*/
+            } else {
+                this.discardExact(this.readShort().toInt() and 0xffff)
+            }
+        } else {
+            try {
+                val len = readUShort().toInt()
+                val data = this.readBytes(len)
+//                println("Writing [${key.toUHexString()}]($len) => ${data.toUHexString()}")
+                map[key] = data
+            } catch (e: Exception) { // BufferUnderflowException, java.io.EOFException
+                // if (expectingEOF) {
+                //     return map
+                // }
+                throw e
+            }
+        }
+    }
+    return map
+}

+ 135 - 0
mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/TlvMapTest.kt

@@ -0,0 +1,135 @@
+/*
+ * Copyright 2019-2023 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.
+ *
+ * https://github.com/mamoe/mirai/blob/dev/LICENSE
+ */
+
+package net.mamoe.mirai.utils
+
+import io.ktor.utils.io.core.*
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+internal class TlvMapTest {
+    private fun dumpTlvMap(map: TlvMap) = buildString {
+        append("tlvMap {\n")
+        map.forEach { (k, v) ->
+            append("  ").append(k.toUHexString()).append(" = ").append(v.toUHexString()).append("\n")
+        }
+        append("}")
+    }
+
+    private fun assertTlvMapEquals(
+        expected: TlvMap, actual: TlvMap,
+    ) {
+        assertEquals(expected.size, actual.size, "map size not match")
+
+        expected.keys.forEach { key ->
+            assertTrue("Missing key[$key] in actual") { actual.containsKey(key) }
+        }
+        actual.keys.forEach { key ->
+            assertTrue("Missing key[$key] in expected") { expected.containsKey(key) }
+        }
+
+        expected.forEach { (key, value) ->
+            assertContentEquals(value, actual[key])
+        }
+    }
+
+    @Test
+    fun testTlvWriterNoLength() {
+        testTlvWriter(true)
+    }
+
+    @Test
+    fun testTlvWriterWithCount() {
+        testTlvWriter(false)
+    }
+
+    private fun testTlvWriter(withCount: Boolean) {
+        repeat(500) {
+            val tlvMap = TlvMap()
+            val rand = buildPacket {
+                _writeTlvMap(Short.SIZE_BYTES, includeCount = withCount) {
+
+                    repeat(Random.nextInt().and(0xFF).coerceAtLeast(20)) {
+                        val nextKey = Random.nextInt().and(0xFF0)
+                        if (!tlvMap.containsKey(nextKey)) {
+                            val randData = ByteArray(Random.nextInt().and(0xFFF))
+                            Random.nextBytes(randData)
+
+                            tlvMap[nextKey] = randData
+
+                            tlv(nextKey, randData)
+                        }
+                    }
+
+                }
+            }.also { pkg ->
+                if (withCount) pkg.discardExact(2)
+            }._readTLVMap()
+
+            try {
+                assertTlvMapEquals(tlvMap, rand)
+            } catch (e: Throwable) {
+
+                println("gen:  " + dumpTlvMap(tlvMap))
+                println("read: " + dumpTlvMap(rand))
+
+                throw e
+            }
+
+        }
+    }
+
+    @Test
+    fun testTlvWriterWithCounter() {
+        val expected = buildPacket {
+            writeShort(4) // count of TLVs
+
+            writeShort(0x01)
+            writeHexWithLength("66ccff")
+
+            writeShort(0x04)
+            writeHexWithLength("114514")
+
+            writeShort(0x19)
+            writeHexWithLength("198100")
+
+            writeShort(0x233)
+            writeHexWithLength("666666")
+        }.readBytes()
+
+        val actual = buildPacket {
+            _writeTlvMap {
+                tlv(0x001) { writeHex("66ccff") }
+                tlv(0x004) { writeHex("114514") }
+                tlv(0x019) { writeHex("198100") }
+                tlv(0x233) { writeHex("666666") }
+
+                println("counter = $counter")
+            }
+        }.readBytes()
+
+        println(expected.toUHexString())
+        println(actual.toUHexString())
+
+        assertContentEquals(expected, actual)
+    }
+
+    private fun Output.writeHex(data: String) {
+        writeFully(data.hexToBytes())
+    }
+
+    private fun Output.writeHexWithLength(data: String) {
+        val hxd = data.hexToBytes()
+        writeShort(hxd.size.toShort())
+        writeFully(hxd)
+    }
+}

+ 157 - 210
mirai-core/src/commonMain/kotlin/network/protocol/packet/Tlv.kt

@@ -49,40 +49,38 @@ internal fun TlvMap.smartToString(leadingLineBreak: Boolean = true, sorted: Bool
 @JvmInline
 internal value class Tlv(val value: ByteArray)
 
-internal fun BytePacketBuilder.t1(uin: Long, ip: ByteArray) {
+internal fun TlvMapWriter.t1(uin: Long, ip: ByteArray) {
     require(ip.size == 4) { "ip.size must == 4" }
-    writeShort(0x1)
-    writeShortLVPacket {
+
+    tlv(0x01) {
         writeShort(1) // _ip_ver
         writeInt(Random.nextInt())
         writeInt(uin.toInt())
         writeInt(currentTimeSeconds().toInt())
         writeFully(ip)
         writeShort(0)
-    } shouldEqualsTo 20
+    }
 }
 
-internal fun BytePacketBuilder.t2(captchaCode: String, captchaToken: ByteArray, sigVer: Short = 0) {
-    writeShort(0x2)
-    writeShortLVPacket {
+internal fun TlvMapWriter.t2(captchaCode: String, captchaToken: ByteArray, sigVer: Short = 0) {
+    tlv(0x02) {
         writeShort(sigVer)
         writeShortLVString(captchaCode)
         writeShortLVByteArray(captchaToken)
     }
 }
 
-internal fun BytePacketBuilder.t8(
+internal fun TlvMapWriter.t8(
     localId: Int = 2052
 ) {
-    writeShort(0x8)
-    writeShortLVPacket {
+    tlv(0x08) {
         writeShort(0)
         writeInt(localId) // localId
         writeShort(0)
     }
 }
 
-internal fun BytePacketBuilder.t16(
+internal fun TlvMapWriter.t16(
     ssoVersion: Int,
     subAppId: Long,
     guid: ByteArray,
@@ -90,8 +88,7 @@ internal fun BytePacketBuilder.t16(
     apkVersionName: ByteArray,
     apkSignatureMd5: ByteArray
 ) {
-    writeShort(0x16)
-    writeShortLVPacket {
+    tlv(0x16) {
         writeInt(ssoVersion)
         writeInt(16)
         writeInt(subAppId.toInt())
@@ -102,14 +99,13 @@ internal fun BytePacketBuilder.t16(
     }
 }
 
-internal fun BytePacketBuilder.t18(
+internal fun TlvMapWriter.t18(
     appId: Long,
     appClientVersion: Int = 0,
     uin: Long,
     constant1_always_0: Int = 0
 ) {
-    writeShort(0x18)
-    writeShortLVPacket {
+    tlv(0x18) {
         writeShort(1) //_ping_version
         writeInt(0x00_00_06_00) //_sso_version=1536
         writeInt(appId.toInt())
@@ -117,10 +113,10 @@ internal fun BytePacketBuilder.t18(
         writeInt(uin.toInt())
         writeShort(constant1_always_0.toShort())
         writeShort(0)
-    } shouldEqualsTo 22
+    }
 }
 
-internal fun BytePacketBuilder.t1b(
+internal fun TlvMapWriter.t1b(
     micro: Int = 0,
     version: Int = 0,
     size: Int = 3,
@@ -129,8 +125,7 @@ internal fun BytePacketBuilder.t1b(
     ecLevel: Int = 2,
     hint: Int = 2
 ) {
-    writeShort(0x1b)
-    writeShortLVPacket {
+    tlv(0x1b) {
         writeInt(micro)
         writeInt(version)
         writeInt(size)
@@ -142,11 +137,10 @@ internal fun BytePacketBuilder.t1b(
     }
 }
 
-internal fun BytePacketBuilder.t1d(
+internal fun TlvMapWriter.t1d(
     miscBitmap: Int,
 ) {
-    writeShort(0x1d)
-    writeShortLVPacket {
+    tlv(0x1d) {
         writeByte(1)
         writeInt(miscBitmap)
         writeInt(0)
@@ -155,7 +149,7 @@ internal fun BytePacketBuilder.t1d(
     }
 }
 
-internal fun BytePacketBuilder.t1f(
+internal fun TlvMapWriter.t1f(
     isRoot: Boolean = false,
     osName: ByteArray,
     osVersion: ByteArray,
@@ -163,8 +157,7 @@ internal fun BytePacketBuilder.t1f(
     apn: ByteArray,
     networkType: Short = 2,
 ) {
-    writeShort(0x1f)
-    writeShortLVPacket {
+    tlv(0x1f) {
         writeByte(if (isRoot) 1 else 0)
         writeShortLVByteArray(osName)
         writeShortLVByteArray(osVersion)
@@ -175,23 +168,21 @@ internal fun BytePacketBuilder.t1f(
     }
 }
 
-internal fun BytePacketBuilder.t33(
+internal fun TlvMapWriter.t33(
     guid: ByteArray,
 ) {
-    writeShort(0x33)
-    writeShortLVByteArray(guid)
+    tlv(0x33, guid)
 }
 
-internal fun BytePacketBuilder.t35(
+internal fun TlvMapWriter.t35(
     productType: Int
 ) {
-    writeShort(0x35)
-    writeShortLVPacket {
+    tlv(0x35) {
         writeInt(productType)
     }
 }
 
-internal fun BytePacketBuilder.t106(
+internal fun TlvMapWriter.t106(
     client: QQAndroidClient,
     appId: Long = 16L,
     passwordMd5: ByteArray,
@@ -213,11 +204,10 @@ internal fun BytePacketBuilder.t106(
     )
 }
 
-internal fun BytePacketBuilder.t106(
+internal fun TlvMapWriter.t106(
     encryptA1: ByteArray
 ) {
-    writeShort(0x106)
-    writeShortLVPacket {
+    tlv(0x106) {
         writeFully(encryptA1)
     }
 }
@@ -225,7 +215,7 @@ internal fun BytePacketBuilder.t106(
 /**
  * A1
  */
-internal fun BytePacketBuilder.t106(
+internal fun TlvMapWriter.t106(
     appId: Long = 16L,
     subAppId: Long,
     appClientVersion: Int = 0,
@@ -240,12 +230,11 @@ internal fun BytePacketBuilder.t106(
     loginType: LoginType,
     ssoVersion: Int,
 ) {
-    writeShort(0x106)
     passwordMd5.requireSize(16)
     tgtgtKey.requireSize(16)
     guid?.requireSize(16)
 
-    writeShortLVPacket {
+    tlv(0x106) {
         encryptAndWrite(
             (passwordMd5 + ByteArray(4) + (salt.takeIf { it != 0L } ?: uin).toInt()
                 .toByteArray()).md5()
@@ -287,13 +276,12 @@ internal fun BytePacketBuilder.t106(
     }
 }
 
-internal fun BytePacketBuilder.t116(
+internal fun TlvMapWriter.t116(
     miscBitmap: Int,
     subSigMap: Int,
     appIdList: LongArray = longArrayOf(1600000226L)
 ) {
-    writeShort(0x116)
-    writeShortLVPacket {
+    tlv(0x116) {
         writeByte(0) // _ver
         writeInt(miscBitmap) // 184024956
         writeInt(subSigMap) // 66560
@@ -305,131 +293,122 @@ internal fun BytePacketBuilder.t116(
 }
 
 
-internal fun BytePacketBuilder.t100(
+internal fun TlvMapWriter.t100(
     appId: Long = 16,
     subAppId: Long,
     appClientVersion: Int,
     ssoVersion: Int,
     mainSigMap: Int
 ) {
-    writeShort(0x100)
-    writeShortLVPacket {
+    tlv(0x100) {
         writeShort(1)//db_buf_ver
         writeInt(ssoVersion)//sso_ver
         writeInt(appId.toInt())
         writeInt(subAppId.toInt())
         writeInt(appClientVersion)
         writeInt(mainSigMap) // sigMap, 34869472?
-    } shouldEqualsTo 22
+    }
 }
 
-internal fun BytePacketBuilder.t10a(
+internal fun TlvMapWriter.t10a(
     tgt: ByteArray,
 ) {
-    writeShort(0x10a)
-    writeShortLVPacket {
+    tlv(0x10a) {
         writeFully(tgt)
     }
 }
 
 
-internal fun BytePacketBuilder.t107(
+internal fun TlvMapWriter.t107(
     picType: Int,
     capType: Int = 0,
     picSize: Int = 0,
     retType: Int = 1
 ) {
-    writeShort(0x107)
-    writeShortLVPacket {
+    tlv(0x107) {
         writeShort(picType.toShort())
         writeByte(capType.toByte())
         writeShort(picSize.toShort())
         writeByte(retType.toByte())
-    } shouldEqualsTo 6
+    }
 }
 
-internal fun BytePacketBuilder.t108(
+internal fun TlvMapWriter.t108(
     ksid: ByteArray
 ) {
     // require(ksid.size == 16) { "ksid should length 16" }
-    writeShort(0x108)
-    writeShortLVPacket {
+    tlv(0x108) {
         writeFully(ksid)
     }
 }
 
-internal fun BytePacketBuilder.t104(
+internal fun TlvMapWriter.t104(
     t104Data: ByteArray
 ) {
-    writeShort(0x104)
-    writeShortLVPacket {
+    tlv(0x104) {
         writeFully(t104Data)
     }
 }
 
-internal fun BytePacketBuilder.t547(
+internal fun TlvMapWriter.t547(
     t547Data: ByteArray
 ) {
-    writeShort(0x547)
-    writeShortLVPacket {
+    tlv(0x547) {
         writeFully(t547Data)
     }
 }
 
-internal fun BytePacketBuilder.t174(
+internal fun TlvMapWriter.t174(
     t174Data: ByteArray
 ) {
-    writeShort(0x174)
-    writeShortLVPacket {
+    tlv(0x174) {
         writeFully(t174Data)
     }
 }
 
 
-internal fun BytePacketBuilder.t17a(
+internal fun TlvMapWriter.t17a(
     value: Int = 0
 ) {
-    writeShort(0x17a)
-    writeShortLVPacket {
+    tlv(0x17a) {
         writeInt(value)
     }
 }
 
-internal fun BytePacketBuilder.t197() {
-    writeShort(0x197)
-    writeFully(byteArrayOf(0, 1, 0))
+internal fun TlvMapWriter.t197() {
+    tlv(0x197) {
+        writeByte(0)
+    }
 }
 
-internal fun BytePacketBuilder.t198() {
-    writeShort(0x198)
-    writeFully(byteArrayOf(0, 1, 0))
+internal fun TlvMapWriter.t198() {
+    tlv(0x198) {
+        writeByte(0)
+    }
 }
 
-internal fun BytePacketBuilder.t19e(
+internal fun TlvMapWriter.t19e(
     value: Int = 0
 ) {
-    writeShort(0x19e)
-    writeShortLVPacket {
+    tlv(0x19e) {
         writeShort(1)
         writeByte(value.toByte())
     }
 }
 
-internal fun BytePacketBuilder.t17c(
+internal fun TlvMapWriter.t17c(
     t17cData: ByteArray
 ) {
-    writeShort(0x17c)
-    writeShortLVPacket {
+    tlv(0x17c) {
         writeShort(t17cData.size.toShort())
         writeFully(t17cData)
     }
 }
 
-internal fun BytePacketBuilder.t401(
+internal fun TlvMapWriter.t401(
     t401Data: ByteArray
 ) {
-    writeShort(0x401)
-    writeShortLVPacket {
+    tlv(0x401) {
         writeFully(t401Data)
     }
 }
@@ -437,35 +416,32 @@ internal fun BytePacketBuilder.t401(
 /**
  * @param apkId application.getPackageName().getBytes()
  */
-internal fun BytePacketBuilder.t142(
+internal fun TlvMapWriter.t142(
     apkId: ByteArray
 ) {
-    writeShort(0x142)
-    writeShortLVPacket {
+    tlv(0x142) {
         writeShort(0) //_version
         writeShortLVByteArrayLimitedLength(apkId, 32)
     }
 }
 
-internal fun BytePacketBuilder.t143(
+internal fun TlvMapWriter.t143(
     d2: ByteArray
 ) {
-    writeShort(0x143)
-    writeShortLVPacket {
+    tlv(0x143) {
         writeFully(d2)
     }
 }
 
-internal fun BytePacketBuilder.t112(
+internal fun TlvMapWriter.t112(
     nonNumberUin: ByteArray
 ) {
-    writeShort(0x112)
-    writeShortLVPacket {
+    tlv(0x112) {
         writeFully(nonNumberUin)
     }
 }
 
-internal fun BytePacketBuilder.t144(
+internal fun TlvMapWriter.t144(
     client: QQAndroidClient
 ) {
     return t144(
@@ -488,7 +464,7 @@ internal fun BytePacketBuilder.t144(
     )
 }
 
-internal fun BytePacketBuilder.t144(
+internal fun TlvMapWriter.t144(
     // t109
     androidId: ByteArray,
 
@@ -515,34 +491,32 @@ internal fun BytePacketBuilder.t144(
     // encrypt
     tgtgtKey: ByteArray
 ) {
-    writeShort(0x144)
-    writeShortLVPacket {
+    tlv(0x144) {
         encryptAndWrite(tgtgtKey) {
-            writeShort(5) // tlv count
-            t109(androidId)
-            t52d(androidDevInfo)
-            t124(osType, osVersion, networkType, simInfo, unknown, apn)
-            t128(isGuidFromFileNull, isGuidAvailable, isGuidChanged, guidFlag, buildModel, guid, buildBrand)
-            t16e(buildModel)
+            _writeTlvMap {
+                t109(androidId)
+                t52d(androidDevInfo)
+                t124(osType, osVersion, networkType, simInfo, unknown, apn)
+                t128(isGuidFromFileNull, isGuidAvailable, isGuidChanged, guidFlag, buildModel, guid, buildBrand)
+                t16e(buildModel)
+            }
         }
     }
 }
 
 
-internal fun BytePacketBuilder.t109(
+internal fun TlvMapWriter.t109(
     androidId: ByteArray
 ) {
-    writeShort(0x109)
-    writeShortLVPacket {
+    tlv(0x109) {
         writeFully(androidId.md5())
-    } shouldEqualsTo 16
+    }
 }
 
-internal fun BytePacketBuilder.t52d(
+internal fun TlvMapWriter.t52d(
     androidDevInfo: ByteArray // oicq.wlogin_sdk.tools.util#get_android_dev_info
 ) {
-    writeShort(0x52d)
-    writeShortLVPacket {
+    tlv(0x52d) {
         writeFully(androidDevInfo)
 
         // 0A  07  75  6E  6B  6E  6F  77  6E  12  7E  4C  69  6E  75  78  20  76  65  72  73  69  6F  6E  20  34  2E  39  2E  33  31  20  28  62  75  69  6C  64  40  42  75  69  6C  64  32  29  20  28  67  63  63  20  76  65  72  73  69  6F  6E  20  34  2E  39  20  32  30  31  35  30  31  32  33  20  28  70  72  65  72  65  6C  65  61  73  65  29  20  28  47  43  43  29  20  29  20  23  31  20  53  4D  50  20  50  52  45  45  4D  50  54  20  54  68  75  20  44  65  63  20  31  32  20  31  35  3A  33  30  3A  35  35  20  49  53  54  20  32  30  31  39  1A  03  52  45  4C  22  03  33  32  37  2A  41  4F  6E  65  50  6C  75  73  2F  4F  6E  65  50  6C  75  73  35  2F  4F  6E  65  50  6C  75  73  35  3A  37  2E  31  2E  31  2F  4E  4D  46  32  36  58  2F  31  30  31  37  31  36  31  37  3A  75  73  65  72  2F  72  65  6C  65  61  73  65  2D  6B  65  79  73  32  24  36  63  39  39  37  36  33  66  2D  66  62  34  32  2D  34  38  38  31  2D  62  37  32  65  2D  63  37  61  61  38  61  36  63  31  63  61  34  3A  10  65  38  63  37  30  35  34  64  30  32  66  33  36  33  64  30  42  0A  6E  6F  20  6D  65  73  73  61  67  65  4A  03  33  32  37
@@ -550,7 +524,7 @@ internal fun BytePacketBuilder.t52d(
     }
 }
 
-internal fun BytePacketBuilder.t124(
+internal fun TlvMapWriter.t124(
     osType: ByteArray = "android".toByteArray(),
     osVersion: ByteArray, // Build.VERSION.RELEASE.toByteArray()
     networkType: NetworkType,  //oicq.wlogin_sdk.tools.util#get_network_type
@@ -558,8 +532,7 @@ internal fun BytePacketBuilder.t124(
     address: ByteArray, // always new byte[0]
     apn: ByteArray = "wifi".toByteArray() // oicq.wlogin_sdk.tools.util#get_apn_string
 ) {
-    writeShort(0x124)
-    writeShortLVPacket {
+    tlv(0x124) {
         writeShortLVByteArrayLimitedLength(osType, 16)
         writeShortLVByteArrayLimitedLength(osVersion, 16)
         writeShort(networkType.value.toShort())
@@ -569,7 +542,7 @@ internal fun BytePacketBuilder.t124(
     }
 }
 
-internal fun BytePacketBuilder.t128(
+internal fun TlvMapWriter.t128(
     isGuidFromFileNull: Boolean = false, // 保存到文件的 GUID 是否为 null
     isGuidAvailable: Boolean = true, // GUID 是否可用(计算/读取成功)
     isGuidChanged: Boolean = false, // GUID 是否有变动
@@ -610,8 +583,7 @@ internal fun BytePacketBuilder.t128(
     guid: ByteArray,
     buildBrand: ByteArray // android.os.Build.BRAND
 ) {
-    writeShort(0x128)
-    writeShortLVPacket {
+    tlv(0x128) {
         writeShort(0)
         writeByte(isGuidFromFileNull.toByte())
         writeByte(isGuidAvailable.toByte())
@@ -623,71 +595,64 @@ internal fun BytePacketBuilder.t128(
     }
 }
 
-internal fun BytePacketBuilder.t16e(
+internal fun TlvMapWriter.t16e(
     buildModel: ByteArray
 ) {
-    writeShort(0x16e)
-    writeShortLVPacket {
+    tlv(0x16e) {
         writeFully(buildModel)
     }
 }
 
-internal fun BytePacketBuilder.t145(
+internal fun TlvMapWriter.t145(
     guid: ByteArray
 ) {
-    writeShort(0x145)
-    writeShortLVPacket {
+    tlv(0x145) {
         writeFully(guid)
     }
 }
 
-internal fun BytePacketBuilder.t147(
+internal fun TlvMapWriter.t147(
     appId: Long,
     apkVersionName: ByteArray,
     apkSignatureMd5: ByteArray
 ) {
-    writeShort(0x147)
-    writeShortLVPacket {
+    tlv(0x147) {
         writeInt(appId.toInt())
         writeShortLVByteArrayLimitedLength(apkVersionName, 32)
         writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32)
     }
 }
 
-internal fun BytePacketBuilder.t166(
+internal fun TlvMapWriter.t166(
     imageType: Int
 ) {
-    writeShort(0x166)
-    writeShortLVPacket {
+    tlv(0x166) {
         writeByte(imageType.toByte())
     }
 }
 
-internal fun BytePacketBuilder.t16a(
+internal fun TlvMapWriter.t16a(
     noPicSig: ByteArray // unknown source
 ) {
-    writeShort(0x16a)
-    writeShortLVPacket {
+    tlv(0x16a) {
         writeFully(noPicSig)
     }
 }
 
-internal fun BytePacketBuilder.t154(
+internal fun TlvMapWriter.t154(
     ssoSequenceId: Int // starts from 0
 ) {
-    writeShort(0x154)
-    writeShortLVPacket {
+    tlv(0x154) {
         writeInt(ssoSequenceId)
     }
 }
 
-internal fun BytePacketBuilder.t141(
+internal fun TlvMapWriter.t141(
     simInfo: ByteArray,
     networkType: NetworkType,
     apn: ByteArray
 ) {
-    writeShort(0x141)
-    writeShortLVPacket {
+    tlv(0x141) {
         writeShort(1) // version
         writeShortLVByteArray(simInfo)
         writeShort(networkType.value.toShort())
@@ -695,7 +660,7 @@ internal fun BytePacketBuilder.t141(
     }
 }
 
-internal fun BytePacketBuilder.t511(
+internal fun TlvMapWriter.t511(
     domains: List<String> = listOf(
         "tenpay.com",
         "openmobile.qq.com",
@@ -713,8 +678,7 @@ internal fun BytePacketBuilder.t511(
         "mma.qq.com",
     )
 ) {
-    writeShort(0x511)
-    writeShortLVPacket {
+    tlv(0x511) {
         val list = domains.filter { it.isNotEmpty() }
         writeShort(list.size.toShort())
         list.forEach { element ->
@@ -737,24 +701,22 @@ internal fun BytePacketBuilder.t511(
     }
 }
 
-internal fun BytePacketBuilder.t172(
+internal fun TlvMapWriter.t172(
     rollbackSig: ByteArray // 由服务器发来的 tlv_t172 获得
 ) {
-    writeShort(0x172)
-    writeShortLVPacket {
+    tlv(0x172) {
         writeFully(rollbackSig)
     }
 }
 
-internal fun BytePacketBuilder.t185() {
-    writeShort(0x185)
-    writeShortLVPacket {
+internal fun TlvMapWriter.t185() {
+    tlv(0x185) {
         writeByte(1)
         writeByte(1)
     }
 }
 
-internal fun BytePacketBuilder.t400(
+internal fun TlvMapWriter.t400(
     /**
      *  if (var1[2] != null && var1[2].length > 0) {
     this._G = (byte[])var1[2].clone();
@@ -768,8 +730,7 @@ internal fun BytePacketBuilder.t400(
     subAppId: Long,
     randomSeed: ByteArray
 ) {
-    writeShort(0x400)
-    writeShortLVPacket {
+    tlv(0x400) {
         encryptAndWrite(g) {
             writeByte(1) // version
             writeLong(uin)
@@ -784,62 +745,56 @@ internal fun BytePacketBuilder.t400(
 }
 
 
-internal fun BytePacketBuilder.t187(
+internal fun TlvMapWriter.t187(
     macAddress: ByteArray
 ) {
-    writeShort(0x187)
-    writeShortLVPacket {
+    tlv(0x187) {
         writeFully(macAddress.md5()) // may be md5
     }
 }
 
 
-internal fun BytePacketBuilder.t188(
+internal fun TlvMapWriter.t188(
     androidId: ByteArray
 ) {
-    writeShort(0x188)
-    writeShortLVPacket {
+    tlv(0x188) {
         writeFully(androidId.md5())
-    } shouldEqualsTo 16
+    }
 }
 
-internal fun BytePacketBuilder.t193(
+internal fun TlvMapWriter.t193(
     ticket: String
 ) {
-    writeShort(0x193)
-    writeShortLVPacket {
+    tlv(0x193) {
         writeFully(ticket.toByteArray())
     }
 }
 
-internal fun BytePacketBuilder.t194(
+internal fun TlvMapWriter.t194(
     imsiMd5: ByteArray
 ) {
     imsiMd5 requireSize 16
 
-    writeShort(0x194)
-    writeShortLVPacket {
+    tlv(0x194) {
         writeFully(imsiMd5)
-    } shouldEqualsTo 16
+    }
 }
 
-internal fun BytePacketBuilder.t191(
+internal fun TlvMapWriter.t191(
     K: Int = 0x82
 ) {
-    writeShort(0x191)
-    writeShortLVPacket {
+    tlv(0x191) {
         writeByte(K.toByte())
     }
 }
 
-internal fun BytePacketBuilder.t201(
+internal fun TlvMapWriter.t201(
     L: ByteArray = byteArrayOf(), // unknown
     channelId: ByteArray = byteArrayOf(),
     clientType: ByteArray = "qq".toByteArray(),
     N: ByteArray
 ) {
-    writeShort(0x201)
-    writeShortLVPacket {
+    tlv(0x201) {
         writeShortLVByteArray(L)
         writeShortLVByteArray(channelId)
         writeShortLVByteArray(clientType)
@@ -847,73 +802,66 @@ internal fun BytePacketBuilder.t201(
     }
 }
 
-internal fun BytePacketBuilder.t202(
+internal fun TlvMapWriter.t202(
     wifiBSSID: ByteArray,
     wifiSSID: ByteArray
 ) {
-    writeShort(0x202)
-    writeShortLVPacket {
+    tlv(0x202) {
         writeShortLVByteArrayLimitedLength(wifiBSSID, 16)
         writeShortLVByteArrayLimitedLength(wifiSSID, 32)
     }
 }
 
-internal fun BytePacketBuilder.t177(
+internal fun TlvMapWriter.t177(
     buildTime: Long = 1571193922L, // wtLogin BuildTime
     buildVersion: String = "6.0.0.2413" // wtLogin SDK Version
 ) {
-    writeShort(0x177)
-    writeShortLVPacket {
+    tlv(0x177) {
         writeByte(1)
         writeInt(buildTime.toInt())
         writeShortLVString(buildVersion)
     } // shouldEqualsTo 0x11
 }
 
-internal fun BytePacketBuilder.t516( // 1302
+internal fun TlvMapWriter.t516( // 1302
     sourceType: Int = 0 // always 0
 ) {
-    writeShort(0x516)
-    writeShortLVPacket {
+    tlv(0x516) {
         writeInt(sourceType)
-    } shouldEqualsTo 4
+    }
 }
 
-internal fun BytePacketBuilder.t521( // 1313
+internal fun TlvMapWriter.t521( // 1313
     productType: Int = 0, // coz setProductType is never used
     unknown: Short = 0 // const
 ) {
-    writeShort(0x521)
-    writeShortLVPacket {
+    tlv(0x521) {
         writeInt(productType)
         writeShort(unknown)
-    } shouldEqualsTo 6
+    }
 }
 
-internal fun BytePacketBuilder.t52c(
+internal fun TlvMapWriter.t52c(
     // ?
 ) {
-    writeShort(0x52c)
-    writeShortLVPacket {
+    tlv(0x52c) {
         writeByte(1)
         writeLong(-1)
     }
 }
 
-internal fun BytePacketBuilder.t536( // 1334
+internal fun TlvMapWriter.t536( // 1334
     loginExtraData: ByteArray
 ) {
-    writeShort(0x536)
-    writeShortLVPacket {
+    tlv(0x536) {
         writeFully(loginExtraData)
     }
 }
 
-internal fun BytePacketBuilder.t536( // 1334
+internal fun TlvMapWriter.t536( // 1334
     loginExtraData: Collection<LoginExtraData>
 ) {
-    writeShort(0x536)
-    writeShortLVPacket {
+    tlv(0x536) {
         //com.tencent.loginsecsdk.ProtocolDet#packExtraData
         writeByte(1) // const
         writeByte(loginExtraData.size.toByte()) // data count
@@ -923,45 +871,44 @@ internal fun BytePacketBuilder.t536( // 1334
     }
 }
 
-internal fun BytePacketBuilder.t525(
+internal fun TlvMapWriter.t525(
     loginExtraData: Collection<LoginExtraData>,
 ) {
-    writeShort(0x525)
-    writeShortLVPacket {
-        writeShort(1)
-        t536(loginExtraData)
+    tlv(0x525) {
+        _writeTlvMap {
+            t536(loginExtraData)
+        }
     }
 }
 
-internal fun BytePacketBuilder.t525(
+internal fun TlvMapWriter.t525(
     t536: ByteReadPacket = buildPacket {
-        t536(buildPacket {
-            //com.tencent.loginsecsdk.ProtocolDet#packExtraData
-            writeByte(1) // const
-            writeByte(0) // data count
-        }.readBytes())
+        _writeTlvMap(includeCount = false) {
+            t536(buildPacket {
+                //com.tencent.loginsecsdk.ProtocolDet#packExtraData
+                writeByte(1) // const
+                writeByte(0) // data count
+            }.readBytes())
+        }
     }
 ) {
-    writeShort(0x525)
-    writeShortLVPacket {
+    tlv(0x525) {
         writeShort(1)
         writePacket(t536)
     }
 }
 
-internal fun BytePacketBuilder.t544( // 1334
+internal fun TlvMapWriter.t544( // 1334
 ) {
-    writeShort(0x544)
-    writeShortLVPacket {
+    tlv(0x544) {
         writeFully(byteArrayOf(0, 0, 0, 11)) // means native throws exception
     }
 }
 
-internal fun BytePacketBuilder.t318(
+internal fun TlvMapWriter.t318(
     tgtQR: ByteArray // unknown
 ) {
-    writeShort(0x318)
-    writeShortLVPacket {
+    tlv(0x318) {
         writeFully(tgtQR)
     }
 }

+ 58 - 56
mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt

@@ -125,20 +125,21 @@ internal class WtLogin {
                 ) {
                     writeOicqRequestPacket(client, commandId = 0x0810) {
                         writeShort(17) // subCommand
-                        writeShort(12)
-                        t100(16, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
-                        t108(client.ksid)
-                        t109(client.device.androidId)
-                        t8(2052)
-                        t142(client.apkId)
-                        t145(client.device.guid)
-                        t154(0)
-                        // 需要 t112, 但在实现 QR 时删除了 phoneNumber
-//                        t112(client.account.phoneNumber.encodeToByteArray())
-                        t116(client.miscBitMap, client.subSigMap)
-                        t521()
-                        t52c()
-                        t52d(client.device.generateDeviceInfoData())
+                        _writeTlvMap {
+                            t100(16, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
+                            t108(client.ksid)
+                            t109(client.device.androidId)
+                            t8(2052)
+                            t142(client.apkId)
+                            t145(client.device.guid)
+                            t154(0)
+                            // 需要 t112, 但在实现 QR 时删除了 phoneNumber
+//t112(client.account.phoneNumber.encodeToByteArray())
+                            t116(client.miscBitMap, client.subSigMap)
+                            t521()
+                            t52c()
+                            t52d(client.device.generateDeviceInfoData())
+                        }
                     }
                 }
             }
@@ -677,53 +678,54 @@ internal class WtLogin {
                         writeByte(8)
                         writeShortLVPacket { }
 
-                        writeShort(6)
-                        t16(
-                            client.ssoVersion,
-                            client.subAppId,
-                            client.device.guid,
-                            client.apkId,
-                            client.apkVersionName,
-                            client.apkSignatureMd5
-                        )
-                        t1b(
-                            size = size,
-                            margin = margin,
-                            ecLevel = ecLevel
-                        )
-                        t1d(client.miscBitMap)
-
-                        val protocol = client.bot.configuration.protocol
-                        when (protocol) {
-                            BotConfiguration.MiraiProtocol.MACOS -> t1f(
-                                false,
-                                "Mac OSX".toByteArray(),
-                                "10".toByteArray(),
-                                "mac carrier".toByteArray(),
-                                client.device.apn,
-                                2
+                        _writeTlvMap {
+                            t16(
+                                client.ssoVersion,
+                                client.subAppId,
+                                client.device.guid,
+                                client.apkId,
+                                client.apkVersionName,
+                                client.apkSignatureMd5
                             )
-
-                            BotConfiguration.MiraiProtocol.ANDROID_WATCH -> t1f(
-                                false,
-                                client.device.osType,
-                                "7.1.2".toByteArray(),
-                                "China Mobile GSM".toByteArray(),
-                                client.device.apn,
-                                2
+                            t1b(
+                                size = size,
+                                margin = margin,
+                                ecLevel = ecLevel
                             )
+                            t1d(client.miscBitMap)
 
-                            else -> error("protocol $protocol doesn't support qrcode login.")
-                        }
-
-                        t33(client.device.guid)
-                        t35(
+                            val protocol = client.bot.configuration.protocol
                             when (protocol) {
-                                BotConfiguration.MiraiProtocol.MACOS -> 5
-                                BotConfiguration.MiraiProtocol.ANDROID_WATCH -> 8
-                                else -> error("assertion")
+                                BotConfiguration.MiraiProtocol.MACOS -> t1f(
+                                    false,
+                                    "Mac OSX".toByteArray(),
+                                    "10".toByteArray(),
+                                    "mac carrier".toByteArray(),
+                                    client.device.apn,
+                                    2
+                                )
+
+                                BotConfiguration.MiraiProtocol.ANDROID_WATCH -> t1f(
+                                    false,
+                                    client.device.osType,
+                                    "7.1.2".toByteArray(),
+                                    "China Mobile GSM".toByteArray(),
+                                    client.device.apn,
+                                    2
+                                )
+
+                                else -> error("protocol $protocol doesn't support qrcode login.")
                             }
-                        )
+
+                            t33(client.device.guid)
+                            t35(
+                                when (protocol) {
+                                    BotConfiguration.MiraiProtocol.MACOS -> 5
+                                    BotConfiguration.MiraiProtocol.ANDROID_WATCH -> 8
+                                    else -> error("assertion")
+                                }
+                            )
+                        }
                     }
                     writeByte(0)
                     writeShort(code2dPacket.remaining.toShort())

+ 42 - 39
mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin10.kt

@@ -16,6 +16,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
 import net.mamoe.mirai.internal.utils.GuidSource
 import net.mamoe.mirai.internal.utils.MacOrAndroidIdChangeFlag
 import net.mamoe.mirai.internal.utils.guidFlag
+import net.mamoe.mirai.utils._writeTlvMap
 import net.mamoe.mirai.utils.generateDeviceInfoData
 import net.mamoe.mirai.utils.md5
 import net.mamoe.mirai.utils.toReadPacket
@@ -44,45 +45,47 @@ internal object WtLogin10 : WtLoginExt {
                 0x0810
             ) {
                 writeShort(11) // subCommand
-                writeShort(18)
-                t100(appId, subAppId, client.appClientVersion, client.ssoVersion, mainSigMap)
-                t10a(client.wLoginSigInfo.tgt)
-                t116(client.miscBitMap, client.subSigMap)
-                t108(client.ksid)
-                t144(
-                    androidId = client.device.androidId,
-                    androidDevInfo = client.device.generateDeviceInfoData(),
-                    osType = client.device.osType,
-                    osVersion = client.device.version.release,
-                    networkType = client.networkType,
-                    simInfo = client.device.simInfo,
-                    unknown = byteArrayOf(),
-                    apn = client.device.apn,
-                    isGuidFromFileNull = false,
-                    isGuidAvailable = true,
-                    isGuidChanged = false,
-                    guidFlag = guidFlag(GuidSource.FROM_STORAGE, MacOrAndroidIdChangeFlag(0)),
-                    buildModel = client.device.model,
-                    guid = client.device.guid,
-                    buildBrand = client.device.brand,
-                    tgtgtKey = client.wLoginSigInfo.d2Key.md5()
-                )
-                //t112(client.account.phoneNumber.encodeToByteArray())
-                t143(client.wLoginSigInfo.d2.data)
-                t142(client.apkId)
-                t154(sequenceId)
-                t18(appId, uin = client.uin)
-                t141(client.device.simInfo, client.networkType, client.device.apn)
-                t8(2052)
-                //t511()
-                t147(appId, client.apkVersionName, client.apkSignatureMd5)
-                t177(client.buildTime, client.sdkVersion)
-                t187(client.device.macAddress)
-                t188(client.device.androidId)
-                t194(client.device.imsiMd5)
-                t511()
-                t202(client.device.wifiBSSID, client.device.wifiSSID)
-                //t544()
+
+                _writeTlvMap(Short.SIZE_BYTES) {
+                    t100(appId, subAppId, client.appClientVersion, client.ssoVersion, mainSigMap)
+                    t10a(client.wLoginSigInfo.tgt)
+                    t116(client.miscBitMap, client.subSigMap)
+                    t108(client.ksid)
+                    t144(
+                        androidId = client.device.androidId,
+                        androidDevInfo = client.device.generateDeviceInfoData(),
+                        osType = client.device.osType,
+                        osVersion = client.device.version.release,
+                        networkType = client.networkType,
+                        simInfo = client.device.simInfo,
+                        unknown = byteArrayOf(),
+                        apn = client.device.apn,
+                        isGuidFromFileNull = false,
+                        isGuidAvailable = true,
+                        isGuidChanged = false,
+                        guidFlag = guidFlag(GuidSource.FROM_STORAGE, MacOrAndroidIdChangeFlag(0)),
+                        buildModel = client.device.model,
+                        guid = client.device.guid,
+                        buildBrand = client.device.brand,
+                        tgtgtKey = client.wLoginSigInfo.d2Key.md5()
+                    )
+                    //t112(client.account.phoneNumber.encodeToByteArray())
+                    t143(client.wLoginSigInfo.d2.data)
+                    t142(client.apkId)
+                    t154(sequenceId)
+                    t18(appId, uin = client.uin)
+                    t141(client.device.simInfo, client.networkType, client.device.apn)
+                    t8(2052)
+                    //t511()
+                    t147(appId, client.apkVersionName, client.apkSignatureMd5)
+                    t177(client.buildTime, client.sdkVersion)
+                    t187(client.device.macAddress)
+                    t188(client.device.androidId)
+                    t194(client.device.imsiMd5)
+                    t511()
+                    t202(client.device.wifiBSSID, client.device.wifiSSID)
+                    //t544()
+                }
 
             }
         }

+ 69 - 66
mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin15.kt

@@ -13,6 +13,7 @@ import io.ktor.utils.io.core.*
 import net.mamoe.mirai.internal.network.*
 import net.mamoe.mirai.internal.network.protocol.packet.*
 import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
+import net.mamoe.mirai.utils._writeTlvMap
 import kotlin.math.abs
 import kotlin.random.Random
 
@@ -36,7 +37,8 @@ internal object WtLogin15 : WtLoginExt {
             commandId = 0x0810
         ) {
             writeShort(subCommand) // subCommand
-            writeShort(24)
+
+            _writeTlvMap {
 
 
 //            writeFully(("00 18 00 16 00 01 00 00 06 00 00 00 00 10 00 00 00 00 76 E4 B8 DD 00 00 00 00 00 01 00 14 00 01 5A 11 60 11 76 E4 B8 DD 60 0D 44 35 90 E8 11 1B 00 00 " +
@@ -51,11 +53,11 @@ internal object WtLogin15 : WtLoginExt {
 //                    "").hexToBytes())
 //            return@writeOicqRequestPacket
 
-            t18(appId, uin = client.uin)
-            t1(client.uin, ByteArray(4))
+                t18(appId, uin = client.uin)
+                t1(client.uin, ByteArray(4))
 
-            //  t106(client = client)
-            t106(client.wLoginSigInfo.encryptA1!!)
+                //  t106(client = client)
+                t106(client.wLoginSigInfo.encryptA1!!)
 //            kotlin.run {
 //                val key = (client.account.passwordMd5 + ByteArray(4) + client.uin.toInt().toByteArray()).md5()
 //                kotlin.runCatching {
@@ -63,67 +65,68 @@ internal object WtLogin15 : WtLoginExt {
 //                }.soutv("DEC") // success
 //            }
 
-            // val a1 = kotlin.runCatching {
-            //     TEA.decrypt(encryptA1, buildPacket {
-            //         writeFully(client.device.guid)
-            //         writeFully(client.dpwd)
-            //         writeFully(client.randSeed)
-            //     }.readBytes().md5())
-            // }.recoverCatching {
-            //     client.tryDecryptOrNull(encryptA1) { it }!!
-            // }.getOrElse {
-            //     encryptA1.soutv("ENCRYPT A1")
-            //     client.soutv("CLIENT")
-            //     // exitProcess(1)
-            //     // error("Failed to decrypt A1")
-            //     encryptA1
-            // }
-
-            t116(client.miscBitMap, client.subSigMap)
-            //t116(0x08F7FF7C, 0x00010400)
-
-            //t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
-            //t100(appId, 1, client.appClientVersion, client.ssoVersion, mainSigMap = 1048768)
-            t100(appId, 2, client.appClientVersion, client.ssoVersion, client.mainSigMap)
-
-            t107(0)
-
-            //t108(client.ksid) // 第一次 exchange 没有 108
-            t144(client)
-            t142(client.apkId)
-            t145(client.device.guid)
-
-            val noPicSig =
-                client.wLoginSigInfo.noPicSig ?: error("Internal error: doing exchange emp 15 while noPicSig=null")
-            t16a(noPicSig)
-
-            t154(0)
-            t141(client.device.simInfo, client.networkType, client.device.apn)
-            t8(2052)
-            t511()
-            t147(appId, client.apkVersionName, client.apkSignatureMd5)
-            t177(buildTime = client.buildTime, buildVersion = client.sdkVersion)
-
-            // new
-            t400(
-                g = client.G,
-                uin = client.uin,
-                guid = client.device.guid,
-                dpwd = client.dpwd,
-                appId = 1,
-                subAppId = 16,
-                randomSeed = client.randSeed
-            )
-
-            t187(client.device.macAddress)
-            t188(client.device.androidId)
-            t194(client.device.imsiMd5)
-            t202(client.device.wifiBSSID, client.device.wifiSSID)
-            t516()
-
-            t521() // new
-            t525(client.loginExtraData) // new
-            //t544() // new
+                // val a1 = kotlin.runCatching {
+                //     TEA.decrypt(encryptA1, buildPacket {
+                //         writeFully(client.device.guid)
+                //         writeFully(client.dpwd)
+                //         writeFully(client.randSeed)
+                //     }.readBytes().md5())
+                // }.recoverCatching {
+                //     client.tryDecryptOrNull(encryptA1) { it }!!
+                // }.getOrElse {
+                //     encryptA1.soutv("ENCRYPT A1")
+                //     client.soutv("CLIENT")
+                //     // exitProcess(1)
+                //     // error("Failed to decrypt A1")
+                //     encryptA1
+                // }
+
+                t116(client.miscBitMap, client.subSigMap)
+                //t116(0x08F7FF7C, 0x00010400)
+
+                //t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
+                //t100(appId, 1, client.appClientVersion, client.ssoVersion, mainSigMap = 1048768)
+                t100(appId, 2, client.appClientVersion, client.ssoVersion, client.mainSigMap)
+
+                t107(0)
+
+                //t108(client.ksid) // 第一次 exchange 没有 108
+                t144(client)
+                t142(client.apkId)
+                t145(client.device.guid)
+
+                val noPicSig =
+                    client.wLoginSigInfo.noPicSig ?: error("Internal error: doing exchange emp 15 while noPicSig=null")
+                t16a(noPicSig)
+
+                t154(0)
+                t141(client.device.simInfo, client.networkType, client.device.apn)
+                t8(2052)
+                t511()
+                t147(appId, client.apkVersionName, client.apkSignatureMd5)
+                t177(buildTime = client.buildTime, buildVersion = client.sdkVersion)
+
+                // new
+                t400(
+                    g = client.G,
+                    uin = client.uin,
+                    guid = client.device.guid,
+                    dpwd = client.dpwd,
+                    appId = 1,
+                    subAppId = 16,
+                    randomSeed = client.randSeed
+                )
+
+                t187(client.device.macAddress)
+                t188(client.device.androidId)
+                t194(client.device.imsiMd5)
+                t202(client.device.wifiBSSID, client.device.wifiSSID)
+                t516()
+
+                t521() // new
+                t525(client.loginExtraData) // new
+                //t544() // new
+            }
         }
 
         //  }

+ 15 - 26
mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin2.kt

@@ -16,6 +16,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.*
 import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
 import net.mamoe.mirai.internal.network.subAppId
 import net.mamoe.mirai.internal.network.subSigMap
+import net.mamoe.mirai.utils._writeTlvMap
 
 
 internal object WtLogin2 : WtLoginExt {
@@ -26,19 +27,13 @@ internal object WtLogin2 : WtLoginExt {
         writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
             writeOicqRequestPacket(client, commandId = 0x0810) {
                 writeShort(2) // subCommand
-                writeShort(
-                    if (client.t547 == null) {
-                        4
-                    } else {
-                        5
-                    }
-                ) // count of TLVs
-                t193(ticket)
-                t8(2052)
-                t104(client.t104)
-                t116(client.miscBitMap, client.subSigMap)
-                client.t547?.let {
-                    t547(it)
+
+                _writeTlvMap {
+                    t193(ticket)
+                    t8(2052)
+                    t104(client.t104)
+                    t116(client.miscBitMap, client.subSigMap)
+                    client.t547?.let { t547(it) }
                 }
             }
         }
@@ -52,19 +47,13 @@ internal object WtLogin2 : WtLoginExt {
         writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
             writeOicqRequestPacket(client, commandId = 0x0810) {
                 writeShort(2) // subCommand
-                writeShort(
-                    if (client.t547 == null) {
-                        4
-                    } else {
-                        5
-                    }
-                ) // count of TLVs
-                t2(captchaAnswer, captchaSign, 0)
-                t8(2052)
-                t104(client.t104)
-                t116(client.miscBitMap, client.subSigMap)
-                client.t547?.let {
-                    t547(it)
+
+                _writeTlvMap {
+                    t2(captchaAnswer, captchaSign, 0)
+                    t8(2052)
+                    t104(client.t104)
+                    t116(client.miscBitMap, client.subSigMap)
+                    client.t547?.let { t547(it) }
                 }
             }
         }

+ 7 - 5
mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin20.kt

@@ -16,6 +16,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.*
 import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
 import net.mamoe.mirai.internal.network.subAppId
 import net.mamoe.mirai.internal.network.subSigMap
+import net.mamoe.mirai.utils._writeTlvMap
 
 internal object WtLogin20 : WtLoginExt {
     operator fun invoke(
@@ -24,11 +25,12 @@ internal object WtLogin20 : WtLoginExt {
         writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
             writeOicqRequestPacket(client, commandId = 0x0810) {
                 writeShort(20) // subCommand
-                writeShort(4) // count of TLVs, probably ignored by server?
-                t8(2052)
-                t104(client.t104)
-                t116(client.miscBitMap, client.subSigMap)
-                t401(client.G) // (client.device.guid + "stMNokHgxZUGhsYp".toByteArray() + t402).md5()
+                _writeTlvMap {
+                    t8(2052)
+                    t104(client.t104)
+                    t116(client.miscBitMap, client.subSigMap)
+                    t401(client.G) // (client.device.guid + "stMNokHgxZUGhsYp".toByteArray() + t402).md5()
+                }
             }
         }
     }

+ 11 - 8
mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin7.kt

@@ -17,6 +17,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
 import net.mamoe.mirai.internal.network.subAppId
 import net.mamoe.mirai.internal.network.subSigMap
 import net.mamoe.mirai.utils.DeviceVerificationRequests
+import net.mamoe.mirai.utils._writeTlvMap
 
 /**
  * Submit SMS.
@@ -33,15 +34,17 @@ internal object WtLogin7 : WtLoginExt {
         writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
             writeOicqRequestPacket(client, commandId = 0x0810) {
                 writeShort(7) // subCommand
-                writeShort(7) // count of TLVs
 
-                t8(2052)
-                t104(client.t104)
-                t116(client.miscBitMap, client.subSigMap)
-                t174(client.t174 ?: t174)
-                t17c(code.encodeToByteArray())
-                t401(client.G)
-                t198()
+                _writeTlvMap {
+
+                    t8(2052)
+                    t104(client.t104)
+                    t116(client.miscBitMap, client.subSigMap)
+                    t174(client.t174 ?: t174)
+                    t17c(code.encodeToByteArray())
+                    t401(client.G)
+                    t198()
+                }
             }
         }
     }

+ 11 - 7
mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin8.kt

@@ -17,6 +17,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
 import net.mamoe.mirai.internal.network.subAppId
 import net.mamoe.mirai.internal.network.subSigMap
 import net.mamoe.mirai.utils.DeviceVerificationRequests
+import net.mamoe.mirai.utils._writeTlvMap
 
 /**
  * Request SMS.
@@ -33,14 +34,17 @@ internal object WtLogin8 : WtLoginExt {
         writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
             writeOicqRequestPacket(client, commandId = 0x0810) {
                 writeShort(subCommand) // subCommand
-                writeShort(6) // count of TLVs
 
-                t8(2052)
-                t104(client.t104)
-                t116(client.miscBitMap, client.subSigMap)
-                t174(t174)
-                t17a(9)
-                t197()
+                _writeTlvMap {
+
+                    t8(2052)
+                    t104(client.t104)
+                    t116(client.miscBitMap, client.subSigMap)
+                    t174(t174)
+                    t17a(9)
+                    t197()
+
+                }
             }
         }
     }

+ 82 - 76
mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin9.kt

@@ -13,6 +13,7 @@ import io.ktor.utils.io.core.*
 import net.mamoe.mirai.internal.network.*
 import net.mamoe.mirai.internal.network.protocol.packet.*
 import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
+import net.mamoe.mirai.utils._writeTlvMap
 
 internal object WtLogin9 : WtLoginExt {
     private const val appId = 16L
@@ -35,19 +36,21 @@ internal object WtLogin9 : WtLoginExt {
                 if (useEncryptA1AndNoPicSig) {
                     tlvCount++;
                 }
-                writeShort(tlvCount.toShort()) // count of TLVs, probably ignored by server?
+                // writeShort(tlvCount.toShort()) // count of TLVs, probably ignored by server?
                 //writeShort(LoginType.PASSWORD.value.toShort())
 
-                t18(appId, client.appClientVersion, client.uin)
-                t1(client.uin, client.device.ipAddress)
+                _writeTlvMap {
 
-                if (useEncryptA1AndNoPicSig) {
-                    t106(client.wLoginSigInfo.encryptA1!!)
-                } else {
-                    t106(client, appId, passwordMd5)
-                }
+                    t18(appId, client.appClientVersion, client.uin)
+                    t1(client.uin, client.device.ipAddress)
 
-                /* // from GetStWithPasswd
+                    if (useEncryptA1AndNoPicSig) {
+                        t106(client.wLoginSigInfo.encryptA1!!)
+                    } else {
+                        t106(client, appId, passwordMd5)
+                    }
+
+                    /* // from GetStWithPasswd
                 int mMiscBitmap = this.mMiscBitmap;
                 if (t.uinDeviceToken) {
                     mMiscBitmap = (this.mMiscBitmap | 0x2000000);
@@ -57,65 +60,66 @@ internal object WtLogin9 : WtLoginExt {
                 // defaults true
                 if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L)
                 */
-                t116(client.miscBitMap, client.subSigMap)
-                t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
-                t107(0)
-                t108(client.device.imei.toByteArray())
+                    t116(client.miscBitMap, client.subSigMap)
+                    t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
+                    t107(0)
+                    t108(client.device.imei.toByteArray())
 
-                // t108(byteArrayOf())
-                // ignored: t104()
-                t142(client.apkId)
+                    // t108(byteArrayOf())
+                    // ignored: t104()
+                    t142(client.apkId)
 
-                // if login with non-number uin
-                // t112()
-                t144(client)
+                    // if login with non-number uin
+                    // t112()
+                    t144(client)
 
-                //this.build().debugPrint("傻逼")
-                t145(client.device.guid)
-                t147(appId, client.apkVersionName, client.apkSignatureMd5)
+                    //this.build().debugPrint("傻逼")
+                    t145(client.device.guid)
+                    t147(appId, client.apkVersionName, client.apkSignatureMd5)
 
-                /*
+                    /*
                 if (client.miscBitMap and 0x80 != 0) {
                     t166(1)
                 }
                 */
-                if (useEncryptA1AndNoPicSig) {
-                    t16a(client.wLoginSigInfo.noPicSig!!)
-                }
+                    if (useEncryptA1AndNoPicSig) {
+                        t16a(client.wLoginSigInfo.noPicSig!!)
+                    }
 
-                t154(sequenceId)
-                t141(client.device.simInfo, client.networkType, client.device.apn)
-                t8(2052)
+                    t154(sequenceId)
+                    t141(client.device.simInfo, client.networkType, client.device.apn)
+                    t8(2052)
 
-                t511()
+                    t511()
 
-                // ignored t172 because rollbackSig is null
-                // ignored t185 because loginType is not SMS
-                // ignored t400 because of first login
+                    // ignored t172 because rollbackSig is null
+                    // ignored t185 because loginType is not SMS
+                    // ignored t400 because of first login
 
-                t187(client.device.macAddress)
-                t188(client.device.androidId)
-                t194(client.device.imsiMd5)
-                if (allowSlider) {
-                    t191()
-                }
+                    t187(client.device.macAddress)
+                    t188(client.device.androidId)
+                    t194(client.device.imsiMd5)
+                    if (allowSlider) {
+                        t191()
+                    }
 
-                /*
+                    /*
                 t201(N = byteArrayOf())*/
 
-                t202(client.device.wifiBSSID, client.device.wifiSSID)
+                    t202(client.device.wifiBSSID, client.device.wifiSSID)
 
-                t177(
-                    buildTime = client.buildTime,
-                    buildVersion = client.sdkVersion,
-                )
-                t516()
-                t521()
+                    t177(
+                        buildTime = client.buildTime,
+                        buildVersion = client.sdkVersion,
+                    )
+                    t516()
+                    t521()
 
-                t525()
-                // this.build().debugPrint("傻逼")
+                    t525()
+                    // this.build().debugPrint("傻逼")
 
-                // ignored t318 because not logging in by QR
+                    // ignored t318 because not logging in by QR
+                }
             }
         }
     }
@@ -130,44 +134,46 @@ internal object WtLogin9 : WtLoginExt {
         writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) {
             writeOicqRequestPacket(client, commandId = 0x0810) {
                 writeShort(9) // subCommand
-                writeShort(0x19) // count of TLVs, probably ignored by server?
+//                writeShort(0x19) // count of TLVs, probably ignored by server?
 
-                t18(appId, client.appClientVersion, client.uin)
-                t1(client.uin, client.device.ipAddress)
+                _writeTlvMap {
+                    t18(appId, client.appClientVersion, client.uin)
+                    t1(client.uin, client.device.ipAddress)
 
-                t106(data.tmpPwd)
+                    t106(data.tmpPwd)
 
-                t116(client.miscBitMap, client.subSigMap)
-                t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
-                t107(0)
-                t108(client.device.imei.toByteArray())
+                    t116(client.miscBitMap, client.subSigMap)
+                    t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap)
+                    t107(0)
+                    t108(client.device.imei.toByteArray())
 
-                t142(client.apkId)
+                    t142(client.apkId)
 
-                t144(client)
+                    t144(client)
 
-                t145(client.device.guid)
-                t147(appId, client.apkVersionName, client.apkSignatureMd5)
+                    t145(client.device.guid)
+                    t147(appId, client.apkVersionName, client.apkSignatureMd5)
 
-                t16a(data.noPicSig)
+                    t16a(data.noPicSig)
 
-                t154(sequenceId)
-                t141(client.device.simInfo, client.networkType, client.device.apn)
-                t8(2052)
+                    t154(sequenceId)
+                    t141(client.device.simInfo, client.networkType, client.device.apn)
+                    t8(2052)
 
-                t511()
+                    t511()
 
-                t187(client.device.macAddress)
-                t188(client.device.androidId)
-                t194(client.device.imsiMd5)
-                t191(0x00)
+                    t187(client.device.macAddress)
+                    t188(client.device.androidId)
+                    t194(client.device.imsiMd5)
+                    t191(0x00)
 
-                t202(client.device.wifiBSSID, client.device.wifiSSID)
+                    t202(client.device.wifiBSSID, client.device.wifiSSID)
 
-                t177(client.buildTime, client.sdkVersion)
-                t516()
-                t521(8)
-                t318(data.tgtQR)
+                    t177(client.buildTime, client.sdkVersion)
+                    t516()
+                    t521(8)
+                    t318(data.tgtQR)
+                }
             }
         }
     }

+ 8 - 9
mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLoginExt.kt

@@ -86,19 +86,18 @@ internal interface WtLoginExt { // so as not to register to global extension
 
         return buildPacket {
             writeByte(64)
-            writeShort(4)
 
-            // TLV
-            writeShort(0x106)
-            writeShortLVByteArray(t106)
+            _writeTlvMap {
 
-            writeShort(0x10c)
-            writeShortLVByteArray(t10c)
+                // TLV
+                tlv(0x106, t106)
 
-            writeShort(0x16a)
-            writeShortLVByteArray(t16a)
+                tlv(0x10c, t10c)
 
-            t145(device.guid)
+                tlv(0x16a, t16a)
+
+                t145(device.guid)
+            }
         }
     }