2

Without the minecraft client, there is a scripted (php, python, whatever) way to ask basic information (what you see in the multiplayer menu) to a minecraft server.

Does anyone knows the few magical bytes to send on the port 25565 ?

3

4 Answers 4

4

Before the 1.7 version, a custom TCP protocol was used, and thus some escaped hexadecimal through a netcat / telnet did worked.

Today, they use JSON objects, and a more complex protocol, as implemented on the next link. On the wiki page little python script was linked : https://gist.github.com/barneygale/1209061.

I made this small implementation (freely inspired from last link) which prints the JSON object answered by the Minecraft server (localhost:25565 by default)

#!/usr/bin/env python3
import sys,json,struct,socket

def popint(s):
  acc = 0
  b = ord(s.recv(1))
  while b & 0x80:
    acc = (acc<<7)+(b&0x7f)
    b = ord(s.recv(1))
  return (acc<<7)+(b&0x7f)

def pack_varint(d):
  return bytes([(0x40*(i!=d.bit_length()//7))+((d>>(7*(i)))%128) for i in range(1+d.bit_length()//7)])

def pack_data(d):
  return pack_varint(len(d)) + d

def get_info(host,port):
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((host, port))
  s.send(pack_data(bytes(2)+pack_data(bytes(host,'utf8'))+struct.pack('>H',port)+bytes([1]))+bytes([1,0]))
  popint(s)   # Packet length
  popint(s)   # Packet ID
  l,d = popint(s),bytes()
  while len(d) < l: d += s.recv(1024)
  s.close()
  return json.loads(d.decode('utf8'))

if __name__ == '__main__':
  host = sys.argv[1] if len(sys.argv) > 1 else 'localhost'
  port = int(sys.argv[2]) if len(sys.argv) > 2 else 25565
  print(get_info(host,port))

Downloadable here https://gist.github.com/qolund/6d10c02f331ca8ee047f

Edit : minimal version, use it with python3 script.py host port

import json,sys,socket as S
h,p=sys.argv[1:]
p=int(p)
u,K,L='utf8',bytes,len
s=S.socket(2,1);s.connect((h,p))
def z():
 a,b=0,s.recv(1)[0]
 while b&128:a,b=(a<<7)+b&127,s.recv(1)[0]
 return b&127+(a<<7)
def V(d,b):return K([(64*(i!=b//7))+((d>>(7*(i)))%128)for i in range(1+b//7)])
def D(d):return V(L(d),L(d).bit_length())+d
s.send(D(K(2)+D(K(h,u))+K([p>>8,p%256,1]))+K([1,0]))
z();z();l,d=z(),K()
while L(d)<l:d+=s.recv(1024)
s.close()
print(json.loads(str(d,u)))
0
3

(Not an answer as it expands on Nope's, but too long for a comment, and I need code formatting)

Nope's code breaks for me (MC 1.10) as popint doesn't seem to decode multi-byte integers correctly; bytes are received in little endian order (lowest byte first). If a server has a large icon, the length doesn't get decoded correctly, which, in some cases, works anyway (as the code always reads 1024 bytes even when l is smaller), in others, you get an error from the JSON decoder about an unterminated string.

Replacing the popint function with this fixes the issue:

def popint(s):
  acc = 0
  shift=0
  b = ord(s.recv(1))
  while b & 0x80:
    acc = acc | ((b&0x7f)<<shift)
    shift = shift + 7
    b = ord(s.recv(1))
  return (acc)|(b<<shift)
1

For the Node.JS users out there, this can be simply achieved with my MinecraftStatusPinger library. It sends the magical bytes for you.

The magic bytes consist of several packets, first one is the handshake, which has the protocol version, server address, server port, and next state, which is always 1 to "get status" (2 would be to log in)

After you handshake, you follow up with a status request, which is just a packet with the ID 0 and no data following.

Then, the server sends you a ping response, which is a JSON representation of the server's status.

Optionally, you can then follow up with a ping request, which you can use to measure the latency.

With the javascriot library, first make sure you download it from NPM and then use this code:

import mc from "minecraftstatuspinger";

(async()=>{
    let response = await mc.lookup({host: "mc.hypixel.net"});
console.log(response)
})()

First, we import it. Then we put it into an async function, which is important, because you either need to await for the response. You can either do this with async and await, or .then. Then we log the response object

In the response object, you will get information such as the server's MOTD, thumbnail image (favicon), player count and player list, ping (latency) and server version. For a modern server, it will look something like this:

{
    "version": {
        "name": "1.19.4",
        "protocol": 762
    },
    "players": {
        "max": 100,
        "online": 5,
        "sample": [
            {
                "name": "woodendoors_7",
                "id": "5e303d20-130f-469f-92fc-6683fd988032"
            }
        ]
    },
    "description": {
        "text": "Hello world"
    },
    "favicon": "data:image/png;base64,<data>",
    "enforcesSecureChat": true,
    "previewsChat": true
}
2
  • 2
    Please disclose that the plugin is by you, otherwise this comes off as spam.
    – Timmy Jim
    Commented Aug 17, 2023 at 17:08
  • 1
    @TimmyJim good idea, I changed it to "with my library" to make it clear that it's mine Commented Aug 17, 2023 at 17:18
0

Yes, there is now a quasi-official Python class to do this; it’s written by this guy.

It can be called directly from the command line (or can be used as a Python library).

To check if your server is up, get player count, etc., is as simple as:

mcstatus minecraft.example.com query

…and the exit code will be set, so you can even integrate this into e.g. automated heartbeat systems for checking uptime.

(You can use ping instead of query to test latency, at the cost of it taking a bit longer to run.)

It, unlike the currently-accepted answer:

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .