GeyserMC / MCProtocolLib

A library for communication with a Minecraft client/server.
MIT License
728 stars 202 forks source link

Message JSON #437

Closed ghost closed 5 years ago

ghost commented 6 years ago

Hello, currently having an issue with messages that I'm having a little trouble diagnosing and fixing. When connecting to mc.civclassic.com which is a server with various mods, there's a case in which a ServerChatPacket's full text will be stringed JSON. Below is an example of what happens when merely dumping packet.getMessage().getFullText() into the console.

[22:57:47] [INFO] You have engaged in combat. Type /ct to check your timer.
[22:57:47] [INFO] You have been Combat Tagged on Login
[22:58:07] [INFO] You are no longer in combat.
[22:58:15] [INFO] {"hoverEvent":{"action":"show_text","value":[{"text":"Location: [world <x> <y> <z>]\nGroup: <group name>\nType: Logging"}]},"text":"ºb * <Player> entered snitch at [world <x> <y> <z>]"}
[22:59:31] [INFO] <Cranite> local chat test
[22:59:57] [INFO] [Group] Cranite: group chat test

There are two possible reasons that I can think of:-

  1. The JukeAlert plugin is sending stringed JSON as actual message contents rather than the message itself, and so this becomes an issue when MCProtocolLib does not recursively attempt to parse messages. JukeAlert's alert sending code is here but I believe this to be unlikely, as both the Minecraft native client and Minecraft Console Client render it without a problem. Although both may recursively parse messages, who knows.

  2. The message itself does not get parsed correctly by MCProtocolLib, either here or here. Not sure which as both have the same outcome, and I can't seem to use MinecraftProtocol.registerIncoming to replace ServerChatPacket with a different class so I can see the raw data involved. I've even gone so far as to packet sniff my own data, but to no avail.

My most recent solution has been to add a listener that listens for any received ServerChatPacket to which it will then attempt to recursively parse its contents into JSON until it fails at which point it will emit that as a MessageEvent, like so:

# Listener
public class MessageListener extends SessionAdapter {

    @Override
    public void packetReceived(PacketReceivedEvent event) {
        Session session = event.getSession();
        if (event.getPacket() instanceof ServerChatPacket) {
            ServerChatPacket packet = event.getPacket();
            Message message = parseMessagePacket(packet.getMessage());
            session.callEvent(new MessageEvent(session, message, packet.getType()));
        }
    }

    public static Message parseMessagePacket(Message message) {
        try {
            JsonParser parser = new JsonParser();
            JsonElement json = parser.parse(message.getFullText());
            Message parsed = Message.fromJson(json);
            return parseMessagePacket(parsed);
        }
        catch(Exception e) {
            return message;
        }
    }

}

# Connected Event
public class MessageEvent extends ConnectedEvent {

    private Message message;
    private MessageType type;

    public MessageEvent(Session session, Message message, MessageType type) {
        super(session);
        this.message = message;
        this.type = type;
    }

    @Override
    public void call(SessionListener listener) {
        listener.connected(this);
    }

    public Message getMessage() {
        return this.message;
    }

    public MessageType getType() {
        return this.type;
    }

}

But not even this works... any suggestions?


If you'd like to test this out yourself, you'll need two accounts, or one account and a volunteer with their own.

  1. Log on to CivClassics
  2. Place down a note block or juke box
  3. Punch it with smooth stone, or iron ingot, or diamond to create a snitch (you should see the enchantment table rune effect collapse into the block.)
  4. Now have the bot login to that account.
  5. Have your second account, or your volunteer run in and out of the snitch's boundary, which is 10 blocks from the snitch.

This should re-create the issue I am having.

0-x-2-2 commented 6 years ago

I have also seen this a long time ago on another server.

ghost commented 6 years ago

