Hook points
Packets: packet.<state>.<direction>.<name>
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 itsid) before any handler reads it. - override: take the packet away from the server. The server's handler does not run,
whatever
handledsays, and thereplypackets are sent to the client at once. - append: change the answer. The
replypackets 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": falsealso keeps the packet from the handler, and itsreplyis 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_encodeandpacket_schemas(packets); the guest SDK wraps them aspacket_fieldsandpacket_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(orpacket.play.*) makes the server decode every prepared chunk buffer for every player to hand it over. Hook the exact packets you need. playerisnullbefore 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_packetin your own hook. send_packetsends only play-state clientbound packets, neverstart_configurationorlogin. A playdisconnectsent 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.