Skip to content

Instantly share code, notes, and snippets.

@johan-boule
Last active January 21, 2026 00:59
Show Gist options
  • Select an option

  • Save johan-boule/f1d916bbc7d9e9f971e84de35e8ff45c to your computer and use it in GitHub Desktop.

Select an option

Save johan-boule/f1d916bbc7d9e9f971e84de35e8ff45c to your computer and use it in GitHub Desktop.
Warzone 2100 Lobby client - list command
#! /usr/bin/env python3
import sys, socket, struct, dataclasses
host = sys.argv[1] if len(sys.argv) > 1 else 'lobby.wz2100.net'
port = int(sys.argv[2]) if len(sys.argv) > 2 else 9990
@dataclasses.dataclass
class Game:
binary_format = '!I64si4B40s2iI2H2I40s40s157sH40s40s64s255s9I'
version: int # ui32
name: str # 64
size: int # i32
alliances: int # ui8
tech_level: int # ui8
power_level: int # ui8
bases_level: int # ui8
host: str # 40
max_players: int # i32
current_players: int # i32
game_type: int # ui32
current_spectators: int # ui16
max_spectators: int # ui16
blind_mode: int # ui32
unused1: int # ui32
host2: str # 40
host3: str # 40
unused2: str # 157
host_port: int # ui16
map_name: str # 40
host_name: str # 40
version_string: str # 64
mod_list: str # 255
version_major: int # ui32
version_minor: int # ui32
private_game: int # ui32
pure_map: int # ui32
mods: int # ui32
game_id: int # ui32
limits: int # ui32
unused3: int # ui32
unused4: int # ui32
def __post_init__(self):
def decode(binary):
return binary.split(b'\0', 1)[0].decode()
self.name = decode(self.name)
self.host = decode(self.host)
self.host2 = decode(self.host2)
self.host3 = decode(self.host3)
self.unused2 = decode(self.unused2)
self.map_name = decode(self.map_name)
self.host_name = decode(self.host_name)
self.version_string = decode(self.version_string)
self.mod_list = decode(self.mod_list)
def receive_list():
with socket.create_connection((host, port)) as s:
def send(binary_format, *data):
s.sendall(struct.pack(binary_format, *data))
def receive(binary_format):
binary_format = struct.Struct(binary_format)
received = bytearray()
size_left = binary_format.size
while size_left > 0:
received += s.recv(size_left)
size_left = binary_format.size - len(received)
return binary_format.unpack(received)
send('!5s', b'list\0')
count, = receive('!I')
games = []
for _ in range(count): games.append(Game(*receive(Game.binary_format)))
status, = receive('!I')
motd_len, = receive('!I')
motd, = receive(f'!{motd_len}s')
motd = motd.decode()
more_games, = receive('!I')
if more_games & 1 == 1: # See explanation in warzone2100/lib/netplay/netplay.cpp function NETenumerateGames
count, = receive('!I')
games = []
for _ in range(count): games.append(Game(*receive(Game.binary_format)))
return status, motd, games
def main():
status, motd, games = receive_list()
print(f'Status: {status}')
print()
print('MotD:')
print('\t' + '\n\t'.join(motd.split('\n')))
print()
print(f'Game count: {len(games)}')
print()
print(' GID Plays Spect Host IP Map Title')
for game in games:
print(f'{game.game_id:>4} {f'{game.current_players:>2}' if game.current_players != 0 else ' '}/{game.max_players:>2} {game.current_spectators:>2}/{game.max_spectators:>2} {game.host_name:15.15} {game.host:15.15} {game.map_name:15.15} {game.name}')
print()
if False:
for game in games:
print(game)
print()
if __name__ == '__main__': main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment