Rewrite packets

This commit is contained in:
Him188 2019-10-16 22:33:02 +08:00
parent 61895b5c8e
commit 09046ae686
35 changed files with 461 additions and 490 deletions

View File

@ -1,7 +1,6 @@
package net.mamoe.mirai.network
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.*
import kotlinx.io.core.Closeable
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocket
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
@ -11,6 +10,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRe
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.LoginConfiguration
import net.mamoe.mirai.utils.PlatformDatagramChannel
import kotlin.coroutines.ContinuationInterceptor
/**
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
@ -69,11 +69,12 @@ interface BotNetworkHandler<Socket : DataPacketSocket> : Closeable {
suspend fun sendPacket(packet: ClientPacket)
override fun close() {
NetworkScope.cancel("handler closed", HandlerClosedException())
//todo check??
NetworkScope.coroutineContext[ContinuationInterceptor]!!.cancelChildren(HandlerClosedException())
}
}
/**
* [BotNetworkHandler] closed
*/
class HandlerClosedException : Exception()
class HandlerClosedException : CancellationException("handler closed")

View File

@ -204,6 +204,24 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
}
}
/* todo 修改为这个模式是否更好?
interface Pk
object TestPacket : Pk {
operator fun invoke(bot: UInt): TestPacket.(BytePacketBuilder) -> Unit {
}
}
override inline fun <reified P : Pk> send(p: P.(BytePacketBuilder) -> Unit): UShort {
val encoded = with(P::class.objectInstance!!){
buildPacket {
this@with.p(this)
}
}
}*/
override suspend fun sendPacket(packet: ClientPacket) = withContext(NetworkScope.coroutineContext) {
check(channel.isOpen) { "channel is not open" }
@ -282,13 +300,22 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
this.loginIP = packet.loginIP
this.loginTime = packet.loginTime
this.token0825 = packet.token0825
socket.sendPacket(ClientPasswordSubmissionPacket(bot.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.privateKey, packet.token0825, socket.configuration.randomDeviceName))
socket.sendPacket(ClientPasswordSubmissionPacket(
bot = bot.qqNumber,
password = bot.account.password,
loginTime = loginTime,
loginIP = loginIP,
privateKey = privateKey,
token0825 = token0825,
token00BA = null,
randomDeviceName = socket.configuration.randomDeviceName
))
}
}
is ServerLoginResponseFailedPacket -> {
loginResult.complete(packet.loginResult)
bot.close()
return
}
@ -296,24 +323,34 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
this.privateKey = getRandomByteArray(16)//似乎是必须的
this.token00BA = packet.token00BA
socket.sendPacket(ClientLoginResendPacket3105(bot.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.privateKey, this.token0825, packet.token00BA, socket.configuration.randomDeviceName))
socket.sendPacket(ClientPasswordSubmissionPacket(
bot = bot.qqNumber,
password = bot.account.password,
loginTime = loginTime,
loginIP = loginIP,
privateKey = privateKey,
token0825 = token0825,
token00BA = packet.token00BA,
randomDeviceName = socket.configuration.randomDeviceName
))
}
is ServerLoginResponseVerificationCodeInitPacket -> {
is ServerLoginResponseCaptchaInitPacket -> {
//[token00BA]来源之一: 验证码
this.token00BA = packet.token00BA
this.captchaCache = packet.verifyCodePart1
if (packet.unknownBoolean == true) {
this.captchaSectionId = 1
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.qqNumber, this.token0825, this.captchaSectionId++, packet.token00BA))
socket.sendPacket(ClientCaptchaTransmissionRequestPacket(bot.qqNumber, this.token0825, this.captchaSectionId++, packet.token00BA))
}
}
is ServerCaptchaTransmissionPacket -> {
if (packet is ServerCaptchaWrongPacket) {
//packet is ServerCaptchaWrongPacket
if (this.captchaSectionId == 0) {
bot.error("验证码错误, 请重新输入")
captchaSectionId = 1
this.captchaSectionId = 1
this.captchaCache = null
}
@ -322,15 +359,17 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
if (packet.transmissionCompleted) {
val code = solveCaptcha(captchaCache!!)
if (code == null) {
this.captchaCache = null
this.captchaSectionId = 1
socket.sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.qqNumber, token0825))
if (code == null) {
this.captchaSectionId = 1//意味着正在刷新验证码
socket.sendPacket(ClientCaptchaRefreshPacket(bot.qqNumber, token0825))
} else {
socket.sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.qqNumber, token0825, code, packet.verificationToken))
this.captchaSectionId = 0//意味着已经提交验证码
socket.sendPacket(ClientCaptchaSubmitPacket(bot.qqNumber, token0825, code, packet.verificationToken))
}
} else {
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.qqNumber, token0825, captchaSectionId++, packet.token00BA))
socket.sendPacket(ClientCaptchaTransmissionRequestPacket(bot.qqNumber, token0825, captchaSectionId++, packet.token00BA))
}
}
@ -339,20 +378,20 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
socket.sendPacket(ClientSessionRequestPacket(bot.qqNumber, socket.serverIp, packet.token38, packet.token88, packet.encryptionKey))
}
//是ClientPasswordSubmissionPacket之后服务器回复的
//是ClientPasswordSubmissionPacket之后服务器回复的可能之一
is ServerLoginResponseKeyExchangePacket -> {
//if (packet.tokenUnknown != null) {
//this.token00BA = packet.token00BA!!
//println("token00BA changed!!! to " + token00BA.toUByteArray())
//}
if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) {
this.privateKey = packet.privateKeyUpdate
socket.sendPacket(ClientLoginResendPacket3104(bot.qqNumber, bot.account.password, loginTime, loginIP, privateKey, token0825, packet.tokenUnknown
?: token00BA, socket.configuration.randomDeviceName, packet.tlv0006))
} else {
socket.sendPacket(ClientLoginResendPacket3106(bot.qqNumber, bot.account.password, loginTime, loginIP, privateKey, token0825, packet.tokenUnknown
?: token00BA, socket.configuration.randomDeviceName, packet.tlv0006))
}
socket.sendPacket(ClientPasswordSubmissionPacket(
bot = bot.qqNumber,
password = bot.account.password,
loginTime = loginTime,
loginIP = loginIP,
privateKey = privateKey,
token0825 = token0825,
token00BA = packet.tokenUnknown ?: token00BA,
randomDeviceName = socket.configuration.randomDeviceName
))
}
is ServerSessionKeyResponsePacket -> {
@ -386,7 +425,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
is ServerCaptchaPacket.Encrypted -> socket.distributePacket(packet.decrypt())
is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> socket.distributePacket(packet.decrypt())
is ServerLoginResponseCaptchaInitPacket.Encrypted -> socket.distributePacket(packet.decrypt())
is ServerLoginResponseKeyExchangePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.privateKey))
is ServerLoginResponseSuccessPacket.Encrypted -> socket.distributePacket(packet.decrypt(this.privateKey))
is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey))

View File

