# 04 — Server List Ping (SLP) How the multiplayer server list fetches a server's MOTD, version, and player count. Covers modern SLP (1.7+) and the three legacy `0xFE` variants. --- ## 1. Modern SLP (1.7+) ### 1.1 Packet sequence ```mermaid sequenceDiagram participant C as Client participant S as Server C->>S: TCP connect (port 25565) C->>S: Handshake (0x00, nextState=1) C->>S: Status Request (0x00, no fields) S-->>C: Status Response (0x00, JSON string) C->>S: Ping Request (0x01, Long timestamp) S-->>C: Pong Response (0x01, echo Long) S->>C: (server closes connection) ``` The client **may** close after receiving the Status Response without sending Ping (e.g. when only scraping the MOTD). Notchian servers will wait up to ~30 s for the Ping Request before timing out if the client leaves the connection open. ### 1.2 Packet definitions All packets use the standard [Minecraft packet framing](https://minecraft.wiki/w/Java_Edition_protocol) (VarInt length prefix + VarInt packet ID + fields). #### Handshaking state → Status | Direction | ID | Name | Fields | |-----------|------|------------|--------| | C→S | 0x00 | Handshake | `protocolVersion` VarInt, `serverAddress` String(255), `serverPort` Unsigned Short, `nextState` VarInt (1=Status) | Source: [minecraft.wiki/w/Java_Edition_protocol/Packets](https://minecraft.wiki/w/Java_Edition_protocol/Packets) The wiki uses **Intent** as the field name (as of 1.20.5+); older docs call it `nextState`. The value `1` always means "go to Status state." #### Status state | Direction | ID | Name | Fields | |-----------|------|-----------------|--------| | C→S | 0x00 | Status Request | *(none)* | | S→C | 0x00 | Status Response | `jsonResponse` String(32767) | | C→S | 0x01 | Ping Request | `timestamp` Long | | S→C | 0x01 | Pong Response | `timestamp` Long (echoed) | Source: [minecraft.wiki/w/Java_Edition_protocol/Packets](https://minecraft.wiki/w/Java_Edition_protocol/Packets) The Ping payload is a `Long` (8 bytes, signed, big-endian) — typically a millisecond epoch timestamp the client uses to measure round-trip latency. `node-minecraft-protocol` sends `[0, 0]` (two 32-bit zero words that compose one 64-bit zero) and measures wall-clock delta separately (`src/ping.js:55`, `src/server/ping.js:62`). ### 1.3 The Status Response JSON The server writes the entire status as a single JSON string inside the Status Response packet. #### Annotated example ```json { "version": { "name": "1.21.8", // free string — shown in client UI on version mismatch "protocol": 772 // actual protocol number client compares }, "players": { "max": 20, // max player slots (can be overridden freely) "online": 1, // current player count "sample": [ // optional list shown in hover tooltip { "name": "thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20" // must be well-formed UUID } ] }, "description": { // the MOTD — see §1.4 for evolution "text": "Hello, world!" }, "favicon": "data:image/png;base64,", // 64×64 PNG, no newlines (1.13+) "enforcesSecureChat": false // added in 1.19.1 } ``` Source: [minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping) — example JSON `node-minecraft-protocol` server-side builds this object at `src/server/ping.js:31-39`: ```js const response = { version: responseVersion, players: { max: server.maxPlayers, online: server.playerCount, sample: [] }, description: server.motdMsg ?? { text: server.motd }, favicon: server.favicon } ``` #### Field reference | Field | Type | Required | Notes | |-------|------|----------|-------| | `version.name` | String | No (1.20+: omission shows "Old") | Display label — **not** version-checked by client | | `version.protocol` | Integer | No | Client compares against its own; mismatch → red/orange label | | `players.max` | Integer | No | `???` shown if `players` omitted; max value 2³¹−1 | | `players.online` | Integer | No | Same | | `players.sample` | Array | No | Objects with `name` (String) + `id` (UUID String) | | `description` | Text Component | No | See §1.4 | | `favicon` | String | No | `data:image/png;base64,…`; exactly 64×64 px; no newlines since 1.13 | | `enforcesSecureChat` | Boolean | No | Added **1.19.1** — client shows warning if server value differs from expectation | | `previewsChat` | Boolean | No | Added alongside `enforcesSecureChat`; deprecated/removed in later 1.19.x releases | Sources: [minecraft.wiki SLP page](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping); `enforcesSecureChat` version from wiki field table. ### 1.4 Description field — chat component evolution The `description` field has gone through three forms: | Era | Format | Example | |-----|--------|---------| | 1.7–1.8 | Plain string with `§` codes | `"§aA §bcoloured §cMOTD"` | | 1.9–1.15 | JSON Text Component object, but vanilla still embeds `§` codes inside `{"text":"…"}` | `{"text":"§aHello"}` | | 1.16+ | Full structured Text Component (Spigot/Paper); vanilla still uses `§`-embedded strings | `{"text":"Hello","color":"green"}` | The wiki notes: "Notchian servers embed section-sign-based codes within the text value, while third-party servers such as Spigot and Paper will return full components." Both forms are valid — clients accept either. `node-minecraft-protocol`'s server prefers a structured object (`server.motdMsg`) but falls back to `{ text: server.motd }` with a plain string (`src/server/ping.js:38`). ### 1.5 Version spoofing `version.name` is a **free string** — the server can write anything here. `version.protocol` is the **number** the client actually compares against its own. Common patterns: | Scenario | `version.name` | `version.protocol` | |----------|----------------|--------------------| | Matching client | `"1.21.8"` | `772` | | Outdated client (server newer) | `"1.21.8"` | `772` — client shows "Outdated client!" | | Outdated server (client newer) | `"1.20.4"` | `765` — client shows "Outdated server!" | | ViaVersion (accepts many) | `"Requires 1.21+ (1.7-1.20.6 supported)"` | client's own protocol number | | Discovery ping (unknown client ver) | `"Paper 1.21"` | `-1` (sentinel) | ViaVersion sets `version.protocol` to the **connecting client's** protocol number so the client always sees a match, then handles translation internally. The wiki notes that for 1.6 legacy responses from a 1.7+ server, the protocol version in the response is always `127` (an incompatibility sentinel). --- ## 2. Legacy `0xFE` Server List Ping Pre-1.7 clients used a simple kick-packet hack. There are three variants, distinguished by how much data the client sends. ### 2.1 Overview | Client version | Client sends | Response separator | |----------------|--------------|--------------------| | Beta 1.8 – 1.3.2 | `FE` | `§` (section sign, U+00A7) | | 1.4 – 1.5.2 | `FE 01` | `\0` (null, U+0000) | | 1.6.x | `FE 01 FA …` (plugin message) | `\0` (null) | All responses are sent as a **kick packet** (`0xFF`) containing a UTF-16BE encoded string. ### 2.2 Variant A — Beta 1.8 through 1.3.2 **Client → Server:** single byte `0xFE` **Server → Client** (kick packet): ``` FF packet ID XX XX UInt16BE: number of UTF-16 code units in the response string [UTF-16BE data] ``` Response string format (fields joined with `§`): ``` §§ ``` Example: `A Minecraft Server§5§20` `node-minecraft-protocol` handles this as `payload === undefined` (neither 0 nor 1), falling into the `else` branch at `src/server/ping.js:73`: ```js sendPingResponse([server.motd, server.playerCount.toString(), server.maxPlayers.toString()].join('\xa7')) ``` (`\xa7` = `§`) ### 2.3 Variant B — 1.4.2 through 1.5.2 **Client → Server:** two bytes `FE 01` **Server → Client:** same kick packet format, but fields joined with **null** (``), and a `§1` prefix to signal the extended format: ``` §1 ``` Where `` is U+0000 (null character). `node-minecraft-protocol` handles this at `src/server/ping.js:68-71` (`packet.payload === 1`): ```js sendPingResponse('\xa7' + [pingVersion, server.mcversion.version, server.mcversion.minecraftVersion, server.motd, server.playerCount.toString(), server.maxPlayers.toString()].join('\0')) ``` Note: `pingVersion` is the hardcoded constant `1` (the ping format version, not the Minecraft protocol version). ### 2.4 Variant C — 1.6.x **Client → Server** (full hex): ``` FE packet ID 01 payload FA plugin message packet ID 00 0B string length: 11 (UTF-16BE code units) 00 4D 00 43 00 7C "MC|PingHost" in UTF-16BE 00 50 00 69 00 6E 00 67 00 48 00 6F 00 73 00 74 XX XX rest-of-data byte length = 7 + len(hostname bytes) XX client's protocol version (single byte) XX XX hostname length in UTF-16BE code units [hostname in UTF-16BE] XX XX XX XX server port (big-endian int32) ``` The wiki notes: "All Notchian servers only care about the first 3 bytes (`FE 01 FA`)." The hostname/port data is informational and typically ignored. **Server → Client** — same kick packet, same null-delimited format as variant B: ``` FF XX XX UInt16BE length 00 A7 00 31 00 00 U+00A7 "§" + U+0031 "1" + U+0000 null separator [UTF-16BE: protocolversionMOTDonlinemax] ``` For a 1.7+ server responding to a 1.6 client, `protocol` is always `127` (incompatibility sentinel). Source: [minecraft.wiki SLP — Legacy ping](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping) ### 2.5 Response wire encoding All legacy responses share the same packet structure (`src/server/ping.js:77-93`): ```js function sendPingResponse (responseString) { function utf16be (s) { return endianToggle(Buffer.from(s, 'utf16le'), 16) // swap to big-endian } const responseBuffer = utf16be(responseString) const length = responseString.length // char count, not byte count const lengthBuffer = Buffer.alloc(2) lengthBuffer.writeUInt16BE(length) const raw = Buffer.concat([Buffer.from('ff', 'hex'), lengthBuffer, responseBuffer]) client.socket.write(raw) // bypasses packet framer } ``` The response is written **directly to the socket** (not through the packet framer) because the length field counts UTF-16 code units, not bytes, and the normal framer would prepend a VarInt length which legacy clients don't expect. ### 2.6 Detection on the server A 1.7+ server recognises a legacy ping by the `0xFE` first byte, which is not a valid VarInt-prefixed packet. `node-minecraft-protocol` maps this to the `legacy_server_list_ping` event and distinguishes variants by `packet.payload`: | `packet.payload` | Variant | |------------------|---------| | `undefined` | Beta 1.8–1.3.2 (`FE` only) | | `1` | 1.4–1.5.2 (`FE 01`) or 1.6.x (`FE 01 FA …`) | Source: `src/server/ping.js:67-93` --- ## 3. Protocol number reference A selection of protocol numbers for version context: | Release | Protocol | |---------|----------| | 1.7.10 | 5 | | 1.8.9 | 47 | | 1.12.2 | 340 | | 1.16.5 | 754 | | 1.19.1 | 760 | | 1.20.4 | 765 | | 1.21.1 | 767 | | 1.21.8 | 772 (example from wiki) | Current protocol: **775** (as of the Packets page at time of writing). Source: [minecraft.wiki/w/Java_Edition_protocol/Packets](https://minecraft.wiki/w/Java_Edition_protocol/Packets) --- ## 4. Sources | Source | Used for | |--------|----------| | [minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping) | JSON field table, legacy ping bytes, response formats, favicon 1.13 note, §1 sentinel | | [minecraft.wiki/w/Java_Edition_protocol/Packets](https://minecraft.wiki/w/Java_Edition_protocol/Packets) | Packet IDs, field types, Handshake Intent field name | | `node-minecraft-protocol` `src/ping.js` | Client-side modern SLP flow (lines 41-68) | | `node-minecraft-protocol` `src/server/ping.js` | Server-side response construction (lines 1-94) | --- ## 5. VERIFY flags (open questions) 1. `` **`enforcesSecureChat` exact version** — wiki does not specify version inline; attributed to 1.19.1 based on the 1.19.1 secure-chat feature rollout. Confirm against `wiki/w/Java_Edition_1.19.1`. 2. `` **`previewsChat` add/remove versions** — present in 1.19.1 initial rollout, removed or no-op'd by 1.19.3. Confirm. 3. `` **`nextState` → `Intent` field rename version** — wiki Packets page uses "Intent" as of current; confirm when the rename happened (cosmetic doc change, wire format unchanged). 4. `` **ViaVersion protocol echo vs −1** — confirm ViaVersion echoes the connecting client's protocol number (rather than using a fixed sentinel). 5. `` **Protocol 772 vs 775** — wiki SLP example JSON shows `"protocol": 772` but Packets page header shows current 775; resolve to exact MC versions. 6. `` **Description plain-string support in 1.7** — confirm that `"description": "plain string"` (no wrapping object) was accepted by the official client in 1.7.x.