diff --git a/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt index a84ba7249..b2251b9a1 100644 --- a/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/impl/netty/NettyNetworkHandler.kt @@ -110,7 +110,7 @@ internal open class NettyNetworkHandler( .addLast(OutgoingPacketEncoder()) .addLast(LengthFieldBasedFrameDecoder(Int.MAX_VALUE, 0, 4, -4, 4)) .addLast(ByteBufToIncomingPacketDecoder()) - .addLast(RawIncomingPacketCollector(decodePipeline)) + .addLast("raw-packet-collector", RawIncomingPacketCollector(decodePipeline)) } protected open fun createDummyDecodePipeline() = PacketDecodePipeline(this@NettyNetworkHandler.coroutineContext) diff --git a/mirai-core/src/jvmTest/kotlin/bootstrap/NetReplayHelper.kt b/mirai-core/src/jvmTest/kotlin/bootstrap/NetReplayHelper.kt new file mode 100644 index 000000000..1c5747a7e --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/bootstrap/NetReplayHelper.kt @@ -0,0 +1,253 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +@file:JvmName("NetReplayHelper") +@file:Suppress("TestFunctionName") + +package net.mamoe.mirai.internal.bootstrap + +import io.netty.channel.* +import net.mamoe.mirai.Bot +import net.mamoe.mirai.BotFactory +import net.mamoe.mirai.internal.asQQAndroidBot +import net.mamoe.mirai.internal.network.components.PacketLoggingStrategyImpl +import net.mamoe.mirai.internal.network.components.RawIncomingPacket +import net.mamoe.mirai.internal.network.components.ServerList +import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext +import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl +import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory +import net.mamoe.mirai.internal.network.handler.selector.KeepAliveNetworkHandlerSelector +import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler +import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler +import net.mamoe.mirai.internal.utils.subLogger +import net.mamoe.mirai.utils.* +import java.awt.Component +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import java.lang.invoke.MethodHandles +import java.net.SocketAddress +import javax.swing.* +import kotlin.reflect.KProperty +import kotlin.reflect.full.declaredMembers +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.jvm.javaField + +private val droppedCommands: Collection by lazy { + listOf( + "Heartbeat.Alive", + "wtlogin.exchange_emp", + "StatSvc.register", + "StatSvc.GetDevLoginInfo", + "MessageSvc.PbGetMsg", + "friendlist.getFriendGroupList", + "friendlist.GetTroopListReqV2", + "friendlist.GetTroopMemberListReq", + "ConfigPushSvc.PushReq", + *PacketLoggingStrategyImpl.getDefaultBlacklist().toTypedArray(), + ) +} + +private fun NetReplayHelperClass(): Class<*> { + return MethodHandles.lookup().lookupClass() +} + +private val logger by lazy { + MiraiLogger.Factory.create(NetReplayHelperClass()) +} + +private fun attachNetReplayHelper(channel: Channel) { + channel.pipeline() + .addBefore("raw-packet-collector", "raw-packet-dumper", newRawPacketDumper()) + + attachNetReplayWView(channel) +} + +private fun newRawPacketDumper(): ChannelHandler = object : ChannelInboundHandlerAdapter() { + override fun channelRead(ctx: ChannelHandlerContext?, msg: Any?) { + if (msg is RawIncomingPacket) { + if (msg.commandName in droppedCommands) { + logger.debug { "sid=${msg.sequenceId}, cmd=${msg.commandName}, body=" } + } else { + logger.debug { "sid=${msg.sequenceId}, cmd=${msg.commandName}, body=${msg.body.toUHexString()}" } + } + } + super.channelRead(ctx, msg) + } +} + +private fun attachNetReplayWView(channel: Channel) { + val frame = JFrame("Net Replay Helper") + val panel = JPanel() + val layout = GroupLayout(panel) + panel.layout = layout + frame.add(panel) + + val cmd = JTextField() + val sid = JTextField() + val bdy = JTextField() + val log = JTextField() + + val cmdLabel = JLabel("cmd").also { it.labelFor = cmd } + val sidLabel = JLabel("seq").also { it.labelFor = sid } + val bdyLabel = JLabel("body").also { it.labelFor = bdy } + val logLabel = JLabel("log").also { it.labelFor = log } + + val fireCustom = JButton("Fire Cus") + val fireLog = JButton("Fire Log") + + // region + layout.setHorizontalGroup( + layout.createParallelGroup().addGroup( + layout.createSequentialGroup().addGroup( + layout.createParallelGroup() + .addComponent(cmdLabel) + .addComponent(sidLabel) + .addComponent(bdyLabel) + .addComponent(logLabel) + ).addGroup( + layout.createParallelGroup() + .addComponent(cmd) + .addComponent(sid) + .addComponent(bdy) + .addComponent(log) + ) + ).addGroup( + layout.createSequentialGroup() + .addComponent(fireCustom) + .addComponent(fireLog) + ) + ) + layout.setVerticalGroup( + layout.createSequentialGroup() + .addGroup( + layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(cmdLabel) + .addComponent(cmd) + ) + .addGroup( + layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(sidLabel) + .addComponent(sid) + ) + .addGroup( + layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(bdyLabel) + .addComponent(bdy) + ) + .addGroup( + layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(logLabel) + .addComponent(log) + ) + .addGroup( + layout.createParallelGroup() + .addComponent(fireCustom) + .addComponent(fireLog) + ) + ) + // endregion + + fun Component.onClick(handle: () -> Unit) { + addMouseListener(object : MouseAdapter() { + override fun mouseClicked(e: MouseEvent?) { + runCatching(handle).onFailure { logger.error(it) } + } + }) + } + + fireCustom.onClick { + val rp = RawIncomingPacket( + commandName = cmd.text.trim(), + sequenceId = sid.text.toInt(), + body = bdy.text.hexToBytes(), + ) + channel.pipeline().fireChannelRead(rp) + } + + @Suppress("LocalVariableName") + fireLog.onClick { + var line = log.text.substringAfter("sid=") + // 2021-11-07 11:49:38 D/NetReplayHelper: sid=123, cmd=HelloWorld!, body=00 + val sid_ = line.substringBefore(",").toInt() + line = line.substringAfter("cmd=") + val cmd_ = line.substringBeforeLast("body=").trim().removeSuffix(",").trim() + val bdy_ = line.substringAfterLast("body=").trim().hexToBytes() + + val rp = RawIncomingPacket( + commandName = cmd_, + sequenceId = sid_, + body = bdy_, + ) + channel.pipeline().fireChannelRead(rp) + } + + frame.pack() + frame.defaultCloseOperation = JFrame.DO_NOTHING_ON_CLOSE + frame.setLocationRelativeTo(null) + frame.isVisible = true + + channel.pipeline().addLast(object : ChannelInboundHandlerAdapter() { + override fun channelInactive(ctx: ChannelHandlerContext?) { + SwingUtilities.invokeLater { + frame.dispose() + } + super.channelInactive(ctx) + } + }) + +} + +private object NRHNettyNetworkHandlerFactory : NetworkHandlerFactory { + override fun create(context: NetworkHandlerContext, address: SocketAddress): NettyNetworkHandler { + return object : NettyNetworkHandler(context, address) { + override fun setupChannelPipeline(pipeline: ChannelPipeline, decodePipeline: PacketDecodePipeline) { + super.setupChannelPipeline(pipeline, decodePipeline) + attachNetReplayHelper(pipeline.channel()) + } + } + } +} + +// Call before bot.login() +fun Bot.attachNetReplayHelper() { + asQQAndroidBot() + val networkLogger = this::class.declaredMembers.first { it.name == "networkLogger" }.let { property -> + property as KProperty<*> + property.isAccessible = true + property.getter.call(this@attachNetReplayHelper) + } as MiraiLogger + + + val snh = network.cast>() + val field = snh::selector.javaField!! + field.isAccessible = true + field.set( + snh, + KeepAliveNetworkHandlerSelector( + maxAttempts = configuration.reconnectionRetryTimes.coerceIn(1, Int.MAX_VALUE), + logger = networkLogger.subLogger("Selector") + ) { + val context = NetworkHandlerContextImpl( + bot, + networkLogger, + createNetworkLevelComponents(), + ) + NRHNettyNetworkHandlerFactory.create( + context, + context[ServerList].pollAny().toSocketAddress(), + ) + }, + ) + +} + +fun main() { + val bot = BotFactory.newBot(0, "") + bot.attachNetReplayHelper() +}