• EN
  • FR
  • Kagami


    Introduction

    Minecraft is one of the most popular game of all time. One thing that massively help its growth was its modding abilities. A mod is an extension of the game code that is added by the user. However, with the popularity of PvP games, a lot of people started creating mods that would give them an unfair advantages over other players, and starting one of its biggest industry, cheating softwares.

    After the failure of server side anticheats, some of the most influential actors tried creating their own Minecraft clients, featuring client side anticheats. The tradeoff was that users could no longer use their own mods, they would have to use whatever the company’s client provide.

    Once again, this was a massive failure. People did use those clients, mostly because it was easier than having to look for dozens of mods and having to remember how to operate each of them, but the anticheat part of it was expensive and in the end, it did not work well. Currently, all of those anticheats have been removed from their client due to how uneffective they were.

    However, for most people, nothing changed. They were here for comfort, ease of use, and for some of them, they had cosmetics and other things they paid for. So what happens when a user wants to customize something ? Well they can’t. But do you know who can ? Cheaters, they had the answer long ago, and never had to care about this. Some devs decided to go this way and create a mod loader using injection.

    I decided to pursue another direction, I knew about some projects using proxies as a way to interact with the game. All of them seemed to be based on a NodeJS lib called prismarine-proxy.

    Why a proxy ?

    This comes from the idea that every data you see on screen has been sent by the server, and in the opposite direction, everything the server knows about you has been sent by your client. By creating a proxy between the client and the server, you gain access to all the data that is being streamed.

    Why Kagami ?

    My biggest wall when building apps around Minecraft was the amount of data you can gather from outside. Last year I built Flow, an automation tool that could automatically update Twitch commands based on user game configuration. Flow, like many similar tools make use of the logs output of the game, which contains chat and other debugging data. Logs are very poor if you compare what they have and the amount of data the game has. With more data, you can build more things.

    I love Rust, but I find it hard to justify building with it so I wanted a project that would highly benefit from it. Also I wanted a challenge, something that I knew would make me learn a lot of new concepts. This approach is also very not known enough to I wanted a way for people to create things easily.

    The beginning

    Since the game uses the Transmission Control Protocol (TCP) as its way to communicate, creating a basic proxy was just a matter of creating two TCP streams, one that would talk with the client and one that would talk with the server. Then each buffer would be mirrored in the other side and voila, you got yourself a proxy.

    Buffers, yay

    So now we should have access to every data that is being sent to either the client or the server, right ?

    Well yes, but actually no

    Technically we do, but not in a format that humans can read. If a client knows what it should receive and vice versa, both machines do not need to label anything to understand each other. Each in-game action is a packet, a list of bytes that represents the action, and each byte is part of some data structure that only the game knows about.

    This saves bandwith since you could store a number up to 255 in a single byte for example without wasting 10 more bytes to give it a name. Our buffers both contains a random amount of bytes. This was not an issue for a basic proxy since we could just copy the same bytes over but if we wanted to edit a packet for example, we would need to understand the data we transfer.

    Deserializing Packets

    A basic packet follow simple rules, its first byte represents the packet length. The following byte is the ID of the packet, this tells the client how it should deserialize the next bytes. For example a Chat packet coming from the client has an ID of 0x01 and only contains a UTF-8 String preceded by the string length.

    One of the biggest challenge I faced was the amount of data structures used in those packets. Single bytes and boolean are easy to read, but data structures that were made by the game dev were very confusing.

    With this done, we can read the content of packets in a format that is readable for humans. This was enough for people to start gathering more data than what they could using other methods.

    Demo :

    Interface

    I started working on a simple interface for the lib. A dev can easily add an handler by calling the add_read_handler and giving it a closure that takes the corresponding packet as argument. We keep two types of handlers, one for reading operations and one for writing operations. This way we can asynchronously execute callbacks that do not modify the packet, after it was sent to the server and therefore, without adding any delay.

    Another benefit from those callbacks is that we do not need to deserialize every packets. Once we get the ID of a packet, we can check if it has any handler and only if it does, deserialize the whole packet.

    mc.handlers.add_read_handler(|packet: &client::WindowClick| {
        Box::pin(async move { println!("Slot clicked: {:#?}", packet.item) })
    });
    

    Serializing Packets

    Writing operations are bit more complicated, because you must do it before the packet is sent. You also need a way to tell the proxy what you want it to do with the packet. For example, you want to create a custom command that when sent, triggers some action on the user system. You would need to deserialize the chat packet, detect if the message sent is the command we wanted, and then decide not to sent this specific packet to the server.

    This is not the only action that we need. I added an action to explicitly define if a packet was modified or not. If a packet was not altered in any way, then the proxy can simply write the raw bytes instead of serializing the packet. (which is also way easier for packets that are not known enough to be fully re serialized)

    mc.handlers.add_write_handler(|packet: &mut client::Chat| {
        Box::pin(async move {
            // Edit the message if it contains "foo"
            if packet.message.contains("foo") {
                packet.message = "I never said that!".into();
                return PacketAction::Edit;
            }
    
            // Do not send the packet to the server if it contains "bar"
            else if packet.message.contains("bar") {
                return PacketAction::Filter;
            }
    
            // Send the packet to the server
            PacketAction::Default
        })
    });
    

    Sending a proxy generated packet

    Editing packets is nice, but if we want to achieve interactivity, we need to be able to communicate in every way possible, this means that the proxy should be able to talk with the client and the server with its own packets.

    For example, if you wanted to add a feedback to the custom command we talked about. You’d need to be able to send a server chat packet to the client. Since we added support to serialize packets, this is easy enough to generate a packet, but to write the packet into an actual buffer, we need an access to it, and safe enough to be use in an asynchronous environment. I used inter channels communication to achieve this. The results were promising, when sending packets as fast as possible to the client (thousands/s), the client would struggle while the proxy was still perfectly fine.

    I haven’t added this interface to the closure handlers but it shouldn’t be much of a challenge. The channels can be sent anywhere without issue, you can even run asynchronous tasks inside with no issue.

    Plans

    This is a fun project, I’m not far from being able to build things with. Which I certainly will in the future, but those are not what I decided to make Kagami. I want Kagami to be the best solution a dev can use to do this. Performance is one aspect of it, but the DX is what I’m going for. Callbacks were the first step, the custom command handler will be the second. Then I will add custom GUI support through inventory.

    There will be a repo for it, but for now I’m focusing on making something that works, then I’ll focus on making it readable for the first release.