2018-11-09 23:31:59 +00:00
|
|
|
#!/bin/env python3
|
2008-03-24 22:59:11 +00:00
|
|
|
# coding: utf-8
|
|
|
|
from socket import *
|
|
|
|
import sys
|
2011-06-27 12:23:03 +00:00
|
|
|
import threading
|
|
|
|
import time
|
2008-03-24 22:59:11 +00:00
|
|
|
|
2018-11-09 23:31:59 +00:00
|
|
|
import random
|
2011-06-27 12:23:03 +00:00
|
|
|
|
|
|
|
NUM_MASTERSERVERS = 4
|
2018-11-09 23:31:59 +00:00
|
|
|
MASTERSERVER_PORT = 8283
|
2011-06-27 12:23:03 +00:00
|
|
|
|
|
|
|
TIMEOUT = 2
|
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
# src/mastersrv/mastersrv.h
|
2018-11-09 23:31:59 +00:00
|
|
|
PACKET_GETLIST = b"\xff\xff\xff\xffreq2"
|
|
|
|
PACKET_LIST = b"\xff\xff\xff\xfflis2"
|
|
|
|
|
|
|
|
PACKET_GETINFO = b"\xff\xff\xff\xffgie3"
|
|
|
|
PACKET_INFO = b"\xff\xff\xff\xffinf3"
|
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
# see CNetBase::SendControlMsgWithToken
|
2018-11-09 23:31:59 +00:00
|
|
|
def pack_control_msg_with_token(token_srv,token_cl):
|
2018-11-21 21:33:29 +00:00
|
|
|
NET_PACKETFLAG_CONTROL = 1
|
|
|
|
NET_CTRLMSG_TOKEN = 5
|
|
|
|
NET_TOKENREQUEST_DATASIZE = 512
|
|
|
|
b = [0]*(4 + 3 + NET_TOKENREQUEST_DATASIZE)
|
|
|
|
# Header
|
2018-11-26 17:32:17 +00:00
|
|
|
b[0] = (NET_PACKETFLAG_CONTROL<<2)&0xfc
|
|
|
|
b[3] = (token_srv >> 24) & 0xff
|
|
|
|
b[4] = (token_srv >> 16) & 0xff
|
|
|
|
b[5] = (token_srv >> 8) & 0xff
|
|
|
|
b[6] = (token_srv) & 0xff
|
2018-11-21 21:33:29 +00:00
|
|
|
# Data
|
|
|
|
b[7] = NET_CTRLMSG_TOKEN
|
|
|
|
b[8] = (token_cl >> 24) & 0xff
|
|
|
|
b[9] = (token_cl >> 16) & 0xff
|
|
|
|
b[10] = (token_cl >> 8) & 0xff
|
|
|
|
b[11] = (token_cl) & 0xff
|
2018-11-09 23:31:59 +00:00
|
|
|
return bytes(b)
|
|
|
|
|
|
|
|
def unpack_control_msg_with_token(msg):
|
|
|
|
b = list(msg)
|
2018-11-26 17:32:17 +00:00
|
|
|
token_cl = (b[3] << 24) + (b[4] << 16) + (b[5] << 8) + (b[6])
|
2018-11-21 21:33:29 +00:00
|
|
|
token_srv = (b[8] << 24) + (b[9] << 16) + (b[10] << 8) + (b[11])
|
2018-11-09 23:31:59 +00:00
|
|
|
return token_cl,token_srv
|
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
# CNetBase::SendPacketConnless
|
2018-11-09 23:31:59 +00:00
|
|
|
def header_connless(token_srv, token_cl):
|
2018-11-21 21:33:29 +00:00
|
|
|
NET_PACKETFLAG_CONNLESS = 8
|
|
|
|
NET_PACKETVERSION = 1
|
|
|
|
b = [0]*9
|
2018-11-26 17:32:17 +00:00
|
|
|
# Header
|
|
|
|
b[0] = ((NET_PACKETFLAG_CONNLESS<<2)&0xfc) | (NET_PACKETVERSION&0x03)
|
|
|
|
b[1] = (token_srv >> 24) & 0xff
|
|
|
|
b[2] = (token_srv >> 16) & 0xff
|
|
|
|
b[3] = (token_srv >> 8) & 0xff
|
|
|
|
b[4] = (token_srv) & 0xff
|
|
|
|
# ResponseToken
|
2018-11-21 21:33:29 +00:00
|
|
|
b[5] = (token_cl >> 24) & 0xff
|
|
|
|
b[6] = (token_cl >> 16) & 0xff
|
|
|
|
b[7] = (token_cl >> 8) & 0xff
|
|
|
|
b[8] = (token_cl) & 0xff
|
2018-11-09 23:31:59 +00:00
|
|
|
return bytes(b)
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
# CVariableInt::Unpack from src/engine/shared/compression.cpp
|
2018-11-09 23:31:59 +00:00
|
|
|
def unpack_int(b):
|
|
|
|
l = list(b[:5])
|
|
|
|
i = 0
|
|
|
|
Sign = (l[i]>>6)&1;
|
|
|
|
res = l[i] & 0x3F;
|
|
|
|
|
|
|
|
for _ in (0,):
|
|
|
|
if not (l[i]&0x80):
|
|
|
|
break
|
|
|
|
i+=1
|
|
|
|
res |= (l[i]&(0x7F))<<(6);
|
|
|
|
|
|
|
|
if not (l[i]&0x80):
|
|
|
|
break
|
|
|
|
i+=1
|
|
|
|
res |= (l[i]&(0x7F))<<(6+7);
|
|
|
|
|
|
|
|
if not (l[i]&0x80):
|
|
|
|
break
|
|
|
|
i+=1
|
|
|
|
res |= (l[i]&(0x7F))<<(6+7+7);
|
|
|
|
|
|
|
|
if not (l[i]&0x80):
|
|
|
|
break
|
|
|
|
i+=1
|
|
|
|
res |= (l[i]&(0x7F))<<(6+7+7+7);
|
|
|
|
|
|
|
|
i += 1;
|
|
|
|
res ^= -Sign
|
|
|
|
return res, b[i:]
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
class Server_Info(threading.Thread):
|
|
|
|
|
|
|
|
def __init__(self, address):
|
|
|
|
self.address = address
|
|
|
|
self.finished = False
|
|
|
|
threading.Thread.__init__(self, target = self.run)
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
self.info = None
|
|
|
|
self.info = get_server_info(self.address)
|
|
|
|
self.finished = True
|
|
|
|
|
2011-06-27 12:23:03 +00:00
|
|
|
def get_server_info(address):
|
2008-03-24 22:59:11 +00:00
|
|
|
try:
|
2011-06-27 12:23:03 +00:00
|
|
|
sock = socket(AF_INET, SOCK_DGRAM)
|
2018-11-09 23:31:59 +00:00
|
|
|
sock.settimeout(TIMEOUT)
|
2018-11-21 21:33:29 +00:00
|
|
|
token = random.randrange(0x100000000)
|
2018-11-21 22:14:59 +00:00
|
|
|
|
|
|
|
# Token request
|
2018-11-09 23:31:59 +00:00
|
|
|
sock.sendto(pack_control_msg_with_token(-1,token),address)
|
2011-06-27 12:23:03 +00:00
|
|
|
data, addr = sock.recvfrom(1024)
|
2018-11-09 23:31:59 +00:00
|
|
|
token_cl, token_srv = unpack_control_msg_with_token(data)
|
2018-11-21 21:00:41 +00:00
|
|
|
assert token_cl == token, "Server %s send wrong token: %d (%d expected)" % (address, token_cl, token)
|
2018-11-21 22:14:59 +00:00
|
|
|
|
|
|
|
# Get info request
|
2018-11-09 23:31:59 +00:00
|
|
|
sock.sendto(header_connless(token_srv, token_cl) + PACKET_GETINFO + b'\x00', address)
|
2011-06-27 12:23:03 +00:00
|
|
|
data, addr = sock.recvfrom(1024)
|
2018-11-09 23:31:59 +00:00
|
|
|
head = header_connless(token_cl, token_srv) + PACKET_INFO + b'\x00'
|
2018-11-21 21:00:41 +00:00
|
|
|
assert data[:len(head)] == head, "Server %s info header mismatch: %r != %r (expected)" % (address, data[:len(head)], head)
|
2011-06-27 12:23:03 +00:00
|
|
|
sock.close()
|
|
|
|
|
2018-11-09 23:31:59 +00:00
|
|
|
data = data[len(head):] # skip header
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2018-11-09 23:31:59 +00:00
|
|
|
slots = data.split(b"\x00", maxsplit=5)
|
2011-06-27 12:23:03 +00:00
|
|
|
|
|
|
|
server_info = {}
|
2018-11-09 23:31:59 +00:00
|
|
|
server_info["version"] = slots[0].decode()
|
|
|
|
server_info["name"] = slots[1].decode()
|
|
|
|
server_info["hostname"] = slots[2].decode()
|
|
|
|
server_info["map"] = slots[3].decode()
|
|
|
|
server_info["gametype"] = slots[4].decode()
|
|
|
|
data = slots[5]
|
|
|
|
# these integers should fit in one byte each
|
|
|
|
server_info["flags"], server_info["skill"], server_info["num_players"], server_info["max_players"], server_info["num_clients"], server_info["max_clients"] = tuple(data[:6])
|
|
|
|
data = data[6:]
|
2011-06-27 12:23:03 +00:00
|
|
|
server_info["players"] = []
|
|
|
|
|
2018-11-09 23:31:59 +00:00
|
|
|
for i in range(server_info["num_clients"]):
|
2011-06-27 12:23:03 +00:00
|
|
|
player = {}
|
2018-11-09 23:31:59 +00:00
|
|
|
slots = data.split(b"\x00", maxsplit=2)
|
|
|
|
player["name"] = slots[0].decode()
|
|
|
|
player["clan"] = slots[1].decode()
|
|
|
|
data = slots[2]
|
|
|
|
player["country"], data = unpack_int(data)
|
|
|
|
player["score"], data = unpack_int(data)
|
|
|
|
is_player, data = unpack_int(data)
|
|
|
|
if is_player:
|
2011-06-27 12:23:03 +00:00
|
|
|
player["player"] = True
|
|
|
|
else:
|
|
|
|
player["player"] = False
|
|
|
|
server_info["players"].append(player)
|
|
|
|
|
|
|
|
return server_info
|
2018-11-21 21:00:41 +00:00
|
|
|
except AssertionError as e:
|
|
|
|
print(*e.args)
|
2018-11-21 22:14:59 +00:00
|
|
|
except OSError as e: # Timeout
|
2018-11-21 21:00:41 +00:00
|
|
|
print('> Server %s did not answer' % (address,))
|
2011-06-27 12:23:03 +00:00
|
|
|
except:
|
2018-11-09 23:31:59 +00:00
|
|
|
# import traceback
|
|
|
|
# traceback.print_exc()
|
2018-11-21 21:00:41 +00:00
|
|
|
pass
|
|
|
|
finally:
|
2011-06-27 12:23:03 +00:00
|
|
|
sock.close()
|
2018-11-21 22:14:59 +00:00
|
|
|
return None
|
2011-06-27 12:23:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Master_Server_Info(threading.Thread):
|
|
|
|
|
|
|
|
def __init__(self, address):
|
|
|
|
self.address = address
|
|
|
|
self.finished = False
|
|
|
|
threading.Thread.__init__(self, target = self.run)
|
|
|
|
|
|
|
|
def run(self):
|
2018-11-09 23:31:59 +00:00
|
|
|
self.servers = get_list(self.address)
|
2011-06-27 12:23:03 +00:00
|
|
|
self.finished = True
|
|
|
|
|
|
|
|
|
|
|
|
def get_list(address):
|
2008-03-24 22:59:11 +00:00
|
|
|
servers = []
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2008-03-24 22:59:11 +00:00
|
|
|
try:
|
2011-06-27 12:23:03 +00:00
|
|
|
sock = socket(AF_INET, SOCK_DGRAM)
|
|
|
|
sock.settimeout(TIMEOUT)
|
2018-11-09 23:31:59 +00:00
|
|
|
|
2018-11-21 21:33:29 +00:00
|
|
|
token = random.randrange(0x100000000)
|
2018-11-21 22:14:59 +00:00
|
|
|
|
|
|
|
# Token request
|
2018-11-09 23:31:59 +00:00
|
|
|
sock.sendto(pack_control_msg_with_token(-1,token),address)
|
|
|
|
data, addr = sock.recvfrom(1024)
|
|
|
|
token_cl, token_srv = unpack_control_msg_with_token(data)
|
2018-11-21 21:00:41 +00:00
|
|
|
assert token_cl == token, "Master %s send wrong token: %d (%d expected)" % (address, token_cl, token)
|
2018-11-21 22:14:59 +00:00
|
|
|
|
|
|
|
# Get list request
|
2018-11-09 23:31:59 +00:00
|
|
|
sock.sendto(header_connless(token_srv, token_cl) + PACKET_GETLIST, addr)
|
|
|
|
head = header_connless(token_cl, token_srv) + PACKET_LIST
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2008-11-16 14:01:40 +00:00
|
|
|
while 1:
|
|
|
|
data, addr = sock.recvfrom(1024)
|
2018-11-21 22:14:59 +00:00
|
|
|
# Header should keep consistent
|
2018-11-21 21:00:41 +00:00
|
|
|
assert data[:len(head)] == head, "Master %s list header mismatch: %r != %r (expected)" % (address, data[:len(head)], head)
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2018-11-09 23:31:59 +00:00
|
|
|
data = data[len(head):]
|
|
|
|
num_servers = len(data) // 18
|
2011-06-27 12:23:03 +00:00
|
|
|
|
|
|
|
for n in range(0, num_servers):
|
2018-11-21 22:14:59 +00:00
|
|
|
# IPv4
|
2018-11-09 23:31:59 +00:00
|
|
|
if data[n*18:n*18+12] == b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff":
|
|
|
|
ip = ".".join(map(str, data[n*18+12:n*18+16]))
|
2018-11-21 22:14:59 +00:00
|
|
|
# IPv6
|
2011-06-27 12:23:03 +00:00
|
|
|
else:
|
2018-11-09 23:31:59 +00:00
|
|
|
ip = ":".join(map(str, data[n*18:n*18+16]))
|
|
|
|
port = ((data[n*18+16])<<8) + data[n*18+17]
|
|
|
|
servers += [(ip, port)]
|
2008-03-24 22:59:11 +00:00
|
|
|
|
2018-11-21 21:00:41 +00:00
|
|
|
except AssertionError as e:
|
|
|
|
print(*e.args)
|
2018-11-21 22:14:59 +00:00
|
|
|
except OSError as e: # Timeout
|
2018-11-21 21:00:41 +00:00
|
|
|
if not servers:
|
|
|
|
print('> Master %s did not answer' % (address,))
|
2011-06-27 12:23:03 +00:00
|
|
|
except:
|
2018-11-09 23:31:59 +00:00
|
|
|
# import traceback
|
|
|
|
# traceback.print_exc()
|
2009-01-10 10:25:27 +00:00
|
|
|
sock.close()
|
2008-03-24 22:59:11 +00:00
|
|
|
|
2008-04-27 05:56:56 +00:00
|
|
|
return servers
|
2008-03-24 22:59:11 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
master_servers = []
|
2009-01-21 00:05:07 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
for i in range(1, NUM_MASTERSERVERS+1):
|
|
|
|
m = Master_Server_Info(("master%d.teeworlds.com"%i, MASTERSERVER_PORT))
|
|
|
|
master_servers.append(m)
|
|
|
|
m.start()
|
|
|
|
time.sleep(0.001) # avoid issues
|
2009-01-21 00:05:07 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
servers = []
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
while len(master_servers) != 0:
|
|
|
|
if master_servers[0].finished == True:
|
|
|
|
if master_servers[0].servers:
|
|
|
|
servers += master_servers[0].servers
|
|
|
|
del master_servers[0]
|
|
|
|
time.sleep(0.001) # be nice
|
2009-01-21 00:05:07 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
servers_info = []
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
print(str(len(servers)) + " servers")
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
for server in servers:
|
|
|
|
s = Server_Info(server)
|
|
|
|
servers_info.append(s)
|
|
|
|
s.start()
|
|
|
|
time.sleep(0.001) # avoid issues
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
num_players = 0
|
|
|
|
num_clients = 0
|
2011-06-27 12:23:03 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
while len(servers_info) != 0:
|
|
|
|
if servers_info[0].finished == True:
|
|
|
|
if servers_info[0].info:
|
|
|
|
num_players += servers_info[0].info["num_players"]
|
|
|
|
num_clients += servers_info[0].info["num_clients"]
|
2009-01-21 00:05:07 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
del servers_info[0]
|
2009-01-21 00:05:07 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
time.sleep(0.001) # be nice
|
2008-03-24 22:59:11 +00:00
|
|
|
|
2018-11-21 22:14:59 +00:00
|
|
|
print(str(num_players) + " players and " + str(num_clients-num_players) + " spectators")
|