From c5e8bdd2d976e193bc88084e925e252cadbf3a90 Mon Sep 17 00:00:00 2001 From: TimePath Date: Sun, 13 May 2018 20:11:01 +1000 Subject: [PATCH] slist: add game server query support --- misc/infrastructure/python/slist/game.py | 62 ++++++++++++++++++++++ misc/infrastructure/python/slist/main.py | 42 +++++++++++++++ misc/infrastructure/python/slist/master.py | 26 --------- 3 files changed, 104 insertions(+), 26 deletions(-) create mode 100644 misc/infrastructure/python/slist/game.py create mode 100644 misc/infrastructure/python/slist/main.py diff --git a/misc/infrastructure/python/slist/game.py b/misc/infrastructure/python/slist/game.py new file mode 100644 index 00000000..1c838f88 --- /dev/null +++ b/misc/infrastructure/python/slist/game.py @@ -0,0 +1,62 @@ +import uuid + +import attr + +from .utils import * + +HEADER = b"\xFF\xFF\xFF\xFF" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class CLGetInfo(Writable): + def encode(self) -> bytes: + return HEADER + f"getinfo {uuid.uuid4()}".encode(UTF_8) + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class SVGetInfoResponse(Readable): + gamename: str + modname: str + gameversion: int + sv_maxclients: int + clients: int + bots: int + mapname: str + hostname: str + protocol: int + qcstatus: Optional[str] + challenge: Optional[str] + d0_blind_id: Optional[str] = None + + @classmethod + @generator + def decode(cls) -> Generator[Optional["SVGetInfoResponse"], bytes, None]: + ret: Optional[SVGetInfoResponse] = None + while True: + buf: bytes + buf = yield ret + parts = buf.decode(UTF_8).split("\\")[1:] + pairs = zip(*[iter(parts)] * 2) + args = dict(pairs) + for k in ("gameversion", "sv_maxclients", "clients", "bots", "protocol"): + args[k] = int(args[k]) + ret = SVGetInfoResponse(**args) + + +SVMessage = Union[SVGetInfoResponse] + + +@generator +def sv_parse() -> Generator[Optional[SVMessage], bytes, None]: + getinfo_response = b"infoResponse\n" + ret: Optional[SVMessage] = None + while True: + buf: bytes + buf = yield ret + ret = None + if buf.startswith(HEADER): + buf = buf[len(HEADER):] + if buf.startswith(getinfo_response): + buf = buf[len(getinfo_response):] + ret = SVGetInfoResponse.decode().send(buf) + continue diff --git a/misc/infrastructure/python/slist/main.py b/misc/infrastructure/python/slist/main.py new file mode 100644 index 00000000..824eea60 --- /dev/null +++ b/misc/infrastructure/python/slist/main.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +import logging +from typing import * + +from . import game +from . import master + +logger = logging.getLogger(__name__) + +if __name__ == "__main__": + import socket + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + connection = Tuple[str, int] + connections: Dict[connection, Generator[Optional[Union[master.SVMessage, game.SVMessage]], bytes, None]] = {} + count_inforesponse = 0 + + q = master.CLGetServersExt(game="Xonotic", protocol=3) + conn = (socket.gethostbyname("dpmaster.deathmask.net"), 27950) + sock.sendto(q.encode(), conn) + connections[conn] = master.sv_parse() + while True: + logger.debug("wait") + data, addr = sock.recvfrom(1400) + logger.debug(f"recv({addr}): {data}") + msg = connections[addr].send(data) + if msg: + logger.info(f"recv({addr}): {msg}") + if isinstance(msg, master.SVGetServersExtResponse): + logger.info(f"servers: {len(msg.servers)}") + for srv in msg.servers: + try: + q_info = game.CLGetInfo() + conn = (str(srv.addr), srv.port) + sock.sendto(q_info.encode(), conn) + connections[conn] = game.sv_parse() + except socket.gaierror: + pass + if isinstance(msg, game.SVGetInfoResponse): + count_inforesponse += 1 + logger.info(f"status-{count_inforesponse}: {msg}") diff --git a/misc/infrastructure/python/slist/master.py b/misc/infrastructure/python/slist/master.py index 12bfd220..62462881 100644 --- a/misc/infrastructure/python/slist/master.py +++ b/misc/infrastructure/python/slist/master.py @@ -1,13 +1,10 @@ import ipaddress -import logging from struct import Struct import attr from .utils import * -logger = logging.getLogger(__name__) - HEADER = b"\xFF\xFF\xFF\xFF" @@ -103,26 +100,3 @@ def sv_parse() -> Generator[Optional[SVMessage], bytes, None]: if ret: getservers_ext_gen = None continue - - -if __name__ == "__main__": - import socket - - connection = Tuple[str, int] - connections: Dict[connection, Generator[Optional[SVMessage], bytes, None]] = {} - - conn = (socket.gethostbyname("dpmaster.deathmask.net"), 27950) - connections[conn] = sv_parse() - - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - q = CLGetServersExt(game="Xonotic", protocol=3) - sock.sendto(q.encode(), conn) - while True: - logger.debug("wait") - data, addr = sock.recvfrom(1400) - logger.debug(f"recv({addr}): {data}") - msg = connections[addr].send(data) - if msg: - logger.info(f"recv({addr}): {msg}") - if isinstance(msg, SVGetServersExtResponse): - logger.info(f"servers: {len(msg.servers)}") -- 2.39.2