--- /dev/null
+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
--- /dev/null
+#!/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}")
import ipaddress
-import logging
from struct import Struct
import attr
from .utils import *
-logger = logging.getLogger(__name__)
-
HEADER = b"\xFF\xFF\xFF\xFF"
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)}")