diff --git a/mirai-api/src/main/java/net/mamoe/mirai/Bot.java b/mirai-api/src/main/java/net/mamoe/mirai/Bot.java new file mode 100644 index 000000000..0c60309dc --- /dev/null +++ b/mirai-api/src/main/java/net/mamoe/mirai/Bot.java @@ -0,0 +1,15 @@ +package net.mamoe.mirai; + +import net.mamoe.mirai.utils.BotAccount; + +import java.io.Closeable; + +/** + * @author Him188moe + */ +public interface Bot extends Closeable { + + BotAccount getAccount(); + + // TODO: 2019/9/13 add more +} diff --git a/mirai-core/src/main/java/net/mamoe/mirai/utils/BotAccount.java b/mirai-api/src/main/java/net/mamoe/mirai/utils/BotAccount.java similarity index 100% rename from mirai-core/src/main/java/net/mamoe/mirai/utils/BotAccount.java rename to mirai-api/src/main/java/net/mamoe/mirai/utils/BotAccount.java diff --git a/mirai-core/pom.xml b/mirai-core/pom.xml index 621bbfe95..735ef7b9e 100644 --- a/mirai-core/pom.xml +++ b/mirai-core/pom.xml @@ -17,6 +17,13 @@ + + net.mamoe + mirai-api + 1.0 + compile + + com.google.protobuf protobuf-java diff --git a/mirai-core/src/main/java/net/mamoe/mirai/Bot.java b/mirai-core/src/main/java/net/mamoe/mirai/Bot.java index dc0f9ee90..d1ad1e95c 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/Bot.java +++ b/mirai-core/src/main/java/net/mamoe/mirai/Bot.java @@ -3,7 +3,7 @@ package net.mamoe.mirai; import lombok.Getter; import net.mamoe.mirai.contact.Group; import net.mamoe.mirai.contact.QQ; -import net.mamoe.mirai.network.BotNetworkHandler; +import net.mamoe.mirai.network.BotNetworkHandlerImpl; import net.mamoe.mirai.utils.BotAccount; import net.mamoe.mirai.utils.ContactList; import net.mamoe.mirai.utils.config.MiraiConfigSection; @@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicInteger; *
* {@link Bot} 由 3 个模块组成. * {@linkplain ContactSystem 联系人管理}: 可通过 {@link Bot#contacts} 访问 - * {@linkplain BotNetworkHandler 网络处理器}: 可通过 {@link Bot#network} 访问 + * {@linkplain BotNetworkHandlerImpl 网络处理器}: 可通过 {@link Bot#network} 访问 * {@linkplain BotAccount 机器人账号信息}: 可通过 {@link Bot#account} 访问 *
* 若你需要得到机器人的 QQ 账号, 请访问 {@link Bot#account} @@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicInteger; * Bot that is the base of the whole program. * It consists of * a {@link ContactSystem}, which manage contacts such as {@link QQ} and {@link Group}; - * a {@link BotNetworkHandler}, which manages the connection to the server; + * a {@link BotNetworkHandlerImpl}, which manages the connection to the server; * a {@link BotAccount}, which stores the account information(e.g. qq number the bot) *
* To get all the QQ contacts, access {@link Bot#account} @@ -53,7 +53,7 @@ public final class Bot implements Closeable { public final ContactSystem contacts = new ContactSystem(); - public final BotNetworkHandler network; + public final BotNetworkHandlerImpl network; @Override public String toString() { @@ -118,7 +118,7 @@ public final class Bot implements Closeable { Objects.requireNonNull(owners); this.account = account; this.owners = Collections.unmodifiableList(owners); - this.network = new BotNetworkHandler(this); + this.network = new BotNetworkHandlerImpl(this); } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/contact/Group.kt b/mirai-core/src/main/java/net/mamoe/mirai/contact/Group.kt index 3bd32fe02..deea82b8c 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/contact/Group.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/contact/Group.kt @@ -26,7 +26,7 @@ class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable { val members = ContactList() override fun sendMessage(message: MessageChain) { - bot.network.messageHandler.sendGroupMessage(this, message) + bot.network.message.sendGroupMessage(this, message) } override fun sendXMLMessage(message: String) { diff --git a/mirai-core/src/main/java/net/mamoe/mirai/contact/QQ.kt b/mirai-core/src/main/java/net/mamoe/mirai/contact/QQ.kt index 39d58bccc..3090e8eed 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/contact/QQ.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/contact/QQ.kt @@ -19,7 +19,7 @@ import net.mamoe.mirai.message.defaults.MessageChain */ class QQ(bot: Bot, number: Long) : Contact(bot, number) { override fun sendMessage(message: MessageChain) { - bot.network.messageHandler.sendFriendMessage(this, message) + bot.network.message.sendFriendMessage(this, message) } override fun sendXMLMessage(message: String) { diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt index 52adfbd8f..470e1a029 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandler.kt @@ -1,428 +1,58 @@ -@file:JvmMultifileClass -@file:JvmName("BotNetworkHandler") - package net.mamoe.mirai.network -import net.mamoe.mirai.Bot -import net.mamoe.mirai.MiraiServer -import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent -import net.mamoe.mirai.event.events.network.BeforePacketSendEvent -import net.mamoe.mirai.event.events.network.PacketSentEvent -import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent -import net.mamoe.mirai.event.hookWhile -import net.mamoe.mirai.network.BotNetworkHandler.Login -import net.mamoe.mirai.network.BotNetworkHandler.SocketHandler +import net.mamoe.mirai.network.BotNetworkHandlerImpl.BotSocket +import net.mamoe.mirai.network.BotNetworkHandlerImpl.Login import net.mamoe.mirai.network.handler.* -import net.mamoe.mirai.network.packet.* -import net.mamoe.mirai.network.packet.login.* -import net.mamoe.mirai.task.MiraiThreadPool -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.network.packet.ClientPacket +import net.mamoe.mirai.network.packet.Packet +import net.mamoe.mirai.network.packet.ServerEventPacket +import net.mamoe.mirai.network.packet.ServerPacket import java.io.Closeable -import java.net.DatagramPacket -import java.net.DatagramSocket -import java.net.InetSocketAddress -import java.util.* -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ScheduledFuture -import java.util.concurrent.TimeUnit -import javax.imageio.ImageIO -import kotlin.reflect.KClass - /** - * Mirai 的网络处理器, 它处理所有数据包([Packet])的发送和接收. - * [BotNetworkHandler] 是全程异步和线程安全的. + * Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务. + * [BotNetworkHandler] 是全异步和线程安全的. * * [BotNetworkHandler] 由 2 个模块构成: - * - [SocketHandler]: 处理数据包底层的发送([ByteArray]) - * - [PacketHandler]: 制作 [Packet] 并传递给 [SocketHandler] 继续处理; 分析来自服务器的数据包并处理 + * - [BotSocket]: 处理数据包底层的发送([ByteArray]) + * - [PacketHandler]: 制作 [ClientPacket] 并传递给 [BotSocket] 发送; 分析 [ServerPacket] 并处理 * * 其中, [PacketHandler] 由 4 个子模块构成: - * - [DebugHandler] 输出 [Packet.toString] + * - [DebugPacketHandler] 输出 [Packet.toString] * - [Login] 处理 touch/login/verification code 相关 - * - [MessageHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket]) - * - [ActionHandler] 处理动作相关(踢人/加入群/好友列表等) + * - [MessagePacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket]) + * - [ActionPacketHandler] 处理动作相关(踢人/加入群/好友列表等) * * A BotNetworkHandler is used to connect with Tencent servers. * * @author Him188moe */ -@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code -class BotNetworkHandler(private val bot: Bot) : Closeable { - private val socket: SocketHandler = SocketHandler() - - fun getSocket(): DataPacketSocket { - return socket - } - - - lateinit var debugHandler: DebugHandler - lateinit var login: Login - lateinit var messageHandler: MessageHandler - lateinit var actionHandler: ActionHandler - - val packetHandlers: PacketHandlerList = PacketHandlerList() - - - //private | internal - +interface BotNetworkHandler : Closeable { /** - * 尝试登录 + * 网络层处理器. 用于编码/解码 [Packet], 发送/接受 [ByteArray] * - * @param touchingTimeoutMillis 连接每个服务器的 timeout + * java 调用方式: `botNetWorkHandler.getSocket()` */ - @JvmOverloads - internal fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture { - val ipQueue: LinkedList = LinkedList(Protocol.SERVER_IP) - val future = CompletableFuture() - - fun login() { - this.socket.close() - val ip = ipQueue.poll() - if (ip == null) { - future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN - return - } - - this@BotNetworkHandler.socket.touch(ip, touchingTimeoutMillis).get().let { state -> - if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) { - login()//超时或未知, 重试连接下一个服务器 - } else { - future.complete(state) - } - } - } - login() - return future - } - - - private lateinit var sessionKey: ByteArray - - override fun close() { - this.packetHandlers.forEach { - it.instance.close() - } - this.socket.close() - } - - - private inner class SocketHandler : Closeable, DataPacketSocket { - override fun distributePacket(packet: ServerPacket) { - try { - packet.decode() - } catch (e: java.lang.Exception) { - e.printStackTrace() - bot.debugPacket(packet) - return - } - - if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) { - debugHandler.onPacketReceived(packet) - return - } - - login.onPacketReceived(packet) - packetHandlers.forEach { - it.instance.onPacketReceived(packet) - } - } - - private var socket: DatagramSocket? = null - - internal var serverIP: String = "" - set(value) { - field = value - - restartSocket() - } - - internal var loginFuture: CompletableFuture? = null - - @Synchronized - private fun restartSocket() { - socket?.close() - socket = DatagramSocket(0) - socket!!.connect(InetSocketAddress(serverIP, 8000)) - Thread { - while (socket!!.isConnected) { - val packet = DatagramPacket(ByteArray(2048), 2048) - kotlin.runCatching { socket?.receive(packet) } - .onSuccess { - MiraiThreadPool.getInstance().submit { - try { - distributePacket(ServerPacket.ofByteArray(packet.data.removeZeroTail())) - } catch (e: Exception) { - e.printStackTrace() - } - } - }.onFailure { - if (it.message == "Socket closed" || it.message == "socket closed") { - return@Thread - } - it.printStackTrace() - } - - } - }.start() - } - - /** - * Start network and touch the server - */ - fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture { - bot.info("Connecting server: $serverAddress") - if (this@BotNetworkHandler::login.isInitialized) { - login.close() - } - login = Login() - this.loginFuture = CompletableFuture() - - serverIP = serverAddress - waitForPacket(ServerPacket::class, timeoutMillis) { - loginFuture!!.complete(LoginState.TIMEOUT) - } - sendPacket(ClientTouchPacket(bot.account.qqNumber, serverIP)) - - return this.loginFuture!! - } - - /** - * Not async - */ - @Synchronized - @ExperimentalUnsignedTypes - override fun sendPacket(packet: ClientPacket) { - checkNotNull(socket) { "network closed" } - if (socket!!.isClosed) { - return - } - - try { - packet.encodePacket() - - if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) { - return - } - - val data = packet.toByteArray() - socket!!.send(DatagramPacket(data, data.size)) - bot.cyanL("Packet sent: $packet") - - PacketSentEvent(bot, packet).broadcast() - } catch (e: Throwable) { - e.printStackTrace() - } - } - - @Suppress("UNCHECKED_CAST") - internal fun

waitForPacket(packetClass: KClass

, timeoutMillis: Long, timeout: () -> Unit) { - var got = false - ServerPacketReceivedEvent::class.hookWhile { - if (packetClass.isInstance(it.packet) && it.bot === bot) { - got = true - true - } else { - false - } - } - - - MiraiThreadPool.getInstance().submit { - val startingTime = System.currentTimeMillis() - while (!got) { - if (System.currentTimeMillis() - startingTime > timeoutMillis) { - timeout.invoke() - return@submit - } - Thread.sleep(10) - } - } - } - - override fun close() { - this.socket?.close() - if (this.loginFuture != null) { - if (!this.loginFuture!!.isDone) { - this.loginFuture!!.cancel(true) - } - this.loginFuture = null - } - } - - override fun isClosed(): Boolean { - return this.socket?.isClosed ?: true - } - } + val socket: DataPacketSocket /** - * 处理登录过程 + * Debug 包处理器. 仅输出包的信息. 调试阶段使用 + * + * java 调用方式: `botNetWorkHandler.getDebug()` */ - inner class Login : Closeable { - private lateinit var token00BA: ByteArray - private lateinit var token0825: ByteArray - private var loginTime: Int = 0 - private lateinit var loginIP: String - private var tgtgtKey: ByteArray = getRandomByteArray(16) + var debug: DebugPacketHandler - private var tlv0105: ByteArray = lazyEncode { - it.writeHex("01 05 00 30") - it.writeHex("00 01 01 02 00 14 01 01 00 10") - it.writeRandom(16) - it.writeHex("00 14 01 02 00 10") - it.writeRandom(16) - } + /** + * 消息处理. 如发送好友消息, 接受群消息并触发事件 + * + * java 调用方式: `botNetWorkHandler.getMessage()` + */ + var message: MessagePacketHandler - /** - * 0828_decr_key - */ - private lateinit var sessionResponseDecryptionKey: ByteArray - - private var captchaSectionId: Int = 1 - private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来 - - private var heartbeatFuture: ScheduledFuture<*>? = null - - - fun onPacketReceived(packet: ServerPacket) { - when (packet) { - is ServerTouchResponsePacket -> { - if (packet.serverIP != null) {//redirection - socket.serverIP = packet.serverIP!! - //connect(packet.serverIP!!) - socket.sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber)) - } else {//password submission - this.loginIP = packet.loginIP - this.loginTime = packet.loginTime - this.token0825 = packet.token0825 - socket.sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825)) - } - } - - is ServerLoginResponseFailedPacket -> { - socket.loginFuture?.complete(packet.loginState) - return - } - - is ServerVerificationCodeCorrectPacket -> { - this.tgtgtKey = getRandomByteArray(16) - this.token00BA = packet.token00BA - socket.sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA)) - } - - is ServerLoginResponseVerificationCodeInitPacket -> { - //[token00BA]来源之一: 验证码 - this.token00BA = packet.token00BA - this.captchaCache = packet.verifyCodePart1 - - if (packet.unknownBoolean != null && packet.unknownBoolean!!) { - this.captchaSectionId = 1 - socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA)) - } - } - - is ServerVerificationCodeTransmissionPacket -> { - if (packet is ServerVerificationCodeWrongPacket) { - bot.error("验证码错误, 请重新输入") - captchaSectionId = 1 - this.captchaCache = byteArrayOf() - } - - this.captchaCache = this.captchaCache!! + packet.captchaSectionN - this.token00BA = packet.token00BA - - if (packet.transmissionCompleted) { - bot.notice(CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream()))) - bot.notice("需要验证码登录, 验证码为 4 字母") - try { - (MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.captchaCache!!) - bot.notice("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png") - } catch (e: Exception) { - bot.notice("无法写出验证码文件, 请尝试查看以上字符图片") - } - bot.notice("若要更换验证码, 请直接回车") - val code = Scanner(System.`in`).nextLine() - if (code.isEmpty() || code.length != 4) { - this.captchaCache = byteArrayOf() - this.captchaSectionId = 1 - socket.sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825)) - } else { - socket.sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken)) - } - } else { - socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA)) - } - } - - is ServerLoginResponseSuccessPacket -> { - this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey - socket.sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socket.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105)) - } - - //是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.tgtgtKey = packet.tgtgtKey - socket.sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown - ?: this.token00BA, packet.tlv0006)) - } else { - socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown - ?: token00BA, packet.tlv0006)) - } - } - - is ServerSessionKeyResponsePacket -> { - sessionKey = packet.sessionKey - heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({ - socket.sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey)) - }, 90000, 90000, TimeUnit.MILLISECONDS) - - BotLoginSucceedEvent(bot).broadcast() - - //登录成功后会收到大量上次的消息, 忽略掉 - MiraiThreadPool.getInstance().schedule({ - messageHandler.ignoreMessage = false - }, 3, TimeUnit.SECONDS) - - this.tlv0105 = packet.tlv0105 - - socket.loginFuture!!.complete(LoginState.SUCCESS) - } - - - is ServerEventPacket.Raw -> socket.distributePacket(packet.distribute()) - - is ServerVerificationCodePacket.Encrypted -> socket.distributePacket(packet.decrypt()) - is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> socket.distributePacket(packet.decrypt()) - is ServerLoginResponseKeyExchangePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey)) - is ServerLoginResponseSuccessPacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey)) - is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey)) - is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt()) - is ServerAccountInfoResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(sessionKey)) - is ServerEventPacket.Raw.Encrypted -> socket.distributePacket(packet.decrypt(sessionKey)) - - - is ServerLoginSuccessPacket, - is ServerHeartbeatResponsePacket, - is UnknownServerPacket -> { - //ignored - } - else -> { - - } - } - } - - override fun close() { - this.captchaCache = null - - this.heartbeatFuture?.cancel(true) - - this.heartbeatFuture = null - } - } -} + /** + * 动作处理. 如发送好友请求, 处理别人发来的好友请求等 + * + * java 调用方式: `botNetWorkHandler.getAction()` + */ + var action: ActionPacketHandler +} \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt new file mode 100644 index 000000000..a410f4f1d --- /dev/null +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/BotNetworkHandlerImpl.kt @@ -0,0 +1,413 @@ +package net.mamoe.mirai.network + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.MiraiServer +import net.mamoe.mirai.event.events.bot.BotLoginSucceedEvent +import net.mamoe.mirai.event.events.network.BeforePacketSendEvent +import net.mamoe.mirai.event.events.network.PacketSentEvent +import net.mamoe.mirai.event.events.network.ServerPacketReceivedEvent +import net.mamoe.mirai.event.hookWhile +import net.mamoe.mirai.network.handler.* +import net.mamoe.mirai.network.packet.* +import net.mamoe.mirai.network.packet.login.* +import net.mamoe.mirai.task.MiraiThreadPool +import net.mamoe.mirai.utils.* +import java.io.Closeable +import java.net.DatagramPacket +import java.net.DatagramSocket +import java.net.InetSocketAddress +import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit +import javax.imageio.ImageIO +import kotlin.reflect.KClass + +/** + * [BotNetworkHandler] 的内部实现, 该类不会有帮助理解的注解, 请查看 [BotNetworkHandler] 以获取帮助 + * + * @see BotNetworkHandler + * @author Him188moe + */ +@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code +internal class BotNetworkHandlerImpl(private val bot: Bot) : BotNetworkHandler { + override val socket: BotSocket = BotSocket() + + lateinit var login: Login + + override lateinit var debug: DebugPacketHandler + override lateinit var message: MessagePacketHandler + override lateinit var action: ActionPacketHandler + + val packetHandlers: PacketHandlerList = PacketHandlerList() + + + //private | internal + /** + * 尝试登录. 多次重复登录 + * + * @param touchingTimeoutMillis 连接每个服务器的 timeout + */ + @JvmOverloads + internal fun tryLogin(touchingTimeoutMillis: Long = 200): CompletableFuture { + val ipQueue: LinkedList = LinkedList(Protocol.SERVER_IP) + val future = CompletableFuture() + + fun login() { + this.socket.close() + val ip = ipQueue.poll() + if (ip == null) { + future.complete(LoginState.UNKNOWN)//所有服务器均返回 UNKNOWN + return + } + + this.socket.touch(ip, touchingTimeoutMillis).get().let { state -> + if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) { + login()//超时或未知, 重试连接下一个服务器 + } else { + future.complete(state) + } + } + } + login() + return future + } + + private fun onLoggedIn(sessionKey: ByteArray) { + val session = LoginSession(bot, sessionKey, socket) + debug = DebugPacketHandler(session) + message = MessagePacketHandler(session) + action = ActionPacketHandler(session) + + packetHandlers.add(debug.asNode()) + packetHandlers.add(message.asNode()) + packetHandlers.add(action.asNode()) + } + + private lateinit var sessionKey: ByteArray + + override fun close() { + this.packetHandlers.forEach { + it.instance.close() + } + this.socket.close() + } + + + internal inner class BotSocket : Closeable, DataPacketSocket { + override fun distributePacket(packet: ServerPacket) { + try { + packet.decode() + } catch (e: java.lang.Exception) { + e.printStackTrace() + bot.debugPacket(packet) + return + } + + if (ServerPacketReceivedEvent(bot, packet).broadcast().isCancelled) { + debug.onPacketReceived(packet) + return + } + + login.onPacketReceived(packet) + packetHandlers.forEach { + it.instance.onPacketReceived(packet) + } + } + + private var socket: DatagramSocket? = null + + internal var serverIP: String = "" + set(value) { + field = value + + restartSocket() + } + + internal var loginFuture: CompletableFuture? = null + + @Synchronized + private fun restartSocket() { + socket?.close() + socket = DatagramSocket(0) + socket!!.connect(InetSocketAddress(serverIP, 8000)) + Thread { + while (socket!!.isConnected) { + val packet = DatagramPacket(ByteArray(2048), 2048) + kotlin.runCatching { socket?.receive(packet) } + .onSuccess { + MiraiThreadPool.getInstance().submit { + try { + distributePacket(ServerPacket.ofByteArray(packet.data.removeZeroTail())) + } catch (e: Exception) { + e.printStackTrace() + } + } + }.onFailure { + if (it.message == "Socket closed" || it.message == "socket closed") { + return@Thread + } + it.printStackTrace() + } + + } + }.start() + } + + /** + * Start network and touch the server + */ + fun touch(serverAddress: String, timeoutMillis: Long): CompletableFuture { + bot.info("Connecting server: $serverAddress") + if (this@BotNetworkHandlerImpl::login.isInitialized) { + login.close() + } + login = Login() + this.loginFuture = CompletableFuture() + + serverIP = serverAddress + waitForPacket(ServerPacket::class, timeoutMillis) { + loginFuture!!.complete(LoginState.TIMEOUT) + } + sendPacket(ClientTouchPacket(bot.account.qqNumber, serverIP)) + + return this.loginFuture!! + } + + /** + * Not async + */ + @Synchronized + @ExperimentalUnsignedTypes + override fun sendPacket(packet: ClientPacket) { + checkNotNull(socket) { "network closed" } + if (socket!!.isClosed) { + return + } + + try { + packet.encodePacket() + + if (BeforePacketSendEvent(bot, packet).broadcast().isCancelled) { + return + } + + val data = packet.toByteArray() + socket!!.send(DatagramPacket(data, data.size)) + bot.cyanL("Packet sent: $packet") + + PacketSentEvent(bot, packet).broadcast() + } catch (e: Throwable) { + e.printStackTrace() + } + } + + @Suppress("UNCHECKED_CAST") + internal fun

waitForPacket(packetClass: KClass

, timeoutMillis: Long, timeout: () -> Unit) { + var got = false + ServerPacketReceivedEvent::class.hookWhile { + if (packetClass.isInstance(it.packet) && it.bot === bot) { + got = true + true + } else { + false + } + } + + + MiraiThreadPool.getInstance().submit { + val startingTime = System.currentTimeMillis() + while (!got) { + if (System.currentTimeMillis() - startingTime > timeoutMillis) { + timeout.invoke() + return@submit + } + Thread.sleep(10) + } + } + } + + override fun close() { + this.socket?.close() + if (this.loginFuture != null) { + if (!this.loginFuture!!.isDone) { + this.loginFuture!!.cancel(true) + } + this.loginFuture = null + } + } + + override fun isClosed(): Boolean { + return this.socket?.isClosed ?: true + } + } + + /** + * 处理登录过程 + */ + inner class Login : Closeable { + private lateinit var token00BA: ByteArray + private lateinit var token0825: ByteArray + private var loginTime: Int = 0 + private lateinit var loginIP: String + private var tgtgtKey: ByteArray = getRandomByteArray(16) + + private var tlv0105: ByteArray = lazyEncode { + it.writeHex("01 05 00 30") + it.writeHex("00 01 01 02 00 14 01 01 00 10") + it.writeRandom(16) + it.writeHex("00 14 01 02 00 10") + it.writeRandom(16) + } + + /** + * 0828_decr_key + */ + private lateinit var sessionResponseDecryptionKey: ByteArray + + private var captchaSectionId: Int = 1 + private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来 + + private var heartbeatFuture: ScheduledFuture<*>? = null + + + fun onPacketReceived(packet: ServerPacket) { + when (packet) { + is ServerTouchResponsePacket -> { + if (packet.serverIP != null) {//redirection + socket.serverIP = packet.serverIP!! + //connect(packet.serverIP!!) + socket.sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.account.qqNumber)) + } else {//password submission + this.loginIP = packet.loginIP + this.loginTime = packet.loginTime + this.token0825 = packet.token0825 + socket.sendPacket(ClientPasswordSubmissionPacket(bot.account.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.tgtgtKey, packet.token0825)) + } + } + + is ServerLoginResponseFailedPacket -> { + socket.loginFuture?.complete(packet.loginState) + return + } + + is ServerVerificationCodeCorrectPacket -> { + this.tgtgtKey = getRandomByteArray(16) + this.token00BA = packet.token00BA + socket.sendPacket(ClientLoginResendPacket3105(bot.account.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.tgtgtKey, this.token0825, this.token00BA)) + } + + is ServerLoginResponseVerificationCodeInitPacket -> { + //[token00BA]来源之一: 验证码 + this.token00BA = packet.token00BA + this.captchaCache = packet.verifyCodePart1 + + if (packet.unknownBoolean != null && packet.unknownBoolean!!) { + this.captchaSectionId = 1 + socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.account.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA)) + } + } + + is ServerVerificationCodeTransmissionPacket -> { + if (packet is ServerVerificationCodeWrongPacket) { + bot.error("验证码错误, 请重新输入") + captchaSectionId = 1 + this.captchaCache = byteArrayOf() + } + + this.captchaCache = this.captchaCache!! + packet.captchaSectionN + this.token00BA = packet.token00BA + + if (packet.transmissionCompleted) { + bot.notice(CharImageUtil.createCharImg(ImageIO.read(this.captchaCache!!.inputStream()))) + bot.notice("需要验证码登录, 验证码为 4 字母") + try { + (MiraiServer.getInstance().parentFolder + "VerificationCode.png").writeBytes(this.captchaCache!!) + bot.notice("若看不清字符图片, 请查看 Mirai 根目录下 VerificationCode.png") + } catch (e: Exception) { + bot.notice("无法写出验证码文件, 请尝试查看以上字符图片") + } + bot.notice("若要更换验证码, 请直接回车") + val code = Scanner(System.`in`).nextLine() + if (code.isEmpty() || code.length != 4) { + this.captchaCache = byteArrayOf() + this.captchaSectionId = 1 + socket.sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825)) + } else { + socket.sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, code, packet.verificationToken)) + } + } else { + socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.account.qqNumber, token0825, captchaSectionId++, token00BA)) + } + } + + is ServerLoginResponseSuccessPacket -> { + this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey + socket.sendPacket(ClientSessionRequestPacket(bot.account.qqNumber, socket.serverIP, packet.token38, packet.token88, packet.encryptionKey, this.tlv0105)) + } + + //是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.tgtgtKey = packet.tgtgtKey + socket.sendPacket(ClientLoginResendPacket3104(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown + ?: this.token00BA, packet.tlv0006)) + } else { + socket.sendPacket(ClientLoginResendPacket3106(bot.account.qqNumber, bot.account.password, loginTime, loginIP, tgtgtKey, token0825, packet.tokenUnknown + ?: token00BA, packet.tlv0006)) + } + } + + is ServerSessionKeyResponsePacket -> { + sessionKey = packet.sessionKey + heartbeatFuture = MiraiThreadPool.getInstance().scheduleWithFixedDelay({ + socket.sendPacket(ClientHeartbeatPacket(bot.account.qqNumber, sessionKey)) + }, 90000, 90000, TimeUnit.MILLISECONDS) + + BotLoginSucceedEvent(bot).broadcast() + + //登录成功后会收到大量上次的消息, 忽略掉 + MiraiThreadPool.getInstance().schedule({ + message.ignoreMessage = false + }, 3, TimeUnit.SECONDS) + + this.tlv0105 = packet.tlv0105 + + socket.loginFuture!!.complete(LoginState.SUCCESS) + + onLoggedIn(sessionKey) + } + + + is ServerVerificationCodePacket.Encrypted -> socket.distributePacket(packet.decrypt()) + is ServerLoginResponseVerificationCodeInitPacket.Encrypted -> socket.distributePacket(packet.decrypt()) + is ServerLoginResponseKeyExchangePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey)) + is ServerLoginResponseSuccessPacket.Encrypted -> socket.distributePacket(packet.decrypt(this.tgtgtKey)) + is ServerSessionKeyResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt(this.sessionResponseDecryptionKey)) + is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt()) + + + is ServerLoginSuccessPacket, + is ServerHeartbeatResponsePacket, + is UnknownServerPacket -> { + //ignored + } + else -> { + + } + } + } + + override fun close() { + this.captchaCache = null + + this.heartbeatFuture?.cancel(true) + + this.heartbeatFuture = null + } + } +} diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt index a5bab9af8..022848293 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/LoginSession.kt @@ -5,7 +5,8 @@ import net.mamoe.mirai.network.handler.DataPacketSocket import net.mamoe.mirai.utils.getGTK /** - * 一次会话. 当登录完成后, 客户端会拿到 sessionKey. 此时建立 session, 开始处理消息等事务 + * 登录会话. 当登录完成后, 客户端会拿到 sessionKey. + * 此时建立 session, 然后开始处理事务. * * @author Him188moe */ diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt similarity index 90% rename from mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionHandler.kt rename to mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt index 2a45138ff..e31639d04 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionHandler.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/ActionPacketHandler.kt @@ -12,9 +12,7 @@ import net.mamoe.mirai.network.packet.image.ServerTryUploadGroupImageSuccessPack import net.mamoe.mirai.network.packet.login.ClientChangeOnlineStatusPacket import net.mamoe.mirai.task.MiraiThreadPool import net.mamoe.mirai.utils.ClientLoginStatus -import net.mamoe.mirai.utils.ImageNetworkUtils import net.mamoe.mirai.utils.getGTK -import net.mamoe.mirai.utils.toUHexString import java.awt.image.BufferedImage import java.io.Closeable import java.util.* @@ -26,8 +24,10 @@ import java.util.function.Supplier /** * 动作: 获取好友列表, 点赞, 踢人等. * 处理动作事件, 承担动作任务. + * + * @author Him188moe */ -class ActionHandler(session: LoginSession) : PacketHandler(session) { +class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { private val addFriendSessions = Collections.synchronizedCollection(mutableListOf()) private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf()) @@ -42,7 +42,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) { } } is ServerTryUploadGroupImageSuccessPacket -> { - ImageNetworkUtils.postImage(packet.uKey.toUHexString(), ) + // ImageNetworkUtils.postImage(packet.uKey.toUHexString(), ) } is ServerTryUploadGroupImageFailedPacket -> { @@ -51,6 +51,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) { is ServerTryUploadGroupImageResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) + is ServerAccountInfoResponsePacket.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) is ServerAccountInfoResponsePacket -> { } @@ -67,6 +68,9 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) { session.gtk = getGTK(session.sKey) } + is ServerEventPacket.Raw.Encrypted -> session.socket.distributePacket(packet.decrypt(session.sessionKey)) + is ServerEventPacket.Raw -> session.socket.distributePacket(packet.distribute()) + else -> { } } @@ -82,7 +86,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) { fun addFriend(qqNumber: Long, message: Lazy = lazyOf("")): CompletableFuture { val future = CompletableFuture() val session = AddFriendSession(qqNumber, future, message) - uploadImageSessions.add(session) + // uploadImageSessions.add(session) session.sendAddRequest(); return future } @@ -138,7 +142,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) { } ServerCanAddFriendResponsePacket.State.REQUIRE_VERIFICATION -> { - session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey)) + // session.socket.sendPacket(ClientAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey)) } ServerCanAddFriendResponsePacket.State.NOT_REQUIRE_VERIFICATION -> { @@ -210,7 +214,7 @@ class ActionHandler(session: LoginSession) : PacketHandler(session) { } override fun close() { - uploadImageSessions.remove(this) + // uploadImageSessions.remove(this) } } } \ No newline at end of file diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt index 9c5aac23d..3384c314d 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DataPacketSocket.kt @@ -1,10 +1,15 @@ package net.mamoe.mirai.network.handler +import net.mamoe.mirai.network.BotNetworkHandlerImpl import net.mamoe.mirai.network.packet.ClientPacket import net.mamoe.mirai.network.packet.ServerPacket import java.io.Closeable /** + * 网络接口. + * 发包 / 处理包. + * 仅可通过 [BotNetworkHandlerImpl.socket] 得到实例. + * * @author Him188moe */ interface DataPacketSocket : Closeable { diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DebugHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DebugPacketHandler.kt similarity index 83% rename from mirai-core/src/main/java/net/mamoe/mirai/network/handler/DebugHandler.kt rename to mirai-core/src/main/java/net/mamoe/mirai/network/handler/DebugPacketHandler.kt index 5be246db3..30efadf6a 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DebugHandler.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/DebugPacketHandler.kt @@ -8,13 +8,15 @@ import net.mamoe.mirai.utils.notice /** * Kind of [PacketHandler] that prints all packets received in the format of hex byte array. + * + * @author Him188moe */ -sealed class DebugHandler(session: LoginSession) : PacketHandler(session) { +class DebugPacketHandler(session: LoginSession) : PacketHandler(session) { @ExperimentalUnsignedTypes override fun onPacketReceived(packet: ServerPacket) { if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) { - session.bot notice "Packet received: $packet" + session.bot.notice("Packet received: $packet") } if (packet is ServerEventPacket) { diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessageHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt similarity index 94% rename from mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessageHandler.kt rename to mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt index e542cb7a3..e71e0d932 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessageHandler.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/MessagePacketHandler.kt @@ -14,8 +14,11 @@ import net.mamoe.mirai.network.packet.action.ServerSendGroupMessageResponsePacke /** * 处理消息事件, 承担消息发送任务. + * + * @author Him188moe */ -class MessageHandler(session: LoginSession) : PacketHandler(session) { +@Suppress("EXPERIMENTAL_API_USAGE") +class MessagePacketHandler(session: LoginSession) : PacketHandler(session) { internal var ignoreMessage: Boolean = true init { @@ -67,7 +70,6 @@ class MessageHandler(session: LoginSession) : PacketHandler(session) { } } - @ExperimentalUnsignedTypes fun sendFriendMessage(qq: QQ, message: MessageChain) { session.socket.sendPacket(ClientSendFriendMessagePacket(session.bot.account.qqNumber, qq.number, session.sessionKey, message)) } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt index f2dd3e4a2..7803b0f12 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/handler/PacketHandler.kt @@ -24,8 +24,8 @@ class PacketHandlerNode( val instance: T ) -infix fun PacketHandler.to(handler: PacketHandler): PacketHandlerNode { - return PacketHandlerNode(handler.javaClass, handler) +fun PacketHandler.asNode(): PacketHandlerNode { + return PacketHandlerNode(this.javaClass, this) } class PacketHandlerList : LinkedList>() { diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt index f0e2c6514..48f64a1d1 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/ServerEvent.kt @@ -119,7 +119,7 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray, 0x19 -> MessageType.ANONYMOUS else -> { - MiraiLogger debug ("ServerGroupMessageEventPacket id=$id") + MiraiLogger.debug("ServerGroupMessageEventPacket id=$id") MessageType.OTHER } } diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Session.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Session.kt index 2484adfb8..206c82f7a 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Session.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/Session.kt @@ -64,6 +64,7 @@ class ClientSessionRequestPacket( /** * @author Him188moe */ +@PacketId("08 28 04 34") class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val dataLength: Int) : ServerPacket(inputStream) { lateinit var sessionKey: ByteArray lateinit var tlv0105: ByteArray diff --git a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt index 8a6b696b7..09720cc70 100644 --- a/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt +++ b/mirai-core/src/main/java/net/mamoe/mirai/network/packet/VerificationCode.kt @@ -1,7 +1,9 @@ package net.mamoe.mirai.network.packet import net.mamoe.mirai.network.Protocol -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.TEA +import net.mamoe.mirai.utils.TestedSuccessfully +import net.mamoe.mirai.utils.hexToBytes import java.io.DataInputStream /** @@ -18,9 +20,6 @@ class ClientVerificationCodeTransmissionRequestPacket( ) : ClientPacket() { @TestedSuccessfully override fun encode() { - MiraiLogger debug "packetId=$packetId" - MiraiLogger debug "verificationSequence=$verificationSequence" - this.writeByte(packetId)//part of packet id this.writeQQ(qq) @@ -90,34 +89,6 @@ class ClientVerificationCodeSubmitPacket( } } -@ExperimentalUnsignedTypes -fun main() { - val token0825 = "6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2".hexToBytes() - val verificationCode = "AAAA" - val verificationToken = "84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94".hexToBytes() -//00 02 00 00 08 04 01 E0 00 00 04 53 00 00 00 01 00 00 15 85 01 00 38 6E AF F9 2C 20 2B DE 21 B6 13 6F 26 43 F4 04 7B 1F 88 08 4E 8E BE E5 D1 3F E7 93 DE DD E0 6E 38 65 C7 C7 D3 20 7D AC 73 AD F9 85 F9 CC 2A 2C 26 C6 B1 5B FD 34 3F D4 F2 01 03 00 19 02 6D 28 41 D2 A5 6F D2 FC 3E 2A 1F 03 75 DE 6E 28 8F A8 19 3E 5F 16 49 D3 14 00 05 00 00 00 00 00 04 58 51 4E 44 00 38 84 2D 1D 9D 07 04 34 80 17 9E 3F 58 02 20 9A 1C 22 D0 73 7D 8A 90 1B 2F F8 E6 79 A6 84 2F 98 F5 1E 66 3D 9A 24 59 18 34 42 BD 45 DA E1 22 2D BC 2D 36 80 86 AD 44 C2 94 00 10 69 20 D1 14 74 F5 B3 93 E4 D5 02 B3 71 1A CD 2A - ByteArrayDataOutputStream().let { - it.writeHex("00 02 00 00 08 04 01 E0") - it.writeHex(Protocol.constantData2) - it.writeHex("01 00 38") - it.write(token0825) - it.writeHex("01 03") - - it.writeShort(25) - it.writeHex(Protocol.publicKey) - - it.writeHex("14 00 05 00 00 00 00 00 04") - it.write(verificationCode.substring(0..3).toByteArray()) - it.writeHex("00 38") - it.write(verificationToken) - - it.writeHex("00 10") - it.writeHex(Protocol.key00BAFix) - - println(it.toByteArray().toUHexString()) - } -} - /** * 刷新验证码 */ @@ -152,7 +123,7 @@ class ClientVerificationCodeRefreshPacket( } /** - * 验证码输入错误 + * 验证码输入错误, 同时也会给一部分验证码 */ @PacketId("00 BA 32") class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, packetId: ByteArray) : ServerVerificationCodeTransmissionPacket(input, dataSize, packetId) @@ -163,7 +134,7 @@ class ServerVerificationCodeWrongPacket(input: DataInputStream, dataSize: Int, p * @author Him188moe */ @PacketId("00 BA 31") -abstract class ServerVerificationCodeTransmissionPacket(input: DataInputStream, private val dataSize: Int, private val packetId: ByteArray) : ServerVerificationCodePacket(input) { +open class ServerVerificationCodeTransmissionPacket(input: DataInputStream, private val dataSize: Int, private val packetId: ByteArray) : ServerVerificationCodePacket(input) { lateinit var captchaSectionN: ByteArray lateinit var verificationToken: ByteArray//56bytes