However, there's absolutely no documentation on how it works. Considering it came out just earlier this day I'm not really surprised, but I thought I'd go fix that. Here's a tech document for you guys on how the status query works. To clarify: This means lots of tech data that probably means little to anyone else. Helpful for those of you who run websites for this kinda stuff, not helpful to Mrs. Sue who's looking for a new recipe for crafting her own bread.
The status system is incredibly simple, with only 2 possible packet types that you can send to it. It runs on UDP and thus supports multicast requests (initially), but individually replies to each one. In addition, it uses a challenge token which is explicitly designed to prevent multicast status updates, so you can request a challenge with no issues but when it comes to actually asking for the server status, you're outta luck. Keep in mind, all my diggings into this is from datamining, I am not an absolute authority over the meaning of any of these values. In addition, the protocol is very likely to change in the future. In the case that it does change, I'll update this post (and make a new one saying the changes).
It's also designed to carry a custom 4 bytes (a single int or 4 chars, whatever floats your castle) between requests so you can easily pair-up any data you receive with a request that you initially sent.
Every packet you send must be prefixed with two magic bytes (0xFE, 0xFD), followed by a packet-type (again, a byte). There are two current packet types: 0x00 and 0x09. 0x00 is responsible for actually giving you the status of the server but cannot be used unless you provide your challenge, whereas 0x09 gives you a challenge token.
To receive your challenge token, you send a request like the following:
[ 0xFE, 0xFD, // Magic bytes 0x09, // Challenge type 0x01, 0x02, 0x03, 0x04 // Your ID token ]
The ID token may be anything you like, you may even omit it entirely (which will default it to 4 NUL bytes). You'll then receive the following:
[ 0x09, // Challenge type 0x01, 0x02, 0x03, 0x04, // Your ID token (or 4 NUL) 0xDE, 0xAD, 0xBE, 0xEF // Your new challenge token (int-32) ]
Simple, right? There's really no need for me to even give you the structures, I've just always wanted to do that. The ID token is the one you provided earlier, and the challenge token is something you will need to save for all future requests. Note that the challenge token is bound to your IP and port (as opposed to the ID token), and lasts up to 30 seconds. You read that right, it's up to; it's not "your token will expire after 30 seconds", it's "every token ever" is expired every 30 seconds. This means it's entirely possible that you may get a token and use it within the same second and have it expire.
Next you'll want to actually find out the status of the server. You'll need to provide your challenge token, or you will not receive any reply. If you provide a token and it's wrong, you still won't receive a reply. With that in mind, if you're going to store your challenge token and use it later then you may want to do some kind of timeout on waiting for a reply, in case the server restarted and your token is no longer valid. It's impossible to identify between an offline server and a server that refused your challenge without any additional requests, so you'll want to try for another challenge token and if that fails then flag them as unavailable.
I said before that there's only one more packet, which is to receive the server status. However, that packet is split into two, depending on exactly how much you want to know about the server. There's no flag saying which you want, instead it's dependent on the structure of your request. Let's go for the "short and sweet" reply, which contains everything you'd want to know at a glance. Send the following:
[ 0xFE, 0xFD, // Magic bytes 0x00, // Status type 0x01, 0x02, 0x03, 0x04, // Your ID token 0xDE, 0xAD, 0xBE, 0xEF // Your challenge token ]
Nothing fancy there. The challenge must be valid and that's it. You'll receive the following:
[ 0x00, // Status type 0x01, 0x02, 0x03, 0x04, // Your ID token (that you registered your challenge with) payload // See below! ]
Payload consists of the following information. All strings are null-terminated C-style, and what I refer to as numbers are integers converted into a string in base 10 (So if you receive "30", it's actually 0x1E). Shorts are little-endian, whereas everything else is big-endian.
string // Server MoTD as displayed in the in-game server browser. string // Game type. Currently hardcoded to "SMP". string // Name of the default world. number // How many players are currently online. number // Maximum number of players this server supports. short // Port the server is listening on. string // Host that the server may receive connections on.
Ok, so that's what most people care about. What possible more could you get with the other request type? Well, quite a bit. Stuff that you didn't even think to ask for. How do we access this mystical data pool? Easy. Send the following:
[ 0xFE, 0xFD, // Magic bytes 0x00, // Status type 0x01, 0x02, 0x03, 0x04, // Your ID token 0xDE, 0xAD, 0xBE, 0xEF, // Your challenge token 0x01, 0x02, 0x03, 0x04 // Your ID token, again ]
Now, this method is actually cached every 5 seconds. Quite unusual, but certainly reasonable. You'll receive the same packet structure, but this time the payload is thus:
string // DEBUG - hardcoded "splitnum". int32 // UNKNOWN - hardcoded "128". (world-height?) int32 // UNKNOWN - hardcoded "0". string // DEBUG - hardcoded "hostname". string // Server MoTD as displayed in the in-game server browser. string // DEBUG - hardcoded "gametype". string // Game type. Currently hardcoded to "SMP". string // DEBUG - hardcoded "game_id". string // Game name. Currently (and probably always will be) hardcoded to "MINECRAFT". string // DEBUG - hardcoded "version". string // Version of the server as shown at startup. Example would be, "Beta 1.9 Prerelease 4". string // DEBUG - hardcoded "plugins". string // List of plugins(?!) that are on the server. Hardcoded to "" (but obviously will be // used by Bukkit and other server mods) string // DEBUG - hardcoded "map". string // Name of the default world. string // DEBUG - hardcoded "numplayers". number // How many players are currently online. string // DEBUG - hardcoded "maxplayers". number // Maximum number of players this server supports. string // DEBUG - hardcoded "hostport". short // Port the server is listening on. string // DEBUG - hardcoded "hostname". string // Host that the server may receive connections on. int32 // UNKNOWN - hardcoded "0". int32 // UNKNOWN - hardcoded "1". string // DEBUG - hardcoded "player_". int32 // UNKNOWN - hardcoded "0". string[numplayers] // Name of all online players. int32 // UNKNOWN - hardcoded "0".
And there we have it. If anyone has any clues as to the unknown values, please drop me an email or leave a comment. The "plugins" field is very interesting, I propose that we (Bukkit) and any other server modders use a convention like thus:
CraftBukkit 1338: WorldEdit 1.2; LogBlock 4.5; Dynmap 8.2beta
Or without any plugins but just a mod:
SomeMod 123 beta C
Where it's just in the format of:
[SERVER_MOD_NAME[: PLUGIN_NAME(; PLUGIN_NAME...)]]