mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-23 14:20:24 +08:00
Fix bugs
This commit is contained in:
parent
b6c1553615
commit
78ee01ded1
@ -14,7 +14,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.PacketId
|
||||
import net.mamoe.mirai.utils.LockFreeLinkedList
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.io.ClosedChannelException
|
||||
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
|
||||
import net.mamoe.mirai.utils.io.PlatformSocket
|
||||
import net.mamoe.mirai.utils.io.ReadPacketInternalException
|
||||
import net.mamoe.mirai.utils.io.debugPrint
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
@ -25,16 +25,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
|
||||
|
||||
private val channel: PlatformDatagramChannel = PlatformDatagramChannel("wtlogin.qq.com", 8000)
|
||||
private lateinit var channel: PlatformSocket
|
||||
|
||||
override suspend fun login() {
|
||||
channel = PlatformSocket()
|
||||
channel.connect("113.96.13.208", 8080)
|
||||
launch(CoroutineName("Incoming Packet Receiver")) { processReceive() }
|
||||
|
||||
println("Sending login")
|
||||
LoginPacket.SubCommand9(bot.client).sendAndExpect<LoginPacket.LoginPacketResponse>()
|
||||
println("Login sent")
|
||||
}
|
||||
|
||||
private suspend inline fun processReceive() {
|
||||
private suspend fun processReceive() {
|
||||
while (channel.isOpen) {
|
||||
val rawInput = try {
|
||||
channel.read()
|
||||
@ -52,21 +54,19 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
}
|
||||
|
||||
launch(CoroutineName("Incoming Packet handler")) {
|
||||
try {
|
||||
rawInput.debugPrint("Received")
|
||||
} catch (e: Exception) {
|
||||
bot.logger.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
rawInput.use {
|
||||
KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int ->
|
||||
if (PacketReceivedEvent(packet).broadcast().cancelled) {
|
||||
return
|
||||
rawInput.debugPrint("Received").use {
|
||||
if (it.remaining == 0L) {
|
||||
bot.logger.error("Empty packet received. Consider if bad packet was sent.")
|
||||
return@launch
|
||||
}
|
||||
packetListeners.forEach { listener ->
|
||||
if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) {
|
||||
listener.complete(packet)
|
||||
KnownPacketFactories.parseIncomingPacket(bot, rawInput) { packet: Packet, packetId: PacketId, sequenceId: Int ->
|
||||
if (PacketReceivedEvent(packet).broadcast().cancelled) {
|
||||
return@parseIncomingPacket
|
||||
}
|
||||
packetListeners.forEach { listener ->
|
||||
if (listener.filter(packetId, sequenceId) && packetListeners.remove(listener)) {
|
||||
listener.complete(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,7 +77,10 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
|
||||
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(): E {
|
||||
val handler = PacketListener(packetId = packetId, sequenceId = sequenceId)
|
||||
packetListeners.addLast(handler)
|
||||
check(channel.send(delegate)) { packetListeners.remove(handler); "Cannot send packet" }
|
||||
//println(delegate.readBytes().toUHexString())
|
||||
println("Sending length=" + delegate.remaining)
|
||||
channel.send(delegate)//) { packetListeners.remove(handler); "Cannot send packet" }
|
||||
println("Packet sent")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return handler.await() as E
|
||||
}
|
||||
|
@ -4,7 +4,10 @@ import kotlinx.atomicfu.AtomicInt
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.io.core.toByteArray
|
||||
import net.mamoe.mirai.BotAccount
|
||||
import net.mamoe.mirai.qqandroid.utils.*
|
||||
import net.mamoe.mirai.qqandroid.utils.Context
|
||||
import net.mamoe.mirai.qqandroid.utils.DeviceInfo
|
||||
import net.mamoe.mirai.qqandroid.utils.NetworkType
|
||||
import net.mamoe.mirai.qqandroid.utils.SystemDeviceInfo
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.cryptor.ECDH
|
||||
import net.mamoe.mirai.utils.io.hexToBytes
|
||||
@ -31,7 +34,7 @@ internal open class QQAndroidClient(
|
||||
val ecdh: ECDH = ECDH(),
|
||||
val device: DeviceInfo = SystemDeviceInfo(context)
|
||||
) {
|
||||
val tgtgtKey: ByteArray = generateTgtgtKey(device.guid)
|
||||
val tgtgtKey: ByteArray = ByteArray(16) // generateTgtgtKey(device.guid)
|
||||
|
||||
var miscBitMap: Int = 184024956 // 也可能是 150470524 ?
|
||||
var mainSigMap: Int = 16724722
|
||||
|
@ -11,7 +11,6 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.cryptor.DecrypterByteArray
|
||||
import net.mamoe.mirai.utils.cryptor.ECDH
|
||||
import net.mamoe.mirai.utils.cryptor.encryptAndWrite
|
||||
import net.mamoe.mirai.utils.cryptor.encryptBy
|
||||
import net.mamoe.mirai.utils.io.*
|
||||
|
||||
/**
|
||||
@ -47,6 +46,7 @@ private val EMPTY_BYTE_ARRAY = ByteArray(0)
|
||||
*
|
||||
* byte[] body encrypted by 16 zero
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class)
|
||||
internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
|
||||
client: QQAndroidClient,
|
||||
subAppId: Long,
|
||||
@ -54,30 +54,33 @@ internal inline fun PacketFactory<*, *>.buildLoginOutgoingPacket(
|
||||
name: String? = null,
|
||||
id: PacketId = this.id,
|
||||
ssoExtraData: ByteReadPacket = BRP_STUB,
|
||||
sequenceId: Int = PacketFactory.atomicNextSequenceId(),
|
||||
body: BytePacketBuilder.(sequenceId: Int) -> Unit
|
||||
): OutgoingPacket = OutgoingPacket(name, id, sequenceId, buildPacket {
|
||||
writeIntLVPacket(lengthOffset = { it + 4 }) {
|
||||
writeInt(0x00_00_00_0A)
|
||||
writeByte(0x02)
|
||||
extraData.let {
|
||||
writeInt(it.size + 4)
|
||||
writeFully(it)
|
||||
}
|
||||
writeByte(0x00)
|
||||
): OutgoingPacket {
|
||||
val sequenceId: Int = client.nextSsoSequenceId()
|
||||
|
||||
client.account.id.toString().let {
|
||||
writeInt(it.length + 4)
|
||||
writeStringUtf8(it)
|
||||
}
|
||||
return OutgoingPacket(name, id, sequenceId, buildPacket {
|
||||
writeIntLVPacket(lengthOffset = { it + 4 }) {
|
||||
writeInt(0x00_00_00_0A)
|
||||
writeByte(0x02)
|
||||
extraData.let {
|
||||
writeInt(it.size + 4)
|
||||
writeFully(it)
|
||||
}
|
||||
writeByte(0x00)
|
||||
|
||||
encryptAndWrite(KEY_16_ZEROS) {
|
||||
writeLoginSsoPacket(client, subAppId, id, ssoExtraData, sequenceId) {
|
||||
body(sequenceId)
|
||||
client.account.id.toString().let {
|
||||
writeInt(it.length + 4)
|
||||
writeStringUtf8(it)
|
||||
}
|
||||
|
||||
encryptAndWrite(KEY_16_ZEROS) {
|
||||
writeLoginSsoPacket(client, subAppId, id, ssoExtraData, sequenceId) {
|
||||
body(sequenceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private val BRP_STUB = ByteReadPacket(EMPTY_BYTE_ARRAY)
|
||||
|
||||
@ -141,7 +144,7 @@ private inline fun BytePacketBuilder.writeLoginSsoPacket(
|
||||
writeInt(4)
|
||||
|
||||
client.device.ksid.let {
|
||||
writeInt(it.length + 4)
|
||||
writeShort((it.length + 2).toShort())
|
||||
writeStringUtf8(it)
|
||||
}
|
||||
|
||||
@ -190,7 +193,7 @@ internal inline fun PacketFactory<*, *>.buildSessionOutgoingPacket(
|
||||
interface EncryptMethod {
|
||||
val id: Int
|
||||
|
||||
fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket)
|
||||
fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket
|
||||
}
|
||||
|
||||
internal interface EncryptMethodSessionKey : EncryptMethod {
|
||||
@ -208,14 +211,14 @@ internal interface EncryptMethodSessionKey : EncryptMethod {
|
||||
* fully encrypted
|
||||
* }
|
||||
*/
|
||||
override fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) {
|
||||
override fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket {
|
||||
require(currentLoginState == 2 || currentLoginState == 3) { "currentLoginState must be either 2 or 3" }
|
||||
writeByte(1) // const
|
||||
writeByte(if (currentLoginState == 2) 3 else 2)
|
||||
writeFully(sessionKey)
|
||||
writeShort(258) // const
|
||||
writeShort(0) // const, length of publicKey
|
||||
body.encryptBy(sessionKey) { encrypted -> writeFully(encrypted) }
|
||||
encryptAndWrite(sessionKey, body)
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,13 +251,22 @@ internal interface EncryptMethodECDH : EncryptMethod {
|
||||
* byte[] [ECDH.publicKey]
|
||||
* byte[] encrypted `body()` by [ECDH.shareKey]
|
||||
*/
|
||||
override fun BytePacketBuilder.encryptAndWrite(body: ByteReadPacket) = ecdh.run {
|
||||
override fun makeBody(body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket {
|
||||
writeByte(1) // const
|
||||
writeByte(1) // const
|
||||
writeFully(keyPair.privateKey.getEncoded())
|
||||
writeFully(ByteArray(16))
|
||||
writeShort(258) // const
|
||||
writeShortLVByteArray(keyPair.publicKey.getEncoded().drop(23).toByteArray().also { check(it.size == 49) { "Bad publicKey generated. Expected size=49, got${it.size}" } })
|
||||
body.encryptBy(keyPair.shareKey) { encrypted -> writeFully(encrypted) }
|
||||
|
||||
writeShortLVByteArray("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".hexToBytes())
|
||||
|
||||
/*writeShortLVByteArray(ecdh.keyPair.publicKey.getEncoded().drop(23).take(49).toByteArray().also {
|
||||
it.toUHexString().debugPrint("PUBLIC KEY")
|
||||
check(it[0].toInt() == 0x04) { "Bad publicKey generated. Expected first element=0x04, got${it[0]}" }
|
||||
//check(ecdh.calculateShareKeyByPeerPublicKey(it.adjustToPublicKey()).contentEquals(ecdh.keyPair.shareKey)) { "PublicKey Validation failed" }
|
||||
})*/
|
||||
|
||||
encryptAndWrite("26 33 BA EC 86 EB 79 E6 BC E0 20 06 5E A9 56 6C".hexToBytes(), body)
|
||||
//encryptAndWrite(ecdh.keyPair.shareKey, body)
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,36 +290,34 @@ internal interface EncryptMethodECDH : EncryptMethod {
|
||||
* byte 3 // tail
|
||||
*/
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
internal inline fun BytePacketBuilder.writeOicqRequestPacket(
|
||||
internal fun BytePacketBuilder.writeOicqRequestPacket(
|
||||
client: QQAndroidClient,
|
||||
encryptMethod: EncryptMethod,
|
||||
packetId: PacketId,
|
||||
bodyBlock: BytePacketBuilder.() -> Unit
|
||||
) {
|
||||
val body = encryptMethod.run {
|
||||
buildPacket { encryptAndWrite(buildPacket { bodyBlock() }) }
|
||||
}
|
||||
writeIntLVPacket(lengthOffset = { it + 4 }) {
|
||||
// Head
|
||||
writeByte(0x02) // head
|
||||
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
|
||||
writeShort(client.protocolVersion)
|
||||
writeShort(1) // const??
|
||||
writeShort(packetId.commandId.toShort())
|
||||
writeQQ(client.account.id)
|
||||
writeByte(3) // originally const
|
||||
writeByte(encryptMethod.id.toByte())
|
||||
writeByte(0) // const8_always_0
|
||||
writeInt(2) // originally const
|
||||
writeInt(client.appClientVersion)
|
||||
writeInt(0) // constp_always_0
|
||||
val body = encryptMethod.makeBody(bodyBlock)
|
||||
// writeIntLVPacket(lengthOffset = { it + 4 }) {
|
||||
// Head
|
||||
writeByte(0x02) // head
|
||||
writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm
|
||||
writeShort(client.protocolVersion)
|
||||
writeShort(packetId.commandId.toShort())
|
||||
writeShort(1) // const??
|
||||
writeQQ(client.account.id)
|
||||
writeByte(3) // originally const
|
||||
writeByte(encryptMethod.id.toByte())
|
||||
writeByte(0) // const8_always_0
|
||||
writeInt(2) // originally const
|
||||
writeInt(client.appClientVersion)
|
||||
writeInt(0) // constp_always_0
|
||||
|
||||
// Body
|
||||
writePacket(body)
|
||||
// Body
|
||||
writePacket(body)
|
||||
|
||||
// Tail
|
||||
writeByte(0x03) // tail
|
||||
}
|
||||
// Tail
|
||||
writeByte(0x03) // tail
|
||||
// }
|
||||
}
|
||||
/*
|
||||
00 00 01 64
|
||||
|
@ -1,9 +1,7 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet
|
||||
|
||||
import kotlinx.atomicfu.AtomicInt
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.Closeable
|
||||
import kotlinx.io.core.IoBuffer
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.io.core.readBytes
|
||||
import net.mamoe.mirai.data.Packet
|
||||
@ -41,25 +39,11 @@ internal abstract class PacketFactory<out TPacket : Packet, TDecrypter : Decrypt
|
||||
* **解码**服务器的回复数据包
|
||||
*/
|
||||
abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket
|
||||
|
||||
companion object {
|
||||
private val sequenceId: AtomicInt = atomic(1)
|
||||
|
||||
fun atomicNextSequenceId(): Int {
|
||||
TODO("使用 SSO ")
|
||||
val id = sequenceId.getAndAdd(1)
|
||||
if (id > Short.MAX_VALUE.toInt() * 2) {
|
||||
sequenceId.value = 0
|
||||
return atomicNextSequenceId()
|
||||
}
|
||||
// return id.toShort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val DECRYPTER_16_ZERO = ByteArray(16)
|
||||
|
||||
internal typealias PacketConsumer = (packet: Packet, packetId: PacketId, ssoSequenceId: Int) -> Unit
|
||||
internal typealias PacketConsumer = suspend (packet: Packet, packetId: PacketId, ssoSequenceId: Int) -> Unit
|
||||
|
||||
internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListOf() {
|
||||
|
||||
@ -67,20 +51,25 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
|
||||
|
||||
fun findPacketFactory(commandId: Int): PacketFactory<*, *> = this.first { it.id.commandName == commandName }
|
||||
|
||||
suspend inline fun parseIncomingPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
|
||||
// do not inline. Exceptions thrown will not be reported correctly
|
||||
suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
|
||||
rawInput.debugPrintIfFail("Incoming packet") {
|
||||
require(rawInput.remaining < Int.MAX_VALUE) { "rawInput is too long" }
|
||||
require(remaining < Int.MAX_VALUE) { "rawInput is too long" }
|
||||
val expectedLength = readInt() - 4
|
||||
check(rawInput.remaining.toInt() == expectedLength) { "Invalid packet length. Expected $expectedLength, got ${rawInput.remaining} Probably packets merged? " }
|
||||
check(remaining.toInt() == expectedLength) { "Invalid packet length. Expected $expectedLength, got ${rawInput.remaining} Probably packets merged? " }
|
||||
// login
|
||||
when (val flag1 = readInt()) {
|
||||
0x0A -> when (val flag2 = readByte().toInt()) {
|
||||
0x02 -> {
|
||||
val extraData = readIoBuffer(readInt() - 4).debugCopyUse {
|
||||
this.debugPrint("Extra data")
|
||||
}
|
||||
val flag3 = readByte().toInt()
|
||||
check(flag3 == 0) { "Illegal flag3. Expected 0, got $flag3" }
|
||||
|
||||
discardExact(readInt() - 4) // uinAccount
|
||||
bot.logger.verbose(readString(readInt() - 4)) // uinAccount
|
||||
|
||||
//debugPrint("remaining")
|
||||
parseLoginSsoPacket(bot, decryptBy(DECRYPTER_16_ZERO), consumer)
|
||||
}
|
||||
else -> error("Illegal flag2. Expected 0x02, got $flag2")
|
||||
@ -90,7 +79,7 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalUnsignedTypes::class)
|
||||
private suspend inline fun parseLoginSsoPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
|
||||
private suspend fun parseLoginSsoPacket(bot: QQAndroidBot, rawInput: ByteReadPacket, consumer: PacketConsumer) =
|
||||
rawInput.debugPrintIfFail("Login sso packet") {
|
||||
val commandName: String
|
||||
val ssoSequenceId: Int
|
||||
@ -136,13 +125,13 @@ internal object KnownPacketFactories : List<PacketFactory<*, *>> by mutableListO
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalContracts::class)
|
||||
internal inline fun <I : Closeable, R> I.withUse(block: I.() -> R): R {
|
||||
internal inline fun <I : IoBuffer, R> I.withUse(block: I.() -> R): R {
|
||||
contract {
|
||||
callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
return try {
|
||||
block(this)
|
||||
} finally {
|
||||
close()
|
||||
this.release(IoBuffer.Pool)
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ fun BytePacketBuilder.t1(uin: Long, ip: String) {
|
||||
writeInt(Random.nextInt())
|
||||
writeInt(uin.toInt())
|
||||
writeTime()
|
||||
writeIP(ip)
|
||||
writeFully(ByteArray(4))
|
||||
writeShort(0)
|
||||
} shouldEqualsTo 20
|
||||
}
|
||||
@ -273,8 +273,8 @@ fun BytePacketBuilder.t109(
|
||||
) {
|
||||
writeShort(0x109)
|
||||
writeShortLVPacket {
|
||||
writeFully(androidId)
|
||||
}
|
||||
writeFully(md5(androidId))
|
||||
} shouldEqualsTo 16
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t52d(
|
||||
@ -283,6 +283,9 @@ fun BytePacketBuilder.t52d(
|
||||
writeShort(0x52d)
|
||||
writeShortLVPacket {
|
||||
writeFully(androidDevInfo)
|
||||
|
||||
// 0A 07 75 6E 6B 6E 6F 77 6E 12 7E 4C 69 6E 75 78 20 76 65 72 73 69 6F 6E 20 34 2E 39 2E 33 31 20 28 62 75 69 6C 64 40 42 75 69 6C 64 32 29 20 28 67 63 63 20 76 65 72 73 69 6F 6E 20 34 2E 39 20 32 30 31 35 30 31 32 33 20 28 70 72 65 72 65 6C 65 61 73 65 29 20 28 47 43 43 29 20 29 20 23 31 20 53 4D 50 20 50 52 45 45 4D 50 54 20 54 68 75 20 44 65 63 20 31 32 20 31 35 3A 33 30 3A 35 35 20 49 53 54 20 32 30 31 39 1A 03 52 45 4C 22 03 33 32 37 2A 41 4F 6E 65 50 6C 75 73 2F 4F 6E 65 50 6C 75 73 35 2F 4F 6E 65 50 6C 75 73 35 3A 37 2E 31 2E 31 2F 4E 4D 46 32 36 58 2F 31 30 31 37 31 36 31 37 3A 75 73 65 72 2F 72 65 6C 65 61 73 65 2D 6B 65 79 73 32 24 36 63 39 39 37 36 33 66 2D 66 62 34 32 2D 34 38 38 31 2D 62 37 32 65 2D 63 37 61 61 38 61 36 63 31 63 61 34 3A 10 65 38 63 37 30 35 34 64 30 32 66 33 36 33 64 30 42 0A 6E 6F 20 6D 65 73 73 61 67 65 4A 03 33 32 37
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,7 +304,6 @@ fun BytePacketBuilder.t124(
|
||||
writeShort(networkType.value.toShort())
|
||||
writeShortLVByteArrayLimitedLength(simInfo, 16)
|
||||
writeShortLVByteArrayLimitedLength(unknown, 32)
|
||||
writeShort(0)
|
||||
writeShortLVByteArrayLimitedLength(apn, 16)
|
||||
}
|
||||
}
|
||||
@ -385,7 +387,7 @@ fun BytePacketBuilder.t147(
|
||||
) {
|
||||
writeShort(0x147)
|
||||
writeShortLVPacket {
|
||||
writeLong(appId)
|
||||
writeInt(appId.toInt())
|
||||
writeShortLVByteArrayLimitedLength(apkVersionName, 32)
|
||||
writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32)
|
||||
}
|
||||
@ -516,16 +518,18 @@ fun BytePacketBuilder.t188(
|
||||
writeShort(0x188)
|
||||
writeShortLVPacket {
|
||||
writeFully(md5(androidId))
|
||||
}
|
||||
} shouldEqualsTo 16
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t194(
|
||||
imsiMd5: ByteArray
|
||||
) {
|
||||
imsiMd5 requireSize 16
|
||||
|
||||
writeShort(0x194)
|
||||
writeShortLVPacket {
|
||||
writeFully(imsiMd5)
|
||||
}
|
||||
} shouldEqualsTo 16
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t191(
|
||||
@ -572,7 +576,7 @@ fun BytePacketBuilder.t177(
|
||||
writeByte(1)
|
||||
writeInt(unknown1.toInt())
|
||||
writeShortLVString(unknown2)
|
||||
}
|
||||
} shouldEqualsTo 0x11
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t516( // 1302
|
||||
@ -581,7 +585,7 @@ fun BytePacketBuilder.t516( // 1302
|
||||
writeShort(0x516)
|
||||
writeShortLVPacket {
|
||||
writeInt(sourceType)
|
||||
}
|
||||
} shouldEqualsTo 4
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t521( // 1313
|
||||
@ -592,7 +596,7 @@ fun BytePacketBuilder.t521( // 1313
|
||||
writeShortLVPacket {
|
||||
writeInt(productType)
|
||||
writeShort(unknown)
|
||||
}
|
||||
} shouldEqualsTo 6
|
||||
}
|
||||
|
||||
fun BytePacketBuilder.t536( // 1334
|
||||
@ -626,8 +630,8 @@ fun BytePacketBuilder.t318(
|
||||
private fun Boolean.toByte(): Byte = if (this) 1 else 0
|
||||
private fun Boolean.toInt(): Int = if (this) 1 else 0
|
||||
|
||||
private infix fun Int.shouldEqualsTo(int: Int) = require(this == int) { "Required $int, but found $this" }
|
||||
private fun ByteArray.requireSize(exactSize: Int) = require(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" }
|
||||
private infix fun Int.shouldEqualsTo(int: Int) = check(this == int) { "Required $int, but found $this" }
|
||||
private infix fun ByteArray.requireSize(exactSize: Int) = check(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" }
|
||||
|
||||
fun randomAndroidId(): String = buildString(15) {
|
||||
repeat(15) { append(Random.nextInt(10)) }
|
||||
|
@ -31,9 +31,10 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient
|
||||
): OutgoingPacket = buildLoginOutgoingPacket(client, subAppId) { sequenceId ->
|
||||
writeOicqRequestPacket(client, EncryptMethodECDH135(client.ecdh), id) {
|
||||
writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), id) {
|
||||
writeShort(9) // subCommand
|
||||
writeShort(LoginType.PASSWORD.value.toShort())
|
||||
writeShort(0x17)
|
||||
//writeShort(LoginType.PASSWORD.value.toShort())
|
||||
|
||||
t18(appId, client.appClientVersion, client.account.id)
|
||||
t1(client.account.id, client.device.ipAddress)
|
||||
@ -91,6 +92,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
|
||||
tgtgtKey = client.tgtgtKey
|
||||
)
|
||||
|
||||
//this.build().debugPrint("傻逼")
|
||||
t145(client.device.guid)
|
||||
t147(appId, client.apkVersionName, client.apkSignatureMd5)
|
||||
|
||||
@ -156,6 +158,7 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log
|
||||
writeByte(0) // data count
|
||||
}.readBytes())
|
||||
})
|
||||
// this.build().debugPrint("傻逼")
|
||||
|
||||
// ignored t318 because not logging in by QR
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
package net.mamoe.mirai.utils.io
|
||||
|
||||
import io.ktor.network.sockets.Socket
|
||||
import io.ktor.network.sockets.aSocket
|
||||
import io.ktor.network.sockets.openReadChannel
|
||||
import io.ktor.network.sockets.openWriteChannel
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import kotlinx.coroutines.io.readAvailable
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.Closeable
|
||||
import kotlinx.io.pool.useInstance
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
|
||||
/**
|
||||
* 多平台适配的 TCP Socket.
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
actual class PlatformSocket : Closeable {
|
||||
@UseExperimental(KtorExperimentalAPI::class)
|
||||
lateinit var channel: Socket
|
||||
|
||||
@UseExperimental(KtorExperimentalAPI::class)
|
||||
actual val isOpen: Boolean
|
||||
get() = channel.socketContext.isActive
|
||||
|
||||
override fun close() = channel.dispose()
|
||||
|
||||
@PublishedApi
|
||||
internal val writeChannel = channel.openWriteChannel(true)
|
||||
@PublishedApi
|
||||
internal val readChannel = channel.openReadChannel()
|
||||
|
||||
/**
|
||||
* @throws SendPacketInternalException
|
||||
*/
|
||||
actual suspend inline fun send(packet: ByteReadPacket) {
|
||||
writeChannel.writePacket(packet)
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ReadPacketInternalException
|
||||
*/
|
||||
actual suspend inline fun read(): ByteReadPacket {
|
||||
// do not use readChannel.readRemaining() !!! this function never returns
|
||||
ByteArrayPool.useInstance { buffer ->
|
||||
val count = readChannel.readAvailable(buffer)
|
||||
return buffer.toReadPacket(0, count)
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(KtorExperimentalAPI::class)
|
||||
actual suspend fun connect(serverHost: String, serverPort: Int) {
|
||||
channel = aSocket(io.ktor.network.selector.ActorSelectorManager(kotlinx.coroutines.Dispatchers.IO)).tcp().connect(serverHost, serverPort)
|
||||
}
|
||||
}
|
@ -94,17 +94,17 @@ fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemain
|
||||
|
||||
// region ByteReadPacket extension
|
||||
|
||||
fun ByteReadPacket.decryptBy(key: ByteArray, offset: Int = 0, length: Int = key.size - offset): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) }
|
||||
fun ByteReadPacket.decryptBy(key: ByteArray, offset: Int = 0, length: Int = (this.remaining - offset).toInt()): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) }
|
||||
|
||||
fun ByteReadPacket.decryptBy(key: IoBuffer, offset: Int = 0, length: Int = key.readRemaining - offset): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) }
|
||||
fun ByteReadPacket.decryptBy(key: IoBuffer, offset: Int = 0, length: Int = (this.remaining - offset).toInt()): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) }
|
||||
|
||||
inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, offset: Int = 0, length: Int = key.size - offset, consumer: (ByteArray) -> R): R =
|
||||
inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R =
|
||||
ByteArrayPool.useInstance {
|
||||
readFully(it, offset, length)
|
||||
consumer(it.decryptBy(key, length))
|
||||
}.also { close() }
|
||||
|
||||
inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, offset: Int = 0, length: Int = key.readRemaining - offset, consumer: (ByteArray) -> R): R =
|
||||
inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R =
|
||||
ByteArrayPool.useInstance {
|
||||
readFully(it, offset, length)
|
||||
consumer(it.decryptBy(key, length))
|
||||
|
@ -0,0 +1,26 @@
|
||||
package net.mamoe.mirai.utils.io
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.Closeable
|
||||
import kotlinx.io.errors.IOException
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
|
||||
/**
|
||||
* 多平台适配的 TCP Socket.
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
expect class PlatformSocket() : Closeable {
|
||||
suspend fun connect(serverHost: String, serverPort: Int)
|
||||
|
||||
/**
|
||||
* @throws SendPacketInternalException
|
||||
*/
|
||||
suspend inline fun send(packet: ByteReadPacket)
|
||||
|
||||
/**
|
||||
* @throws ReadPacketInternalException
|
||||
*/
|
||||
suspend inline fun read(): ByteReadPacket
|
||||
|
||||
val isOpen: Boolean
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package net.mamoe.mirai.utils.cryptor
|
||||
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import java.security.*
|
||||
import java.security.spec.ECGenParameterSpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import javax.crypto.KeyAgreement
|
||||
|
||||
@ -15,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, publicKey)
|
||||
actual val shareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey)
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
@ -23,8 +25,12 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair())
|
||||
|
||||
actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) {
|
||||
actual companion object {
|
||||
init {
|
||||
Security.addProvider(BouncyCastleProvider())
|
||||
}
|
||||
|
||||
actual fun generateKeyPair(): ECDHKeyPair {
|
||||
return ECDHKeyPair(KeyPairGenerator.getInstance("ECDH").genKeyPair())
|
||||
return ECDHKeyPair(KeyPairGenerator.getInstance("EC", "BC").apply { initialize(ECGenParameterSpec("secp192k1")) }.genKeyPair())
|
||||
}
|
||||
|
||||
actual fun calculateShareKey(
|
||||
|
@ -0,0 +1,59 @@
|
||||
package net.mamoe.mirai.utils.io
|
||||
|
||||
import io.ktor.network.sockets.Socket
|
||||
import io.ktor.network.sockets.aSocket
|
||||
import io.ktor.network.sockets.openReadChannel
|
||||
import io.ktor.network.sockets.openWriteChannel
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import kotlinx.coroutines.io.ByteReadChannel
|
||||
import kotlinx.coroutines.io.ByteWriteChannel
|
||||
import kotlinx.coroutines.io.readAvailable
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.Closeable
|
||||
import kotlinx.io.pool.useInstance
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
|
||||
/**
|
||||
* 多平台适配的 TCP Socket.
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
actual class PlatformSocket : Closeable {
|
||||
@UseExperimental(KtorExperimentalAPI::class)
|
||||
lateinit var socket: Socket
|
||||
|
||||
@UseExperimental(KtorExperimentalAPI::class)
|
||||
actual val isOpen: Boolean
|
||||
get() = socket.socketContext.isActive
|
||||
|
||||
override fun close() = socket.dispose()
|
||||
|
||||
@PublishedApi
|
||||
internal lateinit var writeChannel: ByteWriteChannel
|
||||
@PublishedApi
|
||||
internal lateinit var readChannel: ByteReadChannel
|
||||
|
||||
/**
|
||||
* @throws SendPacketInternalException
|
||||
*/
|
||||
actual suspend inline fun send(packet: ByteReadPacket) {
|
||||
writeChannel.writePacket(packet)
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ReadPacketInternalException
|
||||
*/
|
||||
actual suspend inline fun read(): ByteReadPacket {
|
||||
// do not use readChannel.readRemaining() !!! this function never returns
|
||||
ByteArrayPool.useInstance { buffer ->
|
||||
val count = readChannel.readAvailable(buffer)
|
||||
return buffer.toReadPacket(0, count)
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(KtorExperimentalAPI::class)
|
||||
actual suspend fun connect(serverHost: String, serverPort: Int) {
|
||||
socket = aSocket(io.ktor.network.selector.ActorSelectorManager(kotlinx.coroutines.Dispatchers.IO)).tcp().connect(serverHost, serverPort)
|
||||
writeChannel = socket.openWriteChannel(true)
|
||||
readChannel = socket.openReadChannel()
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
package net.mamoe.mirai.utils.io
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.mirai.utils.io.hexToBytes
|
||||
import net.mamoe.mirai.utils.io.toByteArray
|
||||
import net.mamoe.mirai.utils.io.toUHexString
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
class TypeConversionTest {
|
||||
@ -13,6 +17,7 @@ class TypeConversionTest {
|
||||
assertEquals("7F", byteArrayOf(0x7F).toUHexString())
|
||||
assertEquals("FF", ubyteArrayOf(0xffu).toUHexString())
|
||||
assertEquals("7F", ubyteArrayOf(0x7fu).toUHexString())
|
||||
assertTrue { 1994701021.toByteArray().contentEquals("76 E4 B8 DD".hexToBytes()) }
|
||||
assertEquals(byteArrayOf(0, 0, 0, 0x01).toUHexString(), 1.toByteArray().toUHexString())
|
||||
assertEquals(ubyteArrayOf(0x7fu, 0xffu, 0xffu, 0xffu).toByteArray().toUHexString(), Int.MAX_VALUE.toByteArray().toUHexString())
|
||||
}
|
Loading…
Reference in New Issue
Block a user