d73c1c9537
8 topical docs (overview, data types, lifecycle, handshake, status/ping, login+encryption, configuration, version-differences) + proxy-forwarding set + 16 per-version release-line docs, sourced from minecraft.wiki, ViaVersion (source + commits), minecraft-data, node-minecraft-protocol, Velocity, BungeeCord. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
359 lines
14 KiB
Markdown
359 lines
14 KiB
Markdown
# 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.
|
||
<!-- wiki: "Notchian servers will for unknown reasons wait to receive the
|
||
following Ping Request packet for 30 seconds before timing out" -->
|
||
|
||
### 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."
|
||
<!-- VERIFY: field rename to "Intent" exact version -->
|
||
|
||
#### 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,<data>", // 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 <!-- VERIFY: exact removal version --> |
|
||
|
||
Sources: [minecraft.wiki SLP page](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping);
|
||
`enforcesSecureChat` version from wiki field table.
|
||
<!-- VERIFY: previewsChat exact add/remove versions -->
|
||
|
||
### 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`).
|
||
|
||
<!-- VERIFY: exact version 1.7 vs 1.8 for original JSON SLP introduction -->
|
||
|
||
### 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.
|
||
<!-- VERIFY: ViaVersion -1 sentinel vs client-echo behavior — wiki says -1 may cause close -->
|
||
|
||
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 `§`):
|
||
```
|
||
<MOTD>§<online>§<max>
|
||
```
|
||
|
||
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**
|
||
(` |