Query number of players on a minecraft server
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 ?
Solution 1:
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)))
Solution 2:
(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)