waahhhh/minecraft-query

use minecraft queries in php

dev-master 2019-01-19 02:00 UTC

This package is not auto-updated.

Last update: 2020-10-09 22:54:16 UTC


README

Build & Test state: pipeline status

Supported PHP versions

  • 7.1
  • 7.2
  • 7.3

Intro

This Repository contains content of the project https://github.com/winny-/mcstat! Licence of "mcstat" available at https://github.com/winny-/mcstat/blob/master/LICENSE.

:warning: PHP (up to 7.3) is currently unable to deal with unsigned integer - negative integer can't convert to VarInt and vice versa.
See Unit-Test workarounds:

This means that no full-support of the data-type VarInt is possible.
The good news: currently all information returned from the minecraft server are not negative. :tada:

This project was developed on PHP 7.2.5. The client is written in OOP and should keep the usage simple.

Example usage:

<?php
$query = new \MinecraftQuery\ServerListPing('0.0.0.0', 25565);
$response = $query->getStatus();
var_dump($response);

Output:

class MinecraftQuery\ServerListPingResponseStatus#2 (7) {
  private $motd =>
  string(19) "My Minecraft-Server"
  private $playersMax =>
  int(10)
  private $playersOnline =>
  int(2)
  private $serverVersion =>
  string(6) "1.13.2"
  private $protocolVersion =>
  int(404)
  private $handshakeDuration =>
  double(0.13642597198486)
  private $statusDuration =>
  double(2.8133392333984E-5)
}
  • All information available by the getters of \MinecraftQuery\ServerListPingResponseStatus.

Minecraft UDP Query using UT3 Query protocol

Source: http://wiki.vg/Query

Port: Query Port (configured in server.properties)

Request and Response packet formats

Source: http://wiki.vg/Query#Base_packet_format

Request packet format

Magic 2x byte: FE FD (0xFE, 0xFD)
Type byte: 9 (handshake), 0 (stat)
Session ID int32: (should be random)
Payload (Varies): handshake payload or stats payload

Response packet format

Type byte: 9 (handshake), 0 (stat)
Session ID int32: (should be random)
Payload (Varies): handshake payload or stats payload

Handshake

Request

Magic 2x byte: FE FD
Type byte: 09
Session ID int32: 00 00 00 01
Payload: (empty)

Dump:
FE FD 09 00 00 00 01

Response

Type byte: 09
Session ID int32: 00 00 00 01
Challenge token string (Null-terminated): "9513307\0"

Token string converted as int32:
00 91 29 5B

Dump:
09 00 00 00 01 39 35 31 33 33 30 37 00 | .....9513307.

Note: all token expire every 30 seconds.
It's necessary to keep in mind, that the token can be expired.

Basic Stat

Request

Magic 2x byte: FE FD
Type byte: 00
Session ID int32: 00 00 00 01
Challenge token int32: 00 91 29 5B

Dump:
FE FD 00 00 00 00 01 00 91 29 5B

Response

Type byte: 00
Session ID int32: 00 00 00 01
MOTD string (Null-terminated): "A Minecraft Server\0"
gametype string (Null-terminated): "SMP\0"
map string (Null-terminated): "world\0"
numplayers string (Null-terminated): "2\0"
maxplayers string (Null-terminated): "20\0"
hostport short (little endian): DD 63 (25565)
hostip string (Null-terminated): "127.0.0.1\0"
Challenge token string (Null-terminated string): "9513307\0"

Dump:
00 00 00 00 01 41 20 4D 69 6E 65 63 72 61 66 74 | .....A Minecraft
20 53 65 72 76 65 72 00 53 4D 50 00 77 6F 72 6C | Server.SMP.worl
64 00 32 00 32 30 00 DD 63 31 32 37 2E 30 2E 30 | d.2.20.##127.0.0
2E 31 00 | .1.

Full Stat

Is cached every 5 seconds.

Request

Magic 2x byte: FE FD
Type byte: 00
Session ID int32: 00 00 00 01
Challenge token int32: 00 91 29 5B
Padding (Challenge token up to 8 bytes): 00 00 00 00