@ -41,10 +41,13 @@ object TIMProtocol {
*/
const val touchKey = "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D"//16
/**
* Redirection 发出时写入, 并用于加密, 接受 Redirection response 时解密.
*/
const val redirectionKey = "A8 F2 14 5F 58 12 60 AF 07 63 97 D6 76 B2 1A 3B"//16
//统一替换为了 touchKey
///**
// * Redirection 发出时写入, 并用于加密, 接受 Redirection response 时解密.
// * 这个 key 似乎是可以任意的.
// */
//const val redirectionKey = "A8 F2 14 5F 58 12 60 AF 07 63 97 D6 76 B2 1A 3B"//16
/**
* 并非常量. 设置为常量是为了让 [shareKey] 为常量

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.handler
import kotlinx.coroutines.CompletableDeferred
@ -15,6 +17,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerSKeyResponsePacket
import net.mamoe.mirai.utils.getGTK
import net.mamoe.mirai.utils.hexToBytes
import kotlin.properties.Delegates
/**
* 动作: 获取好友列表, 点赞, 踢人等.
@ -107,17 +110,16 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
private val future: CompletableDeferred<AddFriendResult>
//private val image: BufferedImage
) {
lateinit var id: ByteArray
var id: UShort = UninitializedPacketId
fun onPacketReceived(packet: ServerPacket) {
if (!::id.isInitialized) {
if (id == UninitializedPacketId) {
return
}
when (packet) {
is ServerCanAddFriendResponsePacket -> {
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
if (packet.id != id) {
return
}
@ -160,17 +162,17 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
private val future: CompletableDeferred<AddFriendResult>,
private val message: Lazy<String>
) {
lateinit var id: ByteArray
var id: UShort = UninitializedPacketId
suspend fun onPacketReceived(packet: ServerPacket) {
if (!::id.isInitialized) {
if (id == UninitializedPacketId) {
return
}
when (packet) {
is ServerCanAddFriendResponsePacket -> {
if (!(packet.idByteArray.contentEquals(id))) {
if (packet.id != id) {
return
}
@ -201,7 +203,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
suspend fun sendAddRequest() {
session.socket.sendPacket(ClientCanAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey).also { this.id = it.idHex.hexToBytes() })
session.socket.sendPacket(ClientCanAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey))
}
fun close() {
@ -209,3 +211,5 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
}
}
}
private val UninitializedPacketId: UShort = 0u

View File

@ -2,11 +2,14 @@
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.atomicfu.atomic
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.writeHex
//TODO 将序列 ID 从包 ID 中独立出来
/**
* 发给服务器的数据包. 必须有 [PacketId] 注解或 `override` [packetId]. 否则将会抛出 [IllegalStateException]
*/
abstract class ClientPacket : Packet(), Closeable {
/**
* Encode this packet.
@ -15,25 +18,30 @@ abstract class ClientPacket : Packet(), Closeable {
*/
protected abstract fun encode(builder: BytePacketBuilder)
override val sequenceId: UShort by lazy {
atomicNextSequenceId()
}
companion object {
@Suppress("PrivatePropertyName")
private val UninitializedByteReadPacket = ByteReadPacket(IoBuffer.Empty, IoBuffer.EmptyPool)
private val sequenceIdInternal = atomic(1)
internal fun atomicNextSequenceId() = sequenceIdInternal.getAndIncrement().toUShort()
}
/**
* 务必 [ByteReadPacket.close] [close] 或使用 [Closeable.use]
*/
var packet: ByteReadPacket = UninitializedByteReadPacket
internal var packet: ByteReadPacket = UninitializedByteReadPacket
get() {
if (field === UninitializedByteReadPacket) build()
return field
}
private set
private fun build(): ByteReadPacket {
packet = buildPacket {
writeHex(TIMProtocol.head)
writeHex(TIMProtocol.ver)
writeHex(idHex)
writePacketId()
encode(this)
writeHex(TIMProtocol.tail)
}
@ -43,4 +51,11 @@ abstract class ClientPacket : Packet(), Closeable {
override fun toString(): String = packetToString()
override fun close() = if (this.packet === UninitializedByteReadPacket) Unit else this.packet.close()
private fun BytePacketBuilder.writePacketId() {
writeUShort(this@ClientPacket.id)
writeUShort(sequenceId)
}
}
private val UninitializedByteReadPacket = ByteReadPacket(IoBuffer.Empty, IoBuffer.EmptyPool)

View File

@ -1,4 +1,4 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
@ -9,11 +9,10 @@ import kotlinx.io.core.readUInt
import net.mamoe.mirai.utils.OnlineStatus
import kotlin.properties.Delegates
/**
* 好友在线状态改变
*/
@PacketId("00 81")
@PacketId(0x00_81u)
class ServerFieldOnlineStatusChangedPacket(input: ByteReadPacket) : ServerPacket(input) {
var qq: UInt by Delegates.notNull()
lateinit var status: OnlineStatus
@ -27,7 +26,8 @@ class ServerFieldOnlineStatusChangedPacket(input: ByteReadPacket) : ServerPacket
//在线 XX XX XX XX 01 00 00 00 00 00 00 00 0A 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00
//忙碌 XX XX XX XX 01 00 00 00 00 00 00 00 32 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00
@PacketId(0x00_81u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerFieldOnlineStatusChangedPacket = ServerFieldOnlineStatusChangedPacket(this.decryptBy(sessionKey)).setId(this.idHex)
fun decrypt(sessionKey: ByteArray): ServerFieldOnlineStatusChangedPacket = ServerFieldOnlineStatusChangedPacket(this.decryptBy(sessionKey)).applySequence(sequenceId)
}
}

View File

@ -1,4 +1,4 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
@ -16,26 +16,23 @@ import net.mamoe.mirai.utils.writeRandom
*
* @author Him188moe
*/
@PacketId("00 5C")
@PacketId(0x00_5Cu)
class ClientAccountInfoRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
writeUByte(0x88.toUByte())
writeUByte(0x88u)
writeQQ(qq)
writeByte(0x00)
}
}
}
@PacketId("00 5C")
@PacketId(0x00_5Cu)
class ServerAccountInfoResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
//等级
//升级剩余活跃天数
@ -44,8 +41,8 @@ class ServerAccountInfoResponsePacket(input: ByteReadPacket) : ServerPacket(inpu
}
@PacketId("00 5C")
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerAccountInfoResponsePacket = ServerAccountInfoResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
@PacketId(0x00_5Cu)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerAccountInfoResponsePacket = ServerAccountInfoResponsePacket(this.decryptBy(sessionKey)).applySequence(sequenceId)
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
@ -5,16 +7,11 @@ import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.*
@PacketId("00 58")
@PacketId(0x00_58u)
class ClientHeartbeatPacket(
private val bot: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer)
@ -24,4 +21,5 @@ class ClientHeartbeatPacket(
}
}
@PacketId(0x00_58u)
class ServerHeartbeatResponsePacket(input: ByteReadPacket) : ServerPacket(input)

View File

@ -1,31 +1,25 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.utils.hexToUBytes
import kotlinx.io.core.Closeable
import net.mamoe.mirai.utils.toUHexString
/**
* 数据包
* 数据包.
*/
abstract class Packet {
open val idHex: String by lazy {
this::class.annotations.filterIsInstance<PacketId>().firstOrNull()?.value?.trim() ?: ""
}
abstract class Packet : Closeable {
/**
* 2 Ubyte
*/
open val id: UShort = (this::class.annotations.firstOrNull { it is PacketId } as? PacketId)?.value ?: error("Annotation PacketId not found")
open val fixedId: String by lazy {
when (this.idHex.length) {
0 -> "__ __ __ __"
2 -> this.idHex + " __ __ __"
5 -> this.idHex + " __ __"
7 -> this.idHex + " __"
else -> this.idHex
}
}
/**
* 包序列 id. 唯一
*/
abstract val sequenceId: UShort
open val idByteArray: ByteArray by lazy {
idHex.hexToUBytes().toByteArray()
}
val idHexString: String get() = (id.toInt().shl(16) or sequenceId.toInt()).toUHexString()
}
internal expect fun Packet.packetToString(): String

View File

@ -1,11 +1,10 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE)
annotation class PacketId(
/**
* 用于识别的包 ID
*/
val value: String
val value: UShort
)

