Chivalry Server Query



  • Hi,<br /><br />today I’ve set up my first Chivalry server (running on Linux using Wine) and afterwards I was looking for a way to monitor it. Either my Google-Fu was weak today or there doesn’t seem to be a decent solution, so I read up on the query protocol (which is NOT the UT3 protocol, but the Source engine one!) and wrote a quick and dirty Python script.<br /><br />Here’s the output:<br /><br /><pre>
    Therapy Sessions - Team Objective only 24/7 - aocto-stoneshill_p - 24/24

    Players:
    The Kraut 100 (playing for 154 min.)
    Mohasz 70 (playing for 95 min.)
    ?DWN.009?Dark20XX 65 (playing for 42 min.)
    Mantis 0 (playing for 41 min.)
    Wizarda 40 (playing for 41 min.)
    H4nd 110 (playing for 38 min.)
    EK ¦ Artemius 170 (playing for 32 min.)
    lec 90 (playing for 26 min.)
    Pesten 140 (playing for 24 min.)
    Normand 25 (playing for 24 min.)
    Christaras 60 (playing for 22 min.)
    sirmic 25 (playing for 22 min.)
    Gretok 35 (playing for 20 min.)
    Kratos 30 (playing for 18 min.)
    [RNG] ??? (TATTA) 45 (playing for 18 min.)
    Player395 0 (playing for 18 min.)
    nonnodm 90 (playing for 18 min.)
    KillAáaNo_OMerCZ 120 (playing for 11 min.)
    andytribo 115 (playing for 7 min.)
    Gnime[Bouncing Forks!] 50 (playing for 6 min.)
    BoB 85 (playing for 6 min.)
    Jesus 5 (playing for 4 min.)
    Blüna 10 (playing for 3 min.)
    Neckmail 0 (playing for 1 min.)
    </pre><br />And here’s the code:<br /><br /><pre>
    #!/usr/bin/python

    from socket import *
    import binascii
    import struct

    All socket calls timeout within 10 seconds,

    no error handling takes places so this will

    throw an exception

    setdefaulttimeout(10)

    def extract_string(data):
    s = “”
    i = 0
    while data[i] != “\x00”:
    s += data[i]
    i += 1

    i += 1
    
    return (s,data[i])
    

    def extract_byte(data):
    return (ord(data[0]),data[1:])

    def extract_short(data):
    return (struct.unpack("<H",data[:2])[0],data[2:])

    def extract_int(data):
    return (struct.unpack("<I",data[:4])[0],data[4:])

    def extract_float(data):
    return (struct.unpack("<f",data[:4])[0],data[4:])

    def parse_info_response(data):
    (unused,data) = extract_int(data)
    (header,data) = extract_byte(data)
    (protocol,data) = extract_byte(data)
    (name,data) = extract_string(data)
    (map,data) = extract_string(data)
    (folder,data) = extract_string(data)
    (game,data) = extract_string(data)
    (id,data) = extract_short(data)
    (players,data) = extract_byte(data)
    (max_players,data) = extract_byte(data)
    (bots,data) = extract_byte(data)
    (server_type,data) = extract_byte(data)
    (env,data) = extract_byte(data)
    (visible,data) = extract_byte(data)
    (vac,data) = extract_byte(data)

    print "%s - %s - %d/%d" % (name,map,players,max_players)
    

    def parse_player_response(data):
    (unused,data) = extract_int(data)
    (header,data) = extract_byte(data)
    (players,data) = extract_byte(data)

    for i in range(players):
        (index,data)    = extract_byte(data)
        (name,data)     = extract_string(data)
        (score,data)    = extract_int(data)
        (dur,data)      = extract_float(data)
    
        print "%-30s %d (playing for %.00f min.)" % (name,score,dur/60.0)
    

    def query_players(s,target):
    # First, we need to get a challenge ID
    query = “\xFF\xFF\xFF\xFFU\xFF\xFF\xFF\xFF”
    s.sendto(query,target)

    data, addr = s.recvfrom(1024)
    chall_id = data[5:]
    
    # Now we can query the players
    query= "\xFF\xFF\xFF\xFFU" + chall_id
    s.sendto(query,target)
    
    data, addr = s.recvfrom(1024)
    
    print "\nPlayers:"
    parse_player_response(data)
    

    def send_ping(s,target):
    # Chivalry doesn’t seem to answer A2A_PING requests
    # According to the Valve server query wiki, this is expected
    ping = “\xFF\xFF\xFF\xFFi”
    s.sendto(ping,target)
    data, addr = s.recvfrom(1024)
    if data == “\xFF\xFF\xFF\xFF\x6A\x00”:
    return True
    return False

    def get_info(s,target):
    # Query for server info
    query = “\xFF\xFF\xFF\xFFTSource Engine Query\x00”
    s.sendto(query,target)

    data, addr = s.recvfrom(1024)
    
    parse_info_response(data)
    

    def query_server(target):
    s = socket(AF_INET,SOCK_DGRAM,0)

    get_info(s,target)
    query_players(s,target)
    

    t = (“gotham”,7778)
    query_server(t)

    Hope it helps someone else, too. It’s a very simple protocol. You can read up on it here:

    [url]https://developer.valvesoftware.com/wiki/Server_queries[/url]

    I’ve done no error checking at all so you may want to add exception handlers for socket timeouts, and handle receiving faulty packets, and maybe multi-packet replies. See the above link for more info. I might write a proper Python lib for it soon.

    Cheers,
    therapy[/i][/i][/i]</pre>


Log in to reply