Initial commit: Minecraft Java & Bedrock crossplay server setup
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user