Dump:
FE FD 00 00 00 00 01 00 91 29 5B 00 00 00 00

Response

Type byte: 00
Session ID int32: 00 00 00 01
Padding 11x byte (meaningless): 73 70 6C 69 74 6E 75 6D 00 80 00
K,V section pairs of strings (every null-terminated): "hostname\0A Minecraft Server\0gametype\0SMP\0..."
Padding 10x byte (meaningless): 01 70 6C 61 79 65 72 5F 00 00
Players section strings (every null-terminated + double null-terminated at the end): "Player1\0Player2\0...Player5\0\0"

Dump:
00 00 00 00 01 73 70 6C 69 74 6E 75 6D 00 80 00 | .....splitnum...
68 6F 73 74 6E 61 6D 65 00 41 20 4D 69 6E 65 63 | hostname.A minec
72 61 66 74 20 53 65 72 76 65 72 00 67 61 6D 65 | raft Server.game
74 79 70 65 00 53 4D 50 00 67 61 6D 65 5F 69 64 | type.SMP.gameid
00 4D 49 4E 45 43 52 41 46 54 00 76 65 72 73 69 | .MINECRAFT.versi
6F 6E 00 42 65 74 61 20 31 2E 39 20 50 72 65 72 | on.Beta 1.9 Prer
65 6C 65 61 73 65 20 34 00 70 6C 75 67 69 6E 73 | elease 4.plugins
00 00 6D 61 70 00 77 6F 72 6C 64 00 6E 75 6D 70 | ..map.world.nump
6C 61 79 65 72 73 00 32 00 6D 61 78 70 6C 61 79 | layers.2.maxplay
65 72 73 00 32 30 00 68 6F 73 74 70 6F 72 74 00 | ers.20.hostport.
32 35 35 36 35 00 68 6F 73 74 69 70 00 31 32 37 | 25565.hostip.127
2E 30 2E 30 2E 31 00 00 01 70 6C 61 79 65 72 5F | .0.0.1...player

00 00 62 61 72 6E 65 79 67 61 6C 65 00 56 69 76 | ..barneygale.Viv
61 6C 61 68 65 6C 76 69 67 00 00 | alahelvig..

K,V section

hostname (MOTD for the current server): "A Minecraft Server"
gametype (hardcoded to SMP): "SMP"
game_id (hardcoded to MINECRAFT): "MINECRAFT"
version (Server version): "1.2.5"
plugins:
List of plugins, not used by the vanilla server, where it is an empty string (but still null terminated, see the hex dump above).
This is the format proposed by dinnerbone and currently used by bukkit:
[SERVER_MOD_NAME[: PLUGIN_NAME(; PLUGIN_NAME...)]]
'CraftBukkit on Bukkit 1.2.5-R4.0: WorldEdit 5.3; CommandBook 2.1'
map (Name of the current map): "world"
numplayers (Number of online players): "1"
maxplayers (Max number of players on the server): "20"
hostport: "25565"
hostip: "127.0.0.1"

Minecraft TCP Query using Server List Ping

Source: http://wiki.vg/Protocol#Server_List_Ping_.280xFE.29

Handshake Byte: 9
Stats Byte: 0
Port: Main Port

Handshake

Request

Packet ID: 0x00
Protocol Version: VarInt (340 for 1.12.2)
Server Address: String 255 (172.0.0.1 or Host)
Server Port: unsigned Short 1-65535
Next State: VarInt Enum (1: status, 2: login)

Response

No Response

Get Next State

Request

Packet ID: 0x00
No Fields

Response

Packet ID: 0x00
JSON Response: String (UTF-8) max. 32767

Note: Notchian servers will wait to receive the following Ping packet for 30 seconds,
before timing out and sending Response.

VarInt

http://wiki.vg/Data_types#VarInt_and_VarLong

Great thanks to https://github.com/fmoo/python-varint!

Development

Running Docker-Container

docker-compose up

Connect to the Docker-Container

docker-compose exec minecraft-query bash

Install dev dependencies

composer install

Execute Unit-Tests

composer test-unit