Packets

Inspect, cancel, and rewrite the raw packets between client and server.

Endstone raises an event for every packet that crosses the wire: PacketReceiveEvent for packets coming from a client, and PacketSendEvent for packets going to one. Both are cancellable and expose the packet's raw bytes, so you can observe, block, or rewrite traffic the higher-level API doesn't reach. The examples alias the namespace with namespace es = endstone;.

This is the lowest level of the API. getPayload() returns the raw packet data excluding the header - to read or change anything inside it you must decode and re-encode it yourself according to the Bedrock protocol (varints and all). Get it wrong and the client disconnects. Reach for a higher-level API first; use packets only when nothing else exposes what you need.

Register a packet handler like any other event. Each event gives you:

  • getPacketId() - the numeric type of the packet
  • getPayload() / setPayload() - the raw bytes after the header
  • getPlayer() - the player involved, or nullptr for packets sent before login completes
  • getSubClientId() - 0 for the primary client, 1-3 for split-screen

Observe packets

The simplest use is watching traffic. Filter by getPacketId() - logging every packet is overwhelming:

include/my_plugin.h
#include <endstone/endstone.hpp>

namespace es = endstone;

class MyPlugin : public es::Plugin {
public:
    void onEnable() override
    {
        registerEvent(&MyPlugin::onPacketSend, *this);
    }

    // packet IDs come from the Bedrock protocol; they can change between versions
    static constexpr int TEXT_PACKET = 9;

    void onPacketSend(es::PacketSendEvent &event)
    {
        if (event.getPacketId() == TEXT_PACKET) {
            auto *player = event.getPlayer();
            std::string who = player ? player->getName() : "<pre-login>";
            getLogger().info("text packet to {}: {} bytes", who, event.getPayload().size());
        }
    }
};

Drop a packet

Because the events are cancellable, setCancelled(true) stops the packet from being sent or processed:

    void onPacketReceive(es::PacketReceiveEvent &event)
    {
        if (event.getPacketId() == BLOCKED_PACKET) {
            event.setCancelled(true);
        }
    }

Rewrite a payload

To change a packet, copy getPayload(), edit it, and hand the new bytes back with setPayload(). Decoding is on you - the snippet below just shows the read/modify/write shape:

    void onPacketSend(es::PacketSendEvent &event)
    {
        if (event.getPacketId() != TEXT_PACKET) {
            return;
        }

        std::string data{event.getPayload()};  // copy the raw bytes
        // ... decode per the Bedrock protocol, modify, re-encode ...
        event.setPayload(data);
    }

getPlayer() can return nullptr for packets exchanged during the login handshake - always check it before using the player.

On this page