Merge remote-tracking branch 'origin/master'

# Conflicts:
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/data/MsgSvc.kt
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt
#	mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt
This commit is contained in:
jiahua.liu 2020-01-29 20:17:20 +08:00
commit b2542cd6bf
45 changed files with 1532 additions and 536 deletions

View File

@ -5,27 +5,30 @@
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
**[English](README-eng.md)** **[English](README-eng.md)**
**TIM PC 和 QQ Android 协议** 跨平台 QQ 协议支持库. 跨平台 **TIM PC 和 QQ Android** 协议支持库.
**纯 Kotlin 实现协议和支持框架. 目前可运行在 JVM 或 Android.** 纯 Kotlin 实现协议和支持框架,模块全部开源。
目前可运行在 JVM 或 Android。
**一切开发旨在学习,请勿用于非法用途** **一切开发旨在学习,请勿用于非法用途**
您可在 Gitter 提问, 或加入 QQ 群: 655057127 加入 Gitter, 或加入 QQ 群: 655057127
## Update log ## Update log
在 [Project](https://github.com/mamoe/mirai/projects/1) 查看已支持功能和计划 在 [Project](https://github.com/mamoe/mirai/projects/1) 查看已支持功能和计划(更新不及时)
在 [UpdateLog](https://github.com/mamoe/mirai/blob/master/UpdateLog.md) 查看版本更新记录 在 [UpdateLog](https://github.com/mamoe/mirai/blob/master/UpdateLog.md) 查看版本更新记录(准确更新发布的版本)
## Features ## Modules
#### mirai-core #### mirai-core
通用 API 模块,请参考此模块调用 Mirai. 通用 API 模块,一套 API 适配两套协议。
**请参考此模块的 API**
#### mirai-core-timpc #### mirai-core-timpc
TIM PC 2.3.2 版本2019 年 8 月)协议的实现,相较于 core仅新增少量 API. 详见 [README.md](mirai-core-timpc/) TIM PC 2.3.2 版本2019 年 8 月)协议的实现,相较于 core仅新增少量 API. 详见 [README.md](mirai-core-timpc/)
支持的功能: 支持的功能:
- 消息收发:图片文字复合消息,图片消息 - 消息收发:图片文字复合消息,图片消息
- 群管功能:群员列表,禁言 - 群管功能:群员列表,禁言
(目前不再更新,请关注安卓协议) (目前不再更新此协议,请关注下文的安卓协议)
#### mirai-core-qqandroid #### mirai-core-qqandroid
QQ for Android 8.2.0 版本2019 年 12 月)协议的实现,目前还未完成。 QQ for Android 8.2.0 版本2019 年 12 月)协议的实现,目前还未完成。
@ -42,25 +45,31 @@ QQ for Android 8.2.0 版本2019 年 12 月)协议的实现,目前还
- 进行中 图片上传和下载 - 进行中 图片上传和下载
## Use directly ## Use directly
**直接使用Mirai(终端环境/网页面板(将来)).** **直接使用 Mirai(终端环境/网页面板(将来)).**
[Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动Mirai并获得机器人服务 [Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动 Mirai 并获得机器人服务
本模块还未完善。
## Use as a library ## Use as a library
**mirai-core 为独立设计, 可以作为库内置于您的任意 Java/Android 项目中使用.** **mirai-core 为独立设计, 可以作为库内置于任意 Java(JVM)/Android 项目中使用.**
Mirai 只上传在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库
### Gradle
Mirai 只发布在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库:
```kotlin ```kotlin
repositories{ repositories{
jcenter() jcenter()
} }
``` ```
若您需要使用在跨平台项目, 您需要对各个目标平台添加不同的依赖. 若您需要使用在跨平台项目, 则要对各个目标平台添加不同的依赖,这与 kotlin 相关跨平台库的依赖是类似的。
**若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在JVM运行则只需要`-jvm`的依赖** **若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在 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/) [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
**Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.** **Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.**
现在 Mirai 只支持 TIM PC 协议. QQ Android 协议正在开发中. **注意:**
Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。
只添加 API 模块将无法正常工作。
现在只推荐使用 TIMPC 协议,请参照下文选择对应目标平台的依赖添加。
**common** **common**
```kotlin ```kotlin
@ -90,7 +99,7 @@ JVM 上需 120M-150M 内存
您的 star 是对我们最大的鼓励(点击项目右上角); 您的 star 是对我们最大的鼓励(点击项目右上角);
## Wiki ## 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 ## Try
@ -110,6 +119,8 @@ bot.subscribeAlways<MemberPermissionChangedEvent> {
} }
``` ```
我们也考虑到了 Java 兼容的问题,这正在计划中,但不是高优先的。
1. Clone 1. Clone
2. Import as Gradle project 2. Import as Gradle project
3. 运行 Demo 程序: [mirai-demo](#mirai-demo) 示例和演示程序 3. 运行 Demo 程序: [mirai-demo](#mirai-demo) 示例和演示程序
@ -119,6 +130,7 @@ bot.subscribeAlways<MemberPermissionChangedEvent> {
- Kotlin 1.3.61 - Kotlin 1.3.61
- JDK 8 (required) - JDK 8 (required)
- JDK 11for protocol tools, optional
- Android SDK 29 (for Android target, optional) - Android SDK 29 (for Android target, optional)
#### Libraries used #### Libraries used
@ -144,5 +156,5 @@ bot.subscribeAlways<MemberPermissionChangedEvent> {
- (见 LICENSE 第 4 节) 您可以免费或收费地传递这个项目的源代码或目标代码(即编译结果), **但前提是提供明显的版权声明** (您需要标注本 `GitHub` 项目地址) - (见 LICENSE 第 4 节) 您可以免费或收费地传递这个项目的源代码或目标代码(即编译结果), **但前提是提供明显的版权声明** (您需要标注本 `GitHub` 项目地址)
## Acknowledgement ## 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 授权
[<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai) [<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai)

View File

@ -8,14 +8,15 @@ import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient 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.Context
import net.mamoe.mirai.qqandroid.utils.ImageIdQQA
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
internal expect class QQAndroidBot( @UseExperimental(MiraiInternalAPI::class)
internal expect class QQAndroidBot constructor(
context: Context, context: Context,
account: BotAccount, account: BotAccount,
configuration: BotConfiguration configuration: BotConfiguration
@ -28,7 +29,7 @@ internal abstract class QQAndroidBotBase constructor(
configuration: BotConfiguration configuration: BotConfiguration
) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) { ) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) {
val client: QQAndroidClient = QQAndroidClient(context, account, bot = @Suppress("LeakingThis") this as QQAndroidBot) val client: QQAndroidClient = QQAndroidClient(context, account, bot = @Suppress("LeakingThis") this as QQAndroidBot)
override val uin: Long get() = client.uin
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList()) override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
override fun getQQ(id: Long): QQ { override fun getQQ(id: Long): QQ {

View File

@ -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 <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
this.writeFully(v.toByteArray(serializer))
}
/**
* dump
*/
fun <T : ProtoBuf> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray {
return kotlinx.serialization.protobuf.ProtoBuf.dump(serializer, this)
}
/**
* load
*/
fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>): T {
return kotlinx.serialization.protobuf.ProtoBuf.load(deserializer, this)
}
/**
* load
*/
fun <T : ProtoBuf> Input.readRemainingAsProtoBuf(serializer: DeserializationStrategy<T>): T {
return kotlinx.serialization.protobuf.ProtoBuf.load(serializer, this.readBytes())
}

View File

@ -8,7 +8,7 @@ import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule import kotlinx.serialization.modules.SerialModule
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.network.protocol.packet.withUse 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.readIoBuffer
import net.mamoe.mirai.utils.io.readString import net.mamoe.mirai.utils.io.readString
import net.mamoe.mirai.utils.io.toIoBuffer import net.mamoe.mirai.utils.io.toIoBuffer

View File

@ -1,8 +1,11 @@
package net.mamoe.mirai.qqandroid.network package net.mamoe.mirai.qqandroid.network
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.ObjectPool import kotlinx.io.pool.ObjectPool
import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.Cancellable 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.event.PacketReceivedEvent
import net.mamoe.mirai.qqandroid.network.protocol.packet.KnownPacketFactories 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.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
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.* import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket.LoginPacketResponse.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc 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 net.mamoe.mirai.utils.io.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@Suppress("MemberVisibilityCanBePrivate")
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler() { internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler() {
override val bot: QQAndroidBot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
@ -38,9 +44,9 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
when (response) { when (response) {
is UnsafeLogin -> { is UnsafeLogin -> {
bot.logger.info("Login unsuccessful, device auth is needed") 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("Open the following URL in QQ browser and complete the verification")
bot.logger.info("将下面这个链接在QQ浏览器中打开并完成认证后尝试再次登") bot.logger.info("将下面这个链接在QQ浏览器中打开并完成认证后尝试再次登")
bot.logger.info(response.url) bot.logger.info(response.url)
return return
} }
@ -99,10 +105,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
private val PacketProcessDispatcher = newCoroutineDispatcher(1) private val PacketProcessDispatcher = newCoroutineDispatcher(1)
/**
* 缓存超时处理的 [Job]. 超时后将清空缓存, 以免阻碍后续包的处理
*/
private var cachedPacketTimeoutJob: Job? = null
/** /**
* 缓存的包 * 缓存的包
*/ */
private var cachedPacket: ByteReadPacket? = null private val cachedPacket: AtomicRef<ByteReadPacket?> = atomic(null)
/** /**
* 缓存的包还差多少长度 * 缓存的包还差多少长度
*/ */
@ -144,19 +154,42 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
* @param input 一个完整的包的内容, 去掉开头的 int 包长度 * @param input 一个完整的包的内容, 去掉开头的 int 包长度
*/ */
suspend fun parsePacket(input: Input) { suspend fun parsePacket(input: Input) {
try { generifiedParsePacket<Packet>(input)
KnownPacketFactories.parseIncomingPacket(bot, input) { packet: Packet, commandName: String, sequenceId: Int ->
if (PacketReceivedEvent(packet).broadcast().cancelled) {
return@parseIncomingPacket
} }
// pass to listeners (attached by sendAndExpect). private suspend inline fun <P : Packet> generifiedParsePacket(input: Input) {
try {
KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int ->
handlePacket(packetFactory, packet, commandName, sequenceId)
if (packet is MultiPacket<*>) {
packet.forEach {
handlePacket(null, it, commandName, sequenceId)
}
}
}
} finally {
println()
println() // separate for debugging
}
}
/**
* 处理解析完成的包.
*/
suspend fun <P : Packet> handlePacket(packetFactory: PacketFactory<P>?, packet: P, commandName: String, sequenceId: Int) {
// highest priority: pass to listeners (attached by sendAndExpect).
packetListeners.forEach { listener -> packetListeners.forEach { listener ->
if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) { if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) {
listener.complete(packet) listener.complete(packet)
} }
} }
// check top-level cancelling
if (PacketReceivedEvent(packet).broadcast().cancelled) {
return
}
// broadcast // broadcast
if (packet is Subscribable) { if (packet is Subscribable) {
if (packet is BroadcastControllable) { if (packet is BroadcastControllable) {
@ -165,69 +198,87 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
packet.broadcast() packet.broadcast()
} }
if (packet is Cancellable && packet.cancelled) return@parseIncomingPacket if (packet is Cancellable && packet.cancelled) return
} }
bot.logger.info(packet) bot.logger.info(packet)
}
} finally { packetFactory?.run {
println() bot.handle(packet)
println() // separate for debugging
} }
} }
/** /**
* 处理从服务器接收过来的包. 这些包可能是粘在一起的, 也可能是不完整的. 将会自动处理 * 处理从服务器接收过来的包. 这些包可能是粘在一起的, 也可能是不完整的. 将会自动处理.
* 处理后的包会调用 [parsePacketAsync]
*/ */
@UseExperimental(ExperimentalCoroutinesApi::class) @UseExperimental(ExperimentalCoroutinesApi::class)
internal fun processPacket(rawInput: ByteReadPacket): Unit = rawInput.debugPrint("Received").let { input: ByteReadPacket -> internal fun processPacket(rawInput: ByteReadPacket) {
if (input.remaining == 0L) { if (rawInput.remaining == 0L) {
return return
} }
if (cachedPacket == null) { val cache = cachedPacket.value
if (cache == null) {
// 没有缓存 // 没有缓存
var length: Int = input.readInt() - 4 var length: Int = rawInput.readInt() - 4
if (input.remaining == length.toLong()) { if (rawInput.remaining == length.toLong()) {
// 捷径: 当包长度正好, 直接传递剩余数据. // 捷径: 当包长度正好, 直接传递剩余数据.
parsePacketAsync(input) cachedPacketTimeoutJob?.cancel()
parsePacketAsync(rawInput)
return return
} }
// 循环所有完整的包 // 循环所有完整的包
while (input.remaining > length) { while (rawInput.remaining > length) {
parsePacketAsync(input.readIoBuffer(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 expectingRemainingLength = length - rawInput.remaining
cachedPacket = input cachedPacket.value = rawInput
} else { } else {
cachedPacket = null // 表示包长度正好 cachedPacket.value = null // 表示包长度正好
cachedPacketTimeoutJob?.cancel()
return
} }
} else { } else {
// 有缓存 // 有缓存
if (input.remaining >= expectingRemainingLength) { if (rawInput.remaining >= expectingRemainingLength) {
// 剩余长度够, 连接上去, 处理这个包. // 剩余长度够, 连接上去, 处理这个包.
parsePacketAsync(buildPacket { parsePacketAsync(buildPacket {
writePacket(cachedPacket!!) writePacket(cache)
writePacket(input, expectingRemainingLength) writePacket(rawInput, expectingRemainingLength)
}) })
cachedPacket = null // 缺少的长度已经给上了. cachedPacket.value = null // 缺少的长度已经给上了.
if (input.remaining != 0L) { if (rawInput.remaining != 0L) {
processPacket(input) // 继续处理剩下内容 return processPacket(rawInput) // 继续处理剩下内容
} else {
// 处理好了.
cachedPacketTimeoutJob?.cancel()
return
} }
} else { } else {
// 剩余不够, 连接上去 // 剩余不够, 连接上去
expectingRemainingLength -= input.remaining expectingRemainingLength -= rawInput.remaining
cachedPacket = buildPacket { // do not inline `packet`. atomicfu unsupported
writePacket(cachedPacket!!) val packet = buildPacket {
writePacket(input) 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() channel.read()
} catch (e: ClosedChannelException) { } catch (e: ClosedChannelException) {
dispose() dispose()
bot.tryReinitializeNetworkHandler(e)
return return
} catch (e: ReadPacketInternalException) { } catch (e: ReadPacketInternalException) {
bot.logger.error("Socket channel read failed: ${e.message}") bot.logger.error("Socket channel read failed: ${e.message}")
continue dispose()
bot.tryReinitializeNetworkHandler(e)
return
} catch (e: CancellationException) { } catch (e: CancellationException) {
return return
} catch (e: Throwable) { } catch (e: Throwable) {
@ -256,19 +310,24 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
} }
/**
* 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
*/
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E { suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId) val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
packetListeners.addLast(handler) packetListeners.addLast(handler)
channel.send(delegate) channel.send(delegate)
return withTimeout(3000) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return handler.await() as E handler.await() as E
}
} }
@PublishedApi @PublishedApi
internal val packetListeners = LockFreeLinkedList<PacketListener>() internal val packetListeners = LockFreeLinkedList<PacketListener>()
@PublishedApi @PublishedApi
internal inner class PacketListener( internal inner class PacketListener( // callback
val commandName: String, val commandName: String,
val sequenceId: Int val sequenceId: Int
) : CompletableDeferred<Packet> by CompletableDeferred(supervisor) { ) : CompletableDeferred<Packet> by CompletableDeferred(supervisor) {
@ -277,10 +336,5 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
override suspend fun awaitDisconnection() = supervisor.join() override suspend fun awaitDisconnection() = supervisor.join()
override fun dispose(cause: Throwable?) {
println("Closed")
super.dispose(cause)
}
override val coroutineContext: CoroutineContext = bot.coroutineContext override val coroutineContext: CoroutineContext = bot.coroutineContext
} }

View File

@ -5,6 +5,7 @@ import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.RawAccountIdUse
import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.data.OnlineStatus
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
@ -32,7 +33,7 @@ import net.mamoe.mirai.utils.unsafeWeakRef
DOMAINS DOMAINS
Pskey: "openmobile.qq.com" Pskey: "openmobile.qq.com"
*/ */
@UseExperimental(MiraiExperimentalAPI::class, MiraiInternalAPI::class)
@PublishedApi @PublishedApi
internal open class QQAndroidClient( internal open class QQAndroidClient(
context: Context, context: Context,
@ -53,7 +54,7 @@ internal open class QQAndroidClient(
"tgtgtKey" to tgtgtKey, "tgtgtKey" to tgtgtKey,
"tgtKey" to wLoginSigInfo.tgtKey, "tgtKey" to wLoginSigInfo.tgtKey,
"deviceToken" to wLoginSigInfo.deviceToken, "deviceToken" to wLoginSigInfo.deviceToken,
"shareKeyCalculatedByConstPubKey" to ecdh.keyPair.shareKey "shareKeyCalculatedByConstPubKey" to ecdh.keyPair.initialShareKey
//"t108" to wLoginSigInfo.t1, //"t108" to wLoginSigInfo.t1,
//"t10c" to t10c, //"t10c" to t10c,
//"t163" to t163 //"t163" to t163
@ -69,7 +70,6 @@ internal open class QQAndroidClient(
return null return null
} }
@UseExperimental(MiraiInternalAPI::class)
override fun toString(): String { // net.mamoe.mirai.utils.cryptor.ProtoKt.contentToString 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')" 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() val apkVersionName: ByteArray = "8.2.0".toByteArray()
var loginState = 0
val appClientVersion: Int = 0 val appClientVersion: Int = 0
@ -108,14 +107,14 @@ internal open class QQAndroidClient(
*/ */
val protocolVersion: Short = 8001 val protocolVersion: Short = 8001
/*
* 以下登录使用
*/
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
@PublishedApi @PublishedApi
internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray() internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray()
/* var loginState = 0
* 以下登录使用
*/
var t150: Tlv? = null var t150: Tlv? = null
var rollbackSig: ByteArray? = null var rollbackSig: ByteArray? = null
@ -129,8 +128,12 @@ internal open class QQAndroidClient(
* *
* **注意**: 总是使用这个属性, 而不要使用 [BotAccount.id]. 将来它可能会变为 [String] * **注意**: 总是使用这个属性, 而不要使用 [BotAccount.id]. 将来它可能会变为 [String]
*/ */
@UseExperimental(MiraiExperimentalAPI::class, MiraiInternalAPI::class) val uin: Long get() = _uin
var uin: Long = bot.account.id
@UseExperimental(RawAccountIdUse::class)
@Suppress("PropertyName")
internal var _uin: Long = bot.account.id
var t530: ByteArray? = null var t530: ByteArray? = null
var t528: ByteArray? = null var t528: ByteArray? = null
/** /**

View File

@ -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("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)
} }
} }

View File

@ -7,8 +7,6 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.MiraiInternalAPI 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.encryptAndWrite
import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeIntLVPacket import net.mamoe.mirai.utils.io.writeIntLVPacket
@ -26,7 +24,7 @@ internal class OutgoingPacket constructor(
val delegate: ByteReadPacket val delegate: ByteReadPacket
) { ) {
val name: String by lazy { 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 * 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) @UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*>.buildOutgingPacket( internal inline fun PacketFactory<*>.buildOutgoingPacket(
client: QQAndroidClient, client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB?
name: String? = this.commandName, name: String? = this.commandName,
commandName: String = this.commandName, commandName: String = this.commandName,
key: ByteArray, key: ByteArray = client.wLoginSigInfo.d2Key,
body: BytePacketBuilder.(sequenceId: Int) -> Unit body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket { ): OutgoingPacket {
val sequenceId: Int = client.nextSsoSequenceId() val sequenceId: Int = client.nextSsoSequenceId()
@ -73,7 +62,7 @@ internal inline fun PacketFactory<*>.buildOutgingPacket(
return OutgoingPacket(name, commandName, sequenceId, buildPacket { return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) { writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B) writeInt(0x0B)
writeByte(1) writeByte(bodyType)
writeInt(sequenceId) writeInt(sequenceId)
writeByte(0) writeByte(0)
client.uin.toString().let { 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 * Writes a request packet
* This is the innermost packet structure * This is the innermost packet structure
@ -293,6 +313,8 @@ internal fun BytePacketBuilder.writeOicqRequestPacket(
writeByte(0x03) // tail writeByte(0x03) // tail
// } // }
} }
/* /*
00 00 01 64 00 00 01 64
00 00 00 0A 00 00 00 0A

View File

@ -3,18 +3,20 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.Subscribable
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.loadAs 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.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush 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.LoginPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.StatSvc 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.DefaultLogger
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.cryptor.adjustToPublicKey import net.mamoe.mirai.utils.cryptor.adjustToPublicKey
import net.mamoe.mirai.utils.cryptor.decryptBy import net.mamoe.mirai.utils.cryptor.decryptBy
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.unzip
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -26,16 +28,21 @@ import kotlin.jvm.JvmName
* @param TPacket 服务器回复包解析结果 * @param TPacket 服务器回复包解析结果
*/ */
@UseExperimental(ExperimentalUnsignedTypes::class) @UseExperimental(ExperimentalUnsignedTypes::class)
internal abstract class PacketFactory<out TPacket : Packet>( internal abstract class PacketFactory<TPacket : Packet>(
/** /**
* 命令名. `wtlogin.login`, `ConfigPushSvc.PushDomain` * 命令名. `wtlogin.login`, `ConfigPushSvc.PushDomain`
*/ */
val commandName: String val commandName: String
) { ) {
/** /**
* **解码**服务器的回复数据包 * **解码**服务器的回复数据包. 返回的包若是 [Subscribable], 则会 broadcast.
*/ */
abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket
/**
* 可选的处理这个包. 可以在这里面发新的包.
*/
open suspend fun QQAndroidBot.handle(packet: TPacket) {}
} }
@JvmName("decode0") @JvmName("decode0")
@ -43,7 +50,7 @@ private suspend inline fun <P : Packet> PacketFactory<P>.decode(bot: QQAndroidBo
internal val DECRYPTER_16_ZERO = ByteArray(16) internal val DECRYPTER_16_ZERO = ByteArray(16)
internal typealias PacketConsumer = suspend (packet: Packet, commandName: String, ssoSequenceId: Int) -> Unit internal typealias PacketConsumer<T> = suspend (packetFactory: PacketFactory<T>, packet: T, commandName: String, ssoSequenceId: Int) -> Unit
@PublishedApi @PublishedApi
internal val PacketLogger: MiraiLogger = DefaultLogger("Packet") internal val PacketLogger: MiraiLogger = DefaultLogger("Packet")
@ -53,7 +60,8 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
LoginPacket, LoginPacket,
StatSvc.Register, StatSvc.Register,
OnlinePush.PbPushGroupMsg, OnlinePush.PbPushGroupMsg,
MessageSvc.PushNotify MessageSvc.PushNotify,
MessageSvc.PbGetMsg
) { ) {
fun findPacketFactory(commandName: String): PacketFactory<*>? = this.firstOrNull { it.commandName == commandName } fun findPacketFactory(commandName: String): PacketFactory<*>? = this.firstOrNull { it.commandName == commandName }
@ -66,7 +74,8 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
* full packet without length * full packet without length
*/ */
// do not inline. Exceptions thrown will not be reported correctly // do not inline. Exceptions thrown will not be reported correctly
suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: Input, consumer: PacketConsumer) { @Suppress("UNCHECKED_CAST")
suspend fun <T : Packet> parseIncomingPacket(bot: QQAndroidBot, rawInput: Input, consumer: PacketConsumer<T>) {
rawInput.readBytes().let { rawInput.readBytes().let {
PacketLogger.verbose("开始处理包: ${it.toUHexString()}") PacketLogger.verbose("开始处理包: ${it.toUHexString()}")
it.toReadPacket() it.toReadPacket()
@ -111,12 +120,14 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
// 解析外层包装 // 解析外层包装
when (flag1) { when (flag1) {
0x0A -> parseSsoFrame(bot, decryptedData) 0x0A -> parseSsoFrame(bot, decryptedData)
0x0B -> parseUniFrame(bot, decryptedData) 0x0B -> parseSsoFrame(bot, decryptedData) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样.
else -> error("unknown flag1: ${flag1.toByte().toUHexString()}") else -> error("unknown flag1: ${flag1.toByte().toUHexString()}")
} }
}?.let { }?.let {
// 处理内层真实的包 // 处理内层真实的包
if (it.packetFactory == null) { if (it.packetFactory == null) {
PacketLogger.warning("找不到 PacketFactory")
PacketLogger.verbose("传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length -> data.toUHexString(length = length) }}")
return return
} }
@ -124,12 +135,13 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
1 ->//it.data.parseUniResponse(bot, it.packetFactory, it.sequenceId, consumer) 1 ->//it.data.parseUniResponse(bot, it.packetFactory, it.sequenceId, consumer)
{ {
consumer( consumer(
it.packetFactory as PacketFactory<T>,
it.packetFactory.run { decode(bot, it.data) }, it.packetFactory.run { decode(bot, it.data) },
it.packetFactory.commandName, it.packetFactory.commandName,
it.sequenceId it.sequenceId
) )
} }
2 -> it.data.parseOicqResponse(bot, it.packetFactory, it.sequenceId, consumer) 2 -> it.data.parseOicqResponse(bot, it.packetFactory as PacketFactory<T>, it.sequenceId, consumer)
else -> error("unknown flag2: $flag2. Body to be parsed for inner packet=${it.data.readBytes().toUHexString()}") else -> error("unknown flag2: $flag2. Body to be parsed for inner packet=${it.data.readBytes().toUHexString()}")
} }
} ?: inline { } ?: inline {
@ -137,7 +149,6 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
PacketLogger.error("任何key都无法解密: ${data.take(size).toUHexString()}") PacketLogger.error("任何key都无法解密: ${data.take(size).toUHexString()}")
return return
} }
} }
} }
} }
@ -173,7 +184,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket { private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket {
val commandName: String val commandName: String
val ssoSequenceId: Int val ssoSequenceId: Int
val dataCompressed: Int
// head // head
input.readIoBuffer(input.readInt() - 4).withUse { input.readIoBuffer(input.readInt() - 4).withUse {
ssoSequenceId = readInt() ssoSequenceId = readInt()
@ -186,35 +197,46 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
val unknown = readBytes(readInt() - 4) val unknown = readBytes(readInt() - 4)
if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}") 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 // body
val packetFactory = findPacketFactory(commandName) val packetFactory = findPacketFactory(commandName)
bot.logger.verbose(commandName) bot.logger.info("Received: $commandName")
if (packetFactory == null) { return IncomingPacket(packetFactory, ssoSequenceId, packet)
bot.logger.warning("找不到包 PacketFactory")
PacketLogger.verbose("传递给 PacketFactory 的数据 = ${input.readBytes().toUHexString()}")
}
return IncomingPacket(packetFactory, ssoSequenceId, input)
} }
private suspend fun ByteReadPacket.parseOicqResponse(bot: QQAndroidBot, packetFactory: PacketFactory<*>, ssoSequenceId: Int, consumer: PacketConsumer) { private suspend fun <T : Packet> ByteReadPacket.parseOicqResponse(
val qq: Long bot: QQAndroidBot,
packetFactory: PacketFactory<T>,
ssoSequenceId: Int,
consumer: PacketConsumer<T>
) {
readIoBuffer(readInt() - 4).withUse { readIoBuffer(readInt() - 4).withUse {
check(readByte().toInt() == 2) check(readByte().toInt() == 2)
this.discardExact(2) // 27 + 2 + body.size this.discardExact(2) // 27 + 2 + body.size
this.discardExact(2) // const, =8001 this.discardExact(2) // const, =8001
this.readUShort() // commandId this.readUShort() // commandId
this.readShort() // const, =0x0001 this.readShort() // const, =0x0001
qq = this.readUInt().toLong() this.readUInt().toLong() // qq
val encryptionMethod = this.readUShort().toInt() val encryptionMethod = this.readUShort().toInt()
this.discardExact(1) // const = 0 this.discardExact(1) // const = 0
val packet = when (encryptionMethod) { val packet = when (encryptionMethod) {
4 -> { // peer public key, ECDH 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()) val peerShareKey = bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey())
data = data.decryptBy(peerShareKey) data = data.decryptBy(peerShareKey)
@ -228,7 +250,7 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
this.readFully(byteArrayBuffer, 0, size) this.readFully(byteArrayBuffer, 0, size)
runCatching { runCatching {
byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.shareKey, size) byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.initialShareKey, size)
}.getOrElse { }.getOrElse {
byteArrayBuffer.decryptBy(bot.client.randomKey, size) byteArrayBuffer.decryptBy(bot.client.randomKey, size)
} // 这里实际上应该用 privateKey(另一个random出来的key) } // 这里实际上应该用 privateKey(另一个random出来的key)
@ -243,11 +265,16 @@ internal object KnownPacketFactories : List<PacketFactory<*>> by mutableListOf(
else -> error("Illegal encryption method. expected 0 or 4, got $encryptionMethod") 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<Packet>
) {
val uni = readBytes(readInt() - 4).loadAs(RequestPacket.serializer()) val uni = readBytes(readInt() - 4).loadAs(RequestPacket.serializer())
PacketLogger.verbose(uni.toString()) 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)

View File

@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
@Serializable @Serializable
internal class Cmd0x352Packet( internal class Cmd0x352Packet(

View File

@ -3,7 +3,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY 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 import net.mamoe.mirai.utils.currentTimeSeconds
interface ImgReq : ProtoBuf interface ImgReq : ProtoBuf

View File

@ -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
}

View File

@ -4,8 +4,8 @@ import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumberType import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType 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.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
@Serializable @Serializable
class ImCommon : ProtoBuf { class ImCommon : ProtoBuf {
@ -318,59 +318,59 @@ class ImMsgBody : ProtoBuf {
@Serializable @Serializable
class Elem( class Elem(
@SerialId(1) val text: ImMsgBody.Text? = null, @SerialId(1) val text: Text? = null,
@SerialId(2) val face: ImMsgBody.Face? = null, @SerialId(2) val face: Face? = null,
@SerialId(3) val onlineImage: ImMsgBody.OnlineImage? = null, @SerialId(3) val onlineImage: OnlineImage? = null,
@SerialId(4) val notOnlineImage: ImMsgBody.NotOnlineImage? = null, @SerialId(4) val notOnlineImage: NotOnlineImage? = null,
@SerialId(5) val transElemInfo: ImMsgBody.TransElem? = null, @SerialId(5) val transElemInfo: TransElem? = null,
@SerialId(6) val marketFace: ImMsgBody.MarketFace? = null, @SerialId(6) val marketFace: MarketFace? = null,
@SerialId(7) val elemFlags: ImMsgBody.ElemFlags? = null, @SerialId(7) val elemFlags: ElemFlags? = null,
@SerialId(8) val customFace: ImMsgBody.CustomFace? = null, @SerialId(8) val customFace: CustomFace? = null,
@SerialId(9) val elemFlags2: ImMsgBody.ElemFlags2? = null, @SerialId(9) val elemFlags2: ElemFlags2? = null,
@SerialId(10) val funFace: ImMsgBody.FunFace? = null, @SerialId(10) val funFace: FunFace? = null,
@SerialId(11) val secretFile: ImMsgBody.SecretFileMsg? = null, @SerialId(11) val secretFile: SecretFileMsg? = null,
@SerialId(12) val richMsg: ImMsgBody.RichMsg? = null, @SerialId(12) val richMsg: RichMsg? = null,
@SerialId(13) val groupFile: ImMsgBody.GroupFile? = null, @SerialId(13) val groupFile: GroupFile? = null,
@SerialId(14) val pubGroup: ImMsgBody.PubGroup? = null, @SerialId(14) val pubGroup: PubGroup? = null,
@SerialId(15) val marketTrans: ImMsgBody.MarketTrans? = null, @SerialId(15) val marketTrans: MarketTrans? = null,
@SerialId(16) val extraInfo: ImMsgBody.ExtraInfo? = null, @SerialId(16) val extraInfo: ExtraInfo? = null,
@SerialId(17) val shakeWindow: ImMsgBody.ShakeWindow? = null, @SerialId(17) val shakeWindow: ShakeWindow? = null,
@SerialId(18) val pubAccount: ImMsgBody.PubAccount? = null, @SerialId(18) val pubAccount: PubAccount? = null,
@SerialId(19) val videoFile: ImMsgBody.VideoFile? = null, @SerialId(19) val videoFile: VideoFile? = null,
@SerialId(20) val tipsInfo: ImMsgBody.TipsInfo? = null, @SerialId(20) val tipsInfo: TipsInfo? = null,
@SerialId(21) val anonGroupMsg: ImMsgBody.AnonymousGroupMsg? = null, @SerialId(21) val anonGroupMsg: AnonymousGroupMsg? = null,
@SerialId(22) val qqLiveOld: ImMsgBody.QQLiveOld? = null, @SerialId(22) val qqLiveOld: QQLiveOld? = null,
@SerialId(23) val lifeOnline: ImMsgBody.LifeOnlineAccount? = null, @SerialId(23) val lifeOnline: LifeOnlineAccount? = null,
@SerialId(24) val qqwalletMsg: ImMsgBody.QQWalletMsg? = null, @SerialId(24) val qqwalletMsg: QQWalletMsg? = null,
@SerialId(25) val crmElem: ImMsgBody.CrmElem? = null, @SerialId(25) val crmElem: CrmElem? = null,
@SerialId(26) val conferenceTipsInfo: ImMsgBody.ConferenceTipsInfo? = null, @SerialId(26) val conferenceTipsInfo: ConferenceTipsInfo? = null,
@SerialId(27) val redbagInfo: ImMsgBody.RedBagInfo? = null, @SerialId(27) val redbagInfo: RedBagInfo? = null,
@SerialId(28) val lowVersionTips: ImMsgBody.LowVersionTips? = null, @SerialId(28) val lowVersionTips: LowVersionTips? = null,
@SerialId(29) val bankcodeCtrlInfo: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(29) val bankcodeCtrlInfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(30) val nearByMsg: ImMsgBody.NearByMessageType? = null, @SerialId(30) val nearByMsg: NearByMessageType? = null,
@SerialId(31) val customElem: ImMsgBody.CustomElem? = null, @SerialId(31) val customElem: CustomElem? = null,
@SerialId(32) val locationInfo: ImMsgBody.LocationInfo? = null, @SerialId(32) val locationInfo: LocationInfo? = null,
@SerialId(33) val pubAccInfo: ImMsgBody.PubAccInfo? = null, @SerialId(33) val pubAccInfo: PubAccInfo? = null,
@SerialId(34) val smallEmoji: ImMsgBody.SmallEmoji? = null, @SerialId(34) val smallEmoji: SmallEmoji? = null,
@SerialId(35) val fsjMsgElem: ImMsgBody.FSJMessageElem? = null, @SerialId(35) val fsjMsgElem: FSJMessageElem? = null,
@SerialId(36) val arkApp: ImMsgBody.ArkAppElem? = null, @SerialId(36) val arkApp: ArkAppElem? = null,
@SerialId(37) val generalFlags: ImMsgBody.GeneralFlags? = null, @SerialId(37) val generalFlags: GeneralFlags? = null,
@SerialId(38) val hcFlashPic: ImMsgBody.CustomFace? = null, @SerialId(38) val hcFlashPic: CustomFace? = null,
@SerialId(39) val deliverGiftMsg: ImMsgBody.DeliverGiftMsg? = null, @SerialId(39) val deliverGiftMsg: DeliverGiftMsg? = null,
@SerialId(40) val bitappMsg: ImMsgBody.BitAppMsg? = null, @SerialId(40) val bitappMsg: BitAppMsg? = null,
@SerialId(41) val openQqData: ImMsgBody.OpenQQData? = null, @SerialId(41) val openQqData: OpenQQData? = null,
@SerialId(42) val apolloMsg: ImMsgBody.ApolloActMsg? = null, @SerialId(42) val apolloMsg: ApolloActMsg? = null,
@SerialId(43) val groupPubAccInfo: ImMsgBody.GroupPubAccountInfo? = null, @SerialId(43) val groupPubAccInfo: GroupPubAccountInfo? = null,
@SerialId(44) val blessMsg: ImMsgBody.BlessingMessage? = null, @SerialId(44) val blessMsg: BlessingMessage? = null,
@SerialId(45) val srcMsg: ImMsgBody.SourceMsg? = null, @SerialId(45) val srcMsg: SourceMsg? = null,
@SerialId(46) val lolaMsg: ImMsgBody.LolaMsg? = null, @SerialId(46) val lolaMsg: LolaMsg? = null,
@SerialId(47) val groupBusinessMsg: ImMsgBody.GroupBusinessMsg? = null, @SerialId(47) val groupBusinessMsg: GroupBusinessMsg? = null,
@SerialId(48) val msgWorkflowNotify: ImMsgBody.WorkflowNotifyMsg? = null, @SerialId(48) val msgWorkflowNotify: WorkflowNotifyMsg? = null,
@SerialId(49) val patElem: ImMsgBody.PatsElem? = null, @SerialId(49) val patElem: PatsElem? = null,
@SerialId(50) val groupPostElem: ImMsgBody.GroupPostElem? = null, @SerialId(50) val groupPostElem: GroupPostElem? = null,
@SerialId(51) val lightApp: ImMsgBody.LightAppElem? = null, @SerialId(51) val lightApp: LightAppElem? = null,
@SerialId(52) val eimInfo: ImMsgBody.EIMInfo? = null, @SerialId(52) val eimInfo: EIMInfo? = null,
@SerialId(53) val commonElem: ImMsgBody.CommonElem? = null @SerialId(53) val commonElem: CommonElem? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -387,13 +387,13 @@ class ImMsgBody : ProtoBuf {
@SerialId(4) val pttChangeBit: Int = 0, @SerialId(4) val pttChangeBit: Int = 0,
@SerialId(5) val vipStatus: Int = 0, @SerialId(5) val vipStatus: Int = 0,
@SerialId(6) val compatibleId: Int = 0, @SerialId(6) val compatibleId: Int = 0,
@SerialId(7) val insts: List<ImMsgBody.ElemFlags2.Inst>? = null, @SerialId(7) val insts: List<Inst>? = null,
@SerialId(8) val msgRptCnt: Int = 0, @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(10) val longtitude: Int = 0,
@SerialId(11) val latitude: Int = 0, @SerialId(11) val latitude: Int = 0,
@SerialId(12) val customFont: 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 @SerialId(14) val crmFlags: Int = 0
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
@ -433,8 +433,8 @@ class ImMsgBody : ProtoBuf {
@Serializable @Serializable
class FunFace( class FunFace(
@SerialId(1) val msgTurntable: ImMsgBody.FunFace.Turntable? = null, @SerialId(1) val msgTurntable: Turntable? = null,
@SerialId(2) val msgBomb: ImMsgBody.FunFace.Bomb? = null @SerialId(2) val msgBomb: Bomb? = null
) { ) {
@Serializable @Serializable
class Bomb( class Bomb(
@ -580,14 +580,14 @@ class ImMsgBody : ProtoBuf {
@Serializable @Serializable
class MsgBody( class MsgBody(
@SerialId(1) val richText: ImMsgBody.RichText? = null, @SerialId(1) val richText: RichText = RichText(),
@SerialId(2) val msgContent: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(2) val msgContent: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val msgEncryptContent: ByteArray = EMPTY_BYTE_ARRAY @SerialId(3) val msgEncryptContent: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class MsgBodySubtype4( class MsgBodySubtype4(
@SerialId(1) val msgNotOnlineFile: ImMsgBody.NotOnlineFile? = null, @SerialId(1) val msgNotOnlineFile: NotOnlineFile? = null,
@SerialId(2) val msgTime: Int = 0 @SerialId(2) val msgTime: Int = 0
) : ProtoBuf ) : ProtoBuf
@ -622,7 +622,7 @@ class ImMsgBody : ProtoBuf {
@Serializable @Serializable
class NotOnlineImage( class NotOnlineImage(
@SerialId(1) val filePath: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(1) val filePath: String = "",
@SerialId(2) val fileLen: Int = 0, @SerialId(2) val fileLen: Int = 0,
@SerialId(3) val downloadPath: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(3) val downloadPath: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val oldVerSendFile: 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(7) val picMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val picHeight: Int = 0, @SerialId(8) val picHeight: Int = 0,
@SerialId(9) val picWidth: 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(11) val flag: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val thumbUrl: String = "", @SerialId(12) val thumbUrl: String = "",
@SerialId(13) val original: Int = 0, @SerialId(13) val original: Int = 0,
@ -742,8 +742,8 @@ class ImMsgBody : ProtoBuf {
@Serializable @Serializable
class QQWalletAioBody( class QQWalletAioBody(
@SerialId(1) val senduin: Long = 0L, @SerialId(1) val senduin: Long = 0L,
@SerialId(2) val sender: ImMsgBody.QQWalletAioElem? = null, @SerialId(2) val sender: QQWalletAioElem? = null,
@SerialId(3) val receiver: ImMsgBody.QQWalletAioElem? = null, @SerialId(3) val receiver: QQWalletAioElem? = null,
@ProtoType(ProtoNumberType.SIGNED) @SerialId(4) val sint32Channelid: Int = 0, @ProtoType(ProtoNumberType.SIGNED) @SerialId(4) val sint32Channelid: Int = 0,
@ProtoType(ProtoNumberType.SIGNED) @SerialId(5) val sint32Templateid: Int = 0, @ProtoType(ProtoNumberType.SIGNED) @SerialId(5) val sint32Templateid: Int = 0,
@SerialId(6) val resend: Int = 0, @SerialId(6) val resend: Int = 0,
@ -791,7 +791,7 @@ class ImMsgBody : ProtoBuf {
@Serializable @Serializable
class QQWalletMsg( class QQWalletMsg(
@SerialId(1) val aioBody: ImMsgBody.QQWalletAioBody? = null @SerialId(1) val aioBody: QQWalletAioBody? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -811,12 +811,12 @@ class ImMsgBody : ProtoBuf {
@Serializable @Serializable
class RichText( class RichText(
@SerialId(1) val attr: ImMsgBody.Attr? = null, @SerialId(1) val attr: Attr? = null,
@SerialId(2) val elems: List<ImMsgBody.Elem>? = null, @SerialId(2) val elems: MutableList<Elem> = mutableListOf(),
@SerialId(3) val notOnlineFile: ImMsgBody.NotOnlineFile? = null, @SerialId(3) val notOnlineFile: NotOnlineFile? = null,
@SerialId(4) val ptt: ImMsgBody.Ptt? = null, @SerialId(4) val ptt: Ptt? = null,
@SerialId(5) val tmpPtt: ImMsgBody.TmpPtt? = null, @SerialId(5) val tmpPtt: TmpPtt? = null,
@SerialId(6) val trans211TmpMsg: ImMsgBody.Trans211TmpMsg? = null @SerialId(6) val trans211TmpMsg: Trans211TmpMsg? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -832,8 +832,8 @@ class ImMsgBody : ProtoBuf {
@SerialId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val readTimes: Int = 0, @SerialId(10) val readTimes: Int = 0,
@SerialId(11) val leftTime: Int = 0, @SerialId(11) val leftTime: Int = 0,
@SerialId(12) val notOnlineImage: ImMsgBody.NotOnlineImage? = null, @SerialId(12) val notOnlineImage: NotOnlineImage? = null,
@SerialId(13) val elemFlags2: ImMsgBody.ElemFlags2? = null, @SerialId(13) val elemFlags2: ElemFlags2? = null,
@SerialId(14) val opertype: Int = 0, @SerialId(14) val opertype: Int = 0,
@SerialId(15) val fromphonenum: String = "" @SerialId(15) val fromphonenum: String = ""
) : ProtoBuf ) : ProtoBuf
@ -857,7 +857,7 @@ class ImMsgBody : ProtoBuf {
@SerialId(2) val senderUin: Long = 0L, @SerialId(2) val senderUin: Long = 0L,
@SerialId(3) val time: Int = 0, @SerialId(3) val time: Int = 0,
@SerialId(4) val flag: Int = 0, @SerialId(4) val flag: Int = 0,
@SerialId(5) val elems: List<ImMsgBody.Elem>? = null, @SerialId(5) val elems: List<Elem>? = null,
@SerialId(6) val type: Int = 0, @SerialId(6) val type: Int = 0,
@SerialId(7) val richMsg: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(7) val richMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(8) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY,
@ -868,7 +868,7 @@ class ImMsgBody : ProtoBuf {
@Serializable @Serializable
class Text( class Text(
@SerialId(1) val str: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(1) val str: String = "",
@SerialId(2) val link: String = "", @SerialId(2) val link: String = "",
@SerialId(3) val attr6Buf: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(3) val attr6Buf: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val attr7Buf: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(4) val attr7Buf: ByteArray = EMPTY_BYTE_ARRAY,

View File

@ -2,8 +2,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable 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.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
/** /**
* msf.msgcomm.msg_comm * msf.msgcomm.msg_comm
@ -60,7 +60,7 @@ class MsgComm : ProtoBuf {
@SerialId(1) val groupCode: Long = 0L, @SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val groupType: Int = 0, @SerialId(2) val groupType: Int = 0,
@SerialId(3) val groupInfoSeq: Long = 0L, @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(5) val groupRank: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val groupLevel: Int = 0, @SerialId(6) val groupLevel: Int = 0,
@SerialId(7) val groupCardType: Int = 0, @SerialId(7) val groupCardType: Int = 0,
@ -69,9 +69,9 @@ class MsgComm : ProtoBuf {
@Serializable @Serializable
class Msg( class Msg(
@SerialId(1) val msgHead: MsgHead? = null, @SerialId(1) val msgHead: MsgHead,
@SerialId(2) val contentHead: ContentHead? = null, @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 @SerialId(4) val appshareInfo: AppShareInfo? = null
) : ProtoBuf ) : ProtoBuf
@ -145,7 +145,7 @@ class MsgComm : ProtoBuf {
@SerialId(1) val lastReadTime: Int = 0, @SerialId(1) val lastReadTime: Int = 0,
@SerialId(2) val peerUin: Long = 0L, @SerialId(2) val peerUin: Long = 0L,
@SerialId(3) val msgCompleted: Int = 0, @SerialId(3) val msgCompleted: Int = 0,
@SerialId(4) val msg: List<Msg>? = null, @SerialId(4) val msg: List<Msg>,
@SerialId(5) val unreadMsgNum: Int = 0, @SerialId(5) val unreadMsgNum: Int = 0,
@SerialId(8) val c2cType: Int = 0, @SerialId(8) val c2cType: Int = 0,
@SerialId(9) val serviceType: Int = 0, @SerialId(9) val serviceType: Int = 0,

View File

@ -2,10 +2,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable 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.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
@Serializable @Serializable
class MsgSvc : ProtoBuf { class MsgSvc : ProtoBuf {
@ -26,14 +24,14 @@ class MsgSvc : ProtoBuf {
@SerialId(8) val pubaccountCookie: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(8) val pubaccountCookie: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(9) val isPartialSync: Boolean = false, @SerialId(9) val isPartialSync: Boolean = false,
@SerialId(10) val msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY @SerialId(10) val msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf, Packet ) : ProtoBuf
@Serializable @Serializable
class PbGroupMsgWithDrawReq( class PbGroupMsgWithDrawReq(
@SerialId(1) val subCmd: Int = 0, @SerialId(1) val subCmd: Int = 0,
@SerialId(2) val groupType: Int = 0, @SerialId(2) val groupType: Int = 0,
@SerialId(3) val groupCode: Long = 0L, @SerialId(3) val groupCode: Long = 0L,
@SerialId(4) val msgList: List<MsgSvc.PbGroupMsgWithDrawReq.MessageInfo>? = null, @SerialId(4) val msgList: List<MessageInfo>? = null,
@SerialId(5) val userdef: ByteArray = EMPTY_BYTE_ARRAY @SerialId(5) val userdef: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
@ -132,8 +130,8 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbMsgWithDrawResp( class PbMsgWithDrawResp(
@SerialId(1) val c2cWithDraw: List<MsgSvc.PbC2CMsgWithDrawResp>? = null, @SerialId(1) val c2cWithDraw: List<PbC2CMsgWithDrawResp>? = null,
@SerialId(2) val groupWithDraw: List<MsgSvc.PbGroupMsgWithDrawResp>? = null @SerialId(2) val groupWithDraw: List<PbGroupMsgWithDrawResp>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -144,8 +142,8 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbMsgWithDrawReq( class PbMsgWithDrawReq(
@SerialId(1) val c2cWithDraw: List<MsgSvc.PbC2CMsgWithDrawReq>? = null, @SerialId(1) val c2cWithDraw: List<PbC2CMsgWithDrawReq>? = null,
@SerialId(2) val groupWithDraw: List<MsgSvc.PbGroupMsgWithDrawReq>? = null @SerialId(2) val groupWithDraw: List<PbGroupMsgWithDrawReq>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -206,7 +204,7 @@ class MsgSvc : ProtoBuf {
@SerialId(3) val subCmd: Int = 0, @SerialId(3) val subCmd: Int = 0,
@SerialId(4) val groupType: Int = 0, @SerialId(4) val groupType: Int = 0,
@SerialId(5) val groupCode: Long = 0L, @SerialId(5) val groupCode: Long = 0L,
@SerialId(6) val failedMsgList: List<MsgSvc.PbGroupMsgWithDrawResp.MessageResult>? = null, @SerialId(6) val failedMsgList: List<MessageResult>? = null,
@SerialId(7) val userdef: ByteArray = EMPTY_BYTE_ARRAY @SerialId(7) val userdef: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
@ -232,7 +230,7 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbC2CMsgWithDrawReq( class PbC2CMsgWithDrawReq(
@SerialId(1) val msgInfo: List<MsgSvc.PbC2CMsgWithDrawReq.MsgInfo>? = null, @SerialId(1) val msgInfo: List<MsgInfo>? = null,
@SerialId(2) val longMessageFlag: Int = 0, @SerialId(2) val longMessageFlag: Int = 0,
@SerialId(3) val reserved: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(3) val reserved: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val subCmd: Int = 0 @SerialId(4) val subCmd: Int = 0
@ -249,7 +247,7 @@ class MsgSvc : ProtoBuf {
@SerialId(8) val pkgIndex: Int = 0, @SerialId(8) val pkgIndex: Int = 0,
@SerialId(9) val divSeq: Int = 0, @SerialId(9) val divSeq: Int = 0,
@SerialId(10) val msgType: 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( class PbPullGroupMsgSeqResp(
@SerialId(1) val result: Int = 0, @SerialId(1) val result: Int = 0,
@SerialId(2) val errmsg: String = "", @SerialId(2) val errmsg: String = "",
@SerialId(3) val groupInfoResp: List<MsgSvc.PbPullGroupMsgSeqResp.GroupInfoResp>? = null @SerialId(3) val groupInfoResp: List<GroupInfoResp>? = null
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class GroupInfoResp( class GroupInfoResp(
@ -288,17 +286,17 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbSendMsgReq( class PbSendMsgReq(
@SerialId(1) val routingHead: MsgSvc.RoutingHead? = null, @SerialId(1) val routingHead: RoutingHead? = null,
@SerialId(2) val contentHead: MsgComm.ContentHead? = 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(4) val msgSeq: Int = 0,
@SerialId(5) val msgRand: Int = 0, @SerialId(5) val msgRand: Int = 0,
@SerialId(6) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(6) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val appShare: MsgComm.AppShareInfo? = null, @SerialId(7) val appShare: MsgComm.AppShareInfo? = null,
@SerialId(8) val msgVia: Int = 0, @SerialId(8) val msgVia: Int = 0,
@SerialId(9) val dataStatist: Int = 0, @SerialId(9) val dataStatist: Int = 0,
@SerialId(10) val multiMsgAssist: MsgSvc.MultiMsgAssist? = null, @SerialId(10) val multiMsgAssist: MultiMsgAssist? = null,
@SerialId(11) val inputNotifyInfo: MsgSvc.PbInputNotifyInfo? = null, @SerialId(11) val inputNotifyInfo: PbInputNotifyInfo? = null,
@SerialId(12) val msgCtrl: MsgCtrl.MsgCtrl? = null, @SerialId(12) val msgCtrl: MsgCtrl.MsgCtrl? = null,
@SerialId(13) val receiptReq: ImReceipt.ReceiptReq? = null, @SerialId(13) val receiptReq: ImReceipt.ReceiptReq? = null,
@SerialId(14) val multiSendSeq: Int = 0 @SerialId(14) val multiSendSeq: Int = 0
@ -333,16 +331,16 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbUnReadMsgSeqResp( class PbUnReadMsgSeqResp(
@SerialId(1) val c2cUnreadInfo: MsgSvc.PbC2CUnReadMsgNumResp? = null, @SerialId(1) val c2cUnreadInfo: PbC2CUnReadMsgNumResp? = null,
@SerialId(2) val binduinUnreadInfo: List<MsgSvc.PbBindUinUnReadMsgNumResp>? = null, @SerialId(2) val binduinUnreadInfo: List<PbBindUinUnReadMsgNumResp>? = null,
@SerialId(3) val groupUnreadInfo: MsgSvc.PbPullGroupMsgSeqResp? = null, @SerialId(3) val groupUnreadInfo: PbPullGroupMsgSeqResp? = null,
@SerialId(4) val discussUnreadInfo: MsgSvc.PbPullDiscussMsgSeqResp? = null, @SerialId(4) val discussUnreadInfo: PbPullDiscussMsgSeqResp? = null,
@SerialId(5) val thirdqqUnreadInfo: MsgSvc.PbThirdQQUnReadMsgNumResp? = null @SerialId(5) val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumResp? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PbDeleteMsgReq( class PbDeleteMsgReq(
@SerialId(1) val msgItems: List<MsgSvc.PbDeleteMsgReq.MsgItem>? = null @SerialId(1) val msgItems: List<MsgItem>? = null
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class MsgItem( class MsgItem(
@ -357,7 +355,7 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class MultiMsgAssist( class MultiMsgAssist(
@SerialId(1) val repeatedRouting: List<MsgSvc.RoutingHead>? = null, @SerialId(1) val repeatedRouting: List<RoutingHead>? = null,
@SerialId(2) val msgUse: Int /* enum */ = 1, @SerialId(2) val msgUse: Int /* enum */ = 1,
@SerialId(3) val tempId: Long = 0L, @SerialId(3) val tempId: Long = 0L,
@SerialId(4) val vedioLen: Long = 0L, @SerialId(4) val vedioLen: Long = 0L,
@ -369,10 +367,10 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbMsgReadedReportReq( class PbMsgReadedReportReq(
@SerialId(1) val grpReadReport: List<MsgSvc.PbGroupReadedReportReq>? = null, @SerialId(1) val grpReadReport: List<PbGroupReadedReportReq>? = null,
@SerialId(2) val disReadReport: List<MsgSvc.PbDiscussReadedReportReq>? = null, @SerialId(2) val disReadReport: List<PbDiscussReadedReportReq>? = null,
@SerialId(3) val c2cReadReport: MsgSvc.PbC2CReadedReportReq? = null, @SerialId(3) val c2cReadReport: PbC2CReadedReportReq? = null,
@SerialId(4) val bindUinReadReport: MsgSvc.PbBindUinMsgReadedConfirmReq? = null @SerialId(4) val bindUinReadReport: PbBindUinMsgReadedConfirmReq? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -409,28 +407,28 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class RoutingHead( class RoutingHead(
@SerialId(1) val c2c: MsgSvc.C2C? = null, @SerialId(1) val c2c: C2C? = null,
@SerialId(2) val grp: MsgSvc.Grp? = null, @SerialId(2) val grp: Grp? = null,
@SerialId(3) val grpTmp: MsgSvc.GrpTmp? = null, @SerialId(3) val grpTmp: GrpTmp? = null,
@SerialId(4) val dis: MsgSvc.Dis? = null, @SerialId(4) val dis: Dis? = null,
@SerialId(5) val disTmp: MsgSvc.DisTmp? = null, @SerialId(5) val disTmp: DisTmp? = null,
@SerialId(6) val wpaTmp: MsgSvc.WPATmp? = null, @SerialId(6) val wpaTmp: WPATmp? = null,
@SerialId(7) val secretFile: MsgSvc.SecretFileHead? = null, @SerialId(7) val secretFile: SecretFileHead? = null,
@SerialId(8) val publicPlat: MsgSvc.PublicPlat? = null, @SerialId(8) val publicPlat: PublicPlat? = null,
@SerialId(9) val transMsg: MsgSvc.TransMsg? = null, @SerialId(9) val transMsg: TransMsg? = null,
@SerialId(10) val addressList: MsgSvc.AddressListTmp? = null, @SerialId(10) val addressList: AddressListTmp? = null,
@SerialId(11) val richStatusTmp: MsgSvc.RichStatusTmp? = null, @SerialId(11) val richStatusTmp: RichStatusTmp? = null,
@SerialId(12) val transCmd: MsgSvc.TransCmd? = null, @SerialId(12) val transCmd: TransCmd? = null,
@SerialId(13) val accostTmp: MsgSvc.AccostTmp? = null, @SerialId(13) val accostTmp: AccostTmp? = null,
@SerialId(14) val pubGroupTmp: MsgSvc.PubGroupTmp? = null, @SerialId(14) val pubGroupTmp: PubGroupTmp? = null,
@SerialId(15) val trans0x211: MsgSvc.Trans0x211? = null, @SerialId(15) val trans0x211: Trans0x211? = null,
@SerialId(16) val businessWpaTmp: MsgSvc.BusinessWPATmp? = null, @SerialId(16) val businessWpaTmp: BusinessWPATmp? = null,
@SerialId(17) val authTmp: MsgSvc.AuthTmp? = null, @SerialId(17) val authTmp: AuthTmp? = null,
@SerialId(18) val bsnsTmp: MsgSvc.BsnsTmp? = null, @SerialId(18) val bsnsTmp: BsnsTmp? = null,
@SerialId(19) val qqQuerybusinessTmp: MsgSvc.QQQueryBusinessTmp? = null, @SerialId(19) val qqQuerybusinessTmp: QQQueryBusinessTmp? = null,
@SerialId(20) val nearbyDatingTmp: MsgSvc.NearByDatingTmp? = null, @SerialId(20) val nearbyDatingTmp: NearByDatingTmp? = null,
@SerialId(21) val nearbyAssistantTmp: MsgSvc.NearByAssistantTmp? = null, @SerialId(21) val nearbyAssistantTmp: NearByAssistantTmp? = null,
@SerialId(22) val commTmp: MsgSvc.CommTmp? = null @SerialId(22) val commTmp: CommTmp? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -447,9 +445,9 @@ class MsgSvc : ProtoBuf {
@SerialId(2) val errmsg: String = "", @SerialId(2) val errmsg: String = "",
@SerialId(3) val sendTime: Int = 0, @SerialId(3) val sendTime: Int = 0,
@SerialId(4) val svrbusyWaitTime: 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(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(8) val receiptResp: ImReceipt.ReceiptResp? = null,
@SerialId(9) val textAnalysisResult: Int = 0 @SerialId(9) val textAnalysisResult: Int = 0
) : ProtoBuf ) : ProtoBuf
@ -477,12 +475,12 @@ class MsgSvc : ProtoBuf {
class PbC2CMsgWithDrawResp( class PbC2CMsgWithDrawResp(
@SerialId(1) val result: Int = 0, @SerialId(1) val result: Int = 0,
@SerialId(2) val errmsg: String = "", @SerialId(2) val errmsg: String = "",
@SerialId(3) val msgStatus: List<MsgSvc.PbC2CMsgWithDrawResp.MsgStatus>? = null, @SerialId(3) val msgStatus: List<MsgStatus>? = null,
@SerialId(4) val subCmd: Int = 0 @SerialId(4) val subCmd: Int = 0
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class MsgStatus( class MsgStatus(
@SerialId(1) val msgInfo: MsgSvc.PbC2CMsgWithDrawReq.MsgInfo? = null, @SerialId(1) val msgInfo: PbC2CMsgWithDrawReq.MsgInfo? = null,
@SerialId(2) val status: Int = 0 @SerialId(2) val status: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
@ -515,17 +513,17 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbMsgReadedReportResp( class PbMsgReadedReportResp(
@SerialId(1) val grpReadReport: List<MsgSvc.PbGroupReadedReportResp>? = null, @SerialId(1) val grpReadReport: List<PbGroupReadedReportResp>? = null,
@SerialId(2) val disReadReport: List<MsgSvc.PbDiscussReadedReportResp>? = null, @SerialId(2) val disReadReport: List<PbDiscussReadedReportResp>? = null,
@SerialId(3) val c2cReadReport: MsgSvc.PbC2CReadedReportResp? = null, @SerialId(3) val c2cReadReport: PbC2CReadedReportResp? = null,
@SerialId(4) val bindUinReadReport: MsgSvc.PbBindUinMsgReadedConfirmResp? = null @SerialId(4) val bindUinReadReport: PbBindUinMsgReadedConfirmResp? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PbThirdQQUnReadMsgNumResp( class PbThirdQQUnReadMsgNumResp(
@SerialId(1) val result: Int = 0, @SerialId(1) val result: Int = 0,
@SerialId(2) val errmsg: String = "", @SerialId(2) val errmsg: String = "",
@SerialId(3) val thirdqqRespInfo: List<MsgSvc.PbThirdQQUnReadMsgNumResp.ThirdQQRespInfo>? = null, @SerialId(3) val thirdqqRespInfo: List<ThirdQQRespInfo>? = null,
@SerialId(4) val interval: Int = 0 @SerialId(4) val interval: Int = 0
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
@ -554,9 +552,9 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbDelRoamMsgReq( class PbDelRoamMsgReq(
@SerialId(1) val c2cMsg: MsgSvc.PbDelRoamMsgReq.C2CMsg? = null, @SerialId(1) val c2cMsg: C2CMsg? = null,
@SerialId(2) val grpMsg: MsgSvc.PbDelRoamMsgReq.GrpMsg? = null, @SerialId(2) val grpMsg: GrpMsg? = null,
@SerialId(3) val disMsg: MsgSvc.PbDelRoamMsgReq.DisMsg? = null @SerialId(3) val disMsg: DisMsg? = null
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class GrpMsg( class GrpMsg(
@ -582,18 +580,18 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbUnReadMsgSeqReq( class PbUnReadMsgSeqReq(
@SerialId(1) val c2cUnreadInfo: MsgSvc.PbC2CUnReadMsgNumReq? = null, @SerialId(1) val c2cUnreadInfo: PbC2CUnReadMsgNumReq? = null,
@SerialId(2) val binduinUnreadInfo: List<MsgSvc.PbBindUinUnReadMsgNumReq>? = null, @SerialId(2) val binduinUnreadInfo: List<PbBindUinUnReadMsgNumReq>? = null,
@SerialId(3) val groupUnreadInfo: MsgSvc.PbPullGroupMsgSeqReq? = null, @SerialId(3) val groupUnreadInfo: PbPullGroupMsgSeqReq? = null,
@SerialId(4) val discussUnreadInfo: MsgSvc.PbPullDiscussMsgSeqReq? = null, @SerialId(4) val discussUnreadInfo: PbPullDiscussMsgSeqReq? = null,
@SerialId(5) val thirdqqUnreadInfo: MsgSvc.PbThirdQQUnReadMsgNumReq? = null @SerialId(5) val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumReq? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PbPullDiscussMsgSeqResp( class PbPullDiscussMsgSeqResp(
@SerialId(1) val result: Int = 0, @SerialId(1) val result: Int = 0,
@SerialId(2) val errmsg: String = "", @SerialId(2) val errmsg: String = "",
@SerialId(3) val discussInfoResp: List<MsgSvc.PbPullDiscussMsgSeqResp.DiscussInfoResp>? = null @SerialId(3) val discussInfoResp: List<DiscussInfoResp>? = null
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class DiscussInfoResp( class DiscussInfoResp(
@ -605,7 +603,7 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbPullDiscussMsgSeqReq( class PbPullDiscussMsgSeqReq(
@SerialId(1) val discussInfoReq: List<MsgSvc.PbPullDiscussMsgSeqReq.DiscussInfoReq>? = null @SerialId(1) val discussInfoReq: List<DiscussInfoReq>? = null
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class DiscussInfoReq( class DiscussInfoReq(
@ -654,7 +652,7 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbC2CReadedReportReq( class PbC2CReadedReportReq(
@SerialId(1) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(1) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val pairInfo: List<MsgSvc.PbC2CReadedReportReq.UinPairReadInfo>? = null @SerialId(2) val pairInfo: List<UinPairReadInfo>? = null
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class UinPairReadInfo( class UinPairReadInfo(
@ -694,7 +692,7 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbPullGroupMsgSeqReq( class PbPullGroupMsgSeqReq(
@SerialId(1) val groupInfoReq: List<MsgSvc.PbPullGroupMsgSeqReq.GroupInfoReq>? = null @SerialId(1) val groupInfoReq: List<GroupInfoReq>? = null
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class GroupInfoReq( class GroupInfoReq(
@ -731,7 +729,7 @@ class MsgSvc : ProtoBuf {
@Serializable @Serializable
class PbThirdQQUnReadMsgNumReq( class PbThirdQQUnReadMsgNumReq(
@SerialId(1) val thirdqqReqInfo: List<MsgSvc.PbThirdQQUnReadMsgNumReq.ThirdQQReqInfo>? = null, @SerialId(1) val thirdqqReqInfo: List<ThirdQQReqInfo>? = null,
@SerialId(2) val source: Int = 0 @SerialId(2) val source: Int = 0
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
@ -796,7 +794,7 @@ class SubMsgType0xc1 {
@SerialId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val readTimes: Int = 0, @SerialId(10) val readTimes: Int = 0,
@SerialId(11) val leftTime: Int = 0, @SerialId(11) val leftTime: Int = 0,
@SerialId(12) val notOnlineImage: SubMsgType0xc1.NotOnlineImage? = null @SerialId(12) val notOnlineImage: NotOnlineImage? = null
) : ProtoBuf ) : ProtoBuf
} }

View File

@ -2,14 +2,14 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable 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.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf
@Serializable @Serializable
class MsgOnlinePush { class MsgOnlinePush {
@Serializable @Serializable
data class PbPushMsg( class PbPushMsg(
@SerialId(1) val msg: MsgComm.Msg? = null, @SerialId(1) val msg: MsgComm.Msg,
@SerialId(2) val svrip: Int = 0, @SerialId(2) val svrip: Int = 0,
@SerialId(3) val pushToken: ByteArray = EMPTY_BYTE_ARRAY, @SerialId(3) val pushToken: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val pingFlag: Int = 0, @SerialId(4) val pingFlag: Int = 0,

View File

@ -6,8 +6,9 @@ import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
@Suppress("ArrayInDataClass")
@Serializable @Serializable
internal class RequestPushNotify( internal data class RequestPushNotify(
@SerialId(0) val uin: Long = 0L, @SerialId(0) val uin: Long = 0L,
@SerialId(1) val ctype: Byte = 0, @SerialId(1) val ctype: Byte = 0,
@SerialId(2) val strService: String?, @SerialId(2) val strService: String?,

View File

@ -1,2 +0,0 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image

View File

@ -2,22 +2,25 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.serialization.SerialId import net.mamoe.mirai.data.MultiPacket
import kotlinx.serialization.Serializable import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.qqandroid.QQAndroidBot 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.loadAs
import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsJceStruct 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.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2 import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket 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.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.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify 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.firstValue
import net.mamoe.mirai.utils.io.debugPrint import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.utils.io.toReadPacket
class MessageSvc { class MessageSvc {
@ -25,31 +28,64 @@ class MessageSvc {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
discardExact(8) discardExact(8)
@Serializable return readRemainingAsJceStruct(RequestPacket.serializer()).sBuffer
class ResponseDataRequestPushNotify(
@SerialId(0) val notify: RequestPushNotify
) : JceStruct
val requestPushNotify = readRemainingAsJceStruct(RequestPacket.serializer()).sBuffer
.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue() .loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue()
.toReadPacket().apply { discardExact(1) } .toReadPacket().apply { discardExact(1) }
.debugPrint()
.readRemainingAsJceStruct(RequestPushNotify.serializer()) .readRemainingAsJceStruct(RequestPushNotify.serializer())
println(requestPushNotify.contentToString())
with(bot.network) {
GetMsgRequest(
bot.client,
requestPushNotify
).sendAndExpect<MsgSvc.PbGetMsgResp>()
} }
override suspend fun QQAndroidBot.handle(packet: RequestPushNotify) {
return requestPushNotify network.run {
PbGetMsg(client, packet).sendAndExpect<MultiPacket<FriendMessage>>()
}
} }
} }
internal object PbGetMsg : PacketFactory<MultiPacket<FriendMessage>>("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<FriendMessage> {
// 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())
}
}
} }

View File

@ -7,26 +7,12 @@ import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.message.GroupMessage 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.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory 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.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgOnlinePush import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgOnlinePush
import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.qqandroid.utils.toMessageChain
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
internal class OnlinePush { internal class OnlinePush {
internal object PbPushGroupMsg : PacketFactory<GroupMessage>("OnlinePush.PbPushGroupMsg") { internal object PbPushGroupMsg : PacketFactory<GroupMessage>("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 // 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) discardExact(4)
val pbPushMsg = ProtoBuf.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes()) 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 { val group = bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode)
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 flags = extraInfo?.flags ?: 0 val flags = extraInfo?.flags ?: 0
return GroupMessage( return GroupMessage(
bot = bot, bot = bot,
group = group, group = group,
senderName = pbPushMsg.msg.msgHead.groupInfo!!.groupCard.encodeToString(), senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard,
sender = group.getMember(pbPushMsg.msg.msgHead.fromUin), sender = group.getMember(pbPushMsg.msg.msgHead.fromUin),
message = message, message = pbPushMsg.msg.msgBody.richText.toMessageChain(),
permission = when { permission = when {
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER flags and 8 != 0 -> MemberPermission.OWNER

View File

@ -46,7 +46,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
} }
} }
object SubCommand8 { object SubCommand7 {
private const val appId = 16L private const val appId = 16L
private const val subAppId = 537062845L private const val subAppId = 537062845L
@ -59,26 +59,16 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) {
writeShort(8) // subCommand writeShort(7) // subCommand
writeShort(6) // count of TLVs, probably ignored by server?TODO writeShort(7) // count of TLVs, probably ignored by server?TODO
t8(2052) t8(2052)
t104(client.t104) t104(client.t104)
t116(150470524, 66560) t116(150470524, 66560)
t174(t174) t174(t174)
t17a(9) t17c(phoneNumber.toByteArray())
t197(byteArrayOf(0.toByte())) t401(md5(client.device.guid + "1234567890123456".toByteArray() + t402))
//t401(md5(client.device.guid + "12 34567890123456".toByteArray() + t402)) t19e(0)//==tlv408
//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//116this.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//固定
*/
} }
} }
} }

View File

@ -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.toByteArray
import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct import net.mamoe.mirai.qqandroid.io.serialization.writeJceStruct
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataStructSvcReqRegister import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataStructSvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3 import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister 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.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket

View File

@ -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.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY

View File

@ -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.Polymorphic
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId

View File

@ -2,7 +2,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769
import kotlinx.serialization.SerialId import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.network.protocol.protobuf.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
class Oidb0x769 { class Oidb0x769 {
@Serializable @Serializable

View File

@ -1,7 +0,0 @@
package net.mamoe.mirai.qqandroid.network.protocol.protobuf
/**
* 仅有标示作用
*/
interface ProtoBuf {
}

View File

@ -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

View File

@ -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<String, ByteArray>
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<fc.5*6".toByteArray()).toUHexString())
println("With key %4;7t>;28<fc.5*6")
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
package net.mamoe.mirai.qqandroid.io.serialization 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 import net.mamoe.mirai.utils.io.hexToBytes
class TestRequesetPacket { class TestRequesetPacket {

View File

@ -143,7 +143,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass {
val javaClassname = substringBetween("class", "{") val javaClassname = substringBetween("class", "{")
val superclasses = javaClassname.split("$").map { it.trim().adjustClassName() }.toMutableList().apply { removeAt(this.lastIndex) } val superclasses = javaClassname.split("$").map { it.trim().adjustClassName() }.toMutableList().apply { removeAt(this.lastIndex) }
val className = substringBetween("class", "{").substringAfterLast("$").trim().adjustClassName() 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() 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() } val ids = substringBetween("new int[]{", "}").split(",").map { it.trim() }
if (ids.all { it.isBlank() }) { 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() } val names = substringBetween("new String[]{", "}").split(",").map { it.trim() }
@ -326,7 +326,7 @@ fun String.generateProtoBufDataClass(): GeneratedClass {
append("\n") append("\n")
} }
append(")") append(") : ProtoBuf")
} }
return GeneratedClass(superclasses, className, source) return GeneratedClass(superclasses, className, source)

View File

@ -5,6 +5,8 @@ import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.DataInput import java.io.DataInput
import java.io.EOFException 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 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() val inflater = Inflater()
inflater.reset() inflater.reset()
val output = ByteArrayOutputStream() ByteArrayOutputStream().use { output ->
inflater.setInput(this) inflater.setInput(this, offset, length)
val buffer = ByteArray(128) ByteArrayPool.useInstance {
while (!inflater.finished()) { while (!inflater.finished()) {
output.write(buffer, 0, inflater.inflate(buffer)) output.write(it, 0, inflater.inflate(it))
} }
}
inflater.end() inflater.end()
return output.toByteArray() return output.toByteArray()
}
} }
actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher { actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher {

View File

@ -15,7 +15,7 @@ actual class ECDHKeyPair(
actual val privateKey: ECDHPrivateKey get() = delegate.private actual val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate.public 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") @Suppress("FunctionName")

View File

@ -3,8 +3,6 @@
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.md5
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
@ -12,17 +10,17 @@ data class BotAccount(
/** /**
* **注意**: Android 协议, 总是使用 `QQAndroidClient.uin` [Bot.uin], 而不要使用 [BotAccount.id]. 将来 [BotAccount.id] 可能会变为 [String] * **注意**: Android 协议, 总是使用 `QQAndroidClient.uin` [Bot.uin], 而不要使用 [BotAccount.id]. 将来 [BotAccount.id] 可能会变为 [String]
*/ */
@MiraiExperimentalAPI @RawAccountIdUse
val id: Long, val id: Long,
val passwordMd5: ByteArray // md5 val passwordMd5: ByteArray // md5
){ ) {
constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray())) constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray()))
} }
/** /**
* 标记直接访问 [BotAccount.id], 而不是访问 [Bot.uin]. 可能会不兼容未来的 API 修改. * 标记直接访问 [BotAccount.id], 而不是访问 [Bot.uin]. 可能会不兼容未来的 API 修改.
*/ */
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@Experimental @Experimental(level = Experimental.Level.WARNING)
annotation class RawAccountIdUse annotation class RawAccountIdUse

View File

@ -13,6 +13,7 @@ import kotlin.coroutines.CoroutineContext
/* /*
* 泛型 N 不需要向外(接口)暴露. * 泛型 N 不需要向外(接口)暴露.
*/ */
@UseExperimental(MiraiExperimentalAPI::class)
@MiraiInternalAPI @MiraiInternalAPI
abstract class BotImpl<N : BotNetworkHandler> constructor( abstract class BotImpl<N : BotNetworkHandler> constructor(
account: BotAccount, account: BotAccount,
@ -25,10 +26,9 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@Suppress("CanBePrimaryConstructorProperty") // for logger @Suppress("CanBePrimaryConstructorProperty") // for logger
final override val account: BotAccount = account final override val account: BotAccount = account
@UseExperimental(MiraiExperimentalAPI::class) @UseExperimental(RawAccountIdUse::class)
final override val uin: Long override val uin: Long get() = account.id
get() = account.id final override val logger: MiraiLogger by lazy { configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it } }
final override val logger: MiraiLogger = configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it }
init { init {
@Suppress("LeakingThis") @Suppress("LeakingThis")

View File

@ -8,14 +8,14 @@ import net.mamoe.mirai.utils.coerceAtLeastOrFail
/** /**
* . * . QQ Android 中叫做 "Troop"
* *
* Group ID Group Number 并不是同一个值. * Group ID Group Number 并不是同一个值.
* - Group Number([Group.id]) 是通常使用的群号码.( QQ 客户端中可见) * - Group Number([Group.id]) 是通常使用的群号码.( QQ 客户端中可见)
* - Group ID([Group.internalId]) 是与调用 API 时使用的 id.( QQ 客户端中不可见) * - Group ID([Group.internalId]) 是与调用 API 时使用的 id.( QQ 客户端中不可见)
* @author Him188moe * @author Him188moe
*/ */
interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2019/12/4 在 inline 稳定后实现 Map<UInt, Member>. 目前这样做会导致问题 interface Group : Contact, CoroutineScope/*, Map<UInt, Member>*/ { // TODO: 2020/1/29 实现接口 Map<Long, Memebr>
/** /**
* 内部 ID. 内部 ID [GroupId] 的映射 * 内部 ID. 内部 ID [GroupId] 的映射
*/ */
@ -86,7 +86,10 @@ fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this)
/** /**
* 将无符号整数格式的 [Long] 转为 [GroupId]. * 将无符号整数格式的 [Long] 转为 [GroupId].
* *
* : Java 中常用 [Long] 来表示 [UInt] * : Java 中常用 [Long] 来表示 [UInt].
*
* : Kotlin/Java, 有符号的数据类型的二进制最高位为符号标志.
* 如一个 byte, `1000 0000` 最高位为 1, 则为负数.
*/ */
fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0)) fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0))

View File

@ -4,3 +4,12 @@ package net.mamoe.mirai.data
* 从服务器收到的包解析之后的结构化数据. * 从服务器收到的包解析之后的结构化数据.
*/ */
interface Packet interface Packet
/**
* PacketFactory 可以一次解析多个包出来. 它们将会被分别广播.
*/
class MultiPacket<P : Packet>(delegate: List<P>) : List<P> by delegate, Packet {
override fun toString(): String {
return "MultiPacket<${this.firstOrNull()?.let { it::class.simpleName }?: "?"}>"
}
}

View File

@ -112,7 +112,7 @@ inline fun <reified E : Subscribable, T> CoroutineScope.subscribeUntil(valueIfSt
* @see subscribe 获取更多说明 * @see subscribe 获取更多说明
*/ */
inline fun <reified E : Subscribable, T> CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener<E> = inline fun <reified E : Subscribable, T> CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener<E> =
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 // endregion

View File

@ -5,6 +5,7 @@ package net.mamoe.mirai.network
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.io.PlatformDatagramChannel import net.mamoe.mirai.utils.io.PlatformDatagramChannel
@ -18,22 +19,26 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel
* *
* [BotNetworkHandler] 的协程包含: * [BotNetworkHandler] 的协程包含:
* - UDP 包接收: [PlatformDatagramChannel.read] * - UDP 包接收: [PlatformDatagramChannel.read]
* - 心跳 Job [HeartbeatPacket] * - 心跳 Job
* - SKey 刷新 [RequestSKeyPacket] * - Key 刷新
* - 所有数据包处理和发送 * - 所有数据包处理和发送
* *
* [BotNetworkHandler.dispose] 时将会 [取消][kotlin.coroutines.CoroutineContext.cancelChildren] 所有此作用域下的协程 * [BotNetworkHandler.dispose] 时将会 [取消][Job.cancel] 所有此作用域下的协程
*
* A BotNetworkHandler is used to connect with Tencent servers.
*/ */
@Suppress("PropertyName") @Suppress("PropertyName")
abstract class BotNetworkHandler : CoroutineScope { abstract class BotNetworkHandler : CoroutineScope {
/**
* 所属 [Bot]. 为弱引用
*/
abstract val bot: Bot abstract val bot: Bot
/**
* 监管 child [Job]s
*/
abstract val supervisor: CompletableJob abstract val supervisor: CompletableJob
/** /**
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果 * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回.
* 本函数将挂起直到登录成功. * 本函数将挂起直到登录成功.
*/ */
abstract suspend fun login() abstract suspend fun login()
@ -47,21 +52,12 @@ abstract class BotNetworkHandler : CoroutineScope {
* 关闭网络接口, 停止所有有关协程和任务 * 关闭网络接口, 停止所有有关协程和任务
*/ */
open fun dispose(cause: Throwable? = null) { open fun dispose(cause: Throwable? = null) {
if (supervisor.isActive) {
if (cause != null) {
supervisor.cancel(CancellationException("handler closed", cause)) 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
*/
} }

View File

@ -24,7 +24,10 @@ expect val deviceName: String
*/ */
expect fun crc32(key: ByteArray): Int 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 算法 * MD5 算法
@ -49,3 +52,9 @@ expect fun localIpAddress(): String
expect val Http: HttpClient expect val Http: HttpClient
expect fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher 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})" }
}

View File

@ -15,23 +15,46 @@ expect class ECDHKeyPair {
val privateKey: ECDHPrivateKey val privateKey: ECDHPrivateKey
val publicKey: ECDHPublicKey val publicKey: ECDHPublicKey
val shareKey: ByteArray /**
* 私匙和固定公匙([initialPublicKey]) 计算得到的 shareKey
*/
val initialShareKey: ByteArray
} }
/**
* 椭圆曲线密码, ECDH 加密
*/
expect class ECDH(keyPair: ECDHKeyPair) { expect class ECDH(keyPair: ECDHKeyPair) {
val keyPair: ECDHKeyPair val keyPair: ECDHKeyPair
/**
* [keyPair] 的私匙和 [peerPublicKey] 计算 shareKey
*/
fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray
companion object { companion object {
/**
* 由完整的 publicKey ByteArray 得到 [ECDHPublicKey]
*/
fun constructPublicKey(key: ByteArray): ECDHPublicKey fun constructPublicKey(key: ByteArray): ECDHPublicKey
/**
* 生成随机密匙对
*/
fun generateKeyPair(): ECDHKeyPair fun generateKeyPair(): ECDHKeyPair
/**
* 由一对密匙计算 shareKey
*/
fun calculateShareKey(privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey): ByteArray fun calculateShareKey(privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey): ByteArray
} }
override fun toString(): String override fun toString(): String
} }
/**
*
*/
@Suppress("FunctionName") @Suppress("FunctionName")
expect fun ECDH(): ECDH expect fun ECDH(): ECDH

View File

@ -38,6 +38,14 @@ fun ByteReadPacket.transferTo(outputStream: OutputStream) {
} }
} }
fun <R> 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( fun ByteReadPacket.readRemainingBytes(
n: Int = remaining.toInt()//not that safe but adequate n: Int = remaining.toInt()//not that safe but adequate
): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) } ): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) }
@ -75,10 +83,21 @@ fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().
private inline fun <R> inline(block: () -> R): R = block() private inline fun <R> inline(block: () -> R): R = block()
fun Input.readTLVMap(tagSize: Int = 2): MutableMap<Int, ByteArray> = readTLVMap(true, tagSize)
typealias TlvMap = MutableMap<Int, ByteArray>
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") @Suppress("DuplicatedCode")
fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): MutableMap<Int, ByteArray> { fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int): TlvMap {
val map = mutableMapOf<Int, ByteArray>() val map = mutableMapOf<Int, ByteArray>()
var key = 0 var key = 0

View File

@ -7,8 +7,10 @@ import io.ktor.client.engine.cio.CIO
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.io.core.copyTo import kotlinx.io.core.copyTo
import kotlinx.io.pool.useInstance
import kotlinx.io.streams.asInput import kotlinx.io.streams.asInput
import kotlinx.io.streams.asOutput import kotlinx.io.streams.asOutput
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.* import java.io.*
import java.net.InetAddress import java.net.InetAddress
import java.security.MessageDigest import java.security.MessageDigest
@ -56,18 +58,23 @@ actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
actual val Http: HttpClient get() = HttpClient(CIO) 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() val inflater = Inflater()
inflater.reset() inflater.reset()
val input = this ByteArrayOutputStream().use { output ->
val output = ByteArrayOutputStream() inflater.setInput(this, offset, length)
inflater.setInput(input) ByteArrayPool.useInstance {
val buffer = ByteArray(128)
while (!inflater.finished()) { while (!inflater.finished()) {
output.write(buffer, 0, inflater.inflate(buffer)) output.write(it, 0, inflater.inflate(it))
} }
}
inflater.end() inflater.end()
return output.toByteArray() return output.toByteArray()
}
} }
actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher { actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher {

View File

@ -17,7 +17,7 @@ actual class ECDHKeyPair(
actual val privateKey: ECDHPrivateKey get() = delegate.private actual val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate.public 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") @Suppress("FunctionName")

View File

@ -36,7 +36,11 @@ actual class PlatformSocket : Closeable {
* @throws SendPacketInternalException * @throws SendPacketInternalException
*/ */
actual suspend inline fun send(packet: ByteReadPacket) { actual suspend inline fun send(packet: ByteReadPacket) {
try {
writeChannel.writePacket(packet) writeChannel.writePacket(packet)
} catch (e: Exception) {
throw SendPacketInternalException(e)
}
} }
/** /**
@ -45,7 +49,11 @@ actual class PlatformSocket : Closeable {
actual suspend inline fun read(): ByteReadPacket { actual suspend inline fun read(): ByteReadPacket {
// do not use readChannel.readRemaining() !!! this function never returns // do not use readChannel.readRemaining() !!! this function never returns
ByteArrayPool.useInstance { buffer -> 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) return buffer.toReadPacket(0, count)
} }
} }