diff --git a/README.md b/README.md index 1c3119fe2..a7327eaf2 100644 --- a/README.md +++ b/README.md @@ -5,27 +5,30 @@ [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) **[English](README-eng.md)** -**TIM PC 和 QQ Android 协议** 跨平台 QQ 协议支持库. -**纯 Kotlin 实现协议和支持框架. 目前可运行在 JVM 或 Android.** +跨平台 **TIM PC 和 QQ Android** 协议支持库. +纯 Kotlin 实现协议和支持框架,模块全部开源。 +目前可运行在 JVM 或 Android。 **一切开发旨在学习,请勿用于非法用途** -您可在 Gitter 提问, 或加入 QQ 群: 655057127 +加入 Gitter, 或加入 QQ 群: 655057127 ## Update log -在 [Project](https://github.com/mamoe/mirai/projects/1) 查看已支持功能和计划 -在 [UpdateLog](https://github.com/mamoe/mirai/blob/master/UpdateLog.md) 查看版本更新记录 +在 [Project](https://github.com/mamoe/mirai/projects/1) 查看已支持功能和计划(更新不及时) +在 [UpdateLog](https://github.com/mamoe/mirai/blob/master/UpdateLog.md) 查看版本更新记录(准确更新发布的版本) -## Features +## Modules #### mirai-core -通用 API 模块,请参考此模块调用 Mirai. +通用 API 模块,一套 API 适配两套协议。 +**请参考此模块的 API** + #### mirai-core-timpc TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅新增少量 API. 详见 [README.md](mirai-core-timpc/) 支持的功能: - 消息收发:图片文字复合消息,图片消息 - 群管功能:群员列表,禁言 -(目前不再更新,请关注安卓协议) +(目前不再更新此协议,请关注下文的安卓协议) #### mirai-core-qqandroid QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还未完成。 @@ -42,25 +45,31 @@ QQ for Android (8.2.0 版本,2019 年 12 月)协议的实现,目前还 - 进行中 图片上传和下载 ## Use directly -**直接使用Mirai(终端环境/网页面板(将来)).** -[Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动Mirai并获得机器人服务 +**直接使用 Mirai(终端环境/网页面板(将来)).** +[Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动 Mirai 并获得机器人服务 +本模块还未完善。 ## Use as a library -**mirai-core 为独立设计, 可以作为库内置于您的任意 Java/Android 项目中使用.** -Mirai 只上传在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库 +**mirai-core 为独立设计, 可以作为库内置于任意 Java(JVM)/Android 项目中使用.** + +### Gradle +Mirai 只发布在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库: ```kotlin repositories{ jcenter() } ``` -若您需要使用在跨平台项目, 您需要对各个目标平台添加不同的依赖. -**若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在JVM运行则只需要`-jvm`的依赖** +若您需要使用在跨平台项目, 则要对各个目标平台添加不同的依赖,这与 kotlin 相关跨平台库的依赖是类似的。 +**若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在 JVM 运行则只需要`-jvm`的依赖** -您需要将 `VERSION` 替换为最新的版本(如 `0.10.6`): +请将 `VERSION` 替换为最新的版本(如 `0.10.6`): [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) **Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.** -现在 Mirai 只支持 TIM PC 协议. QQ Android 协议正在开发中. +**注意:** +Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。 +只添加 API 模块将无法正常工作。 +现在只推荐使用 TIMPC 协议,请参照下文选择对应目标平台的依赖添加。 **common** ```kotlin @@ -90,7 +99,7 @@ JVM 上需 120M-150M 内存 您的 star 是对我们最大的鼓励(点击项目右上角); ## Wiki -在 [Wiki](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin) 中查看各类帮助,如 API 示例。 +在 [Wiki](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin) 中查看各类帮助,**如 API 示例**(可能过时,待 QQ Android 协议完成后会重写)。 ## Try @@ -110,6 +119,8 @@ bot.subscribeAlways { } ``` +我们也考虑到了 Java 兼容的问题,这正在计划中,但不是高优先的。 + 1. Clone 2. Import as Gradle project 3. 运行 Demo 程序: [mirai-demo](#mirai-demo) 示例和演示程序 @@ -119,6 +130,7 @@ bot.subscribeAlways { - Kotlin 1.3.61 - JDK 8 (required) +- JDK 11(for protocol tools, optional) - Android SDK 29 (for Android target, optional) #### Libraries used @@ -144,5 +156,5 @@ bot.subscribeAlways { - (见 LICENSE 第 4 节) 您可以免费或收费地传递这个项目的源代码或目标代码(即编译结果), **但前提是提供明显的版权声明** (您需要标注本 `GitHub` 项目地址) ## Acknowledgement -特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai) 提供的免费 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai) 等 IDE 授权 +特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai) 为开源项目提供免费的 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai) 等 IDE 的授权 [](https://www.jetbrains.com/?from=mirai) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index 44516ffae..6c925ee99 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -8,14 +8,15 @@ import net.mamoe.mirai.data.ImageLink import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidClient -import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.ImageIdQQA import net.mamoe.mirai.qqandroid.utils.Context +import net.mamoe.mirai.qqandroid.utils.ImageIdQQA import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.MiraiInternalAPI import kotlin.coroutines.CoroutineContext -internal expect class QQAndroidBot( +@UseExperimental(MiraiInternalAPI::class) +internal expect class QQAndroidBot constructor( context: Context, account: BotAccount, configuration: BotConfiguration @@ -28,7 +29,7 @@ internal abstract class QQAndroidBotBase constructor( configuration: BotConfiguration ) : BotImpl(account, configuration) { val client: QQAndroidClient = QQAndroidClient(context, account, bot = @Suppress("LeakingThis") this as QQAndroidBot) - + override val uin: Long get() = client.uin override val qqs: ContactList = ContactList(LockFreeLinkedList()) override fun getQQ(id: Long): QQ { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/ProtoBuf.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/ProtoBuf.kt new file mode 100644 index 000000000..e5ebf2298 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/ProtoBuf.kt @@ -0,0 +1,38 @@ +package net.mamoe.mirai.qqandroid.io + +import kotlinx.io.core.BytePacketBuilder +import kotlinx.io.core.Input +import kotlinx.io.core.readBytes +import kotlinx.io.core.writeFully +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationStrategy + +/** + * 仅有标示作用 + */ +interface ProtoBuf + +fun BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy, v: T) { + this.writeFully(v.toByteArray(serializer)) +} + +/** + * dump + */ +fun T.toByteArray(serializer: SerializationStrategy): ByteArray { + return kotlinx.serialization.protobuf.ProtoBuf.dump(serializer, this) +} + +/** + * load + */ +fun ByteArray.loadAs(deserializer: DeserializationStrategy): T { + return kotlinx.serialization.protobuf.ProtoBuf.load(deserializer, this) +} + +/** + * load + */ +fun Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy): T { + return kotlinx.serialization.protobuf.ProtoBuf.load(serializer, this.readBytes()) +} \ No newline at end of file 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 c4fa4f6fd..9addfcd55 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 @@ -8,7 +8,7 @@ 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.qqandroid.io.ProtoBuf import net.mamoe.mirai.utils.io.readIoBuffer import net.mamoe.mirai.utils.io.readString import net.mamoe.mirai.utils.io.toIoBuffer diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 3551999bc..e19356e81 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -1,8 +1,11 @@ package net.mamoe.mirai.qqandroid.network +import kotlinx.atomicfu.AtomicRef +import kotlinx.atomicfu.atomic import kotlinx.coroutines.* import kotlinx.io.core.* import kotlinx.io.pool.ObjectPool +import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.Packet import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.Cancellable @@ -13,6 +16,8 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent import net.mamoe.mirai.qqandroid.network.protocol.packet.KnownPacketFactories import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory +import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.* import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc @@ -20,6 +25,7 @@ import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.* import kotlin.coroutines.CoroutineContext +@Suppress("MemberVisibilityCanBePrivate") @UseExperimental(MiraiInternalAPI::class) internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler() { override val bot: QQAndroidBot by bot.unsafeWeakRef() @@ -38,9 +44,9 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler when (response) { is UnsafeLogin -> { bot.logger.info("Login unsuccessful, device auth is needed") - bot.logger.info("登陆失败, 原因为非常用设备登陆") + bot.logger.info("登录失败, 原因为非常用设备登录") bot.logger.info("Open the following URL in QQ browser and complete the verification") - bot.logger.info("将下面这个链接在QQ浏览器中打开并完成认证后尝试再次登陆") + bot.logger.info("将下面这个链接在QQ浏览器中打开并完成认证后尝试再次登录") bot.logger.info(response.url) return } @@ -99,10 +105,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler @Suppress("PrivatePropertyName") private val PacketProcessDispatcher = newCoroutineDispatcher(1) + /** + * 缓存超时处理的 [Job]. 超时后将清空缓存, 以免阻碍后续包的处理 + */ + private var cachedPacketTimeoutJob: Job? = null /** * 缓存的包 */ - private var cachedPacket: ByteReadPacket? = null + private val cachedPacket: AtomicRef = atomic(null) /** * 缓存的包还差多少长度 */ @@ -144,31 +154,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler * @param input 一个完整的包的内容, 去掉开头的 int 包长度 */ suspend fun parsePacket(input: Input) { + generifiedParsePacket(input) + } + + private suspend inline fun

