Hook points

Packets: packet.<state>.<direction>.<name>

On this page

Every packet the protocol has, in every connection state and in both directions, is a hook point. That includes packets the vanilla server never sends. Names come from the generated table PACKET_NAMES, so they always match the protocol version the server speaks:

packet.handshake.serverbound.intention
packet.login.clientbound.login_finished
packet.configuration.serverbound.client_information
packet.play.clientbound.level_chunk_with_light
packet.play.serverbound.chat_command

An id that is not in the table (a malformed client) is named by its number: packet.play.serverbound.0x7f.

Input

{"player": 7, "id": 8, "body": "0a0b0c"}

player is the entity id once the connection is in play, and null before. body is the packet body after the id, in lower-case hex.

Clientbound (server → client)

Shape
Output[[id, "hex"], ...]: the packets actually queued, which by default is the one packet
  • prepend: rewrite the packet.
  • override: answer what is written instead. [] drops the packet.
  • append: add packets after it, or replace it.

Every queued packet passes through this point, including chunk data built once and shared between players. That shared buffer is only decoded when something hooks it.

Serverbound (client → server)

Shape
Output{"handled": true, "reply": [[id, "hex"], ...]}
  • prepend: rewrite the packet (its body, or its id) before any handler reads it.
  • override: take the packet away from the server. The server's handler does not run, whatever handled says, and the reply packets are sent to the client at once.
  • append: change the answer. The reply packets of an append are sent once the server's handler for this packet has run, which is when the connection reads its next packet. An append that answers "handled": false also keeps the packet from the handler, and its reply is then sent at once.

Login, configuration and play all read through the same function (Connection::read_packet), so this covers every state.

Pitfalls

  • Bodies are the wire format of this server's protocol version (26.3 today). A packet's layout, and even its name, can change with the next game version. To read or change fields rather than bytes, use the host calls packet_decode, packet_encode and packet_schemas (packets); the guest SDK wraps them as packet_fields and packet_body. Treat packet hooks as version-bound: see stability.
  • A replacement must keep the shape. A prepend answers the whole {player, id, body}, an override or append the whole output. A wrong shape (a missing field, a body that is not hex) is refused, logged once, and the value it would have replaced is kept. A body that is hex but not a valid packet is not caught here: the client disconnects on it.
  • Clientbound answers are not hooked again. The packets a clientbound hook answers are queued as they are. Replies to a serverbound packet, and packets a plugin sends with send_packet, are queued through the clientbound hooks like any other packet.
  • Hooking a busy packet costs every connection. With nothing hooked anywhere, a packet costs one atomic read. Hooking packet.play.clientbound.level_chunk_with_light (or packet.play.*) makes the server decode every prepared chunk buffer for every player to hand it over. Hook the exact packets you need.
  • player is null before play. In handshake, status, login and configuration there is no player yet.
  • A guest that is busy is skipped. A packet hook into a Wasm guest that is already running on this thread (it made a host call that sent a packet it hooks) is skipped and logged, and the packet passes. Do not rely on seeing your own send_packet in your own hook.
  • send_packet sends only play-state clientbound packets, never start_configuration or login. A play disconnect sent that way ends the connection.

Every packet hook point is listed in the generated index. The worked example plugins/packet-demo in the plugins repository rewrites every title on its way out (the guide).

Source: crates/rustmc-server/src/packet_hooks.rs.

From docs/api/reference/hooks/packets.md in the RustMC repository at bbd81209ad.