@Steveice10 I have tried to but I can't seem to get it to package, it fails at the testing stage due to Maven's surefire on MCProtocolLib 1.12.2-2 something about a System.exit or other. The currently in development code 1.13.2-1-SNAPSHOT packages just fine but cannot be used to connect to the server because it's 1.13 specific, not just supporting of it, so it cannot communicate properly with the server.

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.328 s
[INFO] Finished at: 2018-10-28T18:02:03Z
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.20.1:test (default-test) on project mcprotocollib: There are test failures.
[ERROR]
[ERROR] Please refer to C:\JavaProgramming\_libs\MCProtocolLib\target\surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date]-jvmRun[N].dump, [date].dumpstream and [date]-jvmRun[N].dumpstream.
[ERROR] The forked VM terminated without properly saying goodbye. VM crash or System.exit called?
[ERROR] Command was cmd.exe /X /C ""C:\Program Files\Java\jdk1.8.0_161\jre\bin\java" -Dfile.encoding=UTF-8 @{argLine} -jar C:\Users\Cranite\AppData\Local\Temp\surefire2360216413785119101\surefirebooter9116171676436384803.jar C:\Users\Cranite\AppData\Local\Temp\surefire2360216413785119101 2018-10-28T18-02-02_617-jvmRun1 surefire7156408938393632856tmp surefire_07796120184966865874tmp"
[ERROR] Error occurred in starting fork, check output in log
[ERROR] Process Exit Code: 1
[ERROR] org.apache.maven.surefire.booter.SurefireBooterForkException: The forked VM terminated without properly saying goodbye. VM crash or System.exit called?
[ERROR] Command was cmd.exe /X /C ""C:\Program Files\Java\jdk1.8.0_161\jre\bin\java" -Dfile.encoding=UTF-8 @{argLine} -jar C:\Users\Cranite\AppData\Local\Temp\surefire2360216413785119101\surefirebooter9116171676436384803.jar C:\Users\Cranite\AppData\Local\Temp\surefire2360216413785119101 2018-10-28T18-02-02_617-jvmRun1 surefire7156408938393632856tmp surefire_07796120184966865874tmp"
[ERROR] Error occurred in starting fork, check output in log
[ERROR] Process Exit Code: 1
[ERROR]         at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:686)
[ERROR]         at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:535)
[ERROR]         at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:280)
[ERROR]         at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:245)
[ERROR]         at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1124)
[ERROR]         at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:954)
[ERROR]         at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:832)
[ERROR]         at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:137)
[ERROR]         at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208)
[ERROR]         at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:154)
[ERROR]         at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:146)
[ERROR]         at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:117)
[ERROR]         at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:81)
[ERROR]         at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:56)
[ERROR]         at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128)
[ERROR]         at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:305)
[ERROR]         at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:192)
[ERROR]         at org.apache.maven.cli.MavenCli.execute(MavenCli.java:954)
[ERROR]         at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:288)
[ERROR]         at org.apache.maven.cli.MavenCli.main(MavenCli.java:192)
[ERROR]         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[ERROR]         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[ERROR]         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[ERROR]         at java.lang.reflect.Method.invoke(Method.java:498)
[ERROR]         at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
[ERROR]         at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
[ERROR]         at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
[ERROR]         at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
ghost commented 6 years ago

That worked! But I found the issue, it fails to parse the JSON and an exception is caught.

java.lang.IllegalArgumentException: Cannot convert JsonArray to a message.
    at com.github.steveice10.mc.protocol.data.message.Message.fromJson(Message.java:106)
    at com.github.steveice10.mc.protocol.data.message.Message.fromJson(Message.java:81)
    at com.github.steveice10.mc.protocol.data.message.Message.fromString(Message.java:21)
    at com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket.read(ServerChatPacket.java:47)
    at com.github.steveice10.packetlib.tcp.TcpPacketCodec.decode(TcpPacketCodec.java:41)
    at io.netty.handler.codec.ByteToMessageCodec$1.decode(ByteToMessageCodec.java:42)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)
    at io.netty.handler.codec.ByteToMessageCodec.channelRead(ByteToMessageCodec.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
    at io.netty.handler.codec.ByteToMessageCodec.channelRead(ByteToMessageCodec.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
    at io.netty.handler.codec.ByteToMessageCodec.channelRead(ByteToMessageCodec.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
    at io.netty.handler.codec.ByteToMessageCodec.channelRead(ByteToMessageCodec.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:647)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:582)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:499)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:461)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)

As Message.java includes my own code to display the possible errors and such, here are the relevant lines:

ghost commented 6 years ago

If I'm honest, the current Message setup needs an overhaul. It doesn't really fit in with the philosophy of being merely a protocol implantation if it's making decisions over messages, such as relegating anything it cannot parse with its own understanding as a text message. It would be better in my opinion if messages were parsed into a generic message class with hases and getters and letting the developer decide how to deal with the contents. You can provide tools such as getFullText() still, but the ultimate decider of what a message is should be up to the programmer, not the protocol.