generifiedParsePacket(input: Input) { try { - KnownPacketFactories.parseIncomingPacket(bot, input) { packet: Packet, commandName: String, sequenceId: Int -> - if (PacketReceivedEvent(packet).broadcast().cancelled) { - return@parseIncomingPacket - } - - // pass to listeners (attached by sendAndExpect). - packetListeners.forEach { listener -> - if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) { - listener.complete(packet) + KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory

, packet: P, commandName: String, sequenceId: Int -> + handlePacket(packetFactory, packet, commandName, sequenceId) + if (packet is MultiPacket<*>) { + packet.forEach { + handlePacket(null, it, commandName, sequenceId) } } - - // broadcast - if (packet is Subscribable) { - if (packet is BroadcastControllable) { - if (packet.shouldBroadcast) packet.broadcast() - } else { - packet.broadcast() - } - - if (packet is Cancellable && packet.cancelled) return@parseIncomingPacket - } - - bot.logger.info(packet) } } finally { println() @@ -177,57 +174,111 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler } /** - * 处理从服务器接收过来的包. 这些包可能是粘在一起的, 也可能是不完整的. 将会自动处理 + * 处理解析完成的包. */ - @UseExperimental(ExperimentalCoroutinesApi::class) - internal fun processPacket(rawInput: ByteReadPacket): Unit = rawInput.debugPrint("Received").let { input: ByteReadPacket -> - if (input.remaining == 0L) { + suspend fun

handlePacket(packetFactory: PacketFactory

?, packet: P, commandName: String, sequenceId: Int) { + // highest priority: pass to listeners (attached by sendAndExpect). + packetListeners.forEach { listener -> + if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) { + listener.complete(packet) + } + } + + // check top-level cancelling + if (PacketReceivedEvent(packet).broadcast().cancelled) { return } - if (cachedPacket == null) { + + // broadcast + if (packet is Subscribable) { + if (packet is BroadcastControllable) { + if (packet.shouldBroadcast) packet.broadcast() + } else { + packet.broadcast() + } + + if (packet is Cancellable && packet.cancelled) return + } + + bot.logger.info(packet) + + packetFactory?.run { + bot.handle(packet) + } + } + + /** + * 处理从服务器接收过来的包. 这些包可能是粘在一起的, 也可能是不完整的. 将会自动处理. + * 处理后的包会调用 [parsePacketAsync] + */ + @UseExperimental(ExperimentalCoroutinesApi::class) + internal fun processPacket(rawInput: ByteReadPacket) { + if (rawInput.remaining == 0L) { + return + } + + val cache = cachedPacket.value + if (cache == null) { // 没有缓存 - var length: Int = input.readInt() - 4 - if (input.remaining == length.toLong()) { + var length: Int = rawInput.readInt() - 4 + if (rawInput.remaining == length.toLong()) { // 捷径: 当包长度正好, 直接传递剩余数据. - parsePacketAsync(input) + cachedPacketTimeoutJob?.cancel() + parsePacketAsync(rawInput) return } // 循环所有完整的包 - while (input.remaining > length) { - parsePacketAsync(input.readIoBuffer(length)) + while (rawInput.remaining > length) { + parsePacketAsync(rawInput.readIoBuffer(length)) - length = input.readInt() - 4 + length = rawInput.readInt() - 4 } - if (input.remaining != 0L) { + if (rawInput.remaining != 0L) { // 剩余的包长度不够, 缓存后接收下一个包 - expectingRemainingLength = length - input.remaining - cachedPacket = input + expectingRemainingLength = length - rawInput.remaining + cachedPacket.value = rawInput } else { - cachedPacket = null // 表示包长度正好 + cachedPacket.value = null // 表示包长度正好 + cachedPacketTimeoutJob?.cancel() + return } } else { // 有缓存 - if (input.remaining >= expectingRemainingLength) { + if (rawInput.remaining >= expectingRemainingLength) { // 剩余长度够, 连接上去, 处理这个包. parsePacketAsync(buildPacket { - writePacket(cachedPacket!!) - writePacket(input, expectingRemainingLength) + writePacket(cache) + writePacket(rawInput, expectingRemainingLength) }) - cachedPacket = null // 缺少的长度已经给上了. + cachedPacket.value = null // 缺少的长度已经给上了. - if (input.remaining != 0L) { - processPacket(input) // 继续处理剩下内容 + if (rawInput.remaining != 0L) { + return processPacket(rawInput) // 继续处理剩下内容 + } else { + // 处理好了. + cachedPacketTimeoutJob?.cancel() + return } } else { // 剩余不够, 连接上去 - expectingRemainingLength -= input.remaining - cachedPacket = buildPacket { - writePacket(cachedPacket!!) - writePacket(input) + expectingRemainingLength -= rawInput.remaining + // do not inline `packet`. atomicfu unsupported + val packet = buildPacket { + writePacket(cache) + writePacket(rawInput) } + cachedPacket.value = packet + } + } + + cachedPacketTimeoutJob?.cancel() + cachedPacketTimeoutJob = launch { + delay(1000) + if (cachedPacketTimeoutJob == this.coroutineContext[Job] && cachedPacket.getAndSet(null) != null) { + PacketLogger.verbose("等待另一部分包时超时. 将舍弃已接收的半个包") } } } @@ -240,10 +291,13 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler channel.read() } catch (e: ClosedChannelException) { dispose() + bot.tryReinitializeNetworkHandler(e) return } catch (e: ReadPacketInternalException) { bot.logger.error("Socket channel read failed: ${e.message}") - continue + dispose() + bot.tryReinitializeNetworkHandler(e) + return } catch (e: CancellationException) { return } catch (e: Throwable) { @@ -256,19 +310,24 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler } } + /** + * 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms) + */ suspend fun OutgoingPacket.sendAndExpect(): E { val handler = PacketListener(commandName = commandName, sequenceId = sequenceId) packetListeners.addLast(handler) channel.send(delegate) - @Suppress("UNCHECKED_CAST") - return handler.await() as E + return withTimeout(3000) { + @Suppress("UNCHECKED_CAST") + handler.await() as E + } } @PublishedApi internal val packetListeners = LockFreeLinkedList() @PublishedApi - internal inner class PacketListener( + internal inner class PacketListener( // callback val commandName: String, val sequenceId: Int ) : CompletableDeferred by CompletableDeferred(supervisor) { @@ -277,10 +336,5 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler override suspend fun awaitDisconnection() = supervisor.join() - override fun dispose(cause: Throwable?) { - println("Closed") - super.dispose(cause) - } - override val coroutineContext: CoroutineContext = bot.coroutineContext } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index 5915ef359..e5f29721d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -5,6 +5,7 @@ import kotlinx.atomicfu.atomic import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.toByteArray import net.mamoe.mirai.BotAccount +import net.mamoe.mirai.RawAccountIdUse import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger @@ -32,7 +33,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef DOMAINS Pskey: "openmobile.qq.com" */ - +@UseExperimental(MiraiExperimentalAPI::class, MiraiInternalAPI::class) @PublishedApi internal open class QQAndroidClient( context: Context, @@ -53,7 +54,7 @@ internal open class QQAndroidClient( "tgtgtKey" to tgtgtKey, "tgtKey" to wLoginSigInfo.tgtKey, "deviceToken" to wLoginSigInfo.deviceToken, - "shareKeyCalculatedByConstPubKey" to ecdh.keyPair.shareKey + "shareKeyCalculatedByConstPubKey" to ecdh.keyPair.initialShareKey //"t108" to wLoginSigInfo.t1, //"t10c" to t10c, //"t163" to t163 @@ -69,7 +70,6 @@ internal open class QQAndroidClient( return null } - @UseExperimental(MiraiInternalAPI::class) override fun toString(): String { // net.mamoe.mirai.utils.cryptor.ProtoKt.contentToString return "QQAndroidClient(account=$account, ecdh=$ecdh, device=$device, tgtgtKey=${tgtgtKey.contentToString()}, randomKey=${randomKey.contentToString()}, miscBitMap=$miscBitMap, mainSigMap=$mainSigMap, subSigMap=$subSigMap, openAppId=$openAppId, apkVersionName=${apkVersionName.contentToString()}, loginState=$loginState, appClientVersion=$appClientVersion, networkType=$networkType, apkSignatureMd5=${apkSignatureMd5.contentToString()}, protocolVersion=$protocolVersion, apkId=${apkId.contentToString()}, t150=${t150?.contentToString()}, rollbackSig=${rollbackSig?.contentToString()}, ipFromT149=${ipFromT149?.contentToString()}, timeDifference=$timeDifference, uin=$uin, t530=${t530?.contentToString()}, t528=${t528?.contentToString()}, ksid='$ksid', pwdFlag=$pwdFlag, loginExtraData=$loginExtraData, wFastLoginInfo=$wFastLoginInfo, reserveUinInfo=$reserveUinInfo, wLoginSigInfo=$wLoginSigInfo, tlv113=${tlv113?.contentToString()}, qrPushSig=${qrPushSig.contentToString()}, mainDisplayName='$mainDisplayName')" } @@ -95,7 +95,6 @@ internal open class QQAndroidClient( val apkVersionName: ByteArray = "8.2.0".toByteArray() - var loginState = 0 val appClientVersion: Int = 0 @@ -108,14 +107,14 @@ internal open class QQAndroidClient( */ val protocolVersion: Short = 8001 + /* + * 以下登录使用 + */ @Suppress("SpellCheckingInspection") @PublishedApi internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray() - /* - * 以下登录使用 - */ - + var loginState = 0 var t150: Tlv? = null var rollbackSig: ByteArray? = null @@ -129,8 +128,12 @@ internal open class QQAndroidClient( * * **注意**: 总是使用这个属性, 而不要使用 [BotAccount.id]. 将来它可能会变为 [String] */ - @UseExperimental(MiraiExperimentalAPI::class, MiraiInternalAPI::class) - var uin: Long = bot.account.id + val uin: Long get() = _uin + + @UseExperimental(RawAccountIdUse::class) + @Suppress("PropertyName") + internal var _uin: Long = bot.account.id + var t530: ByteArray? = null var t528: ByteArray? = null /** diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/EncryptMethod.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/EncryptMethod.kt index 02bd0fb4f..1a31ed738 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/EncryptMethod.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/EncryptMethod.kt @@ -95,6 +95,6 @@ internal interface EncryptMethodECDH : EncryptMethod { }) // encryptAndWrite("26 33 BA EC 86 EB 79 E6 BC E0 20 06 5E A9 56 6C".hexToBytes(), body) - encryptAndWrite(ecdh.keyPair.shareKey, body) + encryptAndWrite(ecdh.keyPair.initialShareKey, body) } } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt index b184d988d..f2a4d1487 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/OutgoingPacketAndroid.kt @@ -7,8 +7,6 @@ import kotlinx.io.core.buildPacket import kotlinx.io.core.writeFully import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.cryptor.DecrypterByteArray -import net.mamoe.mirai.utils.cryptor.encryptAndWrite import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeIntLVPacket @@ -26,7 +24,7 @@ internal class OutgoingPacket constructor( val delegate: ByteReadPacket ) { val name: String by lazy { - name ?: commandName.toString() + name ?: commandName } } @@ -50,22 +48,13 @@ internal val EMPTY_BYTE_ARRAY = ByteArray(0) * * byte[] body encrypted by 16 zero */ -/* - * 00 00 02 34 // remaining.length + 4 - * 00 00 00 0B - * 01 - * 00 01 4E 73 // sequence - * 00 - * 00 00 00 0E - * 31 39 39 34 37 30 31 30 32 31 - * 18 5D 8F 17 7D 67 71 61 FE DB 30 A4 4D 16 DD 0E 8D 84 0A F2 44 BE FB BB 11 BB B4 AC 79 50 50 9F 4C 99 CC 77 0B AA B6 E0 06 0C F7 91 79 99 57 31 3D EF 38 92 2C C8 81 33 79 83 FF C6 2F BA 18 2A 33 F8 D9 4E CD 62 07 D8 08 B7 1A 1E C7 EB AC AB B4 1E C9 9D A9 15 9C 29 29 2A 99 F6 BB D0 43 65 D6 5E 9C 93 A8 8D 17 08 5B 6A 29 92 58 6A 75 C9 B5 45 B3 0E A5 D3 52 8F 9A A4 88 36 A0 14 3A 21 F2 46 C3 91 66 A3 73 67 6A 3E F7 9D 8E 44 52 87 7B 8A C7 1B E2 D3 98 62 E8 25 30 2A 43 5C 5A B2 C6 45 F5 39 EC 85 81 BF 7D 22 4C E8 01 87 92 48 38 06 6B A0 83 70 0B 51 ED CF 7A FF E2 F2 06 3E A7 95 4E E5 29 23 32 1C FE 79 C6 08 C5 7A 39 B9 AF CD 4F 80 3E 5D 74 4D 0B E1 10 33 8D F0 54 8E 0E 22 96 B4 06 7F 29 01 1E CA 30 35 FD 8A 2E 51 04 20 79 7B 08 DC DF F6 64 21 6B C5 95 34 B3 40 D2 E8 CE BB DC 69 89 75 62 A6 0B 4A 49 9D 90 BA 68 2B BD 8A 50 2D 68 6B 56 40 0C 39 F2 08 20 1B EB A4 A5 20 1D 1F 7E FA 4B B8 2E 58 79 2A 16 54 26 6C C8 44 6C 4F 64 2D 5C 0C 47 2E 90 13 A9 D7 33 4A 51 17 6E 3F 3E 48 AE 39 D8 45 05 2C 0C 3C 9F 92 39 DB 62 B3 BB 64 EE 7E 91 C5 84 92 10 96 D9 F1 13 02 94 00 EA DA 87 7C 85 7B 68 BA 8D A1 AB F5 CD 9C EB 4C CD A0 38 78 43 80 DD E5 1D 28 25 1F F0 25 EF 0D 95 91 0F 21 5D 41 06 00 03 48 77 E0 98 09 3E 04 5A B0 93 63 3B AE 8E 49 0C C2 12 BA DD C3 5A ED FF 68 98 22 C4 5E F6 1E 85 57 15 E8 7E 26 22 E3 70 C2 57 F4 CE 2F CB C4 DC 39 4A 9C FE DE 27 18 D3 36 66 88 92 D7 69 D0 04 8E 93 9B AD E9 2E 5A 2C 91 CD 28 DF BE 62 CF 2C 72 8E FD A9 1F 0E 8E 00 9E 54 28 50 25 0C E7 DC 98 85 C9 B3 59 A8 97 F5 2E 7F 44 4C 43 3C C4 65 E5 AB DB 5B 3C 50 2D 53 B3 EA 74 3C 39 F4 0A 52 31 34 30 F5 E6 82 CD 36 D9 - */ @UseExperimental(MiraiInternalAPI::class) -internal inline fun PacketFactory<*>.buildOutgingPacket( +internal inline fun PacketFactory<*>.buildOutgoingPacket( client: QQAndroidClient, + bodyType: Byte = 1, // 1: PB? name: String? = this.commandName, commandName: String = this.commandName, - key: ByteArray, + key: ByteArray = client.wLoginSigInfo.d2Key, body: BytePacketBuilder.(sequenceId: Int) -> Unit ): OutgoingPacket { val sequenceId: Int = client.nextSsoSequenceId() @@ -73,7 +62,7 @@ internal inline fun PacketFactory<*>.buildOutgingPacket( return OutgoingPacket(name, commandName, sequenceId, buildPacket { writeIntLVPacket(lengthOffset = { it + 4 }) { writeInt(0x0B) - writeByte(1) + writeByte(bodyType) writeInt(sequenceId) writeByte(0) client.uin.toString().let { @@ -87,6 +76,68 @@ internal inline fun PacketFactory<*>.buildOutgingPacket( }) } +/** + * buildOutgoingPacket 与 writeUniPacket 的 fast-path + */ +@UseExperimental(MiraiInternalAPI::class) +internal inline fun PacketFactory<*>.buildOutgoingUniPacket( + client: QQAndroidClient, + bodyType: Byte = 1, // 1: PB? + name: String? = this.commandName, + commandName: String = this.commandName, + key: ByteArray = client.wLoginSigInfo.d2Key, + extraData: ByteReadPacket = BRP_STUB, + body: BytePacketBuilder.(sequenceId: Int) -> Unit +): OutgoingPacket { + val sequenceId: Int = client.nextSsoSequenceId() + + return OutgoingPacket(name, commandName, sequenceId, buildPacket { + writeIntLVPacket(lengthOffset = { it + 4 }) { + writeInt(0x0B) + writeByte(bodyType) + writeInt(sequenceId) + writeByte(0) + client.uin.toString().let { + writeInt(it.length + 4) + writeStringUtf8(it) + } + encryptAndWrite(key) { + writeUniPacket(commandName, extraData) { + body(sequenceId) + } + } + } + }) +} + +@UseExperimental(MiraiInternalAPI::class) +internal inline fun BytePacketBuilder.writeUniPacket( + commandName: String, + extraData: ByteReadPacket = BRP_STUB, + body: BytePacketBuilder.() -> Unit +) { + writeIntLVPacket(lengthOffset = { it + 4 }) { + commandName.let { + writeInt(it.length + 4) + writeStringUtf8(it) + } + + writeInt(4 + 4) + writeInt(45112203) // 02 B0 5B 8B + + if (extraData === BRP_STUB) { + writeInt(0x04) + } else { + writeInt((extraData.remaining + 4).toInt()) + writePacket(extraData) + } + } + + // body + writeIntLVPacket(lengthOffset = { it + 4 }, builder = body) +} + + /** * 最外层的包. 结构适用于登录. * @@ -213,37 +264,6 @@ internal inline fun BytePacketBuilder.writeSsoPacket( } -/** - * Outermost packet, not for login - * - * **Packet structure** - * int remaining.length + 4 - * int 0x0B - * byte 0x01 - * byte 0 - * int [uinAccount].length + 4 - * byte[] uinAccount - * - * byte[] body encrypted by [sessionKey] - */ -internal inline fun PacketFactory<*>.buildSessionOutgoingPacket( - uinAccount: String, - sessionKey: DecrypterByteArray, - body: BytePacketBuilder.() -> Unit -): ByteReadPacket = buildPacket { - writeIntLVPacket(lengthOffset = { it + 4 }) { - writeInt(0x00_00_00_0B) - writeByte(0x01) - - writeByte(0) - - writeInt(uinAccount.length + 4) - writeStringUtf8(uinAccount) - - encryptAndWrite(sessionKey, body) - } -} - /** * Writes a request packet * This is the innermost packet structure @@ -293,6 +313,8 @@ internal fun BytePacketBuilder.writeOicqRequestPacket( writeByte(0x03) // tail // } } + + /* 00 00 01 64 00 00 00 0A 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 019ad2ee6..e95628143 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 @@ -3,18 +3,20 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet import kotlinx.io.core.* import kotlinx.io.pool.useInstance import net.mamoe.mirai.data.Packet +import net.mamoe.mirai.event.Subscribable import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.io.serialization.loadAs -import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc +import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.cryptor.adjustToPublicKey import net.mamoe.mirai.utils.cryptor.decryptBy import net.mamoe.mirai.utils.io.* +import net.mamoe.mirai.utils.unzip import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.jvm.JvmName @@ -26,16 +28,21 @@ import kotlin.jvm.JvmName * @param TPacket 服务器回复包解析结果 */ @UseExperimental(ExperimentalUnsignedTypes::class) -internal abstract class PacketFactory( +internal abstract class PacketFactory( /** * 命令名. 如 `wtlogin.login`, `ConfigPushSvc.PushDomain` */ val commandName: String ) { /** - * **解码**服务器的回复数据包 + * **解码**服务器的回复数据包. 返回的包若是 [Subscribable], 则会 broadcast. */ abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket + + /** + * 可选的处理这个包. 可以在这里面发新的包. + */ + open suspend fun QQAndroidBot.handle(packet: TPacket) {} } @JvmName("decode0") @@ -43,7 +50,7 @@ private suspend inline fun

PacketFactory

.decode(bot: QQAndroidBo internal val DECRYPTER_16_ZERO = ByteArray(16) -internal typealias PacketConsumer = suspend (packet: Packet, commandName: String, ssoSequenceId: Int) -> Unit +internal typealias PacketConsumer = suspend (packetFactory: PacketFactory, packet: T, commandName: String, ssoSequenceId: Int) -> Unit @PublishedApi internal val PacketLogger: MiraiLogger = DefaultLogger("Packet") @@ -53,7 +60,8 @@ internal object KnownPacketFactories : List> by mutableListOf( LoginPacket, StatSvc.Register, OnlinePush.PbPushGroupMsg, - MessageSvc.PushNotify + MessageSvc.PushNotify, + MessageSvc.PbGetMsg ) { fun findPacketFactory(commandName: String): PacketFactory<*>? = this.firstOrNull { it.commandName == commandName } @@ -66,7 +74,8 @@ internal object KnownPacketFactories : List> by mutableListOf( * full packet without length */ // do not inline. Exceptions thrown will not be reported correctly - suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: Input, consumer: PacketConsumer) { + @Suppress("UNCHECKED_CAST") + suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: Input, consumer: PacketConsumer) { rawInput.readBytes().let { PacketLogger.verbose("开始处理包: ${it.toUHexString()}") it.toReadPacket() @@ -111,12 +120,14 @@ internal object KnownPacketFactories : List> by mutableListOf( // 解析外层包装 when (flag1) { 0x0A -> parseSsoFrame(bot, decryptedData) - 0x0B -> parseUniFrame(bot, decryptedData) + 0x0B -> parseSsoFrame(bot, decryptedData) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样. else -> error("unknown flag1: ${flag1.toByte().toUHexString()}") } }?.let { // 处理内层真实的包 if (it.packetFactory == null) { + PacketLogger.warning("找不到 PacketFactory") + PacketLogger.verbose("传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length -> data.toUHexString(length = length) }}") return } @@ -124,12 +135,13 @@ internal object KnownPacketFactories : List> by mutableListOf( 1 ->//it.data.parseUniResponse(bot, it.packetFactory, it.sequenceId, consumer) { consumer( + it.packetFactory as PacketFactory, it.packetFactory.run { decode(bot, it.data) }, it.packetFactory.commandName, it.sequenceId ) } - 2 -> it.data.parseOicqResponse(bot, it.packetFactory, it.sequenceId, consumer) + 2 -> it.data.parseOicqResponse(bot, it.packetFactory as PacketFactory, it.sequenceId, consumer) else -> error("unknown flag2: $flag2. Body to be parsed for inner packet=${it.data.readBytes().toUHexString()}") } } ?: inline { @@ -137,7 +149,6 @@ internal object KnownPacketFactories : List> by mutableListOf( PacketLogger.error("任何key都无法解密: ${data.take(size).toUHexString()}") return } - } } } @@ -173,7 +184,7 @@ internal object KnownPacketFactories : List> by mutableListOf( private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket { val commandName: String val ssoSequenceId: Int - + val dataCompressed: Int // head input.readIoBuffer(input.readInt() - 4).withUse { ssoSequenceId = readInt() @@ -186,35 +197,46 @@ internal object KnownPacketFactories : List> by mutableListOf( val unknown = readBytes(readInt() - 4) if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}") - check(readInt() == 0) + dataCompressed = readInt() + } + + val packet = when (dataCompressed) { + 0 -> input + 1 -> { + input.discardExact(4) + input.useBytes { data, length -> + data.unzip(length = length) + }.toReadPacket() + } + else -> error("unknown dataCompressed flag: $dataCompressed") } // body val packetFactory = findPacketFactory(commandName) - bot.logger.verbose(commandName) - if (packetFactory == null) { - bot.logger.warning("找不到包 PacketFactory") - PacketLogger.verbose("传递给 PacketFactory 的数据 = ${input.readBytes().toUHexString()}") - } - return IncomingPacket(packetFactory, ssoSequenceId, input) + bot.logger.info("Received: $commandName") + return IncomingPacket(packetFactory, ssoSequenceId, packet) } - private suspend fun ByteReadPacket.parseOicqResponse(bot: QQAndroidBot, packetFactory: PacketFactory<*>, ssoSequenceId: Int, consumer: PacketConsumer) { - val qq: Long + private suspend fun ByteReadPacket.parseOicqResponse( + bot: QQAndroidBot, + packetFactory: PacketFactory, + ssoSequenceId: Int, + consumer: PacketConsumer + ) { readIoBuffer(readInt() - 4).withUse { check(readByte().toInt() == 2) this.discardExact(2) // 27 + 2 + body.size this.discardExact(2) // const, =8001 this.readUShort() // commandId this.readShort() // const, =0x0001 - qq = this.readUInt().toLong() + this.readUInt().toLong() // qq val encryptionMethod = this.readUShort().toInt() this.discardExact(1) // const = 0 val packet = when (encryptionMethod) { 4 -> { // peer public key, ECDH - var data = this.decryptBy(bot.client.ecdh.keyPair.shareKey, this.readRemaining - 1) + var data = this.decryptBy(bot.client.ecdh.keyPair.initialShareKey, this.readRemaining - 1) val peerShareKey = bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey()) data = data.decryptBy(peerShareKey) @@ -228,7 +250,7 @@ internal object KnownPacketFactories : List> by mutableListOf( this.readFully(byteArrayBuffer, 0, size) runCatching { - byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.shareKey, size) + byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.initialShareKey, size) }.getOrElse { byteArrayBuffer.decryptBy(bot.client.randomKey, size) } // 这里实际上应该用 privateKey(另一个random出来的key) @@ -243,14 +265,19 @@ internal object KnownPacketFactories : List> by mutableListOf( else -> error("Illegal encryption method. expected 0 or 4, got $encryptionMethod") } - consumer(packet, packetFactory.commandName, ssoSequenceId) + consumer(packetFactory, packet, packetFactory.commandName, ssoSequenceId) } } - private suspend fun ByteReadPacket.parseUniResponse(bot: QQAndroidBot, packetFactory: PacketFactory<*>, ssoSequenceId: Int, consumer: PacketConsumer) { + 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/Cmd0x352Packet.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/Cmd0x352Packet.kt index f08b4a137..ee6eac35f 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/Cmd0x352Packet.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/Cmd0x352Packet.kt @@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable -import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf +import net.mamoe.mirai.qqandroid.io.ProtoBuf @Serializable internal class Cmd0x352Packet( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/ImageRequest.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/ImageRequest.kt index 4fc0c5fa1..ae1e45893 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/ImageRequest.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/ImageRequest.kt @@ -3,7 +3,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY -import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf +import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.utils.currentTimeSeconds interface ImgReq : ProtoBuf diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MessageCommon.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MessageCommon.kt deleted file mode 100644 index 5b63687fb..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MessageCommon.kt +++ /dev/null @@ -1,61 +0,0 @@ -@file:Suppress("ArrayInDataClass") - -package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data - -import kotlinx.serialization.SerialId -import kotlinx.serialization.Serializable -import kotlinx.serialization.protobuf.ProtoNumberType -import kotlinx.serialization.protobuf.ProtoType -import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY -import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf - -class MessageCommon { - - /** - * 1 -> varint - * 2 -> delimi - * 3 -> varint - * 4 -> varint - * 5 -> varint - * 6 -> varint - * 7 -> delimi - * 8 -> delimi - * 9 -> delimi - * 10 -> delimi - * 11 -> delimi - */ - @Serializable - data class PluginInfo( - @SerialId(1) val resId: Int = 0, - @SerialId(2) val packageName: String = "", - @SerialId(3) val newVer: Int = 0, - @SerialId(4) val resType: Int = 0, - @SerialId(5) val lanType: Int = 0, - @SerialId(6) val priority: Int = 0, - @SerialId(7) val resName: String = "", - @SerialId(8) val resDesc: String = "", - @SerialId(9) val resUrlBig: String = "", - @SerialId(10) val resUrlSmall: String = "", - @SerialId(11) val resConf: String = "" - ) : ProtoBuf - - @Serializable - data class AppShareInfo( - @ProtoType(ProtoNumberType.FIXED) @SerialId(1) val id: Int = 0, - @SerialId(2) val cookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val resource: PluginInfo = PluginInfo() - ) : ProtoBuf - - @Serializable - data class ContentHead( - @SerialId(1) val pkgNum: Int = 0, - @SerialId(2) val pkgIndex: Int = 0, - @SerialId(3) val divSeq: Int = 0, - @SerialId(4) val autoReply: Int = 0 - ) : ProtoBuf - - @Serializable - data class Msg( - val s: String - ) : ProtoBuf -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/Msg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/Msg.kt index 908e70c6a..d9236ba06 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/Msg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/Msg.kt @@ -4,8 +4,8 @@ import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumberType import kotlinx.serialization.protobuf.ProtoType +import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY -import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf @Serializable class ImCommon : ProtoBuf { @@ -318,59 +318,59 @@ class ImMsgBody : ProtoBuf { @Serializable class Elem( - @SerialId(1) val text: ImMsgBody.Text? = null, - @SerialId(2) val face: ImMsgBody.Face? = null, - @SerialId(3) val onlineImage: ImMsgBody.OnlineImage? = null, - @SerialId(4) val notOnlineImage: ImMsgBody.NotOnlineImage? = null, - @SerialId(5) val transElemInfo: ImMsgBody.TransElem? = null, - @SerialId(6) val marketFace: ImMsgBody.MarketFace? = null, - @SerialId(7) val elemFlags: ImMsgBody.ElemFlags? = null, - @SerialId(8) val customFace: ImMsgBody.CustomFace? = null, - @SerialId(9) val elemFlags2: ImMsgBody.ElemFlags2? = null, - @SerialId(10) val funFace: ImMsgBody.FunFace? = null, - @SerialId(11) val secretFile: ImMsgBody.SecretFileMsg? = null, - @SerialId(12) val richMsg: ImMsgBody.RichMsg? = null, - @SerialId(13) val groupFile: ImMsgBody.GroupFile? = null, - @SerialId(14) val pubGroup: ImMsgBody.PubGroup? = null, - @SerialId(15) val marketTrans: ImMsgBody.MarketTrans? = null, - @SerialId(16) val extraInfo: ImMsgBody.ExtraInfo? = null, - @SerialId(17) val shakeWindow: ImMsgBody.ShakeWindow? = null, - @SerialId(18) val pubAccount: ImMsgBody.PubAccount? = null, - @SerialId(19) val videoFile: ImMsgBody.VideoFile? = null, - @SerialId(20) val tipsInfo: ImMsgBody.TipsInfo? = null, - @SerialId(21) val anonGroupMsg: ImMsgBody.AnonymousGroupMsg? = null, - @SerialId(22) val qqLiveOld: ImMsgBody.QQLiveOld? = null, - @SerialId(23) val lifeOnline: ImMsgBody.LifeOnlineAccount? = null, - @SerialId(24) val qqwalletMsg: ImMsgBody.QQWalletMsg? = null, - @SerialId(25) val crmElem: ImMsgBody.CrmElem? = null, - @SerialId(26) val conferenceTipsInfo: ImMsgBody.ConferenceTipsInfo? = null, - @SerialId(27) val redbagInfo: ImMsgBody.RedBagInfo? = null, - @SerialId(28) val lowVersionTips: ImMsgBody.LowVersionTips? = null, + @SerialId(1) val text: Text? = null, + @SerialId(2) val face: Face? = null, + @SerialId(3) val onlineImage: OnlineImage? = null, + @SerialId(4) val notOnlineImage: NotOnlineImage? = null, + @SerialId(5) val transElemInfo: TransElem? = null, + @SerialId(6) val marketFace: MarketFace? = null, + @SerialId(7) val elemFlags: ElemFlags? = null, + @SerialId(8) val customFace: CustomFace? = null, + @SerialId(9) val elemFlags2: ElemFlags2? = null, + @SerialId(10) val funFace: FunFace? = null, + @SerialId(11) val secretFile: SecretFileMsg? = null, + @SerialId(12) val richMsg: RichMsg? = null, + @SerialId(13) val groupFile: GroupFile? = null, + @SerialId(14) val pubGroup: PubGroup? = null, + @SerialId(15) val marketTrans: MarketTrans? = null, + @SerialId(16) val extraInfo: ExtraInfo? = null, + @SerialId(17) val shakeWindow: ShakeWindow? = null, + @SerialId(18) val pubAccount: PubAccount? = null, + @SerialId(19) val videoFile: VideoFile? = null, + @SerialId(20) val tipsInfo: TipsInfo? = null, + @SerialId(21) val anonGroupMsg: AnonymousGroupMsg? = null, + @SerialId(22) val qqLiveOld: QQLiveOld? = null, + @SerialId(23) val lifeOnline: LifeOnlineAccount? = null, + @SerialId(24) val qqwalletMsg: QQWalletMsg? = null, + @SerialId(25) val crmElem: CrmElem? = null, + @SerialId(26) val conferenceTipsInfo: ConferenceTipsInfo? = null, + @SerialId(27) val redbagInfo: RedBagInfo? = null, + @SerialId(28) val lowVersionTips: LowVersionTips? = null, @SerialId(29) val bankcodeCtrlInfo: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(30) val nearByMsg: ImMsgBody.NearByMessageType? = null, - @SerialId(31) val customElem: ImMsgBody.CustomElem? = null, - @SerialId(32) val locationInfo: ImMsgBody.LocationInfo? = null, - @SerialId(33) val pubAccInfo: ImMsgBody.PubAccInfo? = null, - @SerialId(34) val smallEmoji: ImMsgBody.SmallEmoji? = null, - @SerialId(35) val fsjMsgElem: ImMsgBody.FSJMessageElem? = null, - @SerialId(36) val arkApp: ImMsgBody.ArkAppElem? = null, - @SerialId(37) val generalFlags: ImMsgBody.GeneralFlags? = null, - @SerialId(38) val hcFlashPic: ImMsgBody.CustomFace? = null, - @SerialId(39) val deliverGiftMsg: ImMsgBody.DeliverGiftMsg? = null, - @SerialId(40) val bitappMsg: ImMsgBody.BitAppMsg? = null, - @SerialId(41) val openQqData: ImMsgBody.OpenQQData? = null, - @SerialId(42) val apolloMsg: ImMsgBody.ApolloActMsg? = null, - @SerialId(43) val groupPubAccInfo: ImMsgBody.GroupPubAccountInfo? = null, - @SerialId(44) val blessMsg: ImMsgBody.BlessingMessage? = null, - @SerialId(45) val srcMsg: ImMsgBody.SourceMsg? = null, - @SerialId(46) val lolaMsg: ImMsgBody.LolaMsg? = null, - @SerialId(47) val groupBusinessMsg: ImMsgBody.GroupBusinessMsg? = null, - @SerialId(48) val msgWorkflowNotify: ImMsgBody.WorkflowNotifyMsg? = null, - @SerialId(49) val patElem: ImMsgBody.PatsElem? = null, - @SerialId(50) val groupPostElem: ImMsgBody.GroupPostElem? = null, - @SerialId(51) val lightApp: ImMsgBody.LightAppElem? = null, - @SerialId(52) val eimInfo: ImMsgBody.EIMInfo? = null, - @SerialId(53) val commonElem: ImMsgBody.CommonElem? = null + @SerialId(30) val nearByMsg: NearByMessageType? = null, + @SerialId(31) val customElem: CustomElem? = null, + @SerialId(32) val locationInfo: LocationInfo? = null, + @SerialId(33) val pubAccInfo: PubAccInfo? = null, + @SerialId(34) val smallEmoji: SmallEmoji? = null, + @SerialId(35) val fsjMsgElem: FSJMessageElem? = null, + @SerialId(36) val arkApp: ArkAppElem? = null, + @SerialId(37) val generalFlags: GeneralFlags? = null, + @SerialId(38) val hcFlashPic: CustomFace? = null, + @SerialId(39) val deliverGiftMsg: DeliverGiftMsg? = null, + @SerialId(40) val bitappMsg: BitAppMsg? = null, + @SerialId(41) val openQqData: OpenQQData? = null, + @SerialId(42) val apolloMsg: ApolloActMsg? = null, + @SerialId(43) val groupPubAccInfo: GroupPubAccountInfo? = null, + @SerialId(44) val blessMsg: BlessingMessage? = null, + @SerialId(45) val srcMsg: SourceMsg? = null, + @SerialId(46) val lolaMsg: LolaMsg? = null, + @SerialId(47) val groupBusinessMsg: GroupBusinessMsg? = null, + @SerialId(48) val msgWorkflowNotify: WorkflowNotifyMsg? = null, + @SerialId(49) val patElem: PatsElem? = null, + @SerialId(50) val groupPostElem: GroupPostElem? = null, + @SerialId(51) val lightApp: LightAppElem? = null, + @SerialId(52) val eimInfo: EIMInfo? = null, + @SerialId(53) val commonElem: CommonElem? = null ) : ProtoBuf @Serializable @@ -387,13 +387,13 @@ class ImMsgBody : ProtoBuf { @SerialId(4) val pttChangeBit: Int = 0, @SerialId(5) val vipStatus: Int = 0, @SerialId(6) val compatibleId: Int = 0, - @SerialId(7) val insts: List? = null, + @SerialId(7) val insts: List? = null, @SerialId(8) val msgRptCnt: Int = 0, - @SerialId(9) val srcInst: ImMsgBody.ElemFlags2.Inst? = null, + @SerialId(9) val srcInst: Inst? = null, @SerialId(10) val longtitude: Int = 0, @SerialId(11) val latitude: Int = 0, @SerialId(12) val customFont: Int = 0, - @SerialId(13) val pcSupportDef: ImMsgBody.PcSupportDef? = null, + @SerialId(13) val pcSupportDef: PcSupportDef? = null, @SerialId(14) val crmFlags: Int = 0 ) : ProtoBuf { @Serializable @@ -433,8 +433,8 @@ class ImMsgBody : ProtoBuf { @Serializable class FunFace( - @SerialId(1) val msgTurntable: ImMsgBody.FunFace.Turntable? = null, - @SerialId(2) val msgBomb: ImMsgBody.FunFace.Bomb? = null + @SerialId(1) val msgTurntable: Turntable? = null, + @SerialId(2) val msgBomb: Bomb? = null ) { @Serializable class Bomb( @@ -580,14 +580,14 @@ class ImMsgBody : ProtoBuf { @Serializable class MsgBody( - @SerialId(1) val richText: ImMsgBody.RichText? = null, + @SerialId(1) val richText: RichText = RichText(), @SerialId(2) val msgContent: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(3) val msgEncryptContent: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class MsgBodySubtype4( - @SerialId(1) val msgNotOnlineFile: ImMsgBody.NotOnlineFile? = null, + @SerialId(1) val msgNotOnlineFile: NotOnlineFile? = null, @SerialId(2) val msgTime: Int = 0 ) : ProtoBuf @@ -622,7 +622,7 @@ class ImMsgBody : ProtoBuf { @Serializable class NotOnlineImage( - @SerialId(1) val filePath: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(1) val filePath: String = "", @SerialId(2) val fileLen: Int = 0, @SerialId(3) val downloadPath: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(4) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, @@ -631,7 +631,7 @@ class ImMsgBody : ProtoBuf { @SerialId(7) val picMd5: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(8) val picHeight: Int = 0, @SerialId(9) val picWidth: Int = 0, - @SerialId(10) val resId: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(10) val resId: String = "", @SerialId(11) val flag: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(12) val thumbUrl: String = "", @SerialId(13) val original: Int = 0, @@ -742,8 +742,8 @@ class ImMsgBody : ProtoBuf { @Serializable class QQWalletAioBody( @SerialId(1) val senduin: Long = 0L, - @SerialId(2) val sender: ImMsgBody.QQWalletAioElem? = null, - @SerialId(3) val receiver: ImMsgBody.QQWalletAioElem? = null, + @SerialId(2) val sender: QQWalletAioElem? = null, + @SerialId(3) val receiver: QQWalletAioElem? = null, @ProtoType(ProtoNumberType.SIGNED) @SerialId(4) val sint32Channelid: Int = 0, @ProtoType(ProtoNumberType.SIGNED) @SerialId(5) val sint32Templateid: Int = 0, @SerialId(6) val resend: Int = 0, @@ -791,7 +791,7 @@ class ImMsgBody : ProtoBuf { @Serializable class QQWalletMsg( - @SerialId(1) val aioBody: ImMsgBody.QQWalletAioBody? = null + @SerialId(1) val aioBody: QQWalletAioBody? = null ) : ProtoBuf @Serializable @@ -811,12 +811,12 @@ class ImMsgBody : ProtoBuf { @Serializable class RichText( - @SerialId(1) val attr: ImMsgBody.Attr? = null, - @SerialId(2) val elems: List? = null, - @SerialId(3) val notOnlineFile: ImMsgBody.NotOnlineFile? = null, - @SerialId(4) val ptt: ImMsgBody.Ptt? = null, - @SerialId(5) val tmpPtt: ImMsgBody.TmpPtt? = null, - @SerialId(6) val trans211TmpMsg: ImMsgBody.Trans211TmpMsg? = null + @SerialId(1) val attr: Attr? = null, + @SerialId(2) val elems: MutableList = mutableListOf(), + @SerialId(3) val notOnlineFile: NotOnlineFile? = null, + @SerialId(4) val ptt: Ptt? = null, + @SerialId(5) val tmpPtt: TmpPtt? = null, + @SerialId(6) val trans211TmpMsg: Trans211TmpMsg? = null ) : ProtoBuf @Serializable @@ -832,8 +832,8 @@ class ImMsgBody : ProtoBuf { @SerialId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(10) val readTimes: Int = 0, @SerialId(11) val leftTime: Int = 0, - @SerialId(12) val notOnlineImage: ImMsgBody.NotOnlineImage? = null, - @SerialId(13) val elemFlags2: ImMsgBody.ElemFlags2? = null, + @SerialId(12) val notOnlineImage: NotOnlineImage? = null, + @SerialId(13) val elemFlags2: ElemFlags2? = null, @SerialId(14) val opertype: Int = 0, @SerialId(15) val fromphonenum: String = "" ) : ProtoBuf @@ -857,7 +857,7 @@ class ImMsgBody : ProtoBuf { @SerialId(2) val senderUin: Long = 0L, @SerialId(3) val time: Int = 0, @SerialId(4) val flag: Int = 0, - @SerialId(5) val elems: List? = null, + @SerialId(5) val elems: List? = null, @SerialId(6) val type: Int = 0, @SerialId(7) val richMsg: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(8) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, @@ -868,7 +868,7 @@ class ImMsgBody : ProtoBuf { @Serializable class Text( - @SerialId(1) val str: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(1) val str: String = "", @SerialId(2) val link: String = "", @SerialId(3) val attr6Buf: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(4) val attr7Buf: ByteArray = EMPTY_BYTE_ARRAY, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgCommon.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgCommon.kt index 6796214c1..1a2185f36 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgCommon.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgCommon.kt @@ -2,8 +2,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable +import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY -import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf /** * msf.msgcomm.msg_comm @@ -60,7 +60,7 @@ class MsgComm : ProtoBuf { @SerialId(1) val groupCode: Long = 0L, @SerialId(2) val groupType: Int = 0, @SerialId(3) val groupInfoSeq: Long = 0L, - @SerialId(4) val groupCard: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(4) val groupCard: String = "", @SerialId(5) val groupRank: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(6) val groupLevel: Int = 0, @SerialId(7) val groupCardType: Int = 0, @@ -69,9 +69,9 @@ class MsgComm : ProtoBuf { @Serializable class Msg( - @SerialId(1) val msgHead: MsgHead? = null, + @SerialId(1) val msgHead: MsgHead, @SerialId(2) val contentHead: ContentHead? = null, - @SerialId(3) val msgBody: ImMsgBody.MsgBody? = null, + @SerialId(3) val msgBody: ImMsgBody.MsgBody, @SerialId(4) val appshareInfo: AppShareInfo? = null ) : ProtoBuf @@ -145,7 +145,7 @@ class MsgComm : ProtoBuf { @SerialId(1) val lastReadTime: Int = 0, @SerialId(2) val peerUin: Long = 0L, @SerialId(3) val msgCompleted: Int = 0, - @SerialId(4) val msg: List? = null, + @SerialId(4) val msg: List, @SerialId(5) val unreadMsgNum: Int = 0, @SerialId(8) val c2cType: Int = 0, @SerialId(9) val serviceType: Int = 0, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgSvc.kt index 29f48e6c7..6840ab328 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgSvc.kt @@ -2,10 +2,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable -import net.mamoe.mirai.data.Packet +import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY -import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory -import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf @Serializable class MsgSvc : ProtoBuf { @@ -26,14 +24,14 @@ class MsgSvc : ProtoBuf { @SerialId(8) val pubaccountCookie: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(9) val isPartialSync: Boolean = false, @SerialId(10) val msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY - ) : ProtoBuf, Packet + ) : ProtoBuf @Serializable class PbGroupMsgWithDrawReq( @SerialId(1) val subCmd: Int = 0, @SerialId(2) val groupType: Int = 0, @SerialId(3) val groupCode: Long = 0L, - @SerialId(4) val msgList: List? = null, + @SerialId(4) val msgList: List? = null, @SerialId(5) val userdef: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf { @Serializable @@ -132,8 +130,8 @@ class MsgSvc : ProtoBuf { @Serializable class PbMsgWithDrawResp( - @SerialId(1) val c2cWithDraw: List? = null, - @SerialId(2) val groupWithDraw: List? = null + @SerialId(1) val c2cWithDraw: List? = null, + @SerialId(2) val groupWithDraw: List? = null ) : ProtoBuf @Serializable @@ -144,8 +142,8 @@ class MsgSvc : ProtoBuf { @Serializable class PbMsgWithDrawReq( - @SerialId(1) val c2cWithDraw: List? = null, - @SerialId(2) val groupWithDraw: List? = null + @SerialId(1) val c2cWithDraw: List? = null, + @SerialId(2) val groupWithDraw: List? = null ) : ProtoBuf @Serializable @@ -206,7 +204,7 @@ class MsgSvc : ProtoBuf { @SerialId(3) val subCmd: Int = 0, @SerialId(4) val groupType: Int = 0, @SerialId(5) val groupCode: Long = 0L, - @SerialId(6) val failedMsgList: List? = null, + @SerialId(6) val failedMsgList: List? = null, @SerialId(7) val userdef: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf { @Serializable @@ -232,7 +230,7 @@ class MsgSvc : ProtoBuf { @Serializable class PbC2CMsgWithDrawReq( - @SerialId(1) val msgInfo: List? = null, + @SerialId(1) val msgInfo: List? = null, @SerialId(2) val longMessageFlag: Int = 0, @SerialId(3) val reserved: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(4) val subCmd: Int = 0 @@ -249,7 +247,7 @@ class MsgSvc : ProtoBuf { @SerialId(8) val pkgIndex: Int = 0, @SerialId(9) val divSeq: Int = 0, @SerialId(10) val msgType: Int = 0, - @SerialId(20) val routingHead: MsgSvc.RoutingHead? = null + @SerialId(20) val routingHead: RoutingHead? = null ) } @@ -276,7 +274,7 @@ class MsgSvc : ProtoBuf { class PbPullGroupMsgSeqResp( @SerialId(1) val result: Int = 0, @SerialId(2) val errmsg: String = "", - @SerialId(3) val groupInfoResp: List? = null + @SerialId(3) val groupInfoResp: List? = null ) : ProtoBuf { @Serializable class GroupInfoResp( @@ -288,17 +286,17 @@ class MsgSvc : ProtoBuf { @Serializable class PbSendMsgReq( - @SerialId(1) val routingHead: MsgSvc.RoutingHead? = null, + @SerialId(1) val routingHead: RoutingHead? = null, @SerialId(2) val contentHead: MsgComm.ContentHead? = null, - @SerialId(3) val msgBody: ImMsgBody.MsgBody? = null, + @SerialId(3) val msgBody: ImMsgBody.MsgBody = ImMsgBody.MsgBody(), @SerialId(4) val msgSeq: Int = 0, @SerialId(5) val msgRand: Int = 0, @SerialId(6) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(7) val appShare: MsgComm.AppShareInfo? = null, @SerialId(8) val msgVia: Int = 0, @SerialId(9) val dataStatist: Int = 0, - @SerialId(10) val multiMsgAssist: MsgSvc.MultiMsgAssist? = null, - @SerialId(11) val inputNotifyInfo: MsgSvc.PbInputNotifyInfo? = null, + @SerialId(10) val multiMsgAssist: MultiMsgAssist? = null, + @SerialId(11) val inputNotifyInfo: PbInputNotifyInfo? = null, @SerialId(12) val msgCtrl: MsgCtrl.MsgCtrl? = null, @SerialId(13) val receiptReq: ImReceipt.ReceiptReq? = null, @SerialId(14) val multiSendSeq: Int = 0 @@ -333,16 +331,16 @@ class MsgSvc : ProtoBuf { @Serializable class PbUnReadMsgSeqResp( - @SerialId(1) val c2cUnreadInfo: MsgSvc.PbC2CUnReadMsgNumResp? = null, - @SerialId(2) val binduinUnreadInfo: List? = null, - @SerialId(3) val groupUnreadInfo: MsgSvc.PbPullGroupMsgSeqResp? = null, - @SerialId(4) val discussUnreadInfo: MsgSvc.PbPullDiscussMsgSeqResp? = null, - @SerialId(5) val thirdqqUnreadInfo: MsgSvc.PbThirdQQUnReadMsgNumResp? = null + @SerialId(1) val c2cUnreadInfo: PbC2CUnReadMsgNumResp? = null, + @SerialId(2) val binduinUnreadInfo: List? = null, + @SerialId(3) val groupUnreadInfo: PbPullGroupMsgSeqResp? = null, + @SerialId(4) val discussUnreadInfo: PbPullDiscussMsgSeqResp? = null, + @SerialId(5) val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumResp? = null ) : ProtoBuf @Serializable class PbDeleteMsgReq( - @SerialId(1) val msgItems: List? = null + @SerialId(1) val msgItems: List? = null ) : ProtoBuf { @Serializable class MsgItem( @@ -357,7 +355,7 @@ class MsgSvc : ProtoBuf { @Serializable class MultiMsgAssist( - @SerialId(1) val repeatedRouting: List? = null, + @SerialId(1) val repeatedRouting: List? = null, @SerialId(2) val msgUse: Int /* enum */ = 1, @SerialId(3) val tempId: Long = 0L, @SerialId(4) val vedioLen: Long = 0L, @@ -369,10 +367,10 @@ class MsgSvc : ProtoBuf { @Serializable class PbMsgReadedReportReq( - @SerialId(1) val grpReadReport: List? = null, - @SerialId(2) val disReadReport: List? = null, - @SerialId(3) val c2cReadReport: MsgSvc.PbC2CReadedReportReq? = null, - @SerialId(4) val bindUinReadReport: MsgSvc.PbBindUinMsgReadedConfirmReq? = null + @SerialId(1) val grpReadReport: List? = null, + @SerialId(2) val disReadReport: List? = null, + @SerialId(3) val c2cReadReport: PbC2CReadedReportReq? = null, + @SerialId(4) val bindUinReadReport: PbBindUinMsgReadedConfirmReq? = null ) : ProtoBuf @Serializable @@ -409,28 +407,28 @@ class MsgSvc : ProtoBuf { @Serializable class RoutingHead( - @SerialId(1) val c2c: MsgSvc.C2C? = null, - @SerialId(2) val grp: MsgSvc.Grp? = null, - @SerialId(3) val grpTmp: MsgSvc.GrpTmp? = null, - @SerialId(4) val dis: MsgSvc.Dis? = null, - @SerialId(5) val disTmp: MsgSvc.DisTmp? = null, - @SerialId(6) val wpaTmp: MsgSvc.WPATmp? = null, - @SerialId(7) val secretFile: MsgSvc.SecretFileHead? = null, - @SerialId(8) val publicPlat: MsgSvc.PublicPlat? = null, - @SerialId(9) val transMsg: MsgSvc.TransMsg? = null, - @SerialId(10) val addressList: MsgSvc.AddressListTmp? = null, - @SerialId(11) val richStatusTmp: MsgSvc.RichStatusTmp? = null, - @SerialId(12) val transCmd: MsgSvc.TransCmd? = null, - @SerialId(13) val accostTmp: MsgSvc.AccostTmp? = null, - @SerialId(14) val pubGroupTmp: MsgSvc.PubGroupTmp? = null, - @SerialId(15) val trans0x211: MsgSvc.Trans0x211? = null, - @SerialId(16) val businessWpaTmp: MsgSvc.BusinessWPATmp? = null, - @SerialId(17) val authTmp: MsgSvc.AuthTmp? = null, - @SerialId(18) val bsnsTmp: MsgSvc.BsnsTmp? = null, - @SerialId(19) val qqQuerybusinessTmp: MsgSvc.QQQueryBusinessTmp? = null, - @SerialId(20) val nearbyDatingTmp: MsgSvc.NearByDatingTmp? = null, - @SerialId(21) val nearbyAssistantTmp: MsgSvc.NearByAssistantTmp? = null, - @SerialId(22) val commTmp: MsgSvc.CommTmp? = null + @SerialId(1) val c2c: C2C? = null, + @SerialId(2) val grp: Grp? = null, + @SerialId(3) val grpTmp: GrpTmp? = null, + @SerialId(4) val dis: Dis? = null, + @SerialId(5) val disTmp: DisTmp? = null, + @SerialId(6) val wpaTmp: WPATmp? = null, + @SerialId(7) val secretFile: SecretFileHead? = null, + @SerialId(8) val publicPlat: PublicPlat? = null, + @SerialId(9) val transMsg: TransMsg? = null, + @SerialId(10) val addressList: AddressListTmp? = null, + @SerialId(11) val richStatusTmp: RichStatusTmp? = null, + @SerialId(12) val transCmd: TransCmd? = null, + @SerialId(13) val accostTmp: AccostTmp? = null, + @SerialId(14) val pubGroupTmp: PubGroupTmp? = null, + @SerialId(15) val trans0x211: Trans0x211? = null, + @SerialId(16) val businessWpaTmp: BusinessWPATmp? = null, + @SerialId(17) val authTmp: AuthTmp? = null, + @SerialId(18) val bsnsTmp: BsnsTmp? = null, + @SerialId(19) val qqQuerybusinessTmp: QQQueryBusinessTmp? = null, + @SerialId(20) val nearbyDatingTmp: NearByDatingTmp? = null, + @SerialId(21) val nearbyAssistantTmp: NearByAssistantTmp? = null, + @SerialId(22) val commTmp: CommTmp? = null ) : ProtoBuf @Serializable @@ -447,9 +445,9 @@ class MsgSvc : ProtoBuf { @SerialId(2) val errmsg: String = "", @SerialId(3) val sendTime: Int = 0, @SerialId(4) val svrbusyWaitTime: Int = 0, - @SerialId(5) val msgSendInfo: MsgSvc.MsgSendInfo? = null, + @SerialId(5) val msgSendInfo: MsgSendInfo? = null, @SerialId(6) val errtype: Int = 0, - @SerialId(7) val transSvrInfo: MsgSvc.TransSvrInfo? = null, + @SerialId(7) val transSvrInfo: TransSvrInfo? = null, @SerialId(8) val receiptResp: ImReceipt.ReceiptResp? = null, @SerialId(9) val textAnalysisResult: Int = 0 ) : ProtoBuf @@ -477,12 +475,12 @@ class MsgSvc : ProtoBuf { class PbC2CMsgWithDrawResp( @SerialId(1) val result: Int = 0, @SerialId(2) val errmsg: String = "", - @SerialId(3) val msgStatus: List? = null, + @SerialId(3) val msgStatus: List? = null, @SerialId(4) val subCmd: Int = 0 ) : ProtoBuf { @Serializable class MsgStatus( - @SerialId(1) val msgInfo: MsgSvc.PbC2CMsgWithDrawReq.MsgInfo? = null, + @SerialId(1) val msgInfo: PbC2CMsgWithDrawReq.MsgInfo? = null, @SerialId(2) val status: Int = 0 ) : ProtoBuf } @@ -515,17 +513,17 @@ class MsgSvc : ProtoBuf { @Serializable class PbMsgReadedReportResp( - @SerialId(1) val grpReadReport: List? = null, - @SerialId(2) val disReadReport: List? = null, - @SerialId(3) val c2cReadReport: MsgSvc.PbC2CReadedReportResp? = null, - @SerialId(4) val bindUinReadReport: MsgSvc.PbBindUinMsgReadedConfirmResp? = null + @SerialId(1) val grpReadReport: List? = null, + @SerialId(2) val disReadReport: List? = null, + @SerialId(3) val c2cReadReport: PbC2CReadedReportResp? = null, + @SerialId(4) val bindUinReadReport: PbBindUinMsgReadedConfirmResp? = null ) : ProtoBuf @Serializable class PbThirdQQUnReadMsgNumResp( @SerialId(1) val result: Int = 0, @SerialId(2) val errmsg: String = "", - @SerialId(3) val thirdqqRespInfo: List? = null, + @SerialId(3) val thirdqqRespInfo: List? = null, @SerialId(4) val interval: Int = 0 ) : ProtoBuf { @Serializable @@ -554,9 +552,9 @@ class MsgSvc : ProtoBuf { @Serializable class PbDelRoamMsgReq( - @SerialId(1) val c2cMsg: MsgSvc.PbDelRoamMsgReq.C2CMsg? = null, - @SerialId(2) val grpMsg: MsgSvc.PbDelRoamMsgReq.GrpMsg? = null, - @SerialId(3) val disMsg: MsgSvc.PbDelRoamMsgReq.DisMsg? = null + @SerialId(1) val c2cMsg: C2CMsg? = null, + @SerialId(2) val grpMsg: GrpMsg? = null, + @SerialId(3) val disMsg: DisMsg? = null ) : ProtoBuf { @Serializable class GrpMsg( @@ -582,18 +580,18 @@ class MsgSvc : ProtoBuf { @Serializable class PbUnReadMsgSeqReq( - @SerialId(1) val c2cUnreadInfo: MsgSvc.PbC2CUnReadMsgNumReq? = null, - @SerialId(2) val binduinUnreadInfo: List? = null, - @SerialId(3) val groupUnreadInfo: MsgSvc.PbPullGroupMsgSeqReq? = null, - @SerialId(4) val discussUnreadInfo: MsgSvc.PbPullDiscussMsgSeqReq? = null, - @SerialId(5) val thirdqqUnreadInfo: MsgSvc.PbThirdQQUnReadMsgNumReq? = null + @SerialId(1) val c2cUnreadInfo: PbC2CUnReadMsgNumReq? = null, + @SerialId(2) val binduinUnreadInfo: List? = null, + @SerialId(3) val groupUnreadInfo: PbPullGroupMsgSeqReq? = null, + @SerialId(4) val discussUnreadInfo: PbPullDiscussMsgSeqReq? = null, + @SerialId(5) val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumReq? = null ) : ProtoBuf @Serializable class PbPullDiscussMsgSeqResp( @SerialId(1) val result: Int = 0, @SerialId(2) val errmsg: String = "", - @SerialId(3) val discussInfoResp: List? = null + @SerialId(3) val discussInfoResp: List? = null ) : ProtoBuf { @Serializable class DiscussInfoResp( @@ -605,7 +603,7 @@ class MsgSvc : ProtoBuf { @Serializable class PbPullDiscussMsgSeqReq( - @SerialId(1) val discussInfoReq: List? = null + @SerialId(1) val discussInfoReq: List? = null ) : ProtoBuf { @Serializable class DiscussInfoReq( @@ -654,7 +652,7 @@ class MsgSvc : ProtoBuf { @Serializable class PbC2CReadedReportReq( @SerialId(1) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val pairInfo: List? = null + @SerialId(2) val pairInfo: List? = null ) : ProtoBuf { @Serializable class UinPairReadInfo( @@ -694,7 +692,7 @@ class MsgSvc : ProtoBuf { @Serializable class PbPullGroupMsgSeqReq( - @SerialId(1) val groupInfoReq: List? = null + @SerialId(1) val groupInfoReq: List? = null ) : ProtoBuf { @Serializable class GroupInfoReq( @@ -731,7 +729,7 @@ class MsgSvc : ProtoBuf { @Serializable class PbThirdQQUnReadMsgNumReq( - @SerialId(1) val thirdqqReqInfo: List? = null, + @SerialId(1) val thirdqqReqInfo: List? = null, @SerialId(2) val source: Int = 0 ) : ProtoBuf { @Serializable @@ -796,7 +794,7 @@ class SubMsgType0xc1 { @SerialId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(10) val readTimes: Int = 0, @SerialId(11) val leftTime: Int = 0, - @SerialId(12) val notOnlineImage: SubMsgType0xc1.NotOnlineImage? = null + @SerialId(12) val notOnlineImage: NotOnlineImage? = null ) : ProtoBuf } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/OnlinePush.kt index a77adcb31..808096850 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/OnlinePush.kt @@ -2,14 +2,14 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable +import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY -import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf @Serializable class MsgOnlinePush { @Serializable - data class PbPushMsg( - @SerialId(1) val msg: MsgComm.Msg? = null, + class PbPushMsg( + @SerialId(1) val msg: MsgComm.Msg, @SerialId(2) val svrip: Int = 0, @SerialId(3) val pushToken: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(4) val pingFlag: Int = 0, 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 234623085..cea4cdc3b 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 @@ -6,8 +6,9 @@ import net.mamoe.mirai.data.Packet import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY +@Suppress("ArrayInDataClass") @Serializable -internal class RequestPushNotify( +internal data class RequestPushNotify( @SerialId(0) val uin: Long = 0L, @SerialId(1) val ctype: Byte = 0, @SerialId(2) val strService: String?, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/GroupImageRequest.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/GroupImageRequest.kt deleted file mode 100644 index c7a18d722..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/GroupImageRequest.kt +++ /dev/null @@ -1,2 +0,0 @@ -package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image - 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 index a4f6ccb86..aab5a6a3f 100644 --- 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 @@ -2,22 +2,25 @@ 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.data.MultiPacket +import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.qqandroid.QQAndroidBot -import net.mamoe.mirai.qqandroid.io.JceStruct +import net.mamoe.mirai.qqandroid.io.readRemainingAsProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.loadAs import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsJceStruct +import net.mamoe.mirai.qqandroid.io.writeProtoBuf import net.mamoe.mirai.qqandroid.network.QQAndroidClient -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.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory +import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify -import net.mamoe.mirai.utils.cryptor.contentToString +import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataVersion2 +import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket +import net.mamoe.mirai.qqandroid.utils.toMessageChain import net.mamoe.mirai.utils.firstValue -import net.mamoe.mirai.utils.io.debugPrint +import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.toReadPacket class MessageSvc { @@ -25,31 +28,64 @@ class MessageSvc { 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 + return readRemainingAsJceStruct(RequestPacket.serializer()).sBuffer .loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue() .toReadPacket().apply { discardExact(1) } - .debugPrint() .readRemainingAsJceStruct(RequestPushNotify.serializer()) + } - println(requestPushNotify.contentToString()) - - with(bot.network) { - GetMsgRequest( - bot.client, - requestPushNotify - ).sendAndExpect() + override suspend fun QQAndroidBot.handle(packet: RequestPushNotify) { + network.run { + PbGetMsg(client, packet).sendAndExpect>() } - - - return requestPushNotify } } + internal object PbGetMsg : PacketFactory>("MessageSvc.PbGetMsg") { + operator fun invoke( + client: QQAndroidClient, + from: RequestPushNotify + ): OutgoingPacket = buildOutgoingUniPacket( + client, + extraData = "08 00 12 33 6D 6F 64 65 6C 3A 78 69 61 6F 6D 69 20 36 3B 6F 73 3A 32 32 3B 76 65 72 73 69 6F 6E 3A 76 32 6D 61 6E 3A 78 69 61 6F 6D 69 73 79 73 3A 4C 4D 59 34 38 5A 18 E4 E1 A4 FF FE 2D 20 E9 E1 A4 FF FE 2D 28 A8 E1 A4 FF FE 2D 30 99 E1 A4 FF FE 2D".hexToBytes().toReadPacket() + ) { + writeProtoBuf( + MsgSvc.PbGetMsgReq.serializer(), + MsgSvc.PbGetMsgReq( + msgReqType = from.ctype.toInt(), + contextFlag = 1, + rambleFlag = 0, + latestRambleNumber = 20, + otherRambleNumber = 3, + onlineSyncFlag = 1, + serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY + ) + ) + } + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): MultiPacket { + // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00 + discardExact(4) + val resp = readRemainingAsProtoBuf(MsgSvc.PbGetMsgResp.serializer()) + //println(resp.contentToString()) + + if (resp.uinPairMsgs == null) { + return MultiPacket(emptyList()) + } + return MultiPacket(resp.uinPairMsgs.asSequence().flatMap { it.msg.asSequence() }.mapNotNull { + when (it.msgHead.msgType) { + 166 -> { + FriendMessage( + bot, + false, // TODO: 2020/1/29 PREVIOUS?? + bot.getQQ(it.msgHead.fromUin), + it.msgBody.richText.toMessageChain() + ) + } + else -> null + } + }.toList()) + } + } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt index 203338c54..ee1fc35f9 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt @@ -7,26 +7,12 @@ import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.contact.MemberPermission -import net.mamoe.mirai.data.ImageLink import net.mamoe.mirai.message.GroupMessage -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.message.data.ImageId -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.toMessage import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgOnlinePush -import net.mamoe.mirai.utils.io.encodeToString - -internal class ImageIdQQA( - override val value: String, - originalLink: String -) : ImageId { - val link: ImageLink = ImageLinkQQA("http://gchat.qpic.cn$originalLink") -} - -internal inline class ImageLinkQQA(override val original: String) : ImageLink +import net.mamoe.mirai.qqandroid.utils.toMessageChain internal class OnlinePush { internal object PbPushGroupMsg : PacketFactory("OnlinePush.PbPushGroupMsg") { @@ -35,27 +21,18 @@ internal class OnlinePush { // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00 discardExact(4) val pbPushMsg = ProtoBuf.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes()) - val message = MessageChain(initialCapacity = pbPushMsg.msg!!.msgBody!!.richText!!.elems!!.size) - var extraInfo: ImMsgBody.ExtraInfo? = null + val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo - pbPushMsg.msg.msgBody!!.richText!!.elems!!.forEach { - when { - it.customFace != null -> message.add(Image(ImageIdQQA(it.customFace.filePath, it.customFace.origUrl))) - it.text != null -> message.add(it.text.str.encodeToString().toMessage()) - it.extraInfo != null -> extraInfo = it.extraInfo - } - } - - val group = bot.getGroup(pbPushMsg.msg.msgHead!!.groupInfo!!.groupCode) + val group = bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) val flags = extraInfo?.flags ?: 0 return GroupMessage( bot = bot, group = group, - senderName = pbPushMsg.msg.msgHead.groupInfo!!.groupCard.encodeToString(), + senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard, sender = group.getMember(pbPushMsg.msg.msgHead.fromUin), - message = message, + message = pbPushMsg.msg.msgBody.richText.toMessageChain(), permission = when { flags and 16 != 0 -> MemberPermission.ADMINISTRATOR flags and 8 != 0 -> MemberPermission.OWNER diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt index b6a456338..a6d8b71dd 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt @@ -46,7 +46,7 @@ internal object LoginPacket : PacketFactory("wt } } - object SubCommand8 { + object SubCommand7 { private const val appId = 16L private const val subAppId = 537062845L @@ -59,26 +59,16 @@ internal object LoginPacket : PacketFactory("wt ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { - writeShort(8) // subCommand - writeShort(6) // count of TLVs, probably ignored by server?TODO + writeShort(7) // subCommand + writeShort(7) // count of TLVs, probably ignored by server?TODO t8(2052) t104(client.t104) t116(150470524, 66560) t174(t174) - t17a(9) - t197(byteArrayOf(0.toByte())) - //t401(md5(client.device.guid + "12 34567890123456".toByteArray() + t402)) - //t19e(0)//==tlv408 + t17c(phoneNumber.toByteArray()) + t401(md5(client.device.guid + "1234567890123456".toByteArray() + t402)) + t19e(0)//==tlv408 } - /** - * - * 0x00000008(8)=00 00 00 00 08 04 00 00,//2052固定 - 0x00000104(260)=41 69 78 39 46 68 4E 44 6C 41 42 30 54 79 46 30 4B 36 67 78 37 45 6E 2B 30 7A 39 35 65 35 30 6E 66 41 3D 3D,//服务器给的tv104 - 0x00000116(278)=00 08 F7 FF 7C 00 01 04 00 01 5F 5E 10 E2//116(this.mMiscBitmap, this.mSubSigMap, var10._sub_appid_list) - 0x00000174(372)=45 66 43 39 46 4B 63 70 47 30 5F 5A 55 41 4F 6A 4E 4C 6F 72 56 30 77 66 4B 67 49 4D 33 33 6E 58 44 37 5F 4B 61 75 56 6D 4F 6F 54 68 6A 64 38 62 72 44 64 69 5F 62 48 51 5A 66 37 6E 4F 6B 78 43 35 6E 47 4E 38 6B 6A 35 39 6D 37 32 71 47 66 78 4E 76 50 51 53 39 33 66 37 6B 72 71 66 71 78 63 5F//服务器给的tv174 - 0x0000017A(378)=00 00 00 09, //9 固定 - 0x00000197(407)=00//固定 - */ } } } 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 7ac9839b5..dee67c23f 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 @@ -7,10 +7,10 @@ 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 -import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket -import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister +import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataStructSvcReqRegister +import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataVersion3 +import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.SvcReqRegister import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket 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/packet/login/data/RequestPacket.kt similarity index 88% rename from mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/RequestPacket.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/data/RequestPacket.kt index 562c2b7c4..b53730888 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/packet/login/data/RequestPacket.kt @@ -1,9 +1,7 @@ -package net.mamoe.mirai.qqandroid.network.protocol.jce +package net.mamoe.mirai.qqandroid.network.protocol.packet.login.data -import kotlinx.serialization.Polymorphic import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY 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/packet/login/data/SvcReqRegister.kt similarity index 96% rename from mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/jce/SvcReqRegister.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/data/SvcReqRegister.kt index 013f7e0b3..34cf297e9 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/packet/login/data/SvcReqRegister.kt @@ -1,4 +1,4 @@ -package net.mamoe.mirai.qqandroid.network.protocol.jce +package net.mamoe.mirai.qqandroid.network.protocol.packet.login.data import kotlinx.serialization.Polymorphic import kotlinx.serialization.SerialId 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 5f82bd34d..89934259c 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,7 +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 +import net.mamoe.mirai.qqandroid.io.ProtoBuf class Oidb0x769 { @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/protobuf/ProtoBuf.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/protobuf/ProtoBuf.kt deleted file mode 100644 index bb876aa6e..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/protobuf/ProtoBuf.kt +++ /dev/null @@ -1,7 +0,0 @@ -package net.mamoe.mirai.qqandroid.network.protocol.protobuf - -/** - * 仅有标示作用 - */ -interface ProtoBuf { -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt new file mode 100644 index 000000000..2ed61c4e4 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/MessageQQA.kt @@ -0,0 +1,60 @@ +package net.mamoe.mirai.qqandroid.utils + +import net.mamoe.mirai.data.ImageLink +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.ImMsgBody +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgSvc + + +internal fun MessageChain.constructPbSendMsgReq(): MsgSvc.PbSendMsgReq { + val request = MsgSvc.PbSendMsgReq() + + this.forEach { + when (it) { + is PlainText -> { + request.msgBody.richText.elems.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue))) + } + is At -> { + + } + } + } + + + return request +} + + +internal fun ImMsgBody.RichText.toMessageChain() : MessageChain{ + val message = MessageChain(initialCapacity = elems.size) + + elems.forEach { + when { + it.notOnlineImage != null -> message.add(Image( + ImageIdQQA( + it.notOnlineImage.resId, + it.notOnlineImage.origUrl + ) + )) + it.customFace != null -> message.add(Image( + ImageIdQQA( + it.customFace.filePath, + it.customFace.origUrl + ) + )) + it.text != null -> message.add(it.text.str.toMessage()) + } + } + + return message +} + +internal class ImageIdQQA( + override val value: String, + originalLink: String +) : ImageId { + val link: ImageLink = + ImageLinkQQA("http://gchat.qpic.cn$originalLink") +} + +internal inline class ImageLinkQQA(override val original: String) : ImageLink \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt new file mode 100644 index 000000000..ed5d20b86 --- /dev/null +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt @@ -0,0 +1,495 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") + +package androidPacketTests + +import kotlinx.io.core.* +import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger +import net.mamoe.mirai.utils.cryptor.* +import net.mamoe.mirai.utils.io.* +import net.mamoe.mirai.utils.io.discardExact +import net.mamoe.mirai.utils.md5 +import kotlin.text.toByteArray + +// sessionTicket = 55 F7 24 8B 04 4E AA A8 98 E6 77 D2 D6 54 A9 B4 43 91 94 A3 0D DA CF 8F E8 94 E0 F4 A2 6B B4 8B 2B 4F 78 8D 21 EE D4 95 A6 F7 A4 3D B5 87 9B 3D +// sessionTicketKey = B6 9D E4 EC 65 38 64 FD C8 3A D8 33 54 35 0C 73 +// randomKey = A4 9A 6A EE 17 5B 7E 3D C0 71 DA 04 1C E1 E4 88 + +// login send 20da22db750806141ef448110800450005aa50ef400080060000c0a8030a71600dd0fe501f908b8c28a908ce7556501001fb487f00000000058c0000000a0200000004000000000e313939343730313032312b87bf2f7c22eb2396f80b887dc6410b85b6f1fc05874d1331d8722ec60d01a47b9ad330945d474934331ec4de12913e927bae5284862329eef2675a122dc83c00407ca3d4e734d772aaae33c4f17b6833a79e12a2ee549a17b08c7e4034874584f3e35e69c55e995e079ed1d0d877be9b1c9a03e62d0f3d714a1506d4a0292a853c6d28b582f5c715f5b6a6d051446b376cd9b55a311b42ba66f20080dc54a429fd240cdc9089534ffc3bcd02ab15f61b86467268e2e743a81f5709c07af6a7b03a90fb1d742988c13fa0b4b946079f9b9b2034fd8a330bc43fe8b2396ec3cac57d6753db28eb3af9e1904e8d4efbe38a354fd1aa71626a8f124498b24d9983d037e5a9f5ad44833df03335e4268cc9489366e793a84f4ed892a7d78345abce6b2888fe081b0317343a7e3bf6dafbe2c1caa0eb956637cf475a16ff1105b10c798a4ee68ea817d6edb0790aba884ced9f638b68cbfeb527d4178769efd533ba032e332f841fc3ede8b50da138470c1118fdb5070eda09215252e7fbca110d6fd4269aca30ed17e642f93caa5b5ef4b1a4abcbb91416e0388f2e625c1a362936360e5935a03a78433a44a56abc936670d44c350a88d0c2e82e092009e6262c1cbd523e6573aa9de5e304b5fc823877fcc4d4c06a08acc62b1b538c9692a70234985347eae2b00956408e2ab1a08a494118f1508364172a0d09eadfcf876855a41894767dd407f75875de1f21f9cdafc6cf8322f62115b721273659995c82987a6e972af56fa0cdd2cde2fe5de5771cafa1db1f70d5c1d70caa9361506533c0495a6f016c0e24fe196ff8a6f288de96255e6e811d6528b26a6c348b99f61b80992033406a05d005c984802ad055360d4e406090b96189c2733436ec3572fe24d6c516214f1a88e46798e2a5244428e61335d6f3f76a8d459715c172016686630d6e20ea6249ea27e4d8d0ae688c79dfc802257614b22f34232eb62d361df629957a05daf743fc8f0e14802035b7534a63628bd10bf128b4ae2ae067a2f6f6f5b62c772ff2afc09a5735e7fd3550886584d99694d001bda4ff945cf0a160b6c21ece0e7fda622ad74d1d15bc6f5173f44d7cbf25d320834e794e7a581350c2330a429a350499335cd1997db163d83de42c3adea77209e4620850900e5dd54a75c393dc89d686ea576fa264d382ead875c30de4462cb32c5f16917d0e2bbad2767a3c4de1a0318a073974f80277b1b9f25af00d0a966511f60de1d4eaa0c576cc27cbc1d910a6dd6c4bfa3ca66bb83cb0f3d791138df8c8afd5d8e8a2635c3d59cbb35d340fb881ad5d57bccbe1042f26ae4dff6b5347938cf5d2cda08f2beef38d666db5333b9168c677acf537b9a37d380ac8c8b4a8da11f5f118316b29f0a465f3616edaa58a0a5360d4b7f503db7685d3b3b702db3b5fb4e1bd8ecd674a1a2d8c85bf0c8c233f3255347991737c2f080f36f3012bfcd9814a205cb5b1ac0d6a99c6f43078d74d4babed46470cde8cf1839f75306d41278020aa94001b9eef595b57a568c03b4f59a5a59d3ac37609d896db7f88aed3a1f3bdc35a7035bcf8249758f41af966e33eb99a33972b5a8f3c1b8e34a27cfb59840a96b944eea462389b5e0c1762d039968bb707874bbbdafa539fbfcfcce58bcfbb913f2cef7a03ace5cdc14c43f195e1494c15cd6af5cc5cb0a13f6e272f65ee3625894c5f950473516c79168944695e295ca3dfd04d1b91ba70eb1c612a570b7bf1ca06c8d83da20c2e442acf5b70b7b3597a802bcf8b024a765f429b964809fcb48f43d727e0606b56fa4545361f743418a042c086f6dbce97ceb5719467dcbbb759e77eebd6c97b9f4ccc6312bbd972215a58ff0a83ff1717430273e11b04751f9beaeca285546ad1d24edc9ff19a9718e4e6eb6ea80e7e16a0518a96059659dc39c50bcbd4aa64f888d3085c7c5899b3a2c2342b598d +// trans emp 1 send 20da22db750806141ef4481108004500038450f2400080060000c0a8030a71600dd0fe501f908b8c2e3508ce7dde50180200465900000000035c0000000a0200000004000000000e31393934373031303231fbb1b5c286bc9eb6a2ae43ce77353dcb0bebf522873dfc23064ea499d060fd11751986d486a6744341c3ff8ee89c30566491a5a449363549f9b917f20e9b19eb04c58d7347e51ec00be55a5e4c2433f4fd981f617726a7f74f66f6b253080103d4754ccd9474a6451123818c94b84a9613875fce9aee86c9f3879df9d0918663ea888389ddb66007827a5bf38c97a7ea6f2ef60468519679c3405444df4a334108f03ca88fdebeb3e3ed39c0b8de6d440469428ceea3fac54ccb4c620d394ec98f94534419f34ec3c2200f6d066cee9d6b3dbc6e46dc313e3863681529f1647bf9d5726747954e3ffa7515105a98bb5a9b17b92a6c56cbcff298d46b653d2f72cbc24165cc0118910aba8c56b1cb6b35b2f7df51f30965bc74cdf422611779e6d82bda537e4790a0acb3b25004fd49cfcae70cc5f52e4c267e1aeb63acf1db34a0f591282024382d99453dee4a75aa6d9e0b69fe42efd1aeb914a4324066aa65037a1c8ca151e562c0bd502f2f5eb80deff7d817ef5cb5a4a03d13f08ce1bfe4485ced084f81376b2fb83f82200725c2a9e5be2f0ebea6b9a68d8ce072c68a62be4eaab170ef94036269267f53f75ed3f6369c80c50ceb9e481c8858e077e16a8d7a80df1406e792a561f635e6a4d5e6662e2422ec88617e350b8686b17ab3c17b6a3b59f9af1519c4c73642e14b9a533073455170daa51fbed6352fa3c957038256079a4395eecc2b6712d0ecdf9a62be9191c2b7cd22dd81c78865ba57626614415f78d8b7812f107acc9110bcff90a376a52e2dd652743770df9eaa9f199bc9e66997fbe121a005c606e9e3855473452379bc4e68f30f3be75f0346c452db79076bd7a47ecc3ad1b8fb2bb796fbf6c3899f1fbc61ee1560d5e9fed4ec157e6e377098e3d7ad43997f342393472e5084b6e4c44be0f2e527f01cb3ea849621ed0108d6109992b08db4d51b13918ca59622f7c0e8389520d79ec96e7818cebd47dd2a700069c32972133cf87083c58571a74c94b2a56c3bbc6f0a94eb95218122e19763deda732536a599138a4bd0b68a59526bba99476c3a5bf065f11b5abe9fd5c74d7bc2b407a362373cb5cf243ae198185b5dc9154d36409153c79097578c8d7c1ae3629dc46c5c9c0302c61c12d05051f82381029e6affe7c65d9b66ec +// GrayUinPro.Check (UniPacket) send 20da22db750806141ef4481108004500018c50f4400080060000c0a8030a71600dd0fe501f908b8c32f508ce7dde5018020044610000000001640000000a0200000004000000000e31393934373031303231f822fc392e93d773a975a2d467d2c40df1021fa5748fd80e8e86af4f4aa9c7745671b903fcb3dea0f314b7e9543b22f02410bd5288fcf358666cb9db4d452cefde2cc9e11b27c7e2ef386a7e8b523af49340e1a9ed10c3a37e6417028f5c019272c7b8e0e1a5af0b27d005c1333777376d960bb41f419842352c2a00e4ede8c642c4f4fd1339d8e81950e9490637cacf42c3ddb5dcb0e987836e77aeb65cf50d6a0867d061b08639f72eafe7b7c5f44240a1e1a9905526bdc6037373bfa20a3fe6d38db3696381831ef1725dfafc5e65b9c1fe77a85080f1a5dfe0c4961d21cd5b70623551b5371f0b4a6d9792d0332b5611cb54e56aa4b99704b34b27a661b7775cc0d16b981c7a7b57283b803b818869d21c91b84ade0ffda282f83bf6619084ef4a17b6301d096211c7bb00768e0d481b11f4907a130f092b4e2fbefdd9570718294c52232eae +// ConfigurationService.ReqGetConfig send 20da22db750806141ef4481108004500017450f7400080060000c0a8030a71600dd0fe501f908b8c345908ce7ff6501801fe444900000000014c0000000a0100000044e0e22a59327abb9ce80cf63be86694210344fab2f2b065d7785a32cacfa4075cd509f49cb37a075ad0685cbd472344bfdef1ede611c0ad81129be9e2d7e476d2000000000e3139393437303130323110192b12a4688c94df8f36df4db7a4f485475e5166166ac2d63858b4d6c0a5748b88373db0353efe12acb0df584dae933d1b6e81f3c19dfeea5e3457db3e82523f71d153dc1e6e14e3f144b4c1fda31aac8321d2d23cad0d4365985e2bcc39e20411a9520dedc8cba4ba1580215e6a27889390a8253aa783315e9ca4dd9f637cd137b4eec24ef8384851b989dfe562e69b9ee519da64abbe9ef25507f42804bd96357a219e62e594f025303dde5901a6b662f704ca3d21e40e2593910315fd3fbb571f513f047ac4b4abf6dea7324459cc6040c6776810bcfd2dd531fc139624394ae11c0143f261ecbbb331e83e1af3 + +// CertifiedAccountSvc.certified_account_read.GetMainPage send 20da22db750806141ef4481108004500025c50fb400080060000c0a8030a71600dd0fe501f908b8c37d908ce913e5018020045310000000002340000000b0100014e74000000000e31393934373031303231c64688d01401061eff81d0ddcdd5a41f2cf87fa1fb2057ef139d96c1e5b27f530e1b7378df9ea59103616c09895434f33523add35ab97703588ea24da0f616cc56a745f76c9071a9408b3d71ff8dbfd7dcfb9b69897c6bf9d42aef2a237f92e0ecf036f9ae20de61d6859d6681d195ccfcaadf6d3eca4211eec7ddb6ee8764ad9061353bf1e1f943a9546a88e4842e2ade0ee6229df968c1d0a119af4bd804af16863152203526dc6057216a676a80c0eb1f678dc2d9d0ab6dd5f6420d67a9889b7559e81a2c1cff31477a9f2dc6ae79c34b6da8564e589e6cb6ed4d201a9e47d85829ec21a5bd34066f8b2bf620dec4ed59dd2268e781cb8d53301e59dccb325a8f21c4e01cd56cf7f06566287afc58a16bd53cb6724f31629a3126a38cb8a29fb58f91fed11d90faacc2607dc853940c9eae2abcb26a6f54d868bbba36bad09a895baaf1915f79ad635a6af00144eac218d0a1664f83a261db7f3500ae85a26deaccc43ee98af0fb2be07b6cdda12931c51fe880e0bfb2f219993e71b4d36f5b22a05648c4ce23d9e767f695e25e6a2a303917d50934f80c1c9dfdfcee585d240269f7145ca4392231b808f8d4836ac56e02d5ece083432ccaf71228ea76a228484f4ec80688a08e452edbb02d033b752c1311bac1132a9bc14e33078b112113d29e305fafa2ac3aaad765420e1e28ff93fbbdf8c94c6d10046f3398d29f06917fcbdcd5c68735ef0e95558312d24df95909f6a7fa4f07 +// x2 + +// OidbSvc.0xcf8 (getShoppingCardInfo)send 20da22db750806141ef4481108004500009450fd400080060000c0a8030a71600dd0fe501f908b8c3b2908ce927e501801fe436900000000006c0000000b0100014e78000000000e31393934373031303231fde67e3ef90dfab4c8706743671f3abf660fc3f77e4efa3fabdeb96e9984107b3f4bf7677b5d34c72e88a6e212af9107e3c52c5f97b7e41c2c3cecfe9301293ab4be504d4d2eca28a816ec514deca805 +// OidbSvc.0x59f (requestIsFirstLogin) send 20da22db750806141ef4481108004500008c50fe400080060000c0a8030a71600dd0fe501f908b8c3b9508ce9366501801fe43610000000000640000000b0100014e7a000000000e313939343730313032319798479335a617d6037b4351bf340716d7095f01a83a19090ced9170a5d2e45578915b186770906f21b623304ee25b7ccb52126e3ab84275d2063f4801b3ea88f4b8fd064be98e75 +// friendlist.GetTroopListReqV2 send 20da22db750806141ef4481108004500011450ff400080060000c0a8030a71600dd0fe501f908b8c3bf908ce963e501801fb43e90000000000ec0000000b0100014e7b000000000e31393934373031303231e92012cde1f02af5088df9ee11e33f90ffc5416dce23cff1193985eb747ea1f453957b000b9f642708e394f590d5c8e7030064ef6f56210330319d4dd4fe21f0463095d74c6d5e21d94fcfb318c58e18d5a83ae548c03827b28c384e9a598f2ec9758ccb313fb825d44b727c10f8929682c5b6d34a8721f06e7ebb6768d39c6e1283b29bbdc15c6f35943fcc26195d1db57ae8de55d96637c7ae4caaa432bcf6768f17a39aa7f49882a86af04876146a2e44c3dcebf3ab106944cf7de19c85111add7b97c8d6bb53f773f11c09525614 +// OidbSvc.0x791_0 (getRedPointInfo) send 20da22db750806141ef448110800450000bc5101400080060000c0a8030a71600dd0fe501f908b8c3ce508ce96a6501801fa43910000000000940000000b0100014e7d000000000e313939343730313032311ca9030fef4676440544c30ee8455143b3d29bf4b96fcb3923e437c30fd64795a325c777e38cd08f173f5dd6814541ca160a2de1ebdb46dcd73386cbf35ee2faae6cdac5c91aa81cf0beca175d7fdee366bacea06859de8b3e163e95d6765256fc523b7e435dc8f73d1a3f5a5c6c793706c88e103a55efa5 +// OidbSvc.0x480_9 (getDetailCardInfo) send 20da22db750806141ef4481108004500008c5102400080060000c0a8030a71600dd0fe501f908b8c3d7908ce97165018020043610000000000640000000b0100014e7e000000000e3139393437303130323183063dc1abd81bf314960d1a00d2a457ff44cb694c4d17d048b888600262a1a957713055cd6e2a68a55d239e1d70d26f9a39bfc61418a8cfbdd96dac2ae1be32e34f94a0271339d8 +// OidbSvc.0x5eb_15 (getHiddenSwitch) send 20da22db750806141ef4481108004500009c5105400080060000c0a8030a71600dd0fe501f908b8c3ddd08ce9e7e501801fe43710000000000740000000b0100014e7f000000000e31393934373031303231e80e3bc84057f0f3cdbfcde52cf82c5c640f69afd3680638b3bbd86bab49ddb8e29e297815f1f41ddfecfe241f76b57f999b02988fe5bb6f92da0693e27e023baea0504b7ca768c541630e636c31a01bdce3df4c27f88906 + +// CliLogSvc.UploadReq send 20da22db750806141ef448110800450001cc5106400080060000c0a8030a71600dd0fe501f908b8c3e5108ce9eee501801fd44a10000000001a40000000b0100014e80000000000e31393934373031303231393e687d1ca6ee2f38f879c89a930bfbd7a9d0d5d6a84e3e76574b272980e8222c35564db3952d09f452bd954248922527ac9aee51a742b3508d50d3794ce81120245bff0a4e3ac74b026fb5444e9e5a5fad8a5c9865e855ede492fa445cc27e48a27f05d81f39e7f9818890717bffeab38df651f21e884cf5a7292c0f94aa3a849cbfdc84bc7e560e371fbbc4d62c1bd134b41f66915272cee5e4094ef47a5641cdb7e19a070a6babd955a02e722b2a5fbad6adc161f58f295d9a6406dd8bd9ac14bfc520e454eb2f0422ae20beaf6789d25ca367cb22fae28488670f5871e4fa7cd8953eb854a4b53cdd8ef0406f5efbd46870afb095efb7b375f956d4f085b6e03c7f7eada6b35d3cdc6e17c2e0169177f6bc24074f1a5fe90ea54cd8af627a00cb78518e6b7f3daab0811fc0872408581f385d25efeecfa4634998ffa81d43868521467262ff08f7fc338fac60499c3452f530bce46272599401fb4cf363ff74e8813c8e12a51b6bfb992e24d3dcf1dfde34af16e6e892f0a3cf146d2d3e444b62432e5e2932 +// CliLogSvc.UploadReq send 20da22db750806141ef448110800450001c45107400080060000c0a8030a71600dd0fe501f908b8c3ff508ce9eee501801fd449900000000019c0000000b0100014e81000000000e3139393437303130323128f6ceffe231f5a913fe9b4c59d24ea84d7dd8a67ef3c802f5eb2705ffeec4e98ddb95ec61e2adb2a52bcf4f744613a956a7af05be228b1dc95d40a0c4e187e79b495946e95bf2ffcabfc550c4f384269f87fea6d550a744a493cc0e11fea05cd7816fcf625b48ee6ed270e13fbacaeaef462a43dd17be5fa587e41e28fa40c083caa7e632cdcdef307404d75c30dce888de061ad715192916ec0b740c5614589b8e06683526042f8b74e896ba1049b71b8deff9354b1b2e83991d7426e2844cc592f511d9d51697361f6ef54370c4043b1bec4d7aa227804dc2b539ef95313b77561faa4d23f7392a2d18f5d796f7768af59d3faa65892169636d9c0e26a7b55be18bcb78719bd0a8ab5fa446f55e515ec5f2d0bfe9e0977ac98fe9af99f0426bc7d65066248d605eefed9b3c42f9d95d578e82515afce2fe239b857619bf64faadee7b10268bd3a079c23a413fffa209fdb9c7d23adab92e4c42fb446add76c24556d2f176f9020183814dfb2fa68861576bfcc41dfdd254d3a63dc8528b95 +// OidbSvc.0x787_11 (getTroopMemberListBy0x787) send 20da22db750806141ef448110800450000a45108400080060000c0a8030a71600dd0fe501f908b8c419108ce9eee501801fd437900000000007c0000000b0100014e84000000000e31393934373031303231de7a3d7936e5679df9be661fdb98fa181243fed121a7c37e0a0a035537b27705ed59f5707bf86495d9b4781418f5c8dc36f561d8b659e2936d4549b55ec6c807650b4e5af7c79fe881ee9cb3df4b4b307748103b3b52eef06aeaf81adb25767c +// OidbSvc.0xaf6_0 (getTroopMemberListForHeadBatch) send 20da22db750806141ef44811080045000094510a400080060000c0a8030a71600dd0fe501f908b8c420d08ce9f86501801fd436900000000006c0000000b0100014e85000000000e313939343730313032311425f3e63a4e0a0454c45b48639abbe10c101b19f0a66f37e774289e87cc3c54def76486925c7291f5d37392120d7e0d30a9caba77f73808b201f49ff2a04a55c5ec502b10a6f861dbffb6eed7324b92 +// OidbSvc.0xdc9 (getHostTroopHonorList) send 20da22db750806141ef448110800450000d4510b400080060000c0a8030a71600dd0fe501f908b8c427908cea04e501801fc43a90000000000ac0000000b0100014e86000000000e31393934373031303231abc2f4d9170210b0504e058282363e45527ce2ec6080f48a99d24ca40171ab146ae451517cc60d98585c746d2d00d434d3b44a9e3c9bcbe0a5ee23d406d8ae9c88b9d51beae9d05a3c22fa67f5a03bea60c17017848d32852b7e6abbe290974f74183d7af2b20d6c43c133b52ec2506f9e752676a40518cf73b922505e1e5497fb58146e526627c8fdd2c8ec4424ff96 +// IncreaseURLSvr.QQHeadUrlReq, IncreaseURLSvr.QQHeadUrlReq send 20da22db750806141ef44811080045000150510c400080060000c0a8030a71600dd0fe501f908b8c432508cea116501801fb44250000000000b40000000b0100014e87000000000e31393934373031303231a3e5c904a29879bfe01d903db3522e51d9d388a6f976387267c78f937ebfb4b800cf847d9af9522bcf8b4e710d905935a904be7e144a8f85db87e784b42bdcc1f19b3dbf307b37d62ba3ee42e6145249161da0a9c608289f139e4f545b9efe40f868a9d7fc06381e59499b04da34e36aac978bb42ad6b93efa55fd63ddc18db1216c68afafc50c954caace2c159cc2ae5cdda25d4bcb5f2b000000740000000b0100014e88000000000e31393934373031303231c571c13b189ca8a129aeecfc734352ff401917c63da218afd73b3f95eaf5121a54331f4ae9534b5c5c5add7307c612fe935cbdfedb775ff1b386adba02b35984aac175015452125c1e93cb333e43c314d53cbb0201ebf0a5 +// OidbSvc.0xc42 send 20da22db750806141ef448110800450004ec510d400080060000c0a8030a71600dd0fe501f908b8c444d08cea3ee5018020047c10000000004c40000000b0100014e89000000000e31393934373031303231e59b7a31945585845fec9d893236808c4868e458601b681c89bdefb2b52d4c2b4f3f3d6e6abe0e0e0fc8df28dccc8733c80a4bb9869cfd7203664227c9c485d1e22d02f7fb24944d70542610a73cdb657fb915b1ded3c1a5a52eece81fddbe4df82302b948c88b51bb4fa1bcc9af6302304ffd89ae1fa47f6b732c84c1deb3d4b1bbd6818359f066be51e868d90bd500bb5b3d65ae0cc11a40c3aa03e3aa1e1144dea014d65d1d1ac82cbf6df2415c4b0629bd07323d61f5735bc768d0bbcc7be7fd66ebd9b65232b5491a7c2b33c1079f0af971c0ff178868361cd0fb40b05e3d421145e43a92696ef394f3fbc9e1ac0c037791aa57ad1c6542195c681eb951e7e59532fc9c1eed68c96e79e720d3057f1d6ae4cf4ef253af35fdb2feadec3bac82091d8146a7022b5f2578e3f126a474e9f1cec9d987b055c9217c003931a5cf03530a0062641e6f4549cedf50ea16c75feb7035a849a3229a3663c135cd8905f17176e878fe29942a55107895041595e5b497ca8796d9489d0682e8823589965240ade202ea0efa9f84f1ebc0e25d33e3e81b3a43d7bdc75005fe8b43d09fe914c6af785ba4ad4cb40cb41f5bc622af6dc1cad1460871b8c7fdec63e08a80536ddf340e907b1eddd8fa90d2323d3e2920c7cd80fd379d633e9f8b8c526f47f9e101d6a1edeccaf15d61c893cc55e9c192cd23b7a417c3175461096146a70a08717a8e654086d8db042b674e72dc54a057a80dd7dd291b85af7d4f7ee0b8dabb8a0e58cafb411f1e501b855a2e6ea6b5a089cb8d066d5d9181d0e16d5389123f96a06d630269efe3727ccc2ca84a4b3dbd1f5d786c3a655e2b3e9311bae60acf3855e55b5e96981295193564ab0172ab8fd6fb4a6c05bff477e7800dcfa6d1e6d2face51219e69987a1a03419ce7d6a43b6216f5f3f02d87b8c9e4faf227f4509427f3b26316f3e996a4a9dae90afc693b3eb359ecd2226087305d77bfe3f6fcbb949061f7a4d1076634144fa1b3f2491ca34d75ee4d3c7a4f4ba17233b7e56bcc8c04044ee5c0d0eed5c53814a6efc0347251e47eaf5981bfc5ccc7b3c2c9e6b2d8b5c1b81f2d79d5134420f5909d0526c5d761b94c0afade2dda634f2d44b911b977630dec1c3f7aa0ed1aea422aebb5c6aef49a9873ce5df751bea10926983c43843fa5adc56077f835e84bc07fd434867e4af0d605c64c729baa77e6a22d5b58d4b9378f518b10f57a61a486a815afb0e3591f85b5c5cdb37f5fba5c89b2638b21c27cc369acd6c995932ead6ceb49fdf6122e2c09b385eb43ffbf2e81acb31057beba0700fb388374ce9fb33794b3991e9b3a18123e732b8d14e5aef01391424200dab855c12b47ade4a2c60b760cd9e3c5400d452fa040ead996a21aad6ce6fac722121f35910b39a3cc8e20543fb2539fe3fe538162f086e1de508f0f9b0cc2414b8af6800ece710f944c95031d2536470ca87083a41641392edbf976413844cd7c7d64a6ed61a2f7ce8e09a6948099d62da1f81b28fcb50424f88b1f0a9b271a7172247ff0313e0dff87ef3b2b4d965852d200e0476fb56aba5a71e5e7a2ad500e62e7f15dc165f0a6b315bffde194822420b846fd18ed2f510eb2610d5d23ccaffbded2c495224c0a215859bf03f0cb1ffc7f3373aff418326c2a98c7a44e8bffe93b +// ConfigPushSvc.GetIpDirect send 20da22db750806141ef44811080045000094510e400080060000c0a8030a71600dd0fe501f908b8c491108cea4b6501801ff436900000000006c0000000b0100014e8a000000000e31393934373031303231831dcd7b8023b2e8434f5897e7a34a13b67bcf4def875c650022a4a6b71417d9151938fe206612ae192f55d174e41aa848a02e51441700440260e2fdaf7882cfc60b2a10946dc2e76c172ddefc309389 +// * friendlist.getFriendGroupList send 20da22db750806141ef448110800450001245111400080060000c0a8030a71600dd0fe501f908b8c497d08ceae5e501801fd43f90000000000fc0000000b0100014ea4000000000e31393934373031303231899eb2e0bc999ad0799e23cd538fcb600850fda949fd86368e3897c1e1b736ce01509901fc190da6c2c0eb00edd851ebad2efac593997488b51ec58d289d3a050ff1a732a616e3b4fffd40b7aca1da2d04dc0c9e1205bcc63c9473d9ed5b4386cf41d4a786f7382f94266b09db3e3993a27bff4685ffb7d9eda7f66fcf592c8c8d593aa8c00e3fae4274371e5c4b4c3c23bdbc4f6a255d1f5b66f4bf2d56798099afbb34507f516d16ae24b344607bd23c899e241048c18b227a6094cbdcb12d2a8a33b443f7a4f26bc9b3f04fd8001daa46302bdeb22bf32bdc72d06c4a7959 +// * StatSvc.GetOnlineStatus send 20da22db750806141ef4481108004500008c5112400080060000c0a8030a71600dd0fe501f908b8c4a7908ceae5e501801fd43610000000000640000000b0100014ea5000000000e31393934373031303231954e34510d4f281e8676c7345c1e8d453c99aeca52c7649053a6230953ac2bca49f6e250394081be6153eceaa531aec179450ec3d671d72251db0c36cff7e1ba3b6de0e6ade53de2 +// * account.RequestQueryQQMobileContactsV3 send 20da22db750806141ef448110800450001445115400080060000c0a8030a71600dd0fe501f908b8c4add08ceba36501801ff441900000000011c0000000b0100014ea9000000000e31393934373031303231a78ed17a376177e537dec3ed98b7e47b51e9be299048820b1bf3ae88d8a18324e6773ef89922a7e7cd46f24d18a6f8aa53472bfeb09000bb7c44ef5f646d8a8f9e0d04accb35183b0216bea7dee1ef8a51207f11a0681fbcfcd09364a21b16bd6974fef7d66c7eca43aea623c499f1c81fb995d11d5490e64f7929861b670e2f24dc975684ea3098f0b96aff986b4e0b6b09a45bf7f2e301fa58ba1b462e755685681c112c3d2ac369d785d56c25c12b1c66bf39396030b374400879c922a7c43d6d36cc40aa8482f079ef48355b9a2562669f5bce3d3ace4f014ce0454b158693f2727833d62d29bdc7884b165b93c57e60d443351e9fff7e093494f0c61918 +// OidbSvc.0x58a (getDiscuss) send 20da22db750806141ef448110800450000945116400080060000c0a8030a71600dd0fe501f908b8c4bf908cebb5e501801fe436900000000006c0000000b0100014eaa000000000e3139393437303130323178d1c8e8c5d15a4dca043edc70fa514b19b92706d26a84f2bdb1b3e478d5ef53748b8d6082dea9a74f1c9f0ab519406c1ef36fcc0502cf201f658ecac1cd1682a34bb1ff42ac64da5f35e3af7fabcc8c +// OidbSvc.0x7c4_0, OidbSvc.0xbe8 send 20da22db750806141ef448110800450001085117400080060000c0a8030a71600dd0fe501f908b8c4c6508cebbe6501801fe43dd00000000006c0000000b0100014eab000000000e31393934373031303231a047827363fff4e4596299c74f6d847be9e8b5c2e4327bdaddffb8dd40e8df0eb76c54a6c7b7a56eddbae7136bb2b076386eb3b51091c46e756d1f9241678b656498da02276578cfb1ebfb6445513f51000000740000000b0100014eac000000000e31393934373031303231bd70b59f03df1144ae0ab45768257763ae622d5517bc20092fffaf4a5a3d55118ef6cd02907467c0eaa4a45309661277166f75e8ec4b75c0ec5fbf313e03b4a667ce26dfb0d148039a4d9ef8be7ad8cdf5e5a810edb60dd4 +// OidbSvc.0x58b_0, CertifiedAccountSvc.certified_account_read.GetFollowList send 20da22db750806141ef448110800450002f05118400080060000c0a8030a71600dd0fe501f908b8c4d4508cebcd6501801fd45c500000000009c0000000b0100014ead000000000e313939343730313032311c087c0ad4a44631d3e9eb9a6ebb4456a003db2e7eaa5e27ffd4c3ee592b8de7b89b9f6a56fc8260e57eef4f3b6616a5255ab830548dd50e9a99cf87011584f3811591c35fc95cadfc364bbeb0ed337063a518aca85fd17c30ab57408389f5c071ed2626ec47b1086478164d0ae0bd0f21222eb7d4b7a5601bc160f99a8f70330000022c0000000b0100014eae000000000e313939343730313032312dd572fad1b71384d3fdb16a1c57f3cbadac7e41ae6de9d13cc2827262e4c5a4bb95829233b72b5f9076b4652c1ffd1ebba874003bcf7e3be86cdc1034ac656efde24be90a49484209c954b78dec531b3d194cc92a297efef340d8273de90a63335bf543109e96a5aa958283161e7e78f50b74b49adb176472ac82fdad36adb68b38421deba0d1c1dcf24f8342bb8d39c1181545a675abb92a3e28431e60a02aaca6c2b4f5ea79c3f4431865444932c9af983f07dd39dd6d81e47f0f59e386e5320051472fe9235f3b98b8850a9335074173730103565ddf81fb57fedb200f328d9ba16d2e52ecf1cc590382087529678501b1ab6b5866afb25b70f0d03b0773f84691e6b94e6d03c3c47f1baa4e17552fafce1bcc90e336ae0d7fca76babcb035378ce191aee9315885197a39301f319857c33cd25a017f6a0480e16030b623547216bafe886f41014f65ae2e6cf3b4c58a4ce96b8f0967c4b0e0be6a9e669c25a06e6b412f115522522af613c6b8fee922d4f3f189d3c2696521afd63fdfc4103bd827a3eb4aba949a6e07424510af29c74e3de4faa1706bf128ac7f9f66306852da2390bb6cb8ed34c885b49d577e22b86222e95142b351dec31690804b98d98baa66b405cb6d649069d9e21254f2f81a6963962955c86e6a74be6c159eb9dce9eff7eb7e0c34f864d826ececbc1e577b4498b403140983fd716ca2b04aa5c23b8c18fe28367bc5ba76081c4aafd3 +// CertifiedAccountSvc.certified_account_read.GetFollowList send 20da22db750806141ef44811080045000274511c400080060000c0a8030a71600dd0fe501f908b8c500d08ceca66501801ff454900000000024c0000000b0100014eaf000000000e313939343730313032318b6198dc33ae901346ecbe82c9821a5e2e7e68d070b2baa3dc8525246a625cb7461bf329a93acc9d20b0221108cd838505d2c0e2c9b944cf7c57eb17b74e74c99c873febfc6e89395b139c033c68d66b731b0ad4d9d7444471b7638628924c3ebb4c2ed326a87b1761d5d5a72043c5e3e538d5797843333867177b5f52445e0b5fa5805cba497e5f0f7da203da3504d74977180e1b86dcdb0c3b7298c4b10d6e53a145c277033e20445a82d0fcc78a1ce597294bf767e43ca42608e7531577d185cc14f91db8f3b71b614f5299ab75c448dd7462a0ce66b1a0992db05ebbab6667ca79ff39d2e9e5f58f73eb2b47cf1896e5560ebe389289b2222687ea8af254b4518ccc6d3468dd03ebce6247e7aac4278ba99210b4c91c49c6f29fd3ecb2653eae174e7bddaf40ef743f8da7605d09d36bca5ef84ee9b75d126db616fbe2b67cdbbb26809d56e97b2e4f6175da52694f0ee640ea1990dc79e5a0c2b266ddc16d0b7bf19818ad7727add2fd2ab379ba1b41763d953d0e21dd26e2f469e8273059f5130355a0976d5a01ca438f3b698b96a2c5394b6ebe381b3818b032ee5226a1a246935a741d39fb84feb917f9680d6c7b2e950e74def6523c318cbbe592904167f2ff8dbaf10616fb2b39a5af68fa19443e874ad2a6bc0639240903fccf8d2eecaacb9404c15ec387faf459f438c229e4a571234403078d270499a2634b13baf5051979ebe31ec8fedb55854e73d58534e51a924c637ee797389de4a27da494dbe7b50c69e1c4857537d3f369752e +// AuthSvr.ThemeAuth send 20da22db750806141ef448110800450000a4511d400080060000c0a8030a71600dd0fe501f908b8c525908ceca66501801ff437900000000007c0000000b0100014eb0000000000e31393934373031303231080fdebf8f489c29fc1aa0611a0ee8dbcf8ada3129852e779569f93003e9af00101b5b5bf3c4a87ec47420f735c83d1029c82729ba3191fac7059dac5122342dc4560637b8ff15bfc84d8e84e8dfbc5dc8de067ae6b902c50c3e5a1915f0753a +// CertifiedAccountSvc.certified_account_read.GetFollowList send 20da22db750806141ef4481108004500027c5120400080060000c0a8030a71600dd0fe501f908b8c52d508ced606501801ff45510000000002540000000b0100014eb1000000000e31393934373031303231379d9ef3c72b17257dadcc13699bb8b027ccdd304c5ec4df57671b991739b43493c60017360ba5f88dd8a8afada19c134c82cf9ae4b5216f2abd4bc30563b981cf22865a55edf3856e46c6a3d7081eed19eb3c4f0a45bfe3120dca2d49e105827873ca33d5c2abcfb89428a5bff03fdb6337d86bff7421ffbc35887803ea9820abb58a0f7ccf607b41e203a8c3902933246a1e3b2f751ff97f8e2cc4661185f8f62f5183306222b8926c00249a80a285d9b43bee57935a825b2b07c1b4850953f04e866006538c8e7202444c25e5d87e6b040de52a7ccfd5cbba56e748b6a1d26d6e14bd4968039ae1c0f3745e7f2d41e1f8b3f46547c0dfcf045cef83bfedc9fc5b59e0adc8ec33d752835f814197e3fd2d5d5d99b20961b33b519e5457c14c84a45d4a5a8cf28ef428b33003a79753c7406181a1ac8de81fdfcac09c6a6265787fc1d27db83d52f5f3676da6589ffc2434465a9363f458b12b56c962eef53d354ed450f0671d5be9df26c6611725840cb5c5bce33ff0bb0ac45cbd1ea735122cb0aa7d489720c31afa83eeef8e2b692e9b83afcfb38700391788335dbc811b43bc018062ff9205273bc8271d2afa958f7f3b7c4077beddc6acb77e1864ff2c614f3af60265e6876a5ecbb95ad78e5f8cf74162a7cf87887db855808181a3cd8d2c363ded3af6105ef8164b408ae7925e5b9311091ceaf8b116be765d29c6d1317c1b4d7febe2e3862ebab3c28eb2ea05587ed09cab27ecaedb38f0e651f2662f605251f90d9b029bd4b617188ab49afe306d1094df0ae0 +// OidbSvc.0x7a2_0 send 20da22db750806141ef448110800450000845123400080060000c0a8030a71600dd0fe501f908b8c552908cedcde50180200435900000000005c0000000b0100014eb6000000000e31393934373031303231d751dc69de793526fd65f55e83c1b8d43435ece7da38acf0b8503d8259484c583dbe89b603a4abc1f1f665d1a92876d711431b5833908d216efd7398286b8adf +// ConfigPushSvc.PushResp, ** StatSvc.register send 20da22db750806141ef448110800450002f85124400080060000c0a8030a71600dd0fe501f908b8c558508cee2605018020045cd0000000000ac0000000b01a4da6eb0000000000e3139393437303130323122465f76192ce454d64e90a36fe651eb96d052893396c002a815851dc04994d16f7a596b06ca02b4743077fb387442f00409806ece8bf2f533a129073db60c8d5a22b9d4224387629e8ce29959a683f505b741aa1a83068239f6df0f97a4e5499c1035092e7466b005307110460211aecd1f11c10cea0cc861007e08882accb0bbaec68cd9c9a83fd19bb5eebda016ff000002240000000a0100000044e0e22a59327abb9ce80cf63be86694210344fab2f2b065d7785a32cacfa4075cd509f49cb37a075ad0685cbd472344bfdef1ede611c0ad81129be9e2d7e476d2000000000e31393934373031303231f4cf6ac405adbbfab64c0905f9f1f4b3236e4b17c34bc8aaefd77b184f012c036978eea06bb7f9e6d443a9d71f6eaf8b08b3c49858269de0405d7acf5076fd3000357cdbced53142200fcd8f87dc2a047ecfdd0d374aa0bb1b5c97b35e59a7cc77f146c930d04be6b70a75be3d71704d1b95794cbc36ef0ffe96533d61521c7b4b676f6b067859c0760ae5689f146b3c1a19668b694a50aa1d561f2feb76b49d507dc530d49007ba08f84297b23290177539d0b2a3ceb0a2ef8a64b65494e574ed78857fc1c93911c7639dce6699d5fde605f432d4ab7dfd6cf8cc5451950b29d79f5e755f90faadf55dfcb3993d9b4fb9479f44e47c1a0702205da298327b0deb180e2976d07be7b6ac0302b128d792200a092e084ca6908fe0c13e142d50f42783009029b3d2da23bd1050a2cc56023f943ce3b164002fa31fdf9624a255455cc77b774223726f36cccb2603240ca528534ee1533eb66872b007ea20bb5e76f87efa35d7dcb4be7e5f410ed7276c09e20f04db63ea4b79fd7d2201ef0ec14b5f9795bd2bd028e58b3ab8d3461a188f113a8b50406e9c3db9f8b2115924faadc9f2707c6715dc2dda73119228079467fbe145cf951cf1948e755aa428f4e478a6ab9708ad39ded4 + + +internal var wtSessionTicketKey = "B6 9D E4 EC 65 38 64 FD C8 3A D8 33 54 35 0C 73".hexToBytes() +internal var tgtgtKey = "D7 71 03 E3 4C E5 8F 6B 05 D8 C7 8C 96 FB FB 23".hexToBytes() +internal var deviceToken = "CE 1E 2E DC 69 24 4F 9B FF 2F 52 D8 8F 69 DD 40".hexToBytes() +internal var D2Key = "44 28 6B 35 7A 54 2D 45 45 5D 56 32 44 33 47 49".hexToBytes() +internal var userStKey = "35 29 42 54 78 62 47 68 5E 77 68 54 6B 76 57 5F".hexToBytes() +internal var tgtKey = "44 24 3F 43 3F 21 37 2B 29 44 6E 47 70 3A 4E 3D".hexToBytes() + +internal val t108 = "BD 12 96 6C 83 53 EF DD 06 16 52 16 B8 1B 25 69".hexToBytes() +internal val t10c = "23 7D 2C 7A 3F 4A 41 35 7D 3B 45 51 6D 3D 2A 56".hexToBytes() +internal val t163 = "2C 7A 7B 23 4E 24 3F 24 24 47 62 6B 69 2E 47 50".hexToBytes() + + +var ecdhPrivateKeyS = "97a52992cb7a2110413629af94a3c249c68a3b731510caa8" + +internal val shareKeyCalculatedByConstPubKey + get() = ECDH.calculateShareKey( + loadPrivateKey(ecdhPrivateKeyS), + initialPublicKey + ) + +var passwordMd5: ByteArray = byteArrayOf() +var uin: Long = 0L + +fun main() { + val data = """ +20da22db750806141ef448110800450000285129400080060000c0a8030a71600dd0fe501f908b8c5bf508cef1de5010020042fd0000 + + + + """.trimIndent() + .trim().split("\n").map { + val bytes = it.trim().autoHexToBytes() + if (bytes[0].toInt() == 0) { + bytes + } else bytes.dropTCPHead() + }.flatMap { it.toList() }.toByteArray() + data.read { decodeMultiClientToServerPackets() } +} + +/** + * 顶层方法. TCP 切掉头后直接来这里 + */ +fun ByteReadPacket.decodeMultiClientToServerPackets() { + println("=======================处理客户端到服务器=======================") + var count = 0 + while (remaining != 0L) { + readBytes((readUInt() - 4u).toInt()).toReadPacket().runCatching { analysisOneFullPacket() }.exceptionOrNull()?.printStackTrace() + count++ + if (remaining != 0L) { + println() + println() + println() + println() + println() + } else DebugLogger.info("=======================共有 $count 个包=======================") + } + println() +} + +fun ByteReadPacket.analysisOneFullPacket(): ByteReadPacket = debugIfFail("Failed", { buildPacket { writeInt(it.size + 4); writeFully(it) } }) { + val flag1 = readInt() + println("flag1=" + flag1.contentToString()) + val flag2 = readByte().toInt() + println("flag2=$flag2") + if (flag1 == 0x0B) { + if (flag2 == 1) { + println("sequenceId = " + readInt().toUHexString()) + } else { + println("extra data=" + readBytes(readInt() - 4).toUHexString()) + } + } else { + //if (flag2 == 1) { + val loginExtraData = readBytes(readInt() - 4) + loginExtraData.debugPrint("loginExtraData") + // } else { + // this.debugPrint() + // error("未知 flag2") + // } + } + + println("flag3=" + readByte().toUHexString()) + println("uin=" + readString(readInt() - 4)) + + println("// 解密 body") + readRemainingBytes().tryDecrypt().toReadPacket().debugPrint("outer body decrypted").apply { + when (flag1) { + 0x0A -> decodeSso() + 0x0B -> decodeUni() + else -> error("unknown flag1: $flag1") + } + + when (flag2) { + + 2 -> { + + this.debugPrint("Oicq Request").apply { + /* + byte 2 // head flag + short 27 + 2 + remaining.length + ushort client.protocolVersion // const 8001 + ushort 0x0001 // const0 + uint client.uin + byte 3 // const1 + ubyte encryptMethod.value // [EncryptMethod] + byte 0 // const2 + int 2 // const3 + int client.appClientVersion + int 0 // const4 + */ + discardExact(3) + readShort().toInt().takeIf { it != 8001 }?.let { + println("这个包不是 oicqRequest") + return@debugIfFail this + println(" got new protocolVersion=$it") + } + val commandId = readUShort().toInt() + println(" commandId=0x${commandId.toShort().toUHexString()}") + readUShort().toInt().takeIf { it != 1 }?.let { + println(" got new const0=$it") + } + println(" uin=${readUInt()}") + readByte().toInt().takeIf { it != 3 }?.let { + println(" got new const1=$it") + } + val encryptionMethod = readUByte().toInt() + readByte().toInt().takeIf { it != 0 }?.let { + println(" got new const2=$it") + } + readInt().takeIf { it != 2 }?.let { + println(" got new const3=$it") + } + readInt().takeIf { it != 0 }?.let { + println(" got new appClientVersion=$it") + } + readInt().takeIf { it != 0 }?.let { + println(" got new const4=$it") + } + + + discardExact(1) + discardExact(1) + val randomKey = readBytes(16) + println("randomKey= ${randomKey.toUHexString()}") + readUShort().toInt().takeIf { it != 258 }?.let { + println(" got new const in ECDH head(originally=258)=$it") + } + val publicKey = readBytes(readShort().toInt()) + println("ecdh publicKey=" + publicKey.toUHexString()) + + + val encrypt = when (encryptionMethod) { + 135, 7 -> { + ECDH.calculateShareKey( + loadPrivateKey(ecdhPrivateKeyS), + //"04cb366698561e936e80c157e074cab13b0bb68ddeb2824548a1b18dd4fb6122afe12fe48c5266d8d7269d7651a8eb6fe7".chunkedHexToBytes().adjustToPublicKey() // QQ: 04cb366698561e936e80c157e074cab13b0bb68ddeb2824548a1b18dd4fb6122afe12fe48c5266d8d7269d7651a8eb6fe7 + publicKey.adjustToPublicKey() + ) + } + + 69 -> { + error("encryptionMethod 69") + } + else -> error("unknown encryptionMethod=$encryptionMethod") + } + + val encryptedBody = readBytes((remaining - 1).toInt()) + + val decrypted = kotlin.runCatching { + encryptedBody.decryptBy(encrypt).also { println("first by calculatedShareKey or sessionKey(method=7)") } + }.getOrElse { + encryptedBody.decryptBy(shareKeyCalculatedByConstPubKey).also { println("first by shareKeyCalculatedByConstPubKey") } + }.let { firstDecrypted -> + runCatching { + firstDecrypted.decryptBy(encrypt).also { println("second by calculatedShareKey") } + }.getOrElse { + kotlin.runCatching { + firstDecrypted.decryptBy(shareKeyCalculatedByConstPubKey) + }.getOrDefault(firstDecrypted) + } + } + + PacketLogger.info("Real body=" + decrypted.toUHexString()) + decrypted.toReadPacket().apply { + if (commandId == 0x0810) { + DebugLogger.info("发送 login!! 正在获取 tgtgtKey") + try { + discardExact(4) + readTLVMap()[0x106] + ?.also { DebugLogger.info("找到了 0x106") } + ?.decryptBy(md5(passwordMd5 + ByteArray(4) + uin.toInt().toByteArray())) + ?.read { + discardExact(2 + 4 * 4 + 8 + 4 + 4 + 1 + 16) + tgtgtKey = readBytes(16) + DebugLogger.info("获取 tgtgtKey=${tgtgtKey.toUHexString()}") + } ?: DebugLogger.info("找不到 0x106") + } catch (e: Exception) { + e.printStackTrace() + } + } + + } + } + } + else -> { + this.debugPrint("uni packet") + } + } + } +} + +fun ByteReadPacket.decodeUni() { + + // 00 00 00 C7 A4 DA 6F A2 20 02 ED BD 20 02 ED BD 01 00 00 00 00 00 00 00 00 00 01 00 00 00 00 4C B8 12 0D E1 DA 19 AF D3 EB 36 76 BD 42 08 F6 DC A5 35 69 C0 8F F2 75 28 B4 CE 09 C9 B7 86 E3 5A 14 D1 0D CA 5D D4 CB 16 77 8B 32 8D 81 3B 3F D9 52 13 77 03 D3 F7 0E CD 7B 21 95 D2 59 CE 0C 31 D6 F1 38 2A FA 82 AD 60 00 00 00 1A 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 50 75 73 68 52 65 73 70 00 00 00 08 02 B0 5B 8B 00 00 00 13 38 35 38 34 31 34 33 36 39 32 31 31 39 39 33 00 00 00 04 00 22 7C 34 35 34 30 30 31 32 32 38 34 33 37 35 39 30 7C 41 38 2E 32 2E 30 2E 32 37 66 36 65 61 39 36 00 00 00 04 00 00 00 5B 10 03 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 08 50 75 73 68 52 65 73 70 7D 00 00 1A 08 00 01 06 08 50 75 73 68 52 65 73 70 1D 00 00 09 0A 10 02 22 14 DA 6F A3 0B 8C 98 0C A8 0C + + // 00 00 00 2A 00 00 00 1A 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 50 75 73 68 52 65 73 70 00 00 00 08 02 B0 5B 8B 00 00 00 04 + // 00 00 00 5B 10 03 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 08 50 75 73 68 52 65 73 70 7D 00 00 1A 08 00 01 06 08 50 75 73 68 52 65 73 70 1D 00 00 09 0A 10 01 22 14 DA 6E B1 0B 8C 98 0C A8 0C + println("// 尝试解 Uni") + println("// head") + //return + readBytes(readInt() - 4).debugPrint("head").toReadPacket().apply { + val commandName = readString(readInt() - 4).also { PacketLogger.warning("commandName=$it") } + println(commandName) + println(" unknown4Bytes=" + readBytes(readInt() - 4).toUHexString()) + // 00 00 00 1A 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 50 75 73 68 52 65 73 70 + // 00 00 00 08 02 B0 5B 8B + // 00 00 00 04 + println(" extraData=" + readBytes(readInt() - 4).toUHexString()) + } + readBytes(readInt() - 4).debugPrint("Real body").read { + // real body + //10 03 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 08 50 75 73 68 52 65 73 70 7D 00 00 1A 08 00 01 06 08 50 75 73 68 52 65 73 70 1D 00 00 09 0A 10 01 22 14 DA 6E B1 0B 8C 98 0C A8 0C + + } +} + +fun ByteReadPacket.decodeSso() { + // 00 00 02 24 + // 00 00 00 0A 01 00 00 00 44 E0 E2 2A 59 32 7A BB 9C E8 0C F6 3B E8 66 94 21 03 44 FA B2 F2 B0 65 D7 78 5A 32 CA CF A4 07 5C D5 09 F4 9C B3 7A 07 5A D0 68 5C BD 47 23 44 BF DE F1 ED E6 11 C0 AD 81 12 9B E9 E2 D7 E4 76 D2 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 F4 CF 6A C4 05 AD BB FA B6 4C 09 05 F9 F1 F4 B3 23 6E 4B 17 C3 4B C8 AA EF D7 7B 18 4F 01 2C 03 69 78 EE A0 6B B7 F9 E6 D4 43 A9 D7 1F 6E AF 8B 08 B3 C4 98 58 26 9D E0 40 5D 7A CF 50 76 FD 30 00 35 7C DB CE D5 31 42 20 0F CD 8F 87 DC 2A 04 7E CF DD 0D 37 4A A0 BB 1B 5C 97 B3 5E 59 A7 CC 77 F1 46 C9 30 D0 4B E6 B7 0A 75 BE 3D 71 70 4D 1B 95 79 4C BC 36 EF 0F FE 96 53 3D 61 52 1C 7B 4B 67 6F 6B 06 78 59 C0 76 0A E5 68 9F 14 6B 3C 1A 19 66 8B 69 4A 50 AA 1D 56 1F 2F EB 76 B4 9D 50 7D C5 30 D4 90 07 BA 08 F8 42 97 B2 32 90 17 75 39 D0 B2 A3 CE B0 A2 EF 8A 64 B6 54 94 E5 74 ED 78 85 7F C1 C9 39 11 C7 63 9D CE 66 99 D5 FD E6 05 F4 32 D4 AB 7D FD 6C F8 CC 54 51 95 0B 29 D7 9F 5E 75 5F 90 FA AD F5 5D FC B3 99 3D 9B 4F B9 47 9F 44 E4 7C 1A 07 02 20 5D A2 98 32 7B 0D EB 18 0E 29 76 D0 7B E7 B6 AC 03 02 B1 28 D7 92 20 0A 09 2E 08 4C A6 90 8F E0 C1 3E 14 2D 50 F4 27 83 00 90 29 B3 D2 DA 23 BD 10 50 A2 CC 56 02 3F 94 3C E3 B1 64 00 2F A3 1F DF 96 24 A2 55 45 5C C7 7B 77 42 23 72 6F 36 CC CB 26 03 24 0C A5 28 53 4E E1 53 3E B6 68 72 B0 07 EA 20 BB 5E 76 F8 7E FA 35 D7 DC B4 BE 7E 5F 41 0E D7 27 6C 09 E2 0F 04 DB 63 EA 4B 79 FD 7D 22 01 EF 0E C1 4B 5F 97 95 BD 2B D0 28 E5 8B 3A B8 D3 46 1A 18 8F 11 3A 8B 50 40 6E 9C 3D B9 F8 B2 11 59 24 FA AD C9 F2 70 7C 67 15 DC 2D DA 73 11 92 28 07 94 67 FB E1 45 CF 95 1C F1 94 8E 75 5A A4 28 F4 E4 78 A6 AB 97 08 AD 39 DE D4 + + // 00 00 00 C1 + // 00 01 4E B9 //sequence + // 20 02 ED BD + // 20 02 ED BD + // 01 00 00 00 00 00 00 00 00 00 01 00 + // + // 00 00 00 4C // extra + // B8 12 0D E1 DA 19 AF D3 EB 36 76 BD 42 08 F6 DC A5 35 69 C0 8F F2 75 28 B4 CE 09 C9 B7 86 E3 5A 14 D1 0D CA 5D D4 CB 16 77 8B 32 8D 81 3B 3F D9 52 13 77 03 D3 F7 0E CD 7B 21 95 D2 59 CE 0C 31 D6 F1 38 2A FA 82 AD 60 + // + // 00 00 00 14 //cmd + // 53 74 61 74 53 76 63 2E 72 65 67 69 73 74 65 72 + // + // 00 00 00 08 + // 02 B0 5B 8B + // + // 00 00 00 13 + // 38 35 38 34 31 34 33 36 39 32 31 31 39 39 33 + // + // 00 00 00 04 + // + // 00 22 7C 34 35 34 30 30 31 32 32 38 34 33 37 35 39 30 7C 41 38 2E 32 2E 30 2E 32 37 66 36 65 61 39 36 + // + // 00 00 00 04 + + // 00 00 00 FD 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 00 CD 08 00 01 06 0E 53 76 63 52 65 71 52 65 67 69 73 74 65 72 1D 00 01 00 B5 0A 02 76 E4 B8 DD 10 07 2C 36 00 40 0B 5C 6C 7C 8C 9C AC B0 19 C0 01 D6 00 EC FD 10 00 00 10 BB 95 0A A0 8F AB 2E 55 38 39 FF 6D 90 40 B3 48 F1 11 08 04 FC 12 F6 13 0D 4F 4E 45 50 4C 55 53 20 41 35 30 30 30 F6 14 0D 4F 4E 45 50 4C 55 53 20 41 35 30 30 30 F6 15 05 37 2E 31 2E 31 F0 16 01 F1 17 06 0F FC 18 FC 1A F3 1B 00 00 00 00 D0 0D 60 71 F6 1C 00 FC 1D F6 1E 0A 5B 75 5D 4F 6E 65 50 6C 75 73 F6 1F 14 3F 4F 4E 45 50 4C 55 53 20 41 35 30 30 30 5F 32 33 5F 31 37 F6 20 00 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 + + + println("// 尝试解 SSO") + println("// head") + discardExact(4) + (" sequenceId=" + readUInt()) + println(" subAppId=" + readUInt()) + println(" subAppId2=" + readUInt()) + println(" unknownHex=" + readBytes(12).toUHexString()) + println(" extraData=" + readBytes(readInt() - 4).toUHexString()) + val commandName = readBytes(readInt() - 4).encodeToString() + PacketLogger.warning(" commandName=$commandName") + (" unknown4Bytes=" + readBytes(readInt() - 4).toUHexString()) + (" imei=" + readBytes(readInt() - 4).toUHexString()) + (" 0 bytes=" + readBytes(readInt() - 4).toUHexString()) + (" ksid=" + readBytes(readShort() - 2).toUHexString()) + (" 0 bytes=" + readBytes(readInt() - 4).toUHexString()) + + println() + discardExact(4) + println("// body(maybe OicqRequest)") +} + +val keys: Map + get() = mapOf( + "16 zero" to ByteArray(16), + "wtSessionTicketKey" to wtSessionTicketKey, + "D2 key" to D2Key, + "tgtgtKey" to tgtgtKey, + "tgtKey" to tgtKey, + "userStKey" to userStKey, + "deviceToken" to deviceToken, + "shareKeyCalculatedByConstPubKey" to shareKeyCalculatedByConstPubKey, + "t108" to t108, + "t10c" to t10c, + "t163" to t163 +) + +fun ByteArray.tryDecrypt(): ByteArray { + return this.tryDecryptOrNull() ?: error("Cannot decrypt. Encrypted data=" + this.toUHexString()) +} + +fun ByteArray.tryDecryptOrNull(): ByteArray? { + keys.forEach { (key, value) -> + kotlin.runCatching { + return decryptBy(value).also { println("outer by $key") } + } + } + return null +} + +fun main1() { + val toUHexString = + "20da22db750806141ef4481108004500012c525d400080060000c0a8030a71600dd0fe501f908b8d83b908d5d6de501803fe44010000000001040000000b01000150ce000000000e31393934373031303231d2d5378a3c47b184e294b2afbf14704d7317bb38be8273dfa287e00a7aba8a8171771de1717fb7c1661d8c3d414f51096ab7b77b8828a65aab7e40259bc8359cc6e23a5f941d700fd7894d416b7a29a270773df81d3265d7d8d16d13429c0c72db48954b66efb9e6e4c13b2c36b0d73fe285c82a8c650f0b1cf1a7c7e11f0c32f50814aa5a43cd8ea88214249763f053794e338d5f1cf81c893b3944cca7635ffcbf8742892da5f4bcb2694954ddaee63fa2a298dc3bd4a22710f2064293c5304ad4faf5baa5b24b56455994ca4c4b1755c723aff08be5dc3a1bb6a72e10bb9ae77054baf54b7091" + .chunkedHexToBytes().drop(16 * 3 + 6).toByteArray().toUHexString() + + println(toUHexString) + println() + println() + + /* + 00 00 01 23 remaining + 4 + + 00 00 00 0A + 02 00 00 00 + 04 00 00 00 + 00 05 + 00 05 + 30 40 3C 5C D4 C3 65 C7 7F A4 40 A3 4F 88 7F D8 56 1C F1 12 EE 3E FC 7E 51 94 F6 D9 2E 01 2D CB BB 1D 7E 3A 01 0E AB 97 FB 55 20 2C 05 82 6D 70 87 33 F0 97 6F B3 04 DC 90 EC C1 D3 C6 C3 66 D8 26 1A B2 08 0B 89 0F 25 AB 8B 91 5C B8 C9 FF A1 DE 43 0F D2 F4 E5 F6 C0 1D DE 65 0B 72 1D 24 D8 7E C0 A6 31 64 71 1C C2 7D 39 93 6B 86 9F 62 2B 76 58 6C 49 5D 60 0B A6 E7 90 AB CB A8 72 E5 3F 6F 25 B2 AD A6 C8 C6 B7 B5 2D 90 19 71 A0 46 57 F4 BD 96 7D E2 EF 86 DA BE B8 F9 EB DA BB D0 B6 F0 73 1C 27 14 DB 3A 66 BF F9 68 CA 4A 7B 4A D2 DF 66 C8 B4 C5 56 93 72 22 D0 38 FD CA 61 74 31 6A C5 3D 0B 3F E2 92 6A 84 16 B3 E5 86 AD D3 87 7C 32 3E 86 DA B4 E7 69 A0 AF A3 C7 97 DF 90 DC 9A 5A 46 5F DA 32 2A 15 21 C6 A0 8C 8D DA AE B2 4D 49 0E 07 05 5F 12 03 1D 0F 5B 53 6A 8E F0 29 78 41 BD 19 AC BB 92 44 D7 2F 7A FB A9 46 39 AF 69 + */ + + // first (cli log) + // 00 00 01 23 + // 00 00 00 0A + // 02 00 00 00 + // 04 00 + // 00 00 00 05 + // 30 + // + // 40 3C 5C D4 C3 65 C7 7F A4 40 A3 4F 88 7F D8 56 1C F1 12 EE 3E FC 7E 51 94 F6 D9 2E 01 2D CB BB 1D 7E 3A 01 0E AB 97 FB 55 20 2C 05 82 6D 70 87 33 F0 97 6F B3 04 DC 90 EC C1 D3 C6 C3 66 D8 26 1A B2 08 0B 89 0F 25 AB 8B 91 5C B8 C9 FF A1 DE 43 0F D2 F4 E5 F6 C0 1D DE 65 0B 72 1D 24 D8 7E C0 A6 31 64 71 1C C2 7D 39 93 6B 86 9F 62 2B 76 58 6C 49 5D 60 0B A6 E7 90 AB CB A8 72 E5 3F 6F 25 B2 AD A6 C8 C6 B7 B5 2D 90 19 71 A0 46 57 F4 BD 96 7D E2 EF 86 DA BE B8 F9 EB DA BB D0 B6 F0 73 1C 27 14 DB 3A 66 BF F9 68 CA 4A 7B 4A D2 DF 66 C8 B4 C5 56 93 72 22 D0 38 FD CA 61 74 31 6A C5 3D 0B 3F E2 92 6A 84 16 B3 E5 86 AD D3 87 7C 32 3E 86 DA B4 E7 69 A0 AF A3 C7 97 DF 90 DC 9A 5A 46 5F DA 32 2A 15 21 C6 A0 8C 8D DA AE B2 4D 49 0E 07 05 5F 12 03 1D 0F 5B 53 6A 8E F0 29 78 41 BD 19 AC BB 92 44 D7 2F 7A FB A9 46 39 AF 69 + // + + + // second, cli log + + + // third, longest + + + // full trans_emp packet: + + /* + 00 00 03 5C // =860 + 00 00 00 0A + 02 + 00 00 00 04 extra data length + 00 + + 00 00 00 0E + 31 39 39 34 37 30 31 30 32 31 // uin + + // encrypted by 16 zero + FB B1 B5 C2 86 BC 9E B6 A2 AE 43 CE 77 35 3D CB 0B EB F5 22 87 3D FC 23 06 4E A4 99 D0 60 FD 11 75 19 86 D4 86 A6 74 43 41 C3 FF 8E E8 9C 30 56 64 91 A5 A4 49 36 35 49 F9 B9 17 F2 0E 9B 19 EB 04 C5 8D 73 47 E5 1E C0 0B E5 5A 5E 4C 24 33 F4 FD 98 1F 61 77 26 A7 F7 4F 66 F6 B2 53 08 01 03 D4 75 4C CD 94 74 A6 45 11 23 81 8C 94 B8 4A 96 13 87 5F CE 9A EE 86 C9 F3 87 9D F9 D0 91 86 63 EA 88 83 89 DD B6 60 07 82 7A 5B F3 8C 97 A7 EA 6F 2E F6 04 68 51 96 79 C3 40 54 44 DF 4A 33 41 08 F0 3C A8 8F DE BE B3 E3 ED 39 C0 B8 DE 6D 44 04 69 42 8C EE A3 FA C5 4C CB 4C 62 0D 39 4E C9 8F 94 53 44 19 F3 4E C3 C2 20 0F 6D 06 6C EE 9D 6B 3D BC 6E 46 DC 31 3E 38 63 68 15 29 F1 64 7B F9 D5 72 67 47 95 4E 3F FA 75 15 10 5A 98 BB 5A 9B 17 B9 2A 6C 56 CB CF F2 98 D4 6B 65 3D 2F 72 CB C2 41 65 CC 01 18 91 0A BA 8C 56 B1 CB 6B 35 B2 F7 DF 51 F3 09 65 BC 74 CD F4 22 61 17 79 E6 D8 2B DA 53 7E 47 90 A0 AC B3 B2 50 04 FD 49 CF CA E7 0C C5 F5 2E 4C 26 7E 1A EB 63 AC F1 DB 34 A0 F5 91 28 20 24 38 2D 99 45 3D EE 4A 75 AA 6D 9E 0B 69 FE 42 EF D1 AE B9 14 A4 32 40 66 AA 65 03 7A 1C 8C A1 51 E5 62 C0 BD 50 2F 2F 5E B8 0D EF F7 D8 17 EF 5C B5 A4 A0 3D 13 F0 8C E1 BF E4 48 5C ED 08 4F 81 37 6B 2F B8 3F 82 20 07 25 C2 A9 E5 BE 2F 0E BE A6 B9 A6 8D 8C E0 72 C6 8A 62 BE 4E AA B1 70 EF 94 03 62 69 26 7F 53 F7 5E D3 F6 36 9C 80 C5 0C EB 9E 48 1C 88 58 E0 77 E1 6A 8D 7A 80 DF 14 06 E7 92 A5 61 F6 35 E6 A4 D5 E6 66 2E 24 22 EC 88 61 7E 35 0B 86 86 B1 7A B3 C1 7B 6A 3B 59 F9 AF 15 19 C4 C7 36 42 E1 4B 9A 53 30 73 45 51 70 DA A5 1F BE D6 35 2F A3 C9 57 03 82 56 07 9A 43 95 EE CC 2B 67 12 D0 EC DF 9A 62 BE 91 91 C2 B7 CD 22 DD 81 C7 88 65 BA 57 62 66 14 41 5F 78 D8 B7 81 2F 10 7A CC 91 10 BC FF 90 A3 76 A5 2E 2D D6 52 74 37 70 DF 9E AA 9F 19 9B C9 E6 69 97 FB E1 21 A0 05 C6 06 E9 E3 85 54 73 45 23 79 BC 4E 68 F3 0F 3B E7 5F 03 46 C4 52 DB 79 07 6B D7 A4 7E CC 3A D1 B8 FB 2B B7 96 FB F6 C3 89 9F 1F BC 61 EE 15 60 D5 E9 FE D4 EC 15 7E 6E 37 70 98 E3 D7 AD 43 99 7F 34 23 93 47 2E 50 84 B6 E4 C4 4B E0 F2 E5 27 F0 1C B3 EA 84 96 21 ED 01 08 D6 10 99 92 B0 8D B4 D5 1B 13 91 8C A5 96 22 F7 C0 E8 38 95 20 D7 9E C9 6E 78 18 CE BD 47 DD 2A 70 00 69 C3 29 72 13 3C F8 70 83 C5 85 71 A7 4C 94 B2 A5 6C 3B BC 6F 0A 94 EB 95 21 81 22 E1 97 63 DE DA 73 25 36 A5 99 13 8A 4B D0 B6 8A 59 52 6B BA 99 47 6C 3A 5B F0 65 F1 1B 5A BE 9F D5 C7 4D 7B C2 B4 07 A3 62 37 3C B5 CF 24 3A E1 98 18 5B 5D C9 15 4D 36 40 91 53 C7 90 97 57 8C 8D 7C 1A E3 62 9D C4 6C 5C 9C 03 02 C6 1C 12 D0 50 51 F8 23 81 02 9E 6A FF E7 C6 5D 9B 66 EC + */ + // decrypted: + + // 00 00 00 C2 + // 00 01 4E 66 // =85606 + // + // 20 02 ED BD // =537062845 + // 20 02 ED BD + // + // 01 00 00 00 00 00 00 00 00 00 01 00 + // + // 00 00 00 4C //72+4, unknown + // B8 12 0D E1 + // DA 19 AF D3 + // EB 36 76 BD + // 42 08 F6 DC A5 35 69 C0 8F F2 75 28 B4 CE 09 C9 B7 86 E3 5A 14 D1 0D CA 5D D4 CB 16 77 8B 32 8D 81 3B 3F D9 52 13 77 03 D3 F7 0E CD 7B 21 95 D2 59 CE 0C 31 D6 F1 38 2A FA 82 AD 60 + // + // 00 00 00 15 // 17+4, command + // 77 74 6C 6F 67 69 6E 2E 74 72 61 6E 73 5F 65 6D 70 // wtlogin.trans_emp + // + // 00 00 00 08 // 4+4 + // 02 B0 5B 8B // unknown, =45112203 + // + // 00 00 00 13 // 15+4, imei: + // 38 35 38 34 31 34 33 36 39 32 31 31 39 39 33 + // + // 00 00 00 04 + // + // 00 22 // 32+2, ksid: + // 7C 34 35 34 30 30 31 32 32 38 34 33 37 35 39 30 7C 41 38 2E 32 2E 30 2E 32 37 66 36 65 61 39 36 + // + // 00 00 00 04 + // + // 00 00 02 70 // remaining size=620+4 + // 02 + // 02 6C // 27+2+body.size = 620 = 27+2+591 + // 1F 41 // 8001 + // 08 12 // commandId 2066 + // 00 01 // const? + // 76 E4 B8 DD // accountId=1994701021 + // 03 + // 07 // EncryptMethod + // 00 + // 00 00 00 02 + // 00 00 00 00 + // 00 00 00 00 // const + // + // // OutgoingPacket.body: ECDH encrypted + // 01 + // 01 + // // private key: len=16 + // A4 9A 6A EE 17 5B 7E 3D C0 71 DA 04 1C E1 E4 88 + // 01 02 // =258 + // // pub key: len=49 + // [00 31] 04 CB 36 66 98 56 1E 93 6E 80 C1 57 E0 74 CA B1 3B 0B B6 8D DE B2 82 45 48 A1 B1 8D D4 FB 61 22 AF E1 2F E4 8C 52 66 D8 D7 26 9D 76 51 A8 EB 6F E7 + // E8 6F F6 C1 D9 8B 9C C1 B2 99 A9 53 68 80 E0 4A 34 F9 C2 F7 6D A5 4E E6 25 F1 31 A7 16 46 6D 2A E5 14 2B 64 8D EA 29 15 19 48 69 34 C4 90 D1 50 9A A6 3F 58 69 94 B1 E2 1E E7 C5 D9 53 5B 6B 71 9F 20 9D 2D 02 B3 0D DF B0 F1 0E 03 1E 2C E8 5F F6 28 D6 97 FB A9 45 C6 E1 FF 79 84 C7 7E 42 79 81 BB 48 48 AD D5 9F 46 7C 45 EA B5 C1 10 F0 41 EF 94 A2 D5 80 67 EB CC 11 05 9E DE 06 A1 9E 5E 71 40 68 4A C8 32 B8 C8 48 73 6D AD 41 51 07 4F 43 E3 C6 7D 8C 7E 49 5D CF A3 D8 3F 29 22 AD 08 AE C4 15 29 90 22 DC 01 5D 81 BA B8 B0 06 ED B1 93 EE CC CC FC 65 97 1F 1F 22 36 AD 85 B1 3D 1B 02 7E C3 0C 1E 6E 4A 30 CB 4F 09 9D 67 C4 D7 DB 06 89 36 A0 7A 03 D0 46 5C 0E C1 B9 24 E4 30 6E BE 0C 60 25 10 57 1E D7 45 CD 0A B3 23 18 1C 47 0C 62 79 29 8F 55 3B F0 0D A2 FB DE 05 B7 71 AD B8 B2 D7 AD 4B 15 E0 ED EB 26 25 CC DE 39 66 8B 1A AE 96 0B E5 4E AB C7 A3 0C 09 82 D6 CD F9 3E 9D 6E C6 73 C5 20 20 F6 8E DF 80 95 13 68 9C 3B C7 EF 71 C9 FC 96 2C 07 48 0A 9A 06 8F 96 7E 90 1F 31 3A 05 86 86 E5 64 5D 5A 08 2C 6C EE 72 7C C2 DF 9B 3C F7 52 5C 17 0E 9B C9 AE 36 8E 54 C9 B5 5E E9 D6 F8 C4 54 81 AC 78 DE 1D 4A C3 31 C6 2E 3F 6D C7 9C FF 5F 7F 88 2C B4 63 CC AC FC 57 1B 84 5D 66 7E C0 14 29 1D 70 74 A8 EE 03 55 07 C2 D7 F5 CE 15 8F 9B DF DA AD C8 F9 33 90 CD 57 98 A1 62 94 E1 3E DD 0F F6 F4 6D 17 35 F1 CE 1D EE 72 72 8B 7A AB B0 7F 68 8F 40 68 96 20 11 BF 05 91 C5 C1 71 AB BF BC A1 9D CC 0B 7D 5F 24 23 3D AB 46 02 43 D6 15 5E 64 BD FD 35 19 C0 47 15 69 C1 EA 0E 9B 5E 8A CD C2 20 CF A7 39 2E 8B D9 89 FC 94 63 18 E9 DD 3F 5B 1B C0 27 4A 85 7A 84 3B C2 50 BC 6A 86 48 0A + // // decrypted by calculated share key: + // 00 00 01 04 + // 00 00 00 0B 01 00 01 50 CE 00 + // 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 D2 D5 37 8A 3C 47 B1 84 E2 94 B2 AF BF 14 70 4D 73 17 BB 38 BE 82 73 DF A2 87 E0 0A 7A BA 8A 81 71 77 1D E1 71 7F B7 C1 66 1D 8C 3D 41 4F 51 09 6A B7 B7 7B 88 28 A6 5A AB 7E 40 25 9B C8 35 9C C6 E2 3A 5F 94 1D 70 0F D7 89 4D 41 6B 7A 29 A2 70 77 3D F8 1D 32 65 D7 D8 D1 6D 13 42 9C 0C 72 DB 48 95 4B 66 EF B9 E6 E4 C1 3B 2C 36 B0 D7 3F E2 85 C8 2A 8C 65 0F 0B 1C F1 A7 C7 E1 1F 0C 32 F5 08 14 AA 5A 43 CD 8E A8 82 14 24 97 63 F0 53 79 4E 33 8D 5F 1C F8 1C 89 3B 39 44 CC A7 63 5F FC BF 87 42 89 2D A5 F4 BC B2 69 49 54 DD AE E6 3F A2 A2 98 DC 3B D4 A2 27 10 F2 06 42 93 C5 30 4A D4 FA F5 BA A5 B2 4B 56 45 59 94 CA 4C 4B 17 55 C7 23 AF F0 8B E5 DC 3A 1B B6 A7 2E 10 BB 9A E7 70 54 BA F5 4B 70 91 + // 03 + + + val data = + """ +E8 6F F6 C1 D9 8B 9C C1 B2 99 A9 53 68 80 E0 4A 34 F9 C2 F7 6D A5 4E E6 25 F1 31 A7 16 46 6D 2A E5 14 2B 64 8D EA 29 15 19 48 69 34 C4 90 D1 50 9A A6 3F 58 69 94 B1 E2 1E E7 C5 D9 53 5B 6B 71 9F 20 9D 2D 02 B3 0D DF B0 F1 0E 03 1E 2C E8 5F F6 28 D6 97 FB A9 45 C6 E1 FF 79 84 C7 7E 42 79 81 BB 48 48 AD D5 9F 46 7C 45 EA B5 C1 10 F0 41 EF 94 A2 D5 80 67 EB CC 11 05 9E DE 06 A1 9E 5E 71 40 68 4A C8 32 B8 C8 48 73 6D AD 41 51 07 4F 43 E3 C6 7D 8C 7E 49 5D CF A3 D8 3F 29 22 AD 08 AE C4 15 29 90 22 DC 01 5D 81 BA B8 B0 06 ED B1 93 EE CC CC FC 65 97 1F 1F 22 36 AD 85 B1 3D 1B 02 7E C3 0C 1E 6E 4A 30 CB 4F 09 9D 67 C4 D7 DB 06 89 36 A0 7A 03 D0 46 5C 0E C1 B9 24 E4 30 6E BE 0C 60 25 10 57 1E D7 45 CD 0A B3 23 18 1C 47 0C 62 79 29 8F 55 3B F0 0D A2 FB DE 05 B7 71 AD B8 B2 D7 AD 4B 15 E0 ED EB 26 25 CC DE 39 66 8B 1A AE 96 0B E5 4E AB C7 A3 0C 09 82 D6 CD F9 3E 9D 6E C6 73 C5 20 20 F6 8E DF 80 95 13 68 9C 3B C7 EF 71 C9 FC 96 2C 07 48 0A 9A 06 8F 96 7E 90 1F 31 3A 05 86 86 E5 64 5D 5A 08 2C 6C EE 72 7C C2 DF 9B 3C F7 52 5C 17 0E 9B C9 AE 36 8E 54 C9 B5 5E E9 D6 F8 C4 54 81 AC 78 DE 1D 4A C3 31 C6 2E 3F 6D C7 9C FF 5F 7F 88 2C B4 63 CC AC FC 57 1B 84 5D 66 7E C0 14 29 1D 70 74 A8 EE 03 55 07 C2 D7 F5 CE 15 8F 9B DF DA AD C8 F9 33 90 CD 57 98 A1 62 94 E1 3E DD 0F F6 F4 6D 17 35 F1 CE 1D EE 72 72 8B 7A AB B0 7F 68 8F 40 68 96 20 11 BF 05 91 C5 C1 71 AB BF BC A1 9D CC 0B 7D 5F 24 23 3D AB 46 02 43 D6 15 5E 64 BD FD 35 19 C0 47 15 69 C1 EA 0E 9B 5E 8A CD C2 20 CF A7 39 2E 8B D9 89 FC 94 63 18 E9 DD 3F 5B 1B C0 27 4A 85 7A 84 3B C2 50 BC 6A 86 48 0A +""".trimIndent().hexToBytes() + try { + println(data.decryptBy("F1 E3 A0 9F AD 63 80 68 43 3C 11 98 53 13 D4 BF".hexToBytes())) + println(data.decryptBy(ByteArray(16)).toUHexString()) + println("With key 16 zero") + } catch (e: Exception) { + println(data.decryptBy("%4;7t>;28;28 = this.readTLVMap() + tlvMap[0x119]?.let { t119Data -> + t119Data.decryptBy(tgtgtKey).toReadPacket().debugPrint("0x119data").apply { + discardExact(2) // always discarded. 00 1C + // 00 1C + // 01 08 00 10 A1 73 76 98 64 E0 38 C6 C8 18 73 FA D3 85 DA D6 01 6A 00 30 1D 99 4A 28 7E B3 B8 AC 74 B9 C4 BB 6D BB 41 72 F7 5C 9F 0F 79 8A 82 4F 1F 69 34 6D 10 D6 BB E8 A3 4A 2B 5D F1 C7 05 3C F8 72 EF CF 67 E4 3C 94 01 06 00 78 B4 ED 9F 44 ED 10 18 A8 85 0A 8A 85 79 45 47 7F 25 AA EE 2C 53 83 80 0A B3 B0 47 3E 95 51 A4 AE 3E CA A0 1D B4 91 F7 BB 2E 94 76 A8 C8 97 02 C4 5B 15 02 B7 03 9A FC C2 58 6D 17 92 46 AE EB 2F 6F 65 B8 69 6C D6 9D AC 18 6F 07 53 AC FE FA BC BD CE 57 13 10 2D 5A C6 50 AA C2 AE 18 D4 FD CD F2 E0 D1 25 29 56 21 35 8F 01 9D D6 69 44 8F 06 D0 23 26 D3 0E E6 E6 B7 01 0C 00 10 73 32 61 4E 2C 72 35 58 68 28 47 3E 2B 6E 52 62 01 0A 00 48 A4 DA 48 FB B4 8D DA 7B 86 D7 A7 FE 01 1B 70 6F 54 F8 55 38 B0 AD 1B 0C 0B B9 F6 94 24 F8 9E 30 32 22 99 0C 22 CD 44 B8 B0 8A A8 65 E1 B8 F0 49 EF E1 23 D7 0D A3 F1 BB 52 B7 4B AF BD 50 EA BF 15 02 78 2B 8B 10 FB 15 01 0D 00 10 29 75 38 72 21 5D 3F 24 37 46 67 79 2B 65 6D 34 01 14 00 60 00 01 5E 19 65 8C 00 58 93 DD 4D 2C 2D 01 44 99 62 B8 7A EF 04 C5 71 0B F1 BE 4C F4 21 F2 97 B0 14 67 0E 14 9F D8 A2 0B 93 40 90 80 F3 59 7A 69 45 D7 D4 53 4C 08 3A 56 1D C9 95 36 2C 7C 5E EE 36 47 5F AE 26 72 76 FD FD 69 E6 0C 2D 3A E8 CF D4 8D 76 C9 17 C3 E3 CD 21 AB 04 6B 70 C5 EC EC 01 0E 00 10 56 48 3E 29 3A 5A 21 74 55 6A 2C 72 58 73 79 71 01 03 00 30 9B A6 5D 85 5C 40 7C 28 E7 05 A9 25 CA F5 FC C0 51 40 85 F3 2F D2 37 F9 09 A6 E6 56 7F 7A 2E 7D 9F B9 1C 00 65 55 D2 A9 60 03 77 AB 6A F5 3F CE 01 33 00 30 F4 3A A7 08 E2 04 FA C8 9D 54 49 DE 63 EA F0 A5 1C C4 03 57 51 B6 AE 0B 55 41 F8 AB 22 F1 DC A3 B0 73 08 55 14 02 BF FF 55 87 42 4C 23 70 91 6A 01 34 00 10 61 C7 02 3F 1D BE A6 27 2F 24 D4 92 95 68 71 EF 05 28 00 1A 7B 22 51 49 4D 5F 69 6E 76 69 74 61 74 69 6F 6E 5F 62 69 74 22 3A 22 31 22 7D 03 22 00 10 CE 1E 2E DC 69 24 4F 9B FF 2F 52 D8 8F 69 DD 40 01 1D 00 76 5F 5E 10 E2 34 36 79 27 23 53 4D 65 6B 6A 33 6D 7D 4E 3C 5F 00 60 00 01 5E 19 65 8C 00 58 67 00 9C 02 E4 BC DB A3 93 98 A1 ED 4C 91 08 6F 0C 06 E0 12 6A DC 14 5B 4D 20 7C 82 83 AE 94 53 A2 4A A0 35 FF 59 9D F3 EF 82 42 61 67 2A 31 E7 87 7E 74 E7 A3 E7 5C A8 3C 87 CF 40 6A 9F E5 F7 20 4E 56 C6 4F 1C 98 3A 8B A9 4F 1D 10 35 C2 3B A1 08 7A 89 0B 25 0C 63 01 1F 00 0A 00 01 51 80 00 00 03 84 00 00 01 38 00 0E 00 00 00 01 01 0A 00 27 8D 00 00 00 00 00 01 1A 00 13 02 5B 06 01 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 05 22 00 14 00 00 00 00 76 E4 B8 DD AB 53 02 9F 5E 19 65 8C 20 02 ED BD 05 37 00 17 01 01 00 00 00 00 76 E4 B8 DD 04 AB 53 02 9F 5E 19 65 8C 20 02 ED BD 01 20 00 0A 4D 39 50 57 50 6E 4C 31 65 4F 01 6D 00 2C 31 7A 50 7A 63 72 70 4D 30 43 6E 31 37 4C 32 32 6E 77 2D 36 7A 4E 71 48 48 59 41 35 48 71 77 41 37 6D 76 4F 63 2D 4A 56 77 47 51 5F 05 12 03 5D 00 0E 00 0A 74 65 6E 70 61 79 2E 63 6F 6D 00 2C 6E 4A 72 55 55 74 63 2A 34 7A 32 76 31 66 6A 75 77 6F 6A 65 73 72 76 4F 68 70 66 45 76 4A 75 55 4B 6D 34 43 2D 76 74 38 4D 77 38 5F 00 00 00 11 6F 70 65 6E 6D 6F 62 69 6C 65 2E 71 71 2E 63 6F 6D 00 2C 78 59 35 65 62 4D 74 48 44 6D 30 53 6F 68 56 71 68 33 43 79 79 34 6F 63 65 4A 46 6A 51 58 65 68 30 44 61 75 55 30 6C 78 65 52 6B 5F 00 00 00 0B 64 6F 63 73 2E 71 71 2E 63 6F 6D 00 2C 64 6A 62 79 47 57 45 4F 34 58 34 6A 36 4A 73 48 45 65 6B 73 69 74 72 78 79 62 57 69 77 49 68 46 45 70 72 4A 59 4F 2D 6B 36 47 6F 5F 00 00 00 0E 63 6F 6E 6E 65 63 74 2E 71 71 2E 63 6F 6D 00 2C 64 4C 31 41 79 32 41 31 74 33 58 36 58 58 2A 74 33 64 4E 70 2A 31 61 2D 50 7A 65 57 67 48 70 2D 65 47 78 6B 59 74 71 62 69 6C 55 5F 00 00 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 2C 75 6A 55 5A 4F 6A 4F 48 52 61 75 6B 32 55 50 38 77 33 34 68 36 69 46 38 2A 77 4E 50 35 2D 66 54 75 37 67 39 56 67 44 57 2A 6B 6F 5F 00 00 00 0A 76 69 70 2E 71 71 2E 63 6F 6D 00 2C 37 47 31 44 6F 54 2D 4D 57 50 63 2D 62 43 46 68 63 62 32 56 38 6E 77 4A 75 41 51 63 54 39 77 45 49 62 57 43 4A 4B 44 4D 6C 6D 34 5F 00 00 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 2C 7A 73 70 5A 56 43 59 45 7A 35 2A 4F 6B 4E 68 6E 74 79 61 69 6E 6F 68 4D 32 6B 41 6C 2A 74 31 63 7A 48 57 77 30 41 6A 4B 50 4B 6B 5F 00 00 00 0B 67 61 6D 65 2E 71 71 2E 63 6F 6D 00 2C 32 6F 2D 51 53 36 65 43 70 37 6A 43 4E 34 6A 74 6E 47 4F 4B 33 67 73 32 63 4A 6F 56 71 58 65 44 48 61 55 39 65 34 2D 32 34 64 30 5F 00 00 00 0C 71 71 77 65 62 2E 71 71 2E 63 6F 6D 00 2C 63 54 4D 79 64 51 43 35 50 74 43 45 51 72 6F 33 53 54 41 66 7A 56 2D 44 76 46 56 35 58 6D 56 6B 49 31 68 4C 55 48 4E 65 76 56 38 5F 00 00 00 0D 6F 66 66 69 63 65 2E 71 71 2E 63 6F 6D 00 2C 6F 73 72 54 36 32 69 37 66 76 6D 49 50 64 6F 58 4B 48 74 38 58 52 59 56 77 72 7A 6E 69 31 58 7A 57 4C 77 2A 71 36 33 44 74 73 6F 5F 00 00 00 09 74 69 2E 71 71 2E 63 6F 6D 00 2C 41 61 77 4D 78 4D 32 79 58 51 47 75 72 75 55 6C 66 53 58 79 5A 57 48 53 78 52 57 58 50 74 6B 6B 4F 78 6F 66 4A 59 47 6C 71 68 34 5F 00 00 00 0B 6D 61 69 6C 2E 71 71 2E 63 6F 6D 00 2C 67 72 57 68 58 77 34 4C 6E 4B 49 4F 67 63 78 45 71 70 33 61 45 67 37 38 46 7A 77 4E 6D 4B 48 56 6E 6F 50 4C 4F 32 6D 57 6D 6E 38 5F 00 00 00 09 71 7A 6F 6E 65 2E 63 6F 6D 00 2C 72 61 47 79 51 35 54 72 4D 55 7A 6E 74 31 4E 52 44 2D 50 72 74 72 41 55 43 35 6A 61 2D 49 47 2D 73 77 4C 6D 49 51 51 41 44 4C 41 5F 00 00 00 0A 6D 6D 61 2E 71 71 2E 63 6F 6D 00 2C 39 73 2D 4F 51 30 67 76 39 42 6A 37 58 71 52 49 4E 30 35 46 32 64 4D 47 67 47 43 58 57 4A 62 68 63 30 38 63 7A 4B 52 76 6B 78 6B 5F 00 00 03 05 00 10 77 75 6E 54 5F 7E 66 7A 72 40 3C 6E 35 50 53 46 01 43 00 40 3A AE 30 87 81 3D EE BA 31 9C EA 9D 0D D4 73 B1 81 12 E0 94 71 73 7A B0 47 3D 09 47 E5 1B E1 E2 06 1A CB A4 E3 71 9E A6 EA 2A 73 5C C8 D3 B1 2A B1 C7 DA 04 A6 6D 12 26 DF 6B 8B EC C7 12 F8 E1 01 18 00 05 00 00 00 01 00 01 63 00 10 67 6B 60 23 24 6A 55 39 4E 58 24 5E 39 2B 7A 69 01 38 00 5E 00 00 00 09 01 06 00 27 8D 00 00 00 00 00 01 0A 00 24 EA 00 00 00 00 00 01 1C 00 1A 5E 00 00 00 00 00 01 02 00 01 51 80 00 00 00 00 01 03 00 00 1C 20 00 00 00 00 01 20 00 01 51 80 00 00 00 00 01 36 00 1B AF 80 00 00 00 00 01 43 00 1B AF 80 00 00 00 00 01 64 00 1B AF 80 00 00 00 00 01 30 00 0E 00 00 5E 19 65 8C 9F 02 53 AB 00 00 00 00 + val tlvMap119 = this.readTLVMap() + + userStKey = tlvMap119.getOrEmpty(0x10e) + wtSessionTicketKey = tlvMap119.getOrEmpty(0x133) + D2Key = tlvMap119.getOrEmpty(0x305) + DebugLogger.info("userStKey=${userStKey.toUHexString()}") + DebugLogger.info("wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}") + DebugLogger.info("D2Key=${D2Key.toUHexString()}") + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } else // always discarded. 00 1C + // 00 1C + // 01 08 00 10 A1 73 76 98 64 E0 38 C6 C8 18 73 FA D3 85 DA D6 01 6A 00 30 1D 99 4A 28 7E B3 B8 AC 74 B9 C4 BB 6D BB 41 72 F7 5C 9F 0F 79 8A 82 4F 1F 69 34 6D 10 D6 BB E8 A3 4A 2B 5D F1 C7 05 3C F8 72 EF CF 67 E4 3C 94 01 06 00 78 B4 ED 9F 44 ED 10 18 A8 85 0A 8A 85 79 45 47 7F 25 AA EE 2C 53 83 80 0A B3 B0 47 3E 95 51 A4 AE 3E CA A0 1D B4 91 F7 BB 2E 94 76 A8 C8 97 02 C4 5B 15 02 B7 03 9A FC C2 58 6D 17 92 46 AE EB 2F 6F 65 B8 69 6C D6 9D AC 18 6F 07 53 AC FE FA BC BD CE 57 13 10 2D 5A C6 50 AA C2 AE 18 D4 FD CD F2 E0 D1 25 29 56 21 35 8F 01 9D D6 69 44 8F 06 D0 23 26 D3 0E E6 E6 B7 01 0C 00 10 73 32 61 4E 2C 72 35 58 68 28 47 3E 2B 6E 52 62 01 0A 00 48 A4 DA 48 FB B4 8D DA 7B 86 D7 A7 FE 01 1B 70 6F 54 F8 55 38 B0 AD 1B 0C 0B B9 F6 94 24 F8 9E 30 32 22 99 0C 22 CD 44 B8 B0 8A A8 65 E1 B8 F0 49 EF E1 23 D7 0D A3 F1 BB 52 B7 4B AF BD 50 EA BF 15 02 78 2B 8B 10 FB 15 01 0D 00 10 29 75 38 72 21 5D 3F 24 37 46 67 79 2B 65 6D 34 01 14 00 60 00 01 5E 19 65 8C 00 58 93 DD 4D 2C 2D 01 44 99 62 B8 7A EF 04 C5 71 0B F1 BE 4C F4 21 F2 97 B0 14 67 0E 14 9F D8 A2 0B 93 40 90 80 F3 59 7A 69 45 D7 D4 53 4C 08 3A 56 1D C9 95 36 2C 7C 5E EE 36 47 5F AE 26 72 76 FD FD 69 E6 0C 2D 3A E8 CF D4 8D 76 C9 17 C3 E3 CD 21 AB 04 6B 70 C5 EC EC 01 0E 00 10 56 48 3E 29 3A 5A 21 74 55 6A 2C 72 58 73 79 71 01 03 00 30 9B A6 5D 85 5C 40 7C 28 E7 05 A9 25 CA F5 FC C0 51 40 85 F3 2F D2 37 F9 09 A6 E6 56 7F 7A 2E 7D 9F B9 1C 00 65 55 D2 A9 60 03 77 AB 6A F5 3F CE 01 33 00 30 F4 3A A7 08 E2 04 FA C8 9D 54 49 DE 63 EA F0 A5 1C C4 03 57 51 B6 AE 0B 55 41 F8 AB 22 F1 DC A3 B0 73 08 55 14 02 BF FF 55 87 42 4C 23 70 91 6A 01 34 00 10 61 C7 02 3F 1D BE A6 27 2F 24 D4 92 95 68 71 EF 05 28 00 1A 7B 22 51 49 4D 5F 69 6E 76 69 74 61 74 69 6F 6E 5F 62 69 74 22 3A 22 31 22 7D 03 22 00 10 CE 1E 2E DC 69 24 4F 9B FF 2F 52 D8 8F 69 DD 40 01 1D 00 76 5F 5E 10 E2 34 36 79 27 23 53 4D 65 6B 6A 33 6D 7D 4E 3C 5F 00 60 00 01 5E 19 65 8C 00 58 67 00 9C 02 E4 BC DB A3 93 98 A1 ED 4C 91 08 6F 0C 06 E0 12 6A DC 14 5B 4D 20 7C 82 83 AE 94 53 A2 4A A0 35 FF 59 9D F3 EF 82 42 61 67 2A 31 E7 87 7E 74 E7 A3 E7 5C A8 3C 87 CF 40 6A 9F E5 F7 20 4E 56 C6 4F 1C 98 3A 8B A9 4F 1D 10 35 C2 3B A1 08 7A 89 0B 25 0C 63 01 1F 00 0A 00 01 51 80 00 00 03 84 00 00 01 38 00 0E 00 00 00 01 01 0A 00 27 8D 00 00 00 00 00 01 1A 00 13 02 5B 06 01 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 05 22 00 14 00 00 00 00 76 E4 B8 DD AB 53 02 9F 5E 19 65 8C 20 02 ED BD 05 37 00 17 01 01 00 00 00 00 76 E4 B8 DD 04 AB 53 02 9F 5E 19 65 8C 20 02 ED BD 01 20 00 0A 4D 39 50 57 50 6E 4C 31 65 4F 01 6D 00 2C 31 7A 50 7A 63 72 70 4D 30 43 6E 31 37 4C 32 32 6E 77 2D 36 7A 4E 71 48 48 59 41 35 48 71 77 41 37 6D 76 4F 63 2D 4A 56 77 47 51 5F 05 12 03 5D 00 0E 00 0A 74 65 6E 70 61 79 2E 63 6F 6D 00 2C 6E 4A 72 55 55 74 63 2A 34 7A 32 76 31 66 6A 75 77 6F 6A 65 73 72 76 4F 68 70 66 45 76 4A 75 55 4B 6D 34 43 2D 76 74 38 4D 77 38 5F 00 00 00 11 6F 70 65 6E 6D 6F 62 69 6C 65 2E 71 71 2E 63 6F 6D 00 2C 78 59 35 65 62 4D 74 48 44 6D 30 53 6F 68 56 71 68 33 43 79 79 34 6F 63 65 4A 46 6A 51 58 65 68 30 44 61 75 55 30 6C 78 65 52 6B 5F 00 00 00 0B 64 6F 63 73 2E 71 71 2E 63 6F 6D 00 2C 64 6A 62 79 47 57 45 4F 34 58 34 6A 36 4A 73 48 45 65 6B 73 69 74 72 78 79 62 57 69 77 49 68 46 45 70 72 4A 59 4F 2D 6B 36 47 6F 5F 00 00 00 0E 63 6F 6E 6E 65 63 74 2E 71 71 2E 63 6F 6D 00 2C 64 4C 31 41 79 32 41 31 74 33 58 36 58 58 2A 74 33 64 4E 70 2A 31 61 2D 50 7A 65 57 67 48 70 2D 65 47 78 6B 59 74 71 62 69 6C 55 5F 00 00 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 2C 75 6A 55 5A 4F 6A 4F 48 52 61 75 6B 32 55 50 38 77 33 34 68 36 69 46 38 2A 77 4E 50 35 2D 66 54 75 37 67 39 56 67 44 57 2A 6B 6F 5F 00 00 00 0A 76 69 70 2E 71 71 2E 63 6F 6D 00 2C 37 47 31 44 6F 54 2D 4D 57 50 63 2D 62 43 46 68 63 62 32 56 38 6E 77 4A 75 41 51 63 54 39 77 45 49 62 57 43 4A 4B 44 4D 6C 6D 34 5F 00 00 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 2C 7A 73 70 5A 56 43 59 45 7A 35 2A 4F 6B 4E 68 6E 74 79 61 69 6E 6F 68 4D 32 6B 41 6C 2A 74 31 63 7A 48 57 77 30 41 6A 4B 50 4B 6B 5F 00 00 00 0B 67 61 6D 65 2E 71 71 2E 63 6F 6D 00 2C 32 6F 2D 51 53 36 65 43 70 37 6A 43 4E 34 6A 74 6E 47 4F 4B 33 67 73 32 63 4A 6F 56 71 58 65 44 48 61 55 39 65 34 2D 32 34 64 30 5F 00 00 00 0C 71 71 77 65 62 2E 71 71 2E 63 6F 6D 00 2C 63 54 4D 79 64 51 43 35 50 74 43 45 51 72 6F 33 53 54 41 66 7A 56 2D 44 76 46 56 35 58 6D 56 6B 49 31 68 4C 55 48 4E 65 76 56 38 5F 00 00 00 0D 6F 66 66 69 63 65 2E 71 71 2E 63 6F 6D 00 2C 6F 73 72 54 36 32 69 37 66 76 6D 49 50 64 6F 58 4B 48 74 38 58 52 59 56 77 72 7A 6E 69 31 58 7A 57 4C 77 2A 71 36 33 44 74 73 6F 5F 00 00 00 09 74 69 2E 71 71 2E 63 6F 6D 00 2C 41 61 77 4D 78 4D 32 79 58 51 47 75 72 75 55 6C 66 53 58 79 5A 57 48 53 78 52 57 58 50 74 6B 6B 4F 78 6F 66 4A 59 47 6C 71 68 34 5F 00 00 00 0B 6D 61 69 6C 2E 71 71 2E 63 6F 6D 00 2C 67 72 57 68 58 77 34 4C 6E 4B 49 4F 67 63 78 45 71 70 33 61 45 67 37 38 46 7A 77 4E 6D 4B 48 56 6E 6F 50 4C 4F 32 6D 57 6D 6E 38 5F 00 00 00 09 71 7A 6F 6E 65 2E 63 6F 6D 00 2C 72 61 47 79 51 35 54 72 4D 55 7A 6E 74 31 4E 52 44 2D 50 72 74 72 41 55 43 35 6A 61 2D 49 47 2D 73 77 4C 6D 49 51 51 41 44 4C 41 5F 00 00 00 0A 6D 6D 61 2E 71 71 2E 63 6F 6D 00 2C 39 73 2D 4F 51 30 67 76 39 42 6A 37 58 71 52 49 4E 30 35 46 32 64 4D 47 67 47 43 58 57 4A 62 68 63 30 38 63 7A 4B 52 76 6B 78 6B 5F 00 00 03 05 00 10 77 75 6E 54 5F 7E 66 7A 72 40 3C 6E 35 50 53 46 01 43 00 40 3A AE 30 87 81 3D EE BA 31 9C EA 9D 0D D4 73 B1 81 12 E0 94 71 73 7A B0 47 3D 09 47 E5 1B E1 E2 06 1A CB A4 E3 71 9E A6 EA 2A 73 5C C8 D3 B1 2A B1 C7 DA 04 A6 6D 12 26 DF 6B 8B EC C7 12 F8 E1 01 18 00 05 00 00 00 01 00 01 63 00 10 67 6B 60 23 24 6A 55 39 4E 58 24 5E 39 2B 7A 69 01 38 00 5E 00 00 00 09 01 06 00 27 8D 00 00 00 00 00 01 0A 00 24 EA 00 00 00 00 00 01 1C 00 1A 5E 00 00 00 00 00 01 02 00 01 51 80 00 00 00 00 01 03 00 00 1C 20 00 00 00 00 01 20 00 01 51 80 00 00 00 00 01 36 00 1B AF 80 00 00 00 00 01 43 00 1B AF 80 00 00 00 00 01 64 00 1B AF 80 00 00 00 00 01 30 00 0E 00 00 5E 19 65 8C 9F 02 53 AB 00 00 00 00 + { + PacketLogger.debug("不是oicq response(可能是 UNI/PB)= " + bytes.toUHexString()) + } + } ?: inline { + PacketLogger.error("任何key都无法解密") + return + } + } +} + +private fun Map.getOrEmpty(key: Int): ByteArray { + return this[key] ?: byteArrayOf() +} + +var randomKey: ByteArray = byteArrayOf() +private fun ByteReadPacket.parseOicqResponse(body: ByteReadPacket.() -> Unit) { + val qq: Long + readIoBuffer(readInt() - 4).withUse { + check(readByte().toInt() == 2) + this.discardExact(2) // 27 + 2 + body.size + this.discardExact(2) // const, =8001 + this.readUShort() // commandId + this.readShort() // const, =0x0001 + qq = this.readUInt().toLong() + val encryptionMethod = this.readUShort().toInt() + + this.discardExact(1) // const = 0 + val packet = when (encryptionMethod) { + 4 -> { // peer public key, ECDH + var data = this.decryptBy(shareKeyCalculatedByConstPubKey, 0, this.readRemaining - 1) + data.read { + println("第一层解密: ${data.toUHexString()}") + val peerShareKey = ECDH.calculateShareKey(loadPrivateKey(ecdhPrivateKeyS), readUShortLVByteArray().adjustToPublicKey()) + body(this.decryptBy(peerShareKey)) + } + } + 0 -> { + val data = if (0 == 0) { + ByteArrayPool.useInstance { byteArrayBuffer -> + val size = this.readRemaining - 1 + this.readFully(byteArrayBuffer, 0, size) + + runCatching { + byteArrayBuffer.decryptBy(shareKeyCalculatedByConstPubKey, size) + }.getOrElse { + byteArrayBuffer.decryptBy(randomKey, size) + } // 这里实际上应该用 privateKey(另一个random出来的key) + } + } else { + this.decryptBy(randomKey, 0, this.readRemaining - 1) + } + + PacketLogger.info("OicqRequest, Real body=" + data.toUHexString()) + body(data.toReadPacket()) + } + else -> error("Illegal encryption method. expected 0 or 4, got $encryptionMethod") + } + } +} + +/** + * 解析 SSO 层包装 + */ +@UseExperimental(ExperimentalUnsignedTypes::class) +private fun parseSsoFrame(input: ByteReadPacket): KnownPacketFactories.IncomingPacket { + val commandName: String + val ssoSequenceId: Int + + // head + input.readIoBuffer(input.readInt() - 4).withUse { + ssoSequenceId = readInt() + PacketLogger.verbose("sequenceId = $ssoSequenceId") + check(readInt() == 0) + val extraData = readBytes(readInt() - 4) + PacketLogger.verbose("sso(inner)extraData = ${extraData.toUHexString()}") + + commandName = readString(readInt() - 4) + DebugLogger.warning("commandName=$commandName") + val unknown = readBytes(readInt() - 4) + if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}") + + check(readInt() == 0) + } + + // body + val packetFactory = KnownPacketFactories.findPacketFactory(commandName) + + if (packetFactory == null) { + println("找不到包 PacketFactory") + PacketLogger.verbose("传递给 PacketFactory 的数据 = ${input.readBytes().toUHexString()}") + } + return KnownPacketFactories.IncomingPacket(packetFactory, ssoSequenceId, input) +} + + +/** + * 解析 Uni 层包装 + */ +@UseExperimental(ExperimentalUnsignedTypes::class) +private fun parseUniFrame(input: ByteReadPacket): KnownPacketFactories.IncomingPacket { + // 00 00 00 30 00 01 2F 7C 00 00 00 00 00 00 00 04 00 00 00 14 67 78 68 72 65 70 6F 72 74 2E 72 65 70 6F 72 74 00 00 00 08 66 82 D3 0B 00 00 00 00 + // 00 00 00 06 08 00 + + + //00 00 00 2D 00 01 2F 7E 00 00 00 00 00 00 00 04 00 00 00 11 4F 69 64 62 53 76 63 2E 30 78 35 39 66 00 00 00 08 66 82 D3 0B 00 00 00 00 + // 00 00 00 19 08 9F 0B 10 01 18 00 22 0C 10 00 18 00 20 00 A8 01 00 A0 06 01 + + val commandName: String + val ssoSequenceId: Int + + // head + input.readIoBuffer(input.readInt() - 4).withUse { + ssoSequenceId = readInt() + PacketLogger.verbose("sequenceId = $ssoSequenceId") + check(readInt() == 0) + val extraData = readBytes(readInt() - 4) + PacketLogger.verbose("sso(inner)extraData = ${extraData.toUHexString()}") + + commandName = readString(readInt() - 4) + DebugLogger.warning("commandName=$commandName") + val unknown = readBytes(readInt() - 4) + if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}") + + check(readInt() == 0) + } + + // body + val packetFactory = KnownPacketFactories.findPacketFactory(commandName) + + if (packetFactory == null) { + println("找不到包 PacketFactory") + PacketLogger.verbose("传递给 PacketFactory 的数据 = ${input.readBytes().toUHexString()}") + } + return KnownPacketFactories.IncomingPacket(packetFactory, ssoSequenceId, input) +} + +private inline fun inline(block: () -> R): R = block() 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 index 29ec6edb3..59d383945 100644 --- 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 @@ -1,6 +1,6 @@ package net.mamoe.mirai.qqandroid.io.serialization -import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket import net.mamoe.mirai.utils.io.hexToBytes class TestRequesetPacket { diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt index 7df524fbc..4b62c40ff 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt @@ -143,7 +143,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass { val javaClassname = substringBetween("class", "{") val superclasses = javaClassname.split("$").map { it.trim().adjustClassName() }.toMutableList().apply { removeAt(this.lastIndex) } val className = substringBetween("class", "{").substringAfterLast("$").trim().adjustClassName() - return GeneratedClass(superclasses, className, "@Serializable\nclass $className") + return GeneratedClass(superclasses, className, "@Serializable\nclass $className : ProtoBuf") } val superclasses = substringBetween("class", "extends").split("$").map { it.trim().adjustClassName() }.toMutableList() @@ -154,7 +154,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass { val ids = substringBetween("new int[]{", "}").split(",").map { it.trim() } if (ids.all { it.isBlank() }) { - return GeneratedClass(superclasses, className, "@Serializable\nclass $className") + return GeneratedClass(superclasses, className, "@Serializable\nclass $className : ProtoBuf") } val names = substringBetween("new String[]{", "}").split(",").map { it.trim() } @@ -326,7 +326,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass { append("\n") } - append(")") + append(") : ProtoBuf") } return GeneratedClass(superclasses, className, source) diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt index d2bb58323..1e7649f67 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt @@ -5,6 +5,8 @@ import io.ktor.client.engine.cio.CIO import io.ktor.util.KtorExperimentalAPI import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.io.pool.useInstance +import net.mamoe.mirai.utils.io.ByteArrayPool import java.io.ByteArrayOutputStream import java.io.DataInput import java.io.EOFException @@ -83,17 +85,23 @@ actual fun crc32(key: ByteArray): Int = CRC32().apply { update(key) }.value.toIn */ actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress -actual fun ByteArray.unzip(): ByteArray { +actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { + this.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + val inflater = Inflater() inflater.reset() - val output = ByteArrayOutputStream() - inflater.setInput(this) - val buffer = ByteArray(128) - while (!inflater.finished()) { - output.write(buffer, 0, inflater.inflate(buffer)) + ByteArrayOutputStream().use { output -> + inflater.setInput(this, offset, length) + ByteArrayPool.useInstance { + while (!inflater.finished()) { + output.write(it, 0, inflater.inflate(it)) + } + } + + inflater.end() + return output.toByteArray() } - inflater.end() - return output.toByteArray() } actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher { diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt index 3684a4d6a..53b936f87 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt @@ -15,7 +15,7 @@ actual class ECDHKeyPair( actual val privateKey: ECDHPrivateKey get() = delegate.private actual val publicKey: ECDHPublicKey get() = delegate.public - actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) + actual val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) } @Suppress("FunctionName") diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt index 6ef17cebb..8642e7bf8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt @@ -3,8 +3,6 @@ package net.mamoe.mirai import kotlinx.io.core.toByteArray -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.md5 import kotlin.annotation.AnnotationTarget.* @@ -12,17 +10,17 @@ data class BotAccount( /** * **注意**: 在 Android 协议, 总是使用 `QQAndroidClient.uin` 或 [Bot.uin], 而不要使用 [BotAccount.id]. 将来 [BotAccount.id] 可能会变为 [String] */ - @MiraiExperimentalAPI + @RawAccountIdUse val id: Long, val passwordMd5: ByteArray // md5 -){ +) { constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray())) } /** - * 标记直接访问 [BotAccount.id], 而不是访问 [Bot.uin]. 这将可能会不兼容未来的 API 修改. + * 标记直接访问 [BotAccount.id], 而不是访问 [Bot.uin]. 这可能会不兼容未来的 API 修改. */ @Retention(AnnotationRetention.SOURCE) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) -@Experimental +@Experimental(level = Experimental.Level.WARNING) annotation class RawAccountIdUse \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt index 7f958fd05..f334e4c40 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -13,6 +13,7 @@ import kotlin.coroutines.CoroutineContext /* * 泛型 N 不需要向外(接口)暴露. */ +@UseExperimental(MiraiExperimentalAPI::class) @MiraiInternalAPI abstract class BotImpl constructor( account: BotAccount, @@ -25,10 +26,9 @@ abstract class BotImpl constructor( @Suppress("CanBePrimaryConstructorProperty") // for logger final override val account: BotAccount = account - @UseExperimental(MiraiExperimentalAPI::class) - final override val uin: Long - get() = account.id - final override val logger: MiraiLogger = configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it } + @UseExperimental(RawAccountIdUse::class) + override val uin: Long get() = account.id + final override val logger: MiraiLogger by lazy { configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it } } init { @Suppress("LeakingThis") diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt index d11c1666f..536068c67 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -8,14 +8,14 @@ import net.mamoe.mirai.utils.coerceAtLeastOrFail /** - * 群. + * 群. 在 QQ Android 中叫做 "Troop" * * Group ID 与 Group Number 并不是同一个值. * - Group Number([Group.id]) 是通常使用的群号码.(在 QQ 客户端中可见) * - Group ID([Group.internalId]) 是与调用 API 时使用的 id.(在 QQ 客户端中不可见) * @author Him188moe */ -interface Group : Contact, CoroutineScope/*, Map*/ { // TODO: 2019/12/4 在 inline 稳定后实现 Map. 目前这样做会导致问题 +interface Group : Contact, CoroutineScope/*, Map*/ { // TODO: 2020/1/29 实现接口 Map /** * 内部 ID. 内部 ID 为 [GroupId] 的映射 */ @@ -86,7 +86,10 @@ fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this) /** * 将无符号整数格式的 [Long] 转为 [GroupId]. * - * 注: 在 Java 中常用 [Long] 来表示 [UInt] + * 注: 在 Java 中常用 [Long] 来表示 [UInt]. + * + * 注: 在 Kotlin/Java, 有符号的数据类型的二进制最高位为符号标志. + * 如一个 byte, `1000 0000` 最高位为 1, 则为负数. */ fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0)) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Packet.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Packet.kt index 0731954ae..1cf217c67 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Packet.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Packet.kt @@ -4,3 +4,12 @@ package net.mamoe.mirai.data * 从服务器收到的包解析之后的结构化数据. */ interface Packet + +/** + * PacketFactory 可以一次解析多个包出来. 它们将会被分别广播. + */ +class MultiPacket

(delegate: List

) : List

by delegate, Packet { + override fun toString(): String { + return "MultiPacket<${this.firstOrNull()?.let { it::class.simpleName }?: "?"}>" + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt index 130f907b8..bd4983bdd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt @@ -112,7 +112,7 @@ inline fun CoroutineScope.subscribeUntil(valueIfSt * @see subscribe 获取更多说明 */ inline fun CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener = - E::class.subscribeInternal(Handler { if (it.listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) + E::class.subscribeInternal(Handler { if (it.listener(it) != valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) // endregion diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt index 5708ecceb..5d5f072aa 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt @@ -5,6 +5,7 @@ package net.mamoe.mirai.network import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.io.PlatformDatagramChannel @@ -18,22 +19,26 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel * * [BotNetworkHandler] 的协程包含: * - UDP 包接收: [PlatformDatagramChannel.read] - * - 心跳 Job [HeartbeatPacket] - * - SKey 刷新 [RequestSKeyPacket] + * - 心跳 Job + * - Key 刷新 * - 所有数据包处理和发送 * - * [BotNetworkHandler.dispose] 时将会 [取消][kotlin.coroutines.CoroutineContext.cancelChildren] 所有此作用域下的协程 - * - * A BotNetworkHandler is used to connect with Tencent servers. + * [BotNetworkHandler.dispose] 时将会 [取消][Job.cancel] 所有此作用域下的协程 */ @Suppress("PropertyName") abstract class BotNetworkHandler : CoroutineScope { + /** + * 所属 [Bot]. 为弱引用 + */ abstract val bot: Bot + /** + * 监管 child [Job]s + */ abstract val supervisor: CompletableJob /** - * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果 + * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回. * 本函数将挂起直到登录成功. */ abstract suspend fun login() @@ -47,21 +52,12 @@ abstract class BotNetworkHandler : CoroutineScope { * 关闭网络接口, 停止所有有关协程和任务 */ open fun dispose(cause: Throwable? = null) { - supervisor.cancel(CancellationException("handler closed", cause)) + if (supervisor.isActive) { + if (cause != null) { + supervisor.cancel(CancellationException("handler closed", cause)) + } else { + supervisor.cancel() + } + } } - -/* - @PublishedApi - internal abstract fun CoroutineScope.QQ(bot: Bot, id: Long, coroutineContext: CoroutineContext): QQ - - @PublishedApi - internal abstract fun CoroutineScope.Group(bot: Bot, groupId: GroupId, info: RawGroupInfo, context: CoroutineContext): Group - - @PublishedApi - internal abstract fun Group.Member(delegate: QQ, permission: MemberPermission, coroutineContext: CoroutineContext): Member - - - */ - - } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt index 974ed9d44..ea91627d7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt @@ -24,7 +24,10 @@ expect val deviceName: String */ expect fun crc32(key: ByteArray): Int -expect fun ByteArray.unzip(): ByteArray +/** + * 解 zip 压缩 + */ +expect fun ByteArray.unzip(offset: Int = 0, length: Int = this.size - offset): ByteArray /** * MD5 算法 @@ -48,4 +51,10 @@ expect fun localIpAddress(): String */ expect val Http: HttpClient -expect fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher \ No newline at end of file +expect fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher + +internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int){ + require(offset >= 0) { "offset shouldn't be negative: $offset" } + require(length >= 0) { "length shouldn't be negative: $length" } + require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt index 5aeedb235..e922dd287 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt @@ -15,23 +15,46 @@ expect class ECDHKeyPair { val privateKey: ECDHPrivateKey val publicKey: ECDHPublicKey - val shareKey: ByteArray + /** + * 私匙和固定公匙([initialPublicKey]) 计算得到的 shareKey + */ + val initialShareKey: ByteArray } +/** + * 椭圆曲线密码, ECDH 加密 + */ expect class ECDH(keyPair: ECDHKeyPair) { val keyPair: ECDHKeyPair + /** + * 由 [keyPair] 的私匙和 [peerPublicKey] 计算 shareKey + */ fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray companion object { + /** + * 由完整的 publicKey ByteArray 得到 [ECDHPublicKey] + */ fun constructPublicKey(key: ByteArray): ECDHPublicKey + + /** + * 生成随机密匙对 + */ fun generateKeyPair(): ECDHKeyPair + + /** + * 由一对密匙计算 shareKey + */ fun calculateShareKey(privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey): ByteArray } override fun toString(): String } +/** + * + */ @Suppress("FunctionName") expect fun ECDH(): ECDH diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt index c5bf29291..2c598eead 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt @@ -38,6 +38,14 @@ fun ByteReadPacket.transferTo(outputStream: OutputStream) { } } +fun ByteReadPacket.useBytes( + n: Int = remaining.toInt(),//not that safe but adequate + block: (data: ByteArray, length: Int) -> R +): R = ByteArrayPool.useInstance { + this.readFully(it, 0, n) + block(it, n) +} + fun ByteReadPacket.readRemainingBytes( n: Int = remaining.toInt()//not that safe but adequate ): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) } @@ -75,10 +83,21 @@ fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort(). private inline fun inline(block: () -> R): R = block() -fun Input.readTLVMap(tagSize: Int = 2): MutableMap = readTLVMap(true, tagSize) + +typealias TlvMap = MutableMap + +fun TlvMap.getOrFail(tag: Int): ByteArray { + return this[tag] ?: error("cannot find tlv 0x${tag.toUHexString("")}($tag)") +} + +inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteArray { + return this[tag] ?: error(lazyMessage(tag)) +} + +fun Input.readTLVMap(tagSize: Int = 2): TlvMap = readTLVMap(true, tagSize) @Suppress("DuplicatedCode") -fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): MutableMap { +fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): TlvMap { val map = mutableMapOf() var key = 0 diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt index 262fdb04c..10c1566cb 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt @@ -7,8 +7,10 @@ import io.ktor.client.engine.cio.CIO import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.io.core.copyTo +import kotlinx.io.pool.useInstance import kotlinx.io.streams.asInput import kotlinx.io.streams.asOutput +import net.mamoe.mirai.utils.io.ByteArrayPool import java.io.* import java.net.InetAddress import java.security.MessageDigest @@ -56,18 +58,23 @@ actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress actual val Http: HttpClient get() = HttpClient(CIO) -actual fun ByteArray.unzip(): ByteArray { +actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { + this.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + val inflater = Inflater() inflater.reset() - val input = this - val output = ByteArrayOutputStream() - inflater.setInput(input) - val buffer = ByteArray(128) - while (!inflater.finished()) { - output.write(buffer, 0, inflater.inflate(buffer)) + ByteArrayOutputStream().use { output -> + inflater.setInput(this, offset, length) + ByteArrayPool.useInstance { + while (!inflater.finished()) { + output.write(it, 0, inflater.inflate(it)) + } + } + + inflater.end() + return output.toByteArray() } - inflater.end() - return output.toByteArray() } actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher { diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt index 66a06aa2a..13c7979a9 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt @@ -17,7 +17,7 @@ actual class ECDHKeyPair( actual val privateKey: ECDHPrivateKey get() = delegate.private actual val publicKey: ECDHPublicKey get() = delegate.public - actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) + actual val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) } @Suppress("FunctionName") diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt index 1889c8bba..bfa804929 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt @@ -36,7 +36,11 @@ actual class PlatformSocket : Closeable { * @throws SendPacketInternalException */ actual suspend inline fun send(packet: ByteReadPacket) { - writeChannel.writePacket(packet) + try { + writeChannel.writePacket(packet) + } catch (e: Exception) { + throw SendPacketInternalException(e) + } } /** @@ -45,7 +49,11 @@ actual class PlatformSocket : Closeable { actual suspend inline fun read(): ByteReadPacket { // do not use readChannel.readRemaining() !!! this function never returns ByteArrayPool.useInstance { buffer -> - val count = readChannel.readAvailable(buffer) + val count = try { + readChannel.readAvailable(buffer) + } catch (e: Exception) { + throw ReadPacketInternalException(e) + } return buffer.toReadPacket(0, count) } }