View File

@ -33,7 +33,6 @@ fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) =
*/
abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: EventPacketIdentity) : ServerPacket(input) {
class Raw(input: ByteReadPacket) : ServerPacket(input) {
fun distribute(): ServerEventPacket = with(input) {
val eventIdentity = EventPacketIdentity(
from = readUInt(),
@ -67,8 +66,9 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
//todo 错了. 可能是 00 79 才是.
return@with ServerFriendTypingCanceledPacket(input, eventIdentity)
/*
if (readUByte().toUInt() == 0x37u) ServerFriendTypingStartedPacket(input, eventIdentity)
else /*0x22*/ ServerFriendTypingCanceledPacket(input, eventIdentity)
else /*0x22*/ ServerFriendTypingCanceledPacket(input, eventIdentity)*/
}
"00 79" -> IgnoredServerEventPacket(type, input, eventIdentity)
@ -78,11 +78,11 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
MiraiLogger.logDebug("UnknownEvent type = ${type.toUHexString()}")
UnknownServerEventPacket(input, eventIdentity)
}
}.setId(idHex)
}.applyId(id).applySequence(sequenceId)
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey)).setId(this.idHex)
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey)).applyId(id).applySequence(sequenceId)
}
}
@ -90,9 +90,8 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
val bot: Long,
val sessionKey: ByteArray
) : ClientPacket() {
override val idHex: String = this@ServerEventPacket.idHex
override val idByteArray: ByteArray = this@ServerEventPacket.idByteArray
override val fixedId: String = idHex
override val id: UShort get() = this@ServerEventPacket.id
override val sequenceId: UShort get() = this@ServerEventPacket.sequenceId
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)

View File

@ -7,22 +7,17 @@ import kotlinx.io.core.Closeable
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.*
import kotlin.properties.Delegates
/**
* 来自服务器的数据包
*
* @see parseServerPacket
*/
abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable {
override var idHex: String = EMPTY_ID_HEX
get() {
if (field === EMPTY_ID_HEX) {
idHex = (this::class.annotations.firstOrNull { it::class == PacketId::class } as? PacketId)?.value?.trim()
?: ""
}
return field
}
var encoded: Boolean = false
override var id: UShort = super.id
override var sequenceId: UShort by Delegates.notNull()
open fun decode() {
@ -30,65 +25,58 @@ abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable {
override fun close() = this.input.close()
companion object {
private const val EMPTY_ID_HEX = "EMPTY_ID_HEX"
}
override fun toString(): String = this.packetToString()
fun getFixedId(id: String): String = when (id.length) {
0 -> "__ __ __ __"
2 -> "$id __ __ __"
5 -> "$id __ __"
7 -> "$id __"
else -> id
}
fun decryptBy(key: ByteArray): ByteReadPacket {
return ByteReadPacket(decryptAsByteArray(key))
}
fun decryptBy(key: IoBuffer): ByteReadPacket {
return ByteReadPacket(decryptAsByteArray(key))
}
fun decryptBy(keyHex: String): ByteReadPacket {
return this.decryptBy(keyHex.hexToBytes())
}
fun decryptBy(key1: ByteArray, key2: ByteArray): ByteReadPacket {
return TEA.decrypt(this.decryptAsByteArray(key1), key2).toReadPacket()
}
fun decryptBy(key1: String, key2: ByteArray): ByteReadPacket {
return this.decryptBy(key1.hexToBytes(), key2)
}
fun decryptBy(key1: String, key2: IoBuffer): ByteReadPacket {
return this.decryptBy(key1.hexToBytes(), key2.readBytes())
}
fun decryptBy(key1: ByteArray, key2: String): ByteReadPacket {
return this.decryptBy(key1, key2.hexToBytes())
}
fun decryptBy(keyHex1: String, keyHex2: String): ByteReadPacket {
return this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes())
}
fun decryptAsByteArray(key: ByteArray): ByteArray {
return TEA.decrypt(input.readRemainingBytes().cutTail(1), key)
}
fun decryptAsByteArray(keyHex: String): ByteArray = this.decryptAsByteArray(keyHex.hexToBytes())
fun decryptAsByteArray(buffer: IoBuffer): ByteArray = this.decryptAsByteArray(buffer.readBytes())
}
fun <P : ServerPacket> P.setId(idHex: String): P {
this.idHex = idHex
fun <S : ServerPacket> S.applyId(id: UShort): S {
this.id = id
return this
}
fun <S : ServerPacket> S.applySequence(sequenceId: UShort): S {
this.sequenceId = sequenceId
return this
}
fun ServerPacket.decryptBy(key: ByteArray): ByteReadPacket {
return ByteReadPacket(decryptAsByteArray(key))
}
fun ServerPacket.decryptBy(key: IoBuffer): ByteReadPacket {
return ByteReadPacket(decryptAsByteArray(key))
}
fun ServerPacket.decryptBy(keyHex: String): ByteReadPacket {
return this.decryptBy(keyHex.hexToBytes())
}
fun ServerPacket.decryptBy(key1: ByteArray, key2: ByteArray): ByteReadPacket {
return TEA.decrypt(this.decryptAsByteArray(key1), key2).toReadPacket()
}
fun ServerPacket.decryptBy(key1: String, key2: ByteArray): ByteReadPacket {
return this.decryptBy(key1.hexToBytes(), key2)
}
fun ServerPacket.decryptBy(key1: String, key2: IoBuffer): ByteReadPacket {
return this.decryptBy(key1.hexToBytes(), key2.readBytes())
}
fun ServerPacket.decryptBy(key1: ByteArray, key2: String): ByteReadPacket {
return this.decryptBy(key1, key2.hexToBytes())
}
fun ServerPacket.decryptBy(keyHex1: String, keyHex2: String): ByteReadPacket {
return this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes())
}
fun ServerPacket.decryptAsByteArray(key: ByteArray): ByteArray {
return TEA.decrypt(input.readRemainingBytes().cutTail(1), key)
}
fun ServerPacket.decryptAsByteArray(keyHex: String): ByteArray = this.decryptAsByteArray(keyHex.hexToBytes())
fun ServerPacket.decryptAsByteArray(buffer: IoBuffer): ByteArray = this.decryptAsByteArray(buffer.readBytes())

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
@ -7,18 +9,26 @@ import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.toUHexString
class UnknownServerPacket(input: ByteReadPacket) : ServerPacket(input) {
class UnknownServerPacket(
input: ByteReadPacket,
override var id: UShort,
override var sequenceId: UShort
) : ServerPacket(input) {
override fun decode() {
val raw = this.input.readBytes()
MiraiLogger.logDebug("UnknownServerPacket data: " + raw.toUHexString())
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): UnknownServerPacket = UnknownServerPacket(this.decryptBy(sessionKey)).setId(this.idHex)
class Encrypted(
input: ByteReadPacket,
override var id: UShort,
override var sequenceId: UShort
) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): UnknownServerPacket = UnknownServerPacket(this.decryptBy(sessionKey), this.id, this.sequenceId)
}
override fun toString(): String {
@Suppress("RemoveRedundantQualifierName")
return LoggerTextFormat.LIGHT_RED.toString() + super.toString()
}
}

