commonMain.network.protocol.packet.Tlv.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mirai-core-jvm Show documentation
Show all versions of mirai-core-jvm Show documentation
Mirai Protocol implementation for QQ Android
/*
* Copyright 2019-2022 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
*/
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.internal.network.protocol.packet
import io.ktor.utils.io.core.*
import net.mamoe.mirai.internal.network.*
import net.mamoe.mirai.internal.network.protocol.LoginType
import net.mamoe.mirai.internal.utils.GuidSource
import net.mamoe.mirai.internal.utils.MacOrAndroidIdChangeFlag
import net.mamoe.mirai.internal.utils.NetworkType
import net.mamoe.mirai.internal.utils.guidFlag
import net.mamoe.mirai.internal.utils.io.*
import net.mamoe.mirai.utils.*
import kotlin.jvm.JvmInline
import kotlin.random.Random
private val Char.isHumanReadable get() = this in '0'..'9' || this in 'a'..'z' || this in 'A'..'Z' || this in """ <>?,.";':/\][{}~!@#$%^&*()_+-=`""" || this in "\n\r"
internal fun TlvMap.smartToString(leadingLineBreak: Boolean = true, sorted: Boolean = true): String {
fun ByteArray.valueToString(): String {
val str = this.decodeToString()
return if (str.all { it.isHumanReadable }) str
else this.toUHexString()
}
val map = if (sorted) entries.sortedBy { it.key } else this.entries
return buildString {
if (leadingLineBreak) appendLine()
appendLine("count=${map.size}")
appendLine(map.joinToString("\n") { (key, value) ->
"0x" + key.toShort().toUHexString("") + " = " + value.valueToString()
})
}
}
/**
* 显式表示一个 [ByteArray] 是一个 tlv 的 body
*/
@JvmInline
internal value class Tlv(val value: ByteArray)
internal fun BytePacketBuilder.t1(uin: Long, ip: ByteArray) {
require(ip.size == 4) { "ip.size must == 4" }
writeShort(0x1)
writeShortLVPacket {
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 {
writeShort(sigVer)
writeShortLVString(captchaCode)
writeShortLVByteArray(captchaToken)
}
}
internal fun BytePacketBuilder.t8(
localId: Int = 2052
) {
writeShort(0x8)
writeShortLVPacket {
writeShort(0)
writeInt(localId) // localId
writeShort(0)
}
}
internal fun BytePacketBuilder.t16(
ssoVersion: Int,
subAppId: Long,
guid: ByteArray,
apkId: ByteArray,
apkVersionName: ByteArray,
apkSignatureMd5: ByteArray
) {
writeShort(0x16)
writeShortLVPacket {
writeInt(ssoVersion)
writeInt(16)
writeInt(subAppId.toInt())
writeFully(guid)
writeShortLVByteArray(apkId)
writeShortLVByteArray(apkVersionName)
writeShortLVByteArray(apkSignatureMd5)
}
}
internal fun BytePacketBuilder.t18(
appId: Long,
appClientVersion: Int = 0,
uin: Long,
constant1_always_0: Int = 0
) {
writeShort(0x18)
writeShortLVPacket {
writeShort(1) //_ping_version
writeInt(0x00_00_06_00) //_sso_version=1536
writeInt(appId.toInt())
writeInt(appClientVersion)
writeInt(uin.toInt())
writeShort(constant1_always_0.toShort())
writeShort(0)
} shouldEqualsTo 22
}
internal fun BytePacketBuilder.t1b(
micro: Int = 0,
version: Int = 0,
size: Int = 3,
margin: Int = 4,
dpi: Int = 72,
ecLevel: Int = 2,
hint: Int = 2
) {
writeShort(0x1b)
writeShortLVPacket {
writeInt(micro)
writeInt(version)
writeInt(size)
writeInt(margin)
writeInt(dpi)
writeInt(ecLevel)
writeInt(hint)
writeShort(0)
}
}
internal fun BytePacketBuilder.t1d(
miscBitmap: Int,
) {
writeShort(0x1d)
writeShortLVPacket {
writeByte(1)
writeInt(miscBitmap)
writeInt(0)
writeByte(0)
writeInt(0)
}
}
internal fun BytePacketBuilder.t1f(
isRoot: Boolean = false,
osName: ByteArray,
osVersion: ByteArray,
simVendor: ByteArray,
apn: ByteArray,
networkType: Short = 2,
) {
writeShort(0x1f)
writeShortLVPacket {
writeByte(if (isRoot) 1 else 0)
writeShortLVByteArray(osName)
writeShortLVByteArray(osVersion)
writeShort(networkType)
writeShortLVByteArray(simVendor)
writeShortLVByteArray(EMPTY_BYTE_ARRAY)
writeShortLVByteArray(apn)
}
}
internal fun BytePacketBuilder.t33(
guid: ByteArray,
) {
writeShort(0x33)
writeShortLVByteArray(guid)
}
internal fun BytePacketBuilder.t35(
productType: Int
) {
writeShort(0x35)
writeShortLVPacket {
writeInt(productType)
}
}
internal fun BytePacketBuilder.t106(
client: QQAndroidClient,
appId: Long = 16L,
passwordMd5: ByteArray,
) {
return t106(
appId,
client.subAppId /* maybe 1*/,
client.appClientVersion,
client.uin,
true,
passwordMd5,
0,
client.uin.toByteArray(),
client.tgtgtKey,
true,
client.device.guid,
LoginType.PASSWORD,
client.ssoVersion
)
}
internal fun BytePacketBuilder.t106(
encryptA1: ByteArray
) {
writeShort(0x106)
writeShortLVPacket {
writeFully(encryptA1)
}
}
/**
* A1
*/
internal fun BytePacketBuilder.t106(
appId: Long = 16L,
subAppId: Long,
appClientVersion: Int = 0,
uin: Long,
isSavePassword: Boolean = true,
passwordMd5: ByteArray,
salt: Long,
uinAccountString: ByteArray,
tgtgtKey: ByteArray,
isGuidAvailable: Boolean = true,
guid: ByteArray?,
loginType: LoginType,
ssoVersion: Int,
) {
writeShort(0x106)
passwordMd5.requireSize(16)
tgtgtKey.requireSize(16)
guid?.requireSize(16)
writeShortLVPacket {
encryptAndWrite(
(passwordMd5 + ByteArray(4) + (salt.takeIf { it != 0L } ?: uin).toInt()
.toByteArray()).md5()
) {
writeShort(4)//TGTGTVer
writeInt(Random.nextInt())
writeInt(ssoVersion)//ssoVer
writeInt(appId.toInt())
writeInt(appClientVersion)
if (uin == 0L) {
writeLong(salt)
} else {
writeLong(uin)
}
writeInt(currentTimeSeconds().toInt())
writeFully(ByteArray(4)) // ip // no need to write actual ip
writeByte(isSavePassword.toByte())
writeFully(passwordMd5)
writeFully(tgtgtKey)
writeInt(0) // wtf
writeByte(isGuidAvailable.toByte())
if (isGuidAvailable) {
require(guid != null) { "Guid must not be null when isGuidAvailable==true" }
}
if (guid == null) {
repeat(4) {
writeInt(Random.nextInt())
}
} else {
writeFully(guid)
}
writeInt(subAppId.toInt())
writeInt(loginType.value)
writeShortLVByteArray(uinAccountString)
writeShort(0)
}
}
}
internal fun BytePacketBuilder.t116(
miscBitmap: Int,
subSigMap: Int,
appIdList: LongArray = longArrayOf(1600000226L)
) {
writeShort(0x116)
writeShortLVPacket {
writeByte(0) // _ver
writeInt(miscBitmap) // 184024956
writeInt(subSigMap) // 66560
writeByte(appIdList.size.toByte())
appIdList.forEach {
writeInt(it.toInt())
}
}
}
internal fun BytePacketBuilder.t100(
appId: Long = 16,
subAppId: Long,
appClientVersion: Int,
ssoVersion: Int,
mainSigMap: Int
) {
writeShort(0x100)
writeShortLVPacket {
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(
tgt: ByteArray,
) {
writeShort(0x10a)
writeShortLVPacket {
writeFully(tgt)
}
}
internal fun BytePacketBuilder.t107(
picType: Int,
capType: Int = 0,
picSize: Int = 0,
retType: Int = 1
) {
writeShort(0x107)
writeShortLVPacket {
writeShort(picType.toShort())
writeByte(capType.toByte())
writeShort(picSize.toShort())
writeByte(retType.toByte())
} shouldEqualsTo 6
}
internal fun BytePacketBuilder.t108(
ksid: ByteArray
) {
// require(ksid.size == 16) { "ksid should length 16" }
writeShort(0x108)
writeShortLVPacket {
writeFully(ksid)
}
}
internal fun BytePacketBuilder.t104(
t104Data: ByteArray
) {
writeShort(0x104)
writeShortLVPacket {
writeFully(t104Data)
}
}
internal fun BytePacketBuilder.t547(
t547Data: ByteArray
) {
writeShort(0x547)
writeShortLVPacket {
writeFully(t547Data)
}
}
internal fun BytePacketBuilder.t174(
t174Data: ByteArray
) {
writeShort(0x174)
writeShortLVPacket {
writeFully(t174Data)
}
}
internal fun BytePacketBuilder.t17a(
value: Int = 0
) {
writeShort(0x17a)
writeShortLVPacket {
writeInt(value)
}
}
internal fun BytePacketBuilder.t197() {
writeShort(0x197)
writeFully(byteArrayOf(0, 1, 0))
}
internal fun BytePacketBuilder.t198() {
writeShort(0x198)
writeFully(byteArrayOf(0, 1, 0))
}
internal fun BytePacketBuilder.t19e(
value: Int = 0
) {
writeShort(0x19e)
writeShortLVPacket {
writeShort(1)
writeByte(value.toByte())
}
}
internal fun BytePacketBuilder.t17c(
t17cData: ByteArray
) {
writeShort(0x17c)
writeShortLVPacket {
writeShort(t17cData.size.toShort())
writeFully(t17cData)
}
}
internal fun BytePacketBuilder.t401(
t401Data: ByteArray
) {
writeShort(0x401)
writeShortLVPacket {
writeFully(t401Data)
}
}
/**
* @param apkId application.getPackageName().getBytes()
*/
internal fun BytePacketBuilder.t142(
apkId: ByteArray
) {
writeShort(0x142)
writeShortLVPacket {
writeShort(0) //_version
writeShortLVByteArrayLimitedLength(apkId, 32)
}
}
internal fun BytePacketBuilder.t143(
d2: ByteArray
) {
writeShort(0x143)
writeShortLVPacket {
writeFully(d2)
}
}
internal fun BytePacketBuilder.t112(
nonNumberUin: ByteArray
) {
writeShort(0x112)
writeShortLVPacket {
writeFully(nonNumberUin)
}
}
internal fun BytePacketBuilder.t144(
client: QQAndroidClient
) {
return 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.tgtgtKey
)
}
internal fun BytePacketBuilder.t144(
// t109
androidId: ByteArray,
// t52d
androidDevInfo: ByteArray,
// t124
osType: ByteArray = "android".toByteArray(),
osVersion: ByteArray,
networkType: NetworkType,
simInfo: ByteArray,
unknown: ByteArray,
apn: ByteArray = "wifi".toByteArray(),
// t128
isGuidFromFileNull: Boolean = false,
isGuidAvailable: Boolean = true,
isGuidChanged: Boolean = false,
guidFlag: Long,
buildModel: ByteArray,
guid: ByteArray,
buildBrand: ByteArray,
// encrypt
tgtgtKey: ByteArray
) {
writeShort(0x144)
writeShortLVPacket {
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)
}
}
}
internal fun BytePacketBuilder.t109(
androidId: ByteArray
) {
writeShort(0x109)
writeShortLVPacket {
writeFully(androidId.md5())
} shouldEqualsTo 16
}
internal fun BytePacketBuilder.t52d(
androidDevInfo: ByteArray // oicq.wlogin_sdk.tools.util#get_android_dev_info
) {
writeShort(0x52d)
writeShortLVPacket {
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
}
}
internal fun BytePacketBuilder.t124(
osType: ByteArray = "android".toByteArray(),
osVersion: ByteArray, // Build.VERSION.RELEASE.toByteArray()
networkType: NetworkType, //oicq.wlogin_sdk.tools.util#get_network_type
simInfo: ByteArray, // oicq.wlogin_sdk.tools.util#get_sim_operator_name
address: ByteArray, // always new byte[0]
apn: ByteArray = "wifi".toByteArray() // oicq.wlogin_sdk.tools.util#get_apn_string
) {
writeShort(0x124)
writeShortLVPacket {
writeShortLVByteArrayLimitedLength(osType, 16)
writeShortLVByteArrayLimitedLength(osVersion, 16)
writeShort(networkType.value.toShort())
writeShortLVByteArrayLimitedLength(simInfo, 16)
writeShortLVByteArrayLimitedLength(address, 32)
writeShortLVByteArrayLimitedLength(apn, 16)
}
}
internal fun BytePacketBuilder.t128(
isGuidFromFileNull: Boolean = false, // 保存到文件的 GUID 是否为 null
isGuidAvailable: Boolean = true, // GUID 是否可用(计算/读取成功)
isGuidChanged: Boolean = false, // GUID 是否有变动
/**
* guidFlag:
* ```java
* GUID_FLAG = 0;
* GUID_FLAG |= GUID_SRC << 24 & 0xFF000000;
* GUID_FLAG |= FLAG_MAC_ANDROIDID_GUID_CHANGE << 8 & 0xFF00;
* ```
*
*
* GUID_SRC:
* 0: 初始值;
* 1: 以前保存的文件;
* 20: 以前没保存且现在生成失败;
* 17: 以前没保存但现在生成成功;
*
*
* FLAG_MAC_ANDROIDID_GUID_CHANGE:
* ```java
* if (!Arrays.equals(currentMac, get_last_mac)) {
* oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x1;
* }
* if (!Arrays.equals(currentAndroidId, get_last_android_id)) {
* oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x2;
* }
* if (!Arrays.equals(currentGuid, get_last_guid)) {
* oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x4;
* }
* ```
*/
guidFlag: Long,
buildModel: ByteArray, // android.os.Build.MODEL
/**
* defaults `"%4;7t>;28 = listOf(
"tenpay.com",
"openmobile.qq.com",
"docs.qq.com",
"connect.qq.com",
"qzone.qq.com",
"vip.qq.com",
"gamecenter.qq.com",
"qun.qq.com",
"game.qq.com",
"qqweb.qq.com",
"office.qq.com",
"ti.qq.com",
"mail.qq.com",
"mma.qq.com",
)
) {
writeShort(0x511)
writeShortLVPacket {
val list = domains.filter { it.isNotEmpty() }
writeShort(list.size.toShort())
list.forEach { element ->
if (element.startsWith('(')) {
val split = element.drop(1).split(')')
val flag = split[0].toInt()
var n = (flag and 0x100000 > 0).toInt()
if (flag and 0x8000000 > 0) {
n = n or 0x2
}
writeByte(n.toByte())
writeShortLVString(split[1])
} else {
writeByte(1)
writeShortLVString(element)
}
}
}
}
internal fun BytePacketBuilder.t172(
rollbackSig: ByteArray // 由服务器发来的 tlv_t172 获得
) {
writeShort(0x172)
writeShortLVPacket {
writeFully(rollbackSig)
}
}
internal fun BytePacketBuilder.t185() {
writeShort(0x185)
writeShortLVPacket {
writeByte(1)
writeByte(1)
}
}
internal fun BytePacketBuilder.t400(
/**
* if (var1[2] != null && var1[2].length > 0) {
this._G = (byte[])var1[2].clone();
}
*/
g: ByteArray, // 用于加密这个 tlv
uin: Long,
guid: ByteArray,
dpwd: ByteArray,
appId: Long,
subAppId: Long,
randomSeed: ByteArray
) {
writeShort(0x400)
writeShortLVPacket {
encryptAndWrite(g) {
writeByte(1) // version
writeLong(uin)
writeFully(guid)
writeFully(dpwd)
writeInt(appId.toInt())
writeInt(subAppId.toInt())
writeInt(currentTimeSeconds().toInt())
writeFully(randomSeed)
}
}
}
internal fun BytePacketBuilder.t187(
macAddress: ByteArray
) {
writeShort(0x187)
writeShortLVPacket {
writeFully(macAddress.md5()) // may be md5
}
}
internal fun BytePacketBuilder.t188(
androidId: ByteArray
) {
writeShort(0x188)
writeShortLVPacket {
writeFully(androidId.md5())
} shouldEqualsTo 16
}
internal fun BytePacketBuilder.t193(
ticket: String
) {
writeShort(0x193)
writeShortLVPacket {
writeFully(ticket.toByteArray())
}
}
internal fun BytePacketBuilder.t194(
imsiMd5: ByteArray
) {
imsiMd5 requireSize 16
writeShort(0x194)
writeShortLVPacket {
writeFully(imsiMd5)
} shouldEqualsTo 16
}
internal fun BytePacketBuilder.t191(
K: Int = 0x82
) {
writeShort(0x191)
writeShortLVPacket {
writeByte(K.toByte())
}
}
internal fun BytePacketBuilder.t201(
L: ByteArray = byteArrayOf(), // unknown
channelId: ByteArray = byteArrayOf(),
clientType: ByteArray = "qq".toByteArray(),
N: ByteArray
) {
writeShort(0x201)
writeShortLVPacket {
writeShortLVByteArray(L)
writeShortLVByteArray(channelId)
writeShortLVByteArray(clientType)
writeShortLVByteArray(N)
}
}
internal fun BytePacketBuilder.t202(
wifiBSSID: ByteArray,
wifiSSID: ByteArray
) {
writeShort(0x202)
writeShortLVPacket {
writeShortLVByteArrayLimitedLength(wifiBSSID, 16)
writeShortLVByteArrayLimitedLength(wifiSSID, 32)
}
}
internal fun BytePacketBuilder.t177(
buildTime: Long = 1571193922L, // wtLogin BuildTime
buildVersion: String = "6.0.0.2413" // wtLogin SDK Version
) {
writeShort(0x177)
writeShortLVPacket {
writeByte(1)
writeInt(buildTime.toInt())
writeShortLVString(buildVersion)
} // shouldEqualsTo 0x11
}
internal fun BytePacketBuilder.t516( // 1302
sourceType: Int = 0 // always 0
) {
writeShort(0x516)
writeShortLVPacket {
writeInt(sourceType)
} shouldEqualsTo 4
}
internal fun BytePacketBuilder.t521( // 1313
productType: Int = 0, // coz setProductType is never used
unknown: Short = 0 // const
) {
writeShort(0x521)
writeShortLVPacket {
writeInt(productType)
writeShort(unknown)
} shouldEqualsTo 6
}
internal fun BytePacketBuilder.t52c(
// ?
) {
writeShort(0x52c)
writeShortLVPacket {
writeByte(1)
writeLong(-1)
}
}
internal fun BytePacketBuilder.t536( // 1334
loginExtraData: ByteArray
) {
writeShort(0x536)
writeShortLVPacket {
writeFully(loginExtraData)
}
}
internal fun BytePacketBuilder.t536( // 1334
loginExtraData: Collection
) {
writeShort(0x536)
writeShortLVPacket {
//com.tencent.loginsecsdk.ProtocolDet#packExtraData
writeByte(1) // const
writeByte(loginExtraData.size.toByte()) // data count
for (extraData in loginExtraData) {
writeLoginExtraData(extraData)
}
}
}
internal fun BytePacketBuilder.t525(
loginExtraData: Collection,
) {
writeShort(0x525)
writeShortLVPacket {
writeShort(1)
t536(loginExtraData)
}
}
internal fun BytePacketBuilder.t525(
t536: ByteReadPacket = buildPacket {
t536(buildPacket {
//com.tencent.loginsecsdk.ProtocolDet#packExtraData
writeByte(1) // const
writeByte(0) // data count
}.readBytes())
}
) {
writeShort(0x525)
writeShortLVPacket {
writeShort(1)
writePacket(t536)
}
}
internal fun BytePacketBuilder.t544( // 1334
) {
writeShort(0x544)
writeShortLVPacket {
writeFully(byteArrayOf(0, 0, 0, 11)) // means native throws exception
}
}
internal fun BytePacketBuilder.t318(
tgtQR: ByteArray // unknown
) {
writeShort(0x318)
writeShortLVPacket {
writeFully(tgtQR)
}
}
private inline fun Boolean.toByte(): Byte = if (this) 1 else 0
private inline fun Boolean.toInt(): Int = if (this) 1 else 0
// noinline: wrong exception stacktrace reported
private infix fun Int.shouldEqualsTo(int: Int) = check(this == int) { "Required $int, but found $this" }
private infix fun ByteArray.requireSize(exactSize: Int) =
check(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" }