diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index 8b8518b04..f02238579 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -7,20 +7,26 @@ import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.ImageLink import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler +import net.mamoe.mirai.qqandroid.network.QQAndroidClient +import net.mamoe.mirai.qqandroid.utils.Context import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.MiraiInternalAPI import kotlin.coroutines.CoroutineContext internal expect class QQAndroidBot( + context: Context, account: BotAccount, configuration: BotConfiguration ) : QQAndroidBotBase @UseExperimental(MiraiInternalAPI::class) internal abstract class QQAndroidBotBase constructor( + context: Context, account: BotAccount, configuration: BotConfiguration ) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) { + val client: QQAndroidClient = QQAndroidClient(context, account) + override val qqs: ContactList<QQ> get() = TODO("not implemented") @@ -47,10 +53,6 @@ internal abstract class QQAndroidBotBase constructor( TODO("not implemented") } - override suspend fun login() { - TODO("not implemented") - } - override suspend fun Image.getLink(): ImageLink { TODO("not implemented") } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 7a0c51b91..45d6d7227 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -1,21 +1,90 @@ package net.mamoe.mirai.qqandroid.network -import kotlinx.coroutines.CompletableJob -import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.* +import kotlinx.io.core.* +import kotlinx.io.pool.useInstance import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket +import net.mamoe.mirai.utils.io.* import kotlin.coroutines.CoroutineContext internal class QQAndroidBotNetworkHandler(override val bot: QQAndroidBot) : BotNetworkHandler() { override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job]) + private val channel: PlatformDatagramChannel = PlatformDatagramChannel("wtlogin.qq.com", 8000) + override suspend fun login() { - TODO("not implemented") + launch { processReceive() } + + val buffer = IoBuffer.Pool.borrow() + buffer.writePacket(LoginPacket(bot.client).delegate) + val shouldBeSent = buffer.readRemaining + check(channel.send(buffer) == shouldBeSent) { + "Buffer is not entirely sent. " + + "Required sent length=$shouldBeSent, but after channel.send, " + + "buffer remains ${buffer.readBytes().toUHexString()}" + } + buffer.release(IoBuffer.Pool) + println("Login sent") + } + + private suspend fun processReceive() { + while (channel.isOpen) { + val buffer = IoBuffer.Pool.borrow() + + try { + channel.read(buffer)// JVM: withContext(IO) + } catch (e: ClosedChannelException) { + dispose() + return + } catch (e: ReadPacketInternalException) { + bot.logger.error("Socket channel read failed: ${e.message}") + continue + } catch (e: CancellationException) { + return + } catch (e: Throwable) { + bot.logger.error("Caught unexpected exceptions", e) + continue + } finally { + if (!buffer.canRead() || buffer.readRemaining == 0) {//size==0 + //bot.logger.debug("processReceive: Buffer cannot be read") + buffer.release(IoBuffer.Pool) + continue + }// sometimes exceptions are thrown without this `if` clause + } + + //buffer.resetForRead() + launch(CoroutineName("handleServerPacket")) { + // `.use`: Ensure that the packet is consumed **totally** + // so that all the buffers are released + ByteArrayPool.useInstance { + val length = buffer.readRemaining - 1 + buffer.readFully(it, 0, length) + buffer.resetForWrite() + buffer.writeFully(it, 0, length) + } + ByteReadPacket(buffer, IoBuffer.Pool).use { input -> + try { + input.debugPrint("Received") + } catch (e: Exception) { + bot.logger.error(e) + } + } + } + } } override suspend fun awaitDisconnection() { - TODO() + while (true) { + delay(100) + // TODO: 2019/12/31 + } + } + + override fun dispose(cause: Throwable?) { + println("Closed") + super.dispose(cause) } override val coroutineContext: CoroutineContext = bot.coroutineContext diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index ddc341c44..79ccf9324 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -3,6 +3,7 @@ package net.mamoe.mirai.qqandroid.network import kotlinx.io.core.toByteArray import net.mamoe.mirai.BotAccount import net.mamoe.mirai.qqandroid.utils.* +import net.mamoe.mirai.utils.io.hexToBytes /* APP ID: @@ -41,7 +42,7 @@ internal open class QQAndroidClient( var networkType: NetworkType = NetworkType.WIFI - val apkSignatureMd5: ByteArray = TODO() + val apkSignatureMd5: ByteArray = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes() /** * 协议版本?, 8.2.0 的为 8001 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt index b495a6ef7..6b3dff8ce 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt @@ -96,6 +96,9 @@ fun BytePacketBuilder.t106( loginType: LoginType ) { writeShort(0x106) + passwordMd5.requireSize(16) + tgtgtKey.requireSize(16) + guid?.requireSize(16) writeShortLVPacket { encryptAndWrite(md5(passwordMd5 + (salt.takeIf { it != 0L } ?: uin).toInt().toByteArray())) { @@ -132,7 +135,7 @@ fun BytePacketBuilder.t106( writeInt(loginType.value) writeShortLVByteArray(uinAccount) // TODO check if should be empty byte[] } - } shouldEqualsTo 98 + } } fun BytePacketBuilder.t116( @@ -620,7 +623,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) +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}" } fun randomAndroidId(): String = buildString(15) { repeat(15) { append(Random.nextInt(10)) } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt index 220fe6768..28404d5ed 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/LoginPacket.kt @@ -21,6 +21,9 @@ class LoginPacketDecrypter(override val value: ByteArray) : DecrypterByteArray { @UseExperimental(ExperimentalUnsignedTypes::class) internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, LoginPacketDecrypter>(LoginPacketDecrypter) { + init { + this._id = PacketId(0x0810, 9) + } operator fun invoke( client: QQAndroidClient @@ -144,6 +147,15 @@ internal object LoginPacket : PacketFactory<LoginPacket.LoginPacketResponse, Log } } + +@Suppress("FunctionName") +fun PacketId(commandId: Int, subCommandId: Int) = object : PacketId { + override val commandId: Int + get() = commandId + override val subCommandId: Int + get() = subCommandId +} + interface PacketId { val commandId: Int // ushort actually val subCommandId: Int // ushort actually diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index 03b0702e3..059749948 100644 --- a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -1,15 +1,15 @@ package net.mamoe.mirai.qqandroid import net.mamoe.mirai.BotAccount -import net.mamoe.mirai.alsoLogin +import net.mamoe.mirai.qqandroid.utils.Context +import net.mamoe.mirai.qqandroid.utils.ContextImpl import net.mamoe.mirai.utils.BotConfiguration +@Suppress("FunctionName") +internal fun QQAndroidBot(account: BotAccount, configuration: BotConfiguration) = QQAndroidBot(ContextImpl(), account, configuration) + internal actual class QQAndroidBot actual constructor( + context: Context, account: BotAccount, configuration: BotConfiguration -) : QQAndroidBotBase(account, configuration) - -suspend fun main() { - val bot = QQAndroidBot(BotAccount(1, ""), BotConfiguration()).alsoLogin() - bot.network.awaitDisconnection() -} \ No newline at end of file +) : QQAndroidBotBase(context, account, configuration) \ No newline at end of file diff --git a/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt b/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt index b68b2663c..823b491ec 100644 --- a/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt +++ b/mirai-core-timpc/src/jvmTest/kotlin/PacketDebugger.kt @@ -16,13 +16,14 @@ import kotlinx.serialization.internal.ArrayListSerializer import kotlinx.serialization.json.Json import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler - import net.mamoe.mirai.timpc.TIMPC import net.mamoe.mirai.timpc.network.TIMProtocol import net.mamoe.mirai.timpc.network.packet.* -import net.mamoe.mirai.timpc.network.packet.event.FriendOnlineStatusChangedPacket import net.mamoe.mirai.timpc.network.packet.event.IgnoredEventPacket -import net.mamoe.mirai.timpc.network.packet.login.* +import net.mamoe.mirai.timpc.network.packet.login.CaptchaKey +import net.mamoe.mirai.timpc.network.packet.login.HeartbeatPacket +import net.mamoe.mirai.timpc.network.packet.login.ShareKey +import net.mamoe.mirai.timpc.network.packet.login.TouchKey import net.mamoe.mirai.utils.cryptor.Decrypter import net.mamoe.mirai.utils.cryptor.DecryptionFailedException import net.mamoe.mirai.utils.cryptor.NoDecrypter @@ -127,7 +128,7 @@ suspend fun main() { listenDevice(localIp, it) } println("Using sessionKey = ${sessionKey.value.toUHexString()}") - println("Filter QQ = ${qq?.toLong()}") + println("Filter QQ = $qq") PacketDebugger.recorder?.let { println("Recorder is enabled") } Runtime.getRuntime().addShutdownHook(thread(false) { PacketDebugger.recorder?.writeTo(File(GMTDate().toString() + ".record"))?.also { println("${PacketDebugger.recorder.list.size} records saved.") } @@ -183,8 +184,7 @@ internal object PacketDebugger { * 7. 运行完 `mov eax,dword ptr ss:[ebp+10]` * 8. 查看内存, `eax` 到 `eax+10` 的 16 字节就是 `sessionKey` */ - val sessionKey: SessionKey = - SessionKey("D8 D0 B0 DE 37 53 9B 05 A5 E7 AB 96 B2 AC AD EC".hexToBytes()) + val sessionKey: SessionKey get() = SessionKey("D8 D0 B0 DE 37 53 9B 05 A5 E7 AB 96 B2 AC AD EC".hexToBytes()) // TODO: 2019/12/7 无法访问 internal 是 kotlin bug, KT-34849 /** @@ -197,9 +197,9 @@ internal object PacketDebugger { val recorder: Recorder? = Recorder() val IgnoredPacketIdList: List<PacketId> = listOf( - KnownPacketId.get<FriendOnlineStatusChangedPacket>(), - KnownPacketId.get<ChangeOnlineStatusPacket>(), - KnownPacketId.get<HeartbeatPacket>() + // KnownPacketId.get<FriendOnlineStatusChangedPacket>(), + // KnownPacketId.get<ChangeOnlineStatusPacket>(), + // KnownPacketId.get<HeartbeatPacket>() ) suspend fun dataReceived(data: ByteArray) { @@ -304,7 +304,7 @@ internal object PacketDebugger { // 3E 03 3F A2 02 00 00 00 01 2E 01 00 00 69 35 discardExact(3)//head - val id = net.mamoe.mirai.timpc.network.packet.matchPacketId(readUShort()) + val id = matchPacketId(readUShort()) val sequence = readUShort().toUHexString() if (IgnoredPacketIdList.contains(id)) { return