View File

@ -25,7 +25,7 @@ abstract class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : Server
println(data.size)
println(data.size)
if (data.size == 209) {
return ServerTryGetImageIDSuccessPacket(data.toReadPacket()).setId(this.idHex)
return ServerTryGetImageIDSuccessPacket(data.toReadPacket()).applySequence(sequenceId)
}
return ServerTryGetImageIDFailedPacket(data.toReadPacket())

View File

@ -2,14 +2,9 @@
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.*
/**
@ -17,59 +12,69 @@ import net.mamoe.mirai.utils.*
*
* @author Him188moe
*/
@PacketId("00 A7")
@PacketId(0x00_A7u)
class ClientCanAddFriendPacket(
val bot: Long,
val qq: Long,
val sessionKey: ByteArray
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
writeQQ(bot)
writeHex(TIMProtocol.fixVer2)
encryptAndWrite(sessionKey) {
writeQQ(qq)
}
}
}
@PacketId("00 A7")
@PacketId(0x00_A7u)
class ServerCanAddFriendResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
lateinit var state: State
enum class State {
/**
* 已经添加
*/
ALREADY_ADDED,
/**
* 需要验证信息
*/
REQUIRE_VERIFICATION,
/**
* 不需要验证信息
*/
NOT_REQUIRE_VERIFICATION,
/**
* 对方拒绝添加
*/
FAILED,
}
override fun decode() {
input.readBytes()
val data = input.readRemainingBytes()
if (data.size == 99) {
override fun decode() = with(input) {
//需要验证信息 00 23 24 8B 00 01
if (input.remaining > 20) {//todo check
state = State.ALREADY_ADDED
return
}
state = when (data[data.size - 1].toUInt()) {
discardExact(4)//对方qq号
state = when (val state = readUShort().toUInt()) {
0x00u -> State.NOT_REQUIRE_VERIFICATION
0x01u -> State.REQUIRE_VERIFICATION
0x01u -> State.REQUIRE_VERIFICATION//需要验证信息
0x99u -> State.ALREADY_ADDED
0x03u,
0x04u -> State.FAILED
else -> throw IllegalArgumentException(data.contentToString())
else -> throw IllegalStateException(state.toString())
}
}
@PacketId("00 A7")
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
@PacketId(0x00_A7u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerCanAddFriendResponsePacket {
return ServerCanAddFriendResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
return ServerCanAddFriendResponsePacket(decryptBy(sessionKey)).applySequence(sequenceId)
}
}
}
@ -78,16 +83,12 @@ class ServerCanAddFriendResponsePacket(input: ByteReadPacket) : ServerPacket(inp
/**
* 请求添加好友
*/
@PacketId("00 AE")
@PacketId(0x00_AEu)
class ClientAddFriendPacket(
val bot: Long,
val qq: Long,
val sessionKey: ByteArray
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2)
@ -109,7 +110,6 @@ class ServerAddGroupResponsePacket(input: ByteReadPacket) : ServerAddContactResp
*/
abstract class ServerAddContactResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
class Raw(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() {
@ -122,7 +122,7 @@ abstract class ServerAddContactResponsePacket(input: ByteReadPacket) : ServerPac
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey))
fun decrypt(sessionKey: ByteArray): Raw = Raw(decryptBy(sessionKey))
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.*
@ -10,7 +12,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.*
@PacketId("00 CD")
@PacketId(0x00_CDu)
class ClientSendFriendMessagePacket(
private val botQQ: Long,
private val targetQQ: Long,
@ -68,5 +70,5 @@ class ClientSendFriendMessagePacket(
}
}
@PacketId("00 CD")
@PacketId(0x00_CDu)
class ServerSendFriendMessageResponsePacket(input: ByteReadPacket) : ServerPacket(input)

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.BytePacketBuilder
@ -11,7 +13,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.*
@PacketId("00 02")
@PacketId(0x00_02u)
class ClientSendGroupMessagePacket(
private val botQQ: Long,
private val groupId: Long,//不是 number
@ -19,7 +21,6 @@ class ClientSendGroupMessagePacket(
private val message: MessageChain
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
this.writeQQ(botQQ)
this.writeHex(TIMProtocol.fixVer2)
@ -48,5 +49,5 @@ class ClientSendGroupMessagePacket(
}
}
@PacketId("00 02")
@PacketId(0x00_02u)
class ServerSendGroupMessageResponsePacket(input: ByteReadPacket) : ServerPacket(input)

View File

@ -0,0 +1,18 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.BytePacketBuilder
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
// 用户资料的头像
/**
* 请求获取头像
*/
@PacketId(0x00_31u)
class ClientProfilePictureRequestPacket : ClientPacket() {
override fun encode(builder: BytePacketBuilder) {
TODO("not implemented")
}
}

View File

@ -1,4 +1,4 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.login
@ -12,16 +12,12 @@ import net.mamoe.mirai.utils.*
/**
* 改变在线状态: "我在线上", "隐身"
*/
@PacketId("00 EC")
@PacketId(0x00_ECu)
class ClientChangeOnlineStatusPacket(
private val qq: Long,
private val sessionKey: ByteArray,
private val loginStatus: OnlineStatus
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2)

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
@ -10,64 +12,22 @@ import net.mamoe.mirai.utils.*
/**
* Password submission (0836_622)
* 提交密码
*/
@PacketId("08 36 31 03")
@Tested
class ClientPasswordSubmissionPacket(
private val qq: Long,
private val password: String,
private val loginTime: Int,
private val loginIP: String,
private val privateKey: ByteArray,//16 random by client
private val token0825: ByteArray,//56 from server
private val randomDeviceName: Boolean
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.passwordSubmissionTLV1)
this.writeShort(25)
this.writeHex(TIMProtocol.publicKey)//25
this.writeHex("00 00 00 10")
this.writeHex(TIMProtocol.key0836)
//TODO shareKey 极大可能为 publicKey, key0836 计算得到
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) {
writePart1(qq, password, loginTime, loginIP, privateKey, token0825, randomDeviceName)
writePart2()
}
}
}
//实际上这些包性质都是一样的. 31 04 仅是一个序列 id, 可随机
//但为简化处理, 特固定这个 id
@PacketId("08 36 31 04")
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, randomDeviceName: Boolean, tlv0006: IoBuffer? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, randomDeviceName, tlv0006)
@PacketId("08 36 31 05")
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, randomDeviceName: Boolean, tlv0006: IoBuffer? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, randomDeviceName, tlv0006)
@PacketId("08 36 31 06")
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, randomDeviceName: Boolean, tlv0006: IoBuffer? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, randomDeviceName, tlv0006)
open class ClientLoginResendPacket constructor(
private val qq: Long,
@PacketId(0x08_36u)
class ClientPasswordSubmissionPacket constructor(
private val bot: Long,
private val password: String,
private val loginTime: Int,
private val loginIP: String,
private val privateKey: ByteArray,
private val token0825: ByteArray,
private val token00BA: ByteArray,
private val token00BA: ByteArray? = null,//
private val randomDeviceName: Boolean = false,
private val tlv0006: IoBuffer? = null
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeQQ(bot)
this.writeHex(TIMProtocol.passwordSubmissionTLV1)
this.writeShort(25)
@ -76,15 +36,18 @@ open class ClientLoginResendPacket constructor(
this.writeHex("00 00 00 10")//=16
this.writeHex(TIMProtocol.key0836)//16
//TODO shareKey 极大可能为 publicKey, key0836 计算得到
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) {
writePart1(qq, password, loginTime, loginIP, privateKey, token0825, randomDeviceName, tlv0006)
writePart1(bot, password, loginTime, loginIP, privateKey, token0825, randomDeviceName, tlv0006)
if (token00BA != null) {
writeHex("01 10")
writeHex("00 3C")
writeHex("00 01")
writeHex("00 38")
writeFully(token00BA)
}
writePart2()
}

View File

@ -1,27 +1,22 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.*
/**
* SKey 用于 http api
*/
@PacketId("00 1D")
@PacketId(0x00_1Du)
class ClientSKeyRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(qq)
writeHex(TIMProtocol.fixVer2)
@ -31,15 +26,11 @@ class ClientSKeyRequestPacket(
}
}
@PacketId("00 1D")
@PacketId(0x00_1Du)
class ClientSKeyRefreshmentRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.encryptAndWrite(sessionKey) {
@ -48,6 +39,7 @@ class ClientSKeyRefreshmentRequestPacket(
}
}
@PacketId(0x00_1Du)
class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
lateinit var sKey: String
@ -57,7 +49,8 @@ class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
MiraiLogger.logDebug("SKey=$sKey")
}
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerSKeyResponsePacket = ServerSKeyResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
@PacketId(0x00_1Du)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerSKeyResponsePacket = ServerSKeyResponsePacket(this.decryptBy(sessionKey)).applySequence(sequenceId)
}
}

