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

View File

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

View File

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

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(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 net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
import net.mamoe.mirai.utils.cryptor.encryptAndWrite
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeHex
import net.mamoe.mirai.utils.io.writeIntLVPacket
@ -26,7 +24,7 @@ internal class OutgoingPacket constructor(
val delegate: ByteReadPacket
) {
val name: String by lazy {
name ?: commandName.toString()
name ?: commandName
}
}
@ -50,22 +48,13 @@ internal val EMPTY_BYTE_ARRAY = ByteArray(0)
*
* byte[] body encrypted by 16 zero
*/
/*
* 00 00 02 34 // remaining.length + 4
* 00 00 00 0B
* 01
* 00 01 4E 73 // sequence
* 00
* 00 00 00 0E
* 31 39 39 34 37 30 31 30 32 31
* 18 5D 8F 17 7D 67 71 61 FE DB 30 A4 4D 16 DD 0E 8D 84 0A F2 44 BE FB BB 11 BB B4 AC 79 50 50 9F 4C 99 CC 77 0B AA B6 E0 06 0C F7 91 79 99 57 31 3D EF 38 92 2C C8 81 33 79 83 FF C6 2F BA 18 2A 33 F8 D9 4E CD 62 07 D8 08 B7 1A 1E C7 EB AC AB B4 1E C9 9D A9 15 9C 29 29 2A 99 F6 BB D0 43 65 D6 5E 9C 93 A8 8D 17 08 5B 6A 29 92 58 6A 75 C9 B5 45 B3 0E A5 D3 52 8F 9A A4 88 36 A0 14 3A 21 F2 46 C3 91 66 A3 73 67 6A 3E F7 9D 8E 44 52 87 7B 8A C7 1B E2 D3 98 62 E8 25 30 2A 43 5C 5A B2 C6 45 F5 39 EC 85 81 BF 7D 22 4C E8 01 87 92 48 38 06 6B A0 83 70 0B 51 ED CF 7A FF E2 F2 06 3E A7 95 4E E5 29 23 32 1C FE 79 C6 08 C5 7A 39 B9 AF CD 4F 80 3E 5D 74 4D 0B E1 10 33 8D F0 54 8E 0E 22 96 B4 06 7F 29 01 1E CA 30 35 FD 8A 2E 51 04 20 79 7B 08 DC DF F6 64 21 6B C5 95 34 B3 40 D2 E8 CE BB DC 69 89 75 62 A6 0B 4A 49 9D 90 BA 68 2B BD 8A 50 2D 68 6B 56 40 0C 39 F2 08 20 1B EB A4 A5 20 1D 1F 7E FA 4B B8 2E 58 79 2A 16 54 26 6C C8 44 6C 4F 64 2D 5C 0C 47 2E 90 13 A9 D7 33 4A 51 17 6E 3F 3E 48 AE 39 D8 45 05 2C 0C 3C 9F 92 39 DB 62 B3 BB 64 EE 7E 91 C5 84 92 10 96 D9 F1 13 02 94 00 EA DA 87 7C 85 7B 68 BA 8D A1 AB F5 CD 9C EB 4C CD A0 38 78 43 80 DD E5 1D 28 25 1F F0 25 EF 0D 95 91 0F 21 5D 41 06 00 03 48 77 E0 98 09 3E 04 5A B0 93 63 3B AE 8E 49 0C C2 12 BA DD C3 5A ED FF 68 98 22 C4 5E F6 1E 85 57 15 E8 7E 26 22 E3 70 C2 57 F4 CE 2F CB C4 DC 39 4A 9C FE DE 27 18 D3 36 66 88 92 D7 69 D0 04 8E 93 9B AD E9 2E 5A 2C 91 CD 28 DF BE 62 CF 2C 72 8E FD A9 1F 0E 8E 00 9E 54 28 50 25 0C E7 DC 98 85 C9 B3 59 A8 97 F5 2E 7F 44 4C 43 3C C4 65 E5 AB DB 5B 3C 50 2D 53 B3 EA 74 3C 39 F4 0A 52 31 34 30 F5 E6 82 CD 36 D9
*/
@UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*>.buildOutgingPacket(
internal inline fun PacketFactory<*>.buildOutgoingPacket(
client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB?
name: String? = this.commandName,
commandName: String = this.commandName,
key: ByteArray,
key: ByteArray = client.wLoginSigInfo.d2Key,
body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket {
val sequenceId: Int = client.nextSsoSequenceId()
@ -73,7 +62,7 @@ internal inline fun PacketFactory<*>.buildOutgingPacket(
return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B)
writeByte(1)
writeByte(bodyType)
writeInt(sequenceId)
writeByte(0)
client.uin.toString().let {
@ -87,6 +76,68 @@ internal inline fun PacketFactory<*>.buildOutgingPacket(
})
}
/**
* buildOutgoingPacket writeUniPacket fast-path
*/
@UseExperimental(MiraiInternalAPI::class)
internal inline fun PacketFactory<*>.buildOutgoingUniPacket(
client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB?
name: String? = this.commandName,
commandName: String = this.commandName,
key: ByteArray = client.wLoginSigInfo.d2Key,
extraData: ByteReadPacket = BRP_STUB,
body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket {
val sequenceId: Int = client.nextSsoSequenceId()
return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B)
writeByte(bodyType)
writeInt(sequenceId)
writeByte(0)
client.uin.toString().let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
encryptAndWrite(key) {
writeUniPacket(commandName, extraData) {
body(sequenceId)
}
}
}
})
}
@UseExperimental(MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeUniPacket(
commandName: String,
extraData: ByteReadPacket = BRP_STUB,
body: BytePacketBuilder.() -> Unit
) {
writeIntLVPacket(lengthOffset = { it + 4 }) {
commandName.let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
writeInt(4 + 4)
writeInt(45112203) // 02 B0 5B 8B
if (extraData === BRP_STUB) {
writeInt(0x04)
} else {
writeInt((extraData.remaining + 4).toInt())
writePacket(extraData)
}
}
// body
writeIntLVPacket(lengthOffset = { it + 4 }, builder = body)
}
/**
* 最外层的包. 结构适用于登录.
*
@ -213,37 +264,6 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
}
/**
* Outermost packet, not for login
*
* **Packet structure**
* int remaining.length + 4
* int 0x0B
* byte 0x01
* byte 0
* int [uinAccount].length + 4
* byte[] uinAccount
*
* byte[] body encrypted by [sessionKey]
*/
internal inline fun PacketFactory<*>.buildSessionOutgoingPacket(
uinAccount: String,
sessionKey: DecrypterByteArray,
body: BytePacketBuilder.() -> Unit
): ByteReadPacket = buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x00_00_00_0B)
writeByte(0x01)
writeByte(0)
writeInt(uinAccount.length + 4)
writeStringUtf8(uinAccount)
encryptAndWrite(sessionKey, body)
}
}
/**
* Writes a request packet
* This is the innermost packet structure
@ -293,6 +313,8 @@ internal fun BytePacketBuilder.writeOicqRequestPacket(
writeByte(0x03) // tail
// }
}
/*
00 00 01 64
00 00 00 0A

View File

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

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

View File

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

View File

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

View File

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

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.discardExact
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.readRemainingAsProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
import net.mamoe.mirai.qqandroid.io.serialization.readRemainingAsJceStruct
import net.mamoe.mirai.qqandroid.io.writeProtoBuf
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion2
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.RequestPushNotify
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataVersion2
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket
import net.mamoe.mirai.qqandroid.utils.toMessageChain
import net.mamoe.mirai.utils.firstValue
import net.mamoe.mirai.utils.io.debugPrint
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.toReadPacket
class MessageSvc {
@ -25,31 +28,64 @@ class MessageSvc {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushNotify {
discardExact(8)
@Serializable
class ResponseDataRequestPushNotify(
@SerialId(0) val notify: RequestPushNotify
) : JceStruct
val requestPushNotify = readRemainingAsJceStruct(RequestPacket.serializer()).sBuffer
return readRemainingAsJceStruct(RequestPacket.serializer()).sBuffer
.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue()
.toReadPacket().apply { discardExact(1) }
.debugPrint()
.readRemainingAsJceStruct(RequestPushNotify.serializer())
println(requestPushNotify.contentToString())
with(bot.network) {
GetMsgRequest(
bot.client,
requestPushNotify
).sendAndExpect<MsgSvc.PbGetMsgResp>()
}
return requestPushNotify
override suspend fun QQAndroidBot.handle(packet: 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.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.ImageLink
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.ImageId
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.toMessage
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.data.MsgOnlinePush
import net.mamoe.mirai.utils.io.encodeToString
internal class ImageIdQQA(
override val value: String,
originalLink: String
) : ImageId {
val link: ImageLink = ImageLinkQQA("http://gchat.qpic.cn$originalLink")
}
internal inline class ImageLinkQQA(override val original: String) : ImageLink
import net.mamoe.mirai.qqandroid.utils.toMessageChain
internal class OnlinePush {
internal object PbPushGroupMsg : PacketFactory<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
discardExact(4)
val pbPushMsg = ProtoBuf.load(MsgOnlinePush.PbPushMsg.serializer(), readBytes())
val message = MessageChain(initialCapacity = pbPushMsg.msg!!.msgBody!!.richText!!.elems!!.size)
var extraInfo: ImMsgBody.ExtraInfo? = null
val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo
pbPushMsg.msg.msgBody!!.richText!!.elems!!.forEach {
when {
it.customFace != null -> message.add(Image(ImageIdQQA(it.customFace.filePath, it.customFace.origUrl)))
it.text != null -> message.add(it.text.str.encodeToString().toMessage())
it.extraInfo != null -> extraInfo = it.extraInfo
}
}
val group = bot.getGroup(pbPushMsg.msg.msgHead!!.groupInfo!!.groupCode)
val group = bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode)
val flags = extraInfo?.flags ?: 0
return GroupMessage(
bot = bot,
group = group,
senderName = pbPushMsg.msg.msgHead.groupInfo!!.groupCard.encodeToString(),
senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard,
sender = group.getMember(pbPushMsg.msg.msgHead.fromUin),
message = message,
message = pbPushMsg.msg.msgBody.richText.toMessageChain(),
permission = when {
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER

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 subAppId = 537062845L
@ -59,26 +59,16 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse>("wt
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) {
writeShort(8) // subCommand
writeShort(6) // count of TLVs, probably ignored by server?TODO
writeShort(7) // subCommand
writeShort(7) // count of TLVs, probably ignored by server?TODO
t8(2052)
t104(client.t104)
t116(150470524, 66560)
t174(t174)
t17a(9)
t197(byteArrayOf(0.toByte()))
//t401(md5(client.device.guid + "12 34567890123456".toByteArray() + t402))
//t19e(0)//==tlv408
t17c(phoneNumber.toByteArray())
t401(md5(client.device.guid + "1234567890123456".toByteArray() + t402))
t19e(0)//==tlv408
}
/**
*
* 0x00000008(8)=00 00 00 00 08 04 00 00,//2052固定
0x00000104(260)=41 69 78 39 46 68 4E 44 6C 41 42 30 54 79 46 30 4B 36 67 78 37 45 6E 2B 30 7A 39 35 65 35 30 6E 66 41 3D 3D,//服务器给的tv104
0x00000116(278)=00 08 F7 FF 7C 00 01 04 00 01 5F 5E 10 E2//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.writeJceStruct
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataStructSvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.jce.SvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataStructSvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.SvcReqRegister
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket

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

View File

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

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
import net.mamoe.mirai.qqandroid.network.protocol.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.data.RequestPacket
import net.mamoe.mirai.utils.io.hexToBytes
class TestRequesetPacket {

View File

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

View File

@ -5,6 +5,8 @@ import io.ktor.client.engine.cio.CIO
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.io.ByteArrayPool
import java.io.ByteArrayOutputStream
import java.io.DataInput
import java.io.EOFException
@ -83,17 +85,23 @@ actual fun crc32(key: ByteArray): Int = CRC32().apply { update(key) }.value.toIn
*/
actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress
actual fun ByteArray.unzip(): ByteArray {
actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
this.checkOffsetAndLength(offset, length)
if (length == 0) return ByteArray(0)
val inflater = Inflater()
inflater.reset()
val output = ByteArrayOutputStream()
inflater.setInput(this)
val buffer = ByteArray(128)
ByteArrayOutputStream().use { output ->
inflater.setInput(this, offset, length)
ByteArrayPool.useInstance {
while (!inflater.finished()) {
output.write(buffer, 0, inflater.inflate(buffer))
output.write(it, 0, inflater.inflate(it))
}
}
inflater.end()
return output.toByteArray()
}
}
actual fun newCoroutineDispatcher(threadCount: Int): CoroutineDispatcher {

View File

@ -15,7 +15,7 @@ actual class ECDHKeyPair(
actual val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate.public
actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
actual val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
}
@Suppress("FunctionName")

View File

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

View File

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

View File

@ -8,14 +8,14 @@ import net.mamoe.mirai.utils.coerceAtLeastOrFail
/**
* .
* . QQ Android 中叫做 "Troop"
*
* Group ID Group Number 并不是同一个值.
* - Group Number([Group.id]) 是通常使用的群号码.( QQ 客户端中可见)
* - Group ID([Group.internalId]) 是与调用 API 时使用的 id.( QQ 客户端中不可见)
* @author Him188moe
*/
interface Group : Contact, CoroutineScope/*, Map<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] 的映射
*/
@ -86,7 +86,10 @@ fun Long.groupInternalId(): GroupInternalId = GroupInternalId(this)
/**
* 将无符号整数格式的 [Long] 转为 [GroupId].
*
* : Java 中常用 [Long] 来表示 [UInt]
* : Java 中常用 [Long] 来表示 [UInt].
*
* : Kotlin/Java, 有符号的数据类型的二进制最高位为符号标志.
* 如一个 byte, `1000 0000` 最高位为 1, 则为负数.
*/
fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0))

