131 lines
4.2 KiB
Python
131 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Prueft, ob Java- und Bedrock-Endpunkt eines Crossplay-Servers antworten.
|
|
|
|
python scripts/check-crossplay.py # localhost
|
|
python scripts/check-crossplay.py minecraft.djnizz.at # Live-Server
|
|
|
|
Der Java-Test spricht das Server-List-Ping-Protokoll, der Bedrock-Test einen
|
|
RakNet Unconnected Ping - also genau das, was die echten Clients tun.
|
|
"""
|
|
import json
|
|
import os
|
|
import socket
|
|
import struct
|
|
import sys
|
|
import time
|
|
|
|
HOST = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
|
|
JAVA_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 25565
|
|
BEDROCK_PORT = int(sys.argv[3]) if len(sys.argv) > 3 else 19132
|
|
TIMEOUT = 10
|
|
|
|
|
|
def _varint(n):
|
|
out = b""
|
|
while True:
|
|
b = n & 0x7F
|
|
n >>= 7
|
|
out += bytes([b | (0x80 if n else 0)])
|
|
if not n:
|
|
return out
|
|
|
|
|
|
def _read_varint(sock):
|
|
n = 0
|
|
for i in range(5):
|
|
b = sock.recv(1)
|
|
if not b:
|
|
raise EOFError("Verbindung vorzeitig geschlossen")
|
|
n |= (b[0] & 0x7F) << (7 * i)
|
|
if not b[0] & 0x80:
|
|
return n
|
|
raise ValueError("VarInt zu lang")
|
|
|
|
|
|
def java_ping(host, port):
|
|
s = socket.create_connection((host, port), timeout=TIMEOUT)
|
|
try:
|
|
host_b = host.encode()
|
|
# Handshake: Protokollversion ist beim Status-Ping beliebig
|
|
payload = (b"\x00" + _varint(772) + _varint(len(host_b)) + host_b
|
|
+ struct.pack(">H", port) + _varint(1))
|
|
s.sendall(_varint(len(payload)) + payload)
|
|
s.sendall(_varint(1) + b"\x00") # Status Request
|
|
_read_varint(s) # Paketlaenge
|
|
_read_varint(s) # Paket-ID
|
|
n = _read_varint(s) # JSON-Laenge
|
|
data = b""
|
|
while len(data) < n:
|
|
chunk = s.recv(n - len(data))
|
|
if not chunk:
|
|
break
|
|
data += chunk
|
|
return json.loads(data.decode("utf-8"))
|
|
finally:
|
|
s.close()
|
|
|
|
|
|
RAKNET_MAGIC = bytes([0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE,
|
|
0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78])
|
|
|
|
|
|
def bedrock_ping(host, port):
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
s.settimeout(TIMEOUT)
|
|
try:
|
|
pkt = (b"\x01" + struct.pack(">Q", int(time.time() * 1000))
|
|
+ RAKNET_MAGIC + os.urandom(8))
|
|
s.sendto(pkt, (host, port))
|
|
data, _ = s.recvfrom(4096)
|
|
if data[0] != 0x1C:
|
|
raise ValueError(f"unerwartete Antwort-ID 0x{data[0]:02x}")
|
|
strlen = struct.unpack(">H", data[33:35])[0]
|
|
return data[35:35 + strlen].decode("utf-8", "replace")
|
|
finally:
|
|
s.close()
|
|
|
|
|
|
def main():
|
|
print(f"Ziel: {HOST} (Java {JAVA_PORT}/TCP, Bedrock {BEDROCK_PORT}/UDP)\n")
|
|
failed = False
|
|
|
|
print("=== Java Edition (TCP) ===")
|
|
try:
|
|
j = java_ping(HOST, JAVA_PORT)
|
|
version = j.get("version", {})
|
|
desc = j.get("description")
|
|
if isinstance(desc, dict):
|
|
desc = desc.get("text", "") or "".join(
|
|
e.get("text", "") for e in desc.get("extra", []))
|
|
players = j.get("players", {})
|
|
print(f" OK {version.get('name')} (Protokoll {version.get('protocol')})")
|
|
print(f" MOTD {desc!r}")
|
|
print(f" Spieler {players.get('online')}/{players.get('max')}")
|
|
except Exception as e:
|
|
failed = True
|
|
print(f" FEHLER {type(e).__name__}: {e}")
|
|
print(" -> Security List (TCP 25565), Instanz-Firewall, Container laeuft?")
|
|
|
|
print("\n=== Bedrock Edition (UDP, RakNet) ===")
|
|
try:
|
|
fields = bedrock_ping(HOST, BEDROCK_PORT).split(";")
|
|
labels = ["Edition", "MOTD Zeile 1", "Protokoll", "Version", "Spieler",
|
|
"Max. Spieler", "Server-GUID", "MOTD Zeile 2", "Gamemode"]
|
|
print(" OK")
|
|
for label, value in zip(labels, fields):
|
|
print(f" {label:<13} {value}")
|
|
except Exception as e:
|
|
failed = True
|
|
print(f" FEHLER {type(e).__name__}: {e}")
|
|
print(" -> Security List (UDP 19132!), Geyser gestartet?")
|
|
|
|
print()
|
|
if failed:
|
|
print("Mindestens ein Endpunkt antwortet nicht.")
|
|
sys.exit(1)
|
|
print("Beide Editionen erreichbar.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|