View File

@ -1,17 +1,17 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.*
import kotlin.properties.Delegates
@PacketId(0x08_36u)
sealed class ServerLoginResponsePacket(input: ByteReadPacket) : ServerPacket(input)
@PacketId(0x08_36u)
class ServerLoginResponseFailedPacket(val loginResult: LoginResult, input: ByteReadPacket) : ServerLoginResponsePacket(input)
/**
@ -19,15 +19,11 @@ class ServerLoginResponseFailedPacket(val loginResult: LoginResult, input: ByteR
*
* @author NaturalHG
*/
@PacketId("08 36 31 03")
class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket, val flag: Flag) : ServerLoginResponsePacket(input) {
enum class Flag {
`08 36 31 03`,
OTHER,
}
@PacketId(0x08_36u)
class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var tlv0006: IoBuffer//120bytes
var tokenUnknown: ByteArray? = null
lateinit var privateKeyUpdate: ByteArray//16bytes
@Tested
@ -37,24 +33,19 @@ class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket, val flag: Flag
this.input.discardExact(4)//00 06 00 78
tlv0006 = this.input.readIoBuffer(0x78)
when (flag) {
Flag.`08 36 31 03` -> {//TODO 在解析时分类而不是在这里
//todo 这边原本会判断是否 `08 36 31 03`, 是才会进行下列2行读取.
try {
this.input.discardExact(8)//01 10 00 3C 00 01 00 38
tokenUnknown = this.input.readBytes(56)
//println(tokenUnknown!!.toUHexString())
}
Flag.OTHER -> {
//do nothing in this packet.
//[this.token] will be set in [BotNetworkHandler]
//token
}
} catch (e: EOFException) {
//什么都不做. 因为有的包就是没有这个数据.
}
}
class Encrypted(input: ByteReadPacket, private val flag: Flag) : ServerPacket(input) {
@PacketId(0x08_36u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
@Tested
fun decrypt(privateKey: ByteArray): ServerLoginResponseKeyExchangePacket = ServerLoginResponseKeyExchangePacket(this.decryptBy(TIMProtocol.shareKey, privateKey), flag).setId(this.idHex)
fun decrypt(privateKey: ByteArray): ServerLoginResponseKeyExchangePacket = ServerLoginResponseKeyExchangePacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).applySequence(sequenceId)
}
}
@ -66,6 +57,7 @@ enum class Gender(val id: Boolean) {
/**
* @author NaturalHG
*/
@PacketId(0x08_36u)
class ServerLoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var sessionResponseDecryptionKey: IoBuffer//16 bytes|
@ -87,12 +79,15 @@ class ServerLoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginRespo
discardExact(60)//00 20 01 60 C5 A1 39 7A 12 8E BC 34 C3 56 70 E3 1A ED 20 67 ED A9 DB 06 C1 70 81 3C 01 69 0D FF 63 DA 00 00 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6
discardExact(when (val flag = readBytes(2).toUHexString()) {
"01 07" -> 0
"00 33" -> 28
"01 10" -> 64
else -> throw IllegalStateException(flag)
})
discardExact(when (readUByte().toUInt()) {
0x00u -> if (readUByte().toUInt() == 0x33u) 28 else null
0x01u -> when (readUByte().toUInt()) {
0x07u -> 0
0x10u -> 64
else -> null
}
else -> null
} ?: error("Unknown length flag"))
discardExact(23 + 3)//01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D DE 00 09 3A 80 00
@ -117,8 +112,9 @@ class ServerLoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginRespo
gender = if (readBoolean()) Gender.FEMALE else Gender.MALE
}
@PacketId(0x08_36u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(privateKey: ByteArray): ServerLoginResponseSuccessPacket = ServerLoginResponseSuccessPacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).setId(this.idHex)
fun decrypt(privateKey: ByteArray): ServerLoginResponseSuccessPacket = ServerLoginResponseSuccessPacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).applySequence(sequenceId)
}
}
@ -128,7 +124,8 @@ class ServerLoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginRespo
*
* @author Him188moe
*/
class ServerLoginResponseVerificationCodeInitPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
@PacketId(0x08_36u)
class ServerLoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var verifyCodePart1: IoBuffer
lateinit var token00BA: ByteArray
@ -151,7 +148,8 @@ class ServerLoginResponseVerificationCodeInitPacket(input: ByteReadPacket) : Ser
}
@PacketId(0x08_36u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket = ServerLoginResponseVerificationCodeInitPacket(this.decryptAsByteArray(TIMProtocol.shareKey).toReadPacket()).setId(this.idHex)
fun decrypt(): ServerLoginResponseCaptchaInitPacket = ServerLoginResponseCaptchaInitPacket(decryptAsByteArray(TIMProtocol.shareKey).toReadPacket()).applySequence(sequenceId)
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.ByteReadPacket
@ -9,5 +11,5 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
*
* @author Him188moe
*/
@PacketId("00 EC")
@PacketId(0x00_ECu)
class ServerLoginSuccessPacket(input: ByteReadPacket) : ServerPacket(input)

View File

@ -1,14 +1,13 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.*
@PacketId("08 28 04 34")
@PacketId(0x08_28u)
class ClientSessionRequestPacket(
private val qq: Long,
private val serverIp: String,
@ -60,8 +59,8 @@ class ClientSessionRequestPacket(
}
@PacketId("08 28 04 34")
class ServerSessionKeyResponsePacket(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
@PacketId(0x08_28u)
class ServerSessionKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
lateinit var sessionKey: ByteArray
lateinit var tlv0105: ByteReadPacket
@ -109,8 +108,9 @@ Discarded(11) =41 01 00 02 03 3C 01 03 00 00 86
}
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
@PacketId(0x08_28u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionResponseDecryptionKey: IoBuffer): ServerSessionKeyResponsePacket =
ServerSessionKeyResponsePacket(this.decryptBy(sessionResponseDecryptionKey)).setId(this.idHex)
ServerSessionKeyResponsePacket(decryptBy(sessionResponseDecryptionKey)).applySequence(sequenceId)
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
@ -5,38 +7,29 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.*
/**
* The packet received when logging in, used to redirect server address
*
* @see ClientServerRedirectionPacket
* @see ClientTouchRedirectionPacket
* @see ClientPasswordSubmissionPacket
*
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@PacketId("08 25 31 01")
class ServerTouchResponsePacket(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
@PacketId(0x08_25u)
class ServerTouchResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
var serverIP: String? = null
var loginTime: Int = 0
lateinit var loginIP: String
lateinit var token0825: ByteArray//56
enum class Type {
TYPE_08_25_31_01,
TYPE_08_25_31_02,
}
override fun decode() = with(input) {
when (val id = readByte().toUByte().toInt()) {
0xFE -> {//todo 在 packet 解析时分类而不是在这里分类
0xFE -> {
discardExact(94)
serverIP = readIP()
}
@ -55,12 +48,9 @@ class ServerTouchResponsePacket(inputStream: ByteReadPacket) : ServerPacket(inpu
}
}
class Encrypted(private val type: Type, inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(): ServerTouchResponsePacket = ServerTouchResponsePacket(decryptBy(when (type) {
Type.TYPE_08_25_31_02 -> TIMProtocol.redirectionKey.hexToBytes()
Type.TYPE_08_25_31_01 -> TIMProtocol.touchKey.hexToBytes()
})).setId(this.idHex)
@PacketId(0x08_25u)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): ServerTouchResponsePacket = ServerTouchResponsePacket(decryptBy(TIMProtocol.touchKey.hexToBytes())).applySequence(sequenceId)
}
}
@ -69,17 +59,17 @@ class ServerTouchResponsePacket(inputStream: ByteReadPacket) : ServerPacket(inpu
*
* @author Him188moe
*/
@PacketId("08 25 31 01")
class ClientTouchPacket(private val qq: Long, private val serverIp: String) : ClientPacket() {
@PacketId(0x08_25u)
class ClientTouchPacket(private val bot: Long, private val serverIp: String) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.touchKey)
this.encryptAndWrite(TIMProtocol.touchKey) {
writeHex(TIMProtocol.constantData1)
writeHex(TIMProtocol.constantData2)
writeQQ(qq)
writeQQ(bot)
writeHex("00 00 00 00 03 09 00 08 00 01")
writeIP(serverIp)
writeHex("00 02 00 36 00 12 00 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 14 00 1D 01 02 00 19")
@ -93,14 +83,14 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl
*
* @author Him188moe
*/
@PacketId("08 25 31 02")
class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() {
@PacketId(0x08_25u)
class ClientTouchRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.redirectionKey)
this.writeHex(TIMProtocol.touchKey)//redirection key
this.encryptAndWrite(TIMProtocol.redirectionKey) {
this.encryptAndWrite(TIMProtocol.touchKey) {
this.writeHex(TIMProtocol.constantData1)
this.writeHex(TIMProtocol.constantData2)
this.writeQQ(qq)

View File

@ -4,27 +4,19 @@ package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.*
/**
* 客户端请求验证码图片数据的第几部分
*/
@PacketId("00 BA 31")
class ClientVerificationCodeTransmissionRequestPacket(
private val packetIdLast: Int,//ubyte
@PacketId(0x00_BAu)
class ClientCaptchaTransmissionRequestPacket(
private val qq: Long,
private val token0825: ByteArray,
private val verificationSequence: Int,
private val token00BA: ByteArray
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + packetIdLast.toByte().toUHexString()
}
@Tested
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
@ -50,9 +42,8 @@ class ClientVerificationCodeTransmissionRequestPacket(
/**
* 提交验证码
*/
@PacketId("00 BA 32")
class ClientVerificationCodeSubmitPacket(
private val packetIdLast: Int,//ubyte
@PacketId(0x00_BAu)
class ClientCaptchaSubmitPacket(
private val qq: Long,
private val token0825: ByteArray,
private val captcha: String,
@ -62,10 +53,6 @@ class ClientVerificationCodeSubmitPacket(
require(captcha.length == 4) { "captcha.length must == 4" }
}
override val idHex: String by lazy {
super.idHex + " " + packetIdLast.toByte().toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
@ -94,16 +81,11 @@ class ClientVerificationCodeSubmitPacket(
/**
* 刷新验证码
*/
@PacketId("00 BA 31")
class ClientVerificationCodeRefreshPacket(
private val packetIdLast: Int,
@PacketId(0x00_BAu)
class ClientCaptchaRefreshPacket(
private val qq: Long,
private val token0825: ByteArray
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + packetIdLast.toByte().toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
@ -122,18 +104,12 @@ class ClientVerificationCodeRefreshPacket(
}
/**
* 验证码输入错误, 同时也会给一部分验证码
*/
@PacketId("00 BA 32")
class ServerCaptchaWrongPacket(input: ByteReadPacket, packetIdLast: Int) : ServerCaptchaTransmissionPacket(input, packetIdLast)
/**
* 服务器发送验证码图片文件一部分过来
* 服务器发送验证码图片文件一部分过来. 当验证码输入错误时, 服务器的返回也会是这个包.
*
* @author Him188moe
*/
@PacketId("00 BA 31")
open class ServerCaptchaTransmissionPacket(input: ByteReadPacket, val packetIdLast: Int) : ServerCaptchaPacket(input) {
@PacketId(0x00_BAu)
open class ServerCaptchaTransmissionPacket(input: ByteReadPacket) : ServerCaptchaPacket(input) {
lateinit var captchaSectionN: IoBuffer
lateinit var verificationToken: IoBuffer//56bytes
@ -160,7 +136,7 @@ open class ServerCaptchaTransmissionPacket(input: ByteReadPacket, val packetIdLa
/*
fun main() {
val data = "13 00 05 01 00 00 01 23 00 38 59 32 29 5A 3E 3D 2D FC F5 22 EB 9E 2D FB 9C 4F AA 06 C8 32 3D F0 3C 2C 2B BA 8D 05 C4 9B C1 74 3B 70 F1 99 90 BB 6E 3E 6F 74 48 97 D3 61 B7 04 C0 A3 F1 DF 40 A4 DC 2B 00 A2 01 2D BB BB E8 FE B8 AF B3 6F 39 7C EA E2 5B 91 BE DB 59 38 CF 58 BC F2 88 F1 09 CF 92 E9 F7 FB 13 76 C5 68 29 23 3F 8E 43 16 2E 50 D7 FA 4D C1 F7 67 EF 27 FB C6 F1 A7 25 A4 BC 45 39 3A EA B2 A5 38 02 FF 4B C9 FF EB BD 89 E5 5D B9 4A 2A BE 5F 52 F1 EB 09 29 CB 3E 66 CF EF 97 89 47 BB 6B E0 7B 4A 3E A1 BC 3F FB F2 0A 83 CB E3 EA B9 43 E1 26 88 03 0B A7 E0 B2 AD 7F 83 CC DA 74 85 83 72 08 EC D2 F9 95 05 15 05 96 F7 1C FF 00 82 C3 90 22 A4 BA 90 D5 00 00 00 00 49 45 4E 44 AE 42 60 82 03 00 00 28 EA 32 5A 85 C8 D2 73 B3 40 39 77 85 65 98 00 FE 03 A2 A5 95 B4 2F E6 79 7A DE 5A 03 10 C8 3D BF 6D 3D 8B 51 84 C2 6D 49 00 10 92 AA 69 FB C6 3D 60 5A 7A A4 AC 7A B0 71 00 36".hexToBytes()
ServerVerificationCodeTransmissionPacket(data.toReadPacket(), data.size, "00 BA 31 01".hexToBytes()).let {
ServerCaptchaTransmissionPacket(data.toReadPacket(), data.size, "00 BA 31 01".hexToBytes()).let {
it.dataDecode()
println(it.toString())
}
@ -171,7 +147,7 @@ fun main() {
*
* @author Him188moe
*/
@PacketId("00 BA 32")
@PacketId(0x00_BAu)
class ServerCaptchaCorrectPacket(input: ByteReadPacket) : ServerCaptchaPacket(input) {
lateinit var token00BA: ByteArray//56 bytes
@ -181,22 +157,20 @@ class ServerCaptchaCorrectPacket(input: ByteReadPacket) : ServerCaptchaPacket(in
}
}
@PacketId(0x00_BAu)
abstract class ServerCaptchaPacket(input: ByteReadPacket) : ServerPacket(input) {
@PacketId("00 BA")
class Encrypted(input: ByteReadPacket, override var idHex: String) : ServerPacket(input) {
@PacketId(0x00_BAu)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): ServerCaptchaPacket {
val data = this.decryptAsByteArray(TIMProtocol.key00BA)
if (idHex.startsWith("00 BA 32")) {
return when (data.size) {
66,
95 -> ServerCaptchaCorrectPacket(data.toReadPacket())
//66 -> ServerVerificationCodeUnknownPacket(data.toReadPacket())
else -> return ServerCaptchaWrongPacket(data.toReadPacket(), idHex.substringAfterLast(" ").toInt(16))
}.setId(this.idHex)
}
return ServerCaptchaTransmissionPacket(data.toReadPacket(), idHex.substringAfterLast(" ").toInt(16)).setId(this.idHex)
//66 -> ServerCaptchaUnknownPacket(data.toReadPacket())
else -> ServerCaptchaTransmissionPacket(data.toReadPacket())
}.applySequence(sequenceId)
}
}
}

View File

@ -1,4 +1,4 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.utils
@ -22,30 +22,24 @@ fun ByteReadPacket.readIoBuffer(
fun ByteReadPacket.readIoBuffer(n: Number) = this.readIoBuffer(n.toInt())
//必须消耗完 packet
fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {//TODO 优化
fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
discardExact(3)
val idHex = readInt().toUHexString(" ")
val id = readUShort()
val sequenceId = readUShort()
discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00. 但更可能是应该 discard 8
return when (idHex) {
"08 25 31 01" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, this)
"08 25 31 02" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_02, this)
"08 36 31 03", "08 36 31 04", "08 36 31 05", "08 36 31 06" -> {
return when (id.toUInt()) {
0x08_25u -> ServerTouchResponsePacket.Encrypted(this)
0x08_36u -> {
when (size) {
271, 207 -> return ServerLoginResponseKeyExchangePacket.Encrypted(this, when (idHex) {
"08 36 31 03" -> ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`
else -> ServerLoginResponseKeyExchangePacket.Flag.OTHER
}).setId(idHex)
871 -> return ServerLoginResponseVerificationCodeInitPacket.Encrypted(this).setId(idHex)
271, 207 -> return ServerLoginResponseKeyExchangePacket.Encrypted(this).applySequence(sequenceId)
871 -> return ServerLoginResponseCaptchaInitPacket.Encrypted(this).applySequence(sequenceId)
}
if (size > 700) {
return ServerLoginResponseSuccessPacket.Encrypted(this).setId(idHex)
}
if (size > 700) return ServerLoginResponseSuccessPacket.Encrypted(this).applySequence(sequenceId)
println(size)
println("登录包size=$size")
return ServerLoginResponseFailedPacket(when (size) {
135 -> {//包数据错误. 目前怀疑是 tlv0006
this.readRemainingBytes().cutTail(1).decryptBy(TIMProtocol.shareKey).read {
@ -58,9 +52,9 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {//TODO 优化
319, 351 -> LoginResult.WRONG_PASSWORD
//135 -> LoginState.RETYPE_PASSWORD
63, 279 -> LoginResult.BLOCKED
63 -> LoginResult.BLOCKED
263 -> LoginResult.UNKNOWN_QQ_NUMBER
551, 487 -> LoginResult.DEVICE_LOCK
279, 495, 551, 487 -> LoginResult.DEVICE_LOCK
343, 359 -> LoginResult.TAKEN_BACK
else -> LoginResult.UNKNOWN
@ -70,36 +64,24 @@ fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {//TODO 优化
351 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown logError)")
else -> throw IllegalArgumentException(bytes.size.toString())*/
}, this).setId(idHex)
}, this).applySequence(sequenceId)
}
0x08_28u -> ServerSessionKeyResponsePacket.Encrypted(this)
"08 28 04 34" -> ServerSessionKeyResponsePacket.Encrypted(this)
0x00_EC_u -> ServerSKeyResponsePacket(this)
0x00_1D_u -> ServerSKeyResponsePacket.Encrypted(this)
0x00_5C_u -> ServerAccountInfoResponsePacket.Encrypted(this)
0x00_58_u -> ServerHeartbeatResponsePacket(this)
0x00_BA_u -> ServerCaptchaPacket.Encrypted(this)
0x00_CE_u, 0x00_17_u -> ServerEventPacket.Raw.Encrypted(this)
0x00_81_u -> ServerFieldOnlineStatusChangedPacket.Encrypted(this)
0x00_CD_u -> ServerSendFriendMessageResponsePacket(this)
0x00_02_u -> ServerSendGroupMessageResponsePacket(this)
0x00_A7_u -> ServerCanAddFriendResponsePacket(this)
0x03_88_u -> ServerTryGetImageIDResponsePacket.Encrypted(this)
else -> when (idHex.substring(0, 5)) {
"00 EC" -> ServerLoginSuccessPacket(this)
"00 1D" -> ServerSKeyResponsePacket.Encrypted(this)
"00 5C" -> ServerAccountInfoResponsePacket.Encrypted(this)
"00 58" -> ServerHeartbeatResponsePacket(this)
"00 BA" -> ServerCaptchaPacket.Encrypted(this, idHex)
"00 CE", "00 17" -> ServerEventPacket.Raw.Encrypted(this)
"00 81" -> ServerFieldOnlineStatusChangedPacket.Encrypted(this)
"00 CD" -> ServerSendFriendMessageResponsePacket(this)
"00 02" -> ServerSendGroupMessageResponsePacket(this)
"00 A7" -> ServerCanAddFriendResponsePacket(this)
"03 88" -> ServerTryGetImageIDResponsePacket.Encrypted(this)
else -> UnknownServerPacket.Encrypted(this)
}
}.setId(idHex)
else -> UnknownServerPacket.Encrypted(this, id, sequenceId)
}.applyId(id).applySequence(sequenceId)
}
fun Input.readIP(): String = buildString(4 + 3) {

View File

@ -4,6 +4,7 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import kotlin.random.Random
import kotlin.random.nextInt

View File

@ -25,6 +25,10 @@ internal object PacketNameFormatter {
private object IgnoreIdList : List<String> by listOf(
"idHex",
"id",
"packetId",
"sequenceIdInternal",
"sequenceId",
"fixedId",
"idByteArray",
"encoded",
@ -36,8 +40,7 @@ private object IgnoreIdList : List<String> by listOf(
"UninitializedByteReadPacket"
)
@UseExperimental(DangerousInternalIoApi::class)
internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.fixedId})") + this::class.java.allDeclaredFields
internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.idHexString})") + this::class.java.allDeclaredFields
.filterNot { it.name in IgnoreIdList || "delegate" in it.name || "$" in it.name }
.joinToString(", ", "{", "}") {
it.isAccessible = true

View File

@ -19,7 +19,7 @@ actual typealias ClientTryGetImageIDPacket = ClientTryGetImageIDPacketJvm
*
* @author Him188moe
*/
@PacketId("03 88")
@PacketId(0x03_88u)
class ClientTryGetImageIDPacketJvm(
private val botNumber: Long,
private val sessionKey: ByteArray,
@ -29,6 +29,10 @@ class ClientTryGetImageIDPacketJvm(
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
//一次 body
//00 00 00 00 00 00 00 00 3C 61 3C 48 85 91 81 B9 DF 27 D9 C3 20 43 F7 1C 73 DA 2A 84 74 AC 78 AC CC 38 54 8F AE 06 8C 22 AA AF 2E C1 E4 70 8C 31 63 52 95 F2 6F C3 9A 2D 77 4B F7 7B 4F C4 1A 6D 7A 3F 22 D8 9D B3 48 99 F3 E7 4F D0 2D 31 94 40 ED A7 5C D9 CE 70 B1 F7 B8 1B 3D CA B3 0E BE 86 33 56 B4 E4 30 AD 66 30 C1 C7 15 6A 71 B6 49 DC DC 0E 74 4B CE 12 3F ED
this.writeQQ(botNumber)
this.writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00")

View File

@ -27,10 +27,10 @@ internal actual suspend fun solveCaptcha(captchaBuffer: IoBuffer): String? = cap
MiraiLogger.logCyan("需要验证码登录, 验证码为 4 字母")
try {
File(System.getProperty("user.dir") + "/temp/Captcha.png")
.also { withContext(Dispatchers.IO) { it.createNewFile(); it.writeBytes(captcha) } }
.let { withContext(Dispatchers.IO) { it.createNewFile(); it.writeBytes(captcha) } }
MiraiLogger.logCyan("若看不清字符图片, 请查看 Mirai 目录下 /temp/Captcha.png")
} catch (e: Exception) {
MiraiLogger.logCyan("无法写出验证码文件, 请尝试查看以上字符图片")
MiraiLogger.logCyan("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
}
MiraiLogger.logCyan("若要更换验证码, 请直接回车")
readLine()?.takeUnless { it.isEmpty() || it.length != 4 }
@ -44,15 +44,11 @@ private val captchaLock = Mutex()
*/
@JvmOverloads
internal fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Double = 0.95): String {
/*
* resize Image
* */
val newHeight = (this.height * (outputWidth.toDouble() / this.width)).toInt()
val tmp = this.getScaledInstance(outputWidth, newHeight, Image.SCALE_SMOOTH)
val image = BufferedImage(outputWidth, newHeight, BufferedImage.TYPE_INT_ARGB)
val g2d = image.createGraphics()
g2d.drawImage(tmp, 0, 0, null)
fun gray(rgb: Int): Int {
val r = rgb and 0xff0000 shr 16
val g = rgb and 0x00ff00 shr 8

View File

@ -17,7 +17,7 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv
actual suspend fun read(buffer: IoBuffer) = withContext(Dispatchers.IO) {
try {
(channel as ReadableByteChannel).read(buffer)
} catch (e: Exception) {
} catch (e: Throwable) {
throw ReadPacketInternalException(e)
}
}
@ -26,7 +26,7 @@ actual class PlatformDatagramChannel actual constructor(serverHost: String, serv
buffer.readDirect {
try {
channel.send(it, serverAddress)
} catch (e: Exception) {
} catch (e: Throwable) {
throw SendPacketInternalException(e)
}
}

View File

@ -1,14 +0,0 @@
import net.mamoe.mirai.utils.CharImageConverter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class ImageOutputTest {
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File((System.getProperty("user.dir") + "/mirai.png").replace("//","/")));
CharImageConverter charImageConvertor = new CharImageConverter(image,80);
System.out.println(charImageConvertor.call());
}
}

View File

@ -65,7 +65,7 @@ object Main {
try {
dataSent(pk.data)
println()
} catch (e: Exception) {
} catch (e: Throwable) {
e.printStackTrace()
}
}
@ -89,12 +89,20 @@ object Main {
* 6. 运行到 `mov eax,dword ptr ss:[ebp+10]`
* 7. 查看内存, `eax` 开始的 16 bytes 便是 `sessionKey`
*/
val sessionKey: ByteArray = "48 C0 11 42 2D FD 8F 36 6E BA BF FD D3 AA B7 AE".hexToBytes()
val sessionKey: ByteArray = "9E A6 16 46 FF 15 FB 73 2F 31 0D 7E CB C4 E6 49".hexToBytes()
fun dataReceived(data: ByteArray) {
//println("--------------")
//println("接收数据包")
//println("raw packet = " + data.toUHexString())
println("--------------")
println("接收数据包")
//println("raw = " + data.toUHexString())
data.read {
discardExact(3)
val idHex = readInt().toUHexString(" ")
discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00. 但更可能是应该 discard 8
println("id=$idHex")
println("解密body=${this.readRemainingBytes().cutTail(1).decryptBy(sessionKey).toUHexString()}")
}
packetReceived(data.read { this.parseServerPacket(data.size) })
}
@ -108,7 +116,7 @@ object Main {
is UnknownServerEventPacket -> {
println("--------------")
println("未知事件ID=" + packet.idHex)
println("未知事件ID=" + packet.idHexString)
println("未知事件: " + packet.input.readBytes().toUHexString())
}
@ -134,7 +142,7 @@ object Main {
discardExact(TIMProtocol.fixVer2.hexToBytes().size + 1 + 5 - 3 + 1)
val encryptedBody = readRemainingBytes()
println("body = ${encryptedBody.toUHexString()}")
println("解密body = ${encryptedBody.decryptBy(sessionKey).toUHexString()}")
encryptedBody.read {
@ -168,3 +176,8 @@ object Main {
}
}
fun main() {
println("00 01 00 23 24 8B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5".hexToBytes().stringOf())
}

View File

@ -1,6 +1,7 @@
package demo1
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.event.events.FriendMessageEvent
@ -16,12 +17,14 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console
import net.mamoe.mirai.utils.MiraiLogger
import kotlin.coroutines.coroutineContext
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
suspend fun main() {
val bot = Bot(BotAccount(//填写你的账号
qqNumber = 1994701121,
password = "abcdefg"
qqNumber = 2903772581,
password = "zxc123456"
), Console())
bot.login {
@ -30,11 +33,10 @@ suspend fun main() {
}.let {
if (it != LoginResult.SUCCESS) {
MiraiLogger.logError("Login failed: " + it.name)
exitProcess(0)
bot.close()
}
}
//提供泛型以监听事件
subscribeOnce<FriendMessageEvent> {
//获取第一个纯文本消息, 获取不到会抛出 NoSuchElementException
@ -97,7 +99,7 @@ suspend fun main() {
fun demo2() {
subscribeAlways<FriendMessageEvent> { event ->
if (event.message eq "记笔记") {
FriendMessageEvent::class.subscribeUntilFalse {
subscribeUntilFalse<FriendMessageEvent> {
it.reply("你发送了 ${it.message}")
it.message eq "停止"