View File

@ -4,3 +4,12 @@ package net.mamoe.mirai.data
* 从服务器收到的包解析之后的结构化数据.
*/
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 获取更多说明
*/
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

View File

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

View File

@ -24,7 +24,10 @@ expect val deviceName: String
*/
expect fun crc32(key: ByteArray): Int
expect fun ByteArray.unzip(): ByteArray
/**
* zip 压缩
*/
expect fun ByteArray.unzip(offset: Int = 0, length: Int = this.size - offset): ByteArray
/**
* MD5 算法
@ -49,3 +52,9 @@ expect fun localIpAddress(): String
expect val Http: HttpClient
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 publicKey: ECDHPublicKey
val shareKey: ByteArray
/**
* 私匙和固定公匙([initialPublicKey]) 计算得到的 shareKey
*/
val initialShareKey: ByteArray
}
/**
* 椭圆曲线密码, ECDH 加密
*/
expect class ECDH(keyPair: ECDHKeyPair) {
val keyPair: ECDHKeyPair
/**
* [keyPair] 的私匙和 [peerPublicKey] 计算 shareKey
*/
fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray
companion object {
/**
* 由完整的 publicKey ByteArray 得到 [ECDHPublicKey]
*/
fun constructPublicKey(key: ByteArray): ECDHPublicKey
/**
* 生成随机密匙对
*/
fun generateKeyPair(): ECDHKeyPair
/**
* 由一对密匙计算 shareKey
*/
fun calculateShareKey(privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey): ByteArray
}
override fun toString(): String
}
/**
*
*/
@Suppress("FunctionName")
expect fun ECDH(): ECDH

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(
n: Int = remaining.toInt()//not that safe but adequate
): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) }
@ -75,10 +83,21 @@ fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().
private inline fun <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")
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>()
var key = 0

View File

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

View File

@ -17,7 +17,7 @@ actual class ECDHKeyPair(
actual val privateKey: ECDHPrivateKey get() = delegate.private
actual val publicKey: ECDHPublicKey get() = delegate.public
actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
actual val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
}
@Suppress("FunctionName")

View File

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