Refresh

From Soldat Community Wiki
Revision as of 21:35, 16 April 2018 by Nosejj (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The REFRESH is a Raw command used in Soldat to give admin clients information about the server. The 1188 byte-long REFRESH-packet is sent right after REFRESH\r\n.

Structure

  • Player
    • Names
    • Teams
    • Kills
    • Deaths
    • Pings
    • IDs
    • IPs
  • Team Scores
  • Map Name
  • Time Limit
  • Kill Limit
  • Gamestyle


Implementations

Pascal

const
PLAYERNAME_CHARS = 24;

TMsg_Refresh = packed record
Name : array[1..32] of string[PLAYERNAME_CHARS];
Team : array[1..32] of byte;
Kills : array[1..32] of word;
Deaths : array[1..32] of word;
Ping : array[1..32] of byte;
Number : array[1..32] of byte;
IP : array[1..32,1..4] of byte;
TeamScore : array[1..4] of word;
MapName : string[16];
TimeLimit, CurrentTime : integer;
KillLimit : word;
GameStyle : byte;
end;

C++

typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned long dword;

#pragma pack(push, 1)
struct RefreshPacket {
struct {
    byte iLen;
    char cName[24];
} sName[32];
byte iTeam[32];
word iKills[32];
word iDeaths[32];
byte iPing[32];
byte iNumber[32];
union {
    dword iLong;
    byte iPiece[4];
} uIp[32];
word iTeamScore[4];
struct {
    byte iLen;
    char cMap[16];
} sMap;
dword iTimeLimit;
dword iCurrentTime;
word iKillLimit;
byte iGameStyle;
};
#pragma pack(pop)

C++ Parser

#ifndef _H_SOLDATINFO_
#define _H_SOLDATINFO_

#include <vector>
#include <string>
#include <sstream>
#include <stdexcept>

class SoldatInfo
{
    public:
        // Class to hold player and spectator data
        class Client
        {
            private:
                int id;
                int team;
                int kills;
                int deaths;
                int ping;
                std::string name;
                std::string ip;

            public:
                Client(int _id, int _team, int _kills, int _deaths, int _ping, std::string _name, std::string _ip)
                    :   id(_id),
                        team(_team),
                        kills(_kills),
                        deaths(_deaths),
                        ping(_ping),
                        name(_name),
                        ip(_ip)
                {
                }

                // Returns player id
                int Id() const
                {
                    return id;
                }

                // Returns player team
                int Team() const
                {
                    return team;
                }

                // Returns player kills
                int Kills() const
                {
                    return kills;
                }

                // Returns player deaths
                int Deaths() const
                {
                    return deaths;
                }

                // Returns player ping
                int Ping() const
                {
                    return ping;
                }

                // Returns player name
                std::string Name() const
                {
                    return name;
                }

                // Returns player id
                std::string Ip() const
                {
                    return ip;
                }
        };

        // typedefs for client iterators
        typedef std::vector<Client>::iterator iterator;
        typedef std::vector<Client>::const_iterator const_iterator;
        typedef std::vector<Client>::reverse_iterator reverse_iterator;
        typedef std::vector<Client>::const_reverse_iterator const_reverse_iterator;

    private:
        // Internal typedefs
        typedef unsigned char byte;
        typedef unsigned short ushort;
        typedef unsigned long ulong;

        // Internal compile-time constants to save some typing (these will be computed by the compiler)
        enum
        {
            MAX_PLAYERS         = 32,
            NAME_LENGTH         = 24,
            MAP_LENGTH          = 16,
            PLAYERS_OFFSET      = 0,
            TEAMS_OFFSET        = PLAYERS_OFFSET + MAX_PLAYERS * (NAME_LENGTH + 1),
            KILLS_OFFSET        = TEAMS_OFFSET + MAX_PLAYERS,
            DEATHS_OFFSET       = KILLS_OFFSET + MAX_PLAYERS * 2,
            PINGS_OFFSET        = DEATHS_OFFSET + MAX_PLAYERS * 2,
            IDS_OFFSET          = PINGS_OFFSET + MAX_PLAYERS,
            IPS_OFFSET          = IDS_OFFSET + MAX_PLAYERS,
            INFO_OFFSET         = IPS_OFFSET + MAX_PLAYERS * 4
        };

        // Some private members for internal usage
        bool empty;

        int team_scores[4];
        int time_limit;
        int cur_time;
        int kill_limit;
        int game_mode;

        std::string map;

        std::vector<Client> players;
        std::vector<Client> specs;

        // Convert ushort from little endian to host byte order
        ushort ltohs(const byte *leshort) const
        {
            // In little endian byte order the least significant byte is first
            // To convert to host byte order we OR the low order byte with the
            // shifted high order byte, this works for both host byte orders
            return (leshort[0] | (leshort[1] << 8));
        }

        // Convert ulong from little endian to host byte order
        ulong ltohl(const byte *lelong) const
        {
            // The same logic applies here, we just need to do it with 4 bytes instead of 2
            return (lelong[0] | (lelong[1] << 8) | (lelong[2] << 16) | (lelong[3] << 24));
        }

        void ThrowOnEmpty() const
        {
            if (empty)
                throw std::logic_error("soldat info object initialized but empty");
        }

    public:
        // This constructor initializes an empty SoldatInfo object
        // NOTE. It is illegal to call any methods on an empty info object
        // Doing so will throw an exception of std::logic_error
        SoldatInfo() : empty(true)
        {
            // We need to tell our indexers that our object is empty as well!
            Players.base = this;
            Specs.base = this;
        }

        // This constructor takes a pointer to the buffer which contains the REFRESH packet
        // it assumes that you send it a valid REFRESH packet, eg. a packet of 1188 bytes
        SoldatInfo(const byte *packet) : empty(false)
        {
            // This will be used to keep track of our get pointer to packet
            const byte *packet_ptr;

            // Iterate through players
            for (int i = 0; i < MAX_PLAYERS; i++)
            {
                // First let's make sure this is a valid player
                packet_ptr = packet + TEAMS_OFFSET + i;
                int team = *packet_ptr;

                // The team variable will be:
                //     0 if the player is a valid player in non team based game mode
                //   1-4 if the player is a valid player in a team based game mode
                //     5 if the player is a spectator
                // This section of code is rather self explationary so I won't comment it much
                if (team > 5)
                    continue;

                // This might require a quick explanation, REFRESH packet stores names as length-prefixed string
                // So we first retrieve the length, and then build a std::string object according to the length
                packet_ptr = packet + PLAYERS_OFFSET + i * (NAME_LENGTH + 1);
                int len = *packet_ptr++;
                const char *name_ptr = reinterpret_cast<const char*>(packet_ptr);
                std::string name(name_ptr, len);

                packet_ptr = packet + KILLS_OFFSET + i * 2;
                int kills = ltohs(packet_ptr);

                packet_ptr = packet + DEATHS_OFFSET + i * 2;
                int deaths = ltohs(packet_ptr);

                packet_ptr = packet + PINGS_OFFSET + i;
                int ping = *packet_ptr;

                packet_ptr = packet + IDS_OFFSET + i;
                int id = *packet_ptr;

                // The following code might also require some briefing
                // Soldat stores IPs in network byte order (big endian), unlike its other data
                // Because most likely many people aren't interested in having an ip in long integral form
                // we'll generate a dotted version of it. If you really need the long version, you can use
                // eg. inet_addr(ip) to convert it (see MSDN for example)
                packet_ptr = packet + IPS_OFFSET + i * 4;
                std::ostringstream ip;
                ip << (int)packet_ptr[0] << "." << (int)packet_ptr[1] << "." << (int)packet_ptr[2] << "." << (int)packet_ptr[3];

                // Now we have all the necessary data, let's build our player object, and then add it to either players or specs
                Client p(id, team, kills, deaths, ping, name, ip.str());

                if (team < 5)
                    players.push_back(p);
                else
                    specs.push_back(p);
            }

            // The rest of the data is easy to retrieve, let's point our get pointer to the section after the players
            packet_ptr = packet + INFO_OFFSET;

            // Again, this code is pretty self explationary, so no comments are needed
            for (int i = 0; i < 4; i++)
            {
                team_scores[i] = ltohs(packet_ptr);
                packet_ptr += 2;
            }

            // We use the same technique to read the map name as we used to read players' names
            int len = *packet_ptr++;
            const char *name_ptr = reinterpret_cast<const char*>(packet_ptr);
            map = std::string(name_ptr, len);
            packet_ptr += MAP_LENGTH;

            // The time data in the packet are stored as ticks (1 sec = 60 ticks)
            // Since time limit can only be specified in minutes, let's convert it to mins as well
            time_limit = TicksToMins(ltohl(packet_ptr));
            packet_ptr += 4;

            // It is unlikely that anyone needs the current time as precise as ticks, but you never know
            // which is why we won't convert it; Instead we'll provide an auxiliary method for converting (just a simple div by 60)
            cur_time = ltohl(packet_ptr);
            packet_ptr += 4;

            kill_limit = ltohs(packet_ptr);
            packet_ptr += 2;

            game_mode = *packet_ptr;

            // Lastly let's initialize our client indexers
            Players.base = this;
            Players.clients = &players;

            Specs.base = this;
            Specs.clients = &specs;
        }

        // NOTE. Because our class uses standard container std::vector and standard string std::string
        // We do not need to provide our own copy constructor, assignment operator or destructor, because
        // these types provide them, so we can rely on default shallow copy -mechanism our compiler provides
        // If you want to make this type extensible, you might want to provide an empty virtual destructor though

        // The following methods are just getters to encapsulate the private data

        // Returns whether object is empty or not
        bool Empty() const
        {
            return empty;
        }

        // Returns the game mode
        int Gamemode() const
        {
            ThrowOnEmpty();
            return game_mode;
        }

        // Returns map name
        std::string Map() const
        {
            ThrowOnEmpty();
            return map;
        }

        // Returns current time in ticks
        int Timeleft() const
        {
            ThrowOnEmpty();
            return cur_time;
        }

        // Returns time limit in mins
        int Timelimit() const
        {
            ThrowOnEmpty();
            return time_limit;
        }

        // Returns the kill/score/point limit
        int Limit() const
        {
            ThrowOnEmpty();
            return kill_limit;
        }

        // Returns team score
        // Throws std::out_of_range on invalid subscript
        int Teamscore(int team) const
        {
            ThrowOnEmpty();

            if (team < 1 || team > 4)
                throw std::out_of_range("invalid team subscript");

            return team_scores[team-1];
        }

        // Returns number of players
        int NumPlayers() const
        {
            ThrowOnEmpty();
            return static_cast<int>(players.size());
        }

        // Returns number of specs
        int NumSpecs() const
        {
            ThrowOnEmpty();
            return static_cast<int>(specs.size());
        }

        // A simple hack to support array-like syntax for clients :)
        // Also provides an interface that supports STL iterators
        struct
        {
            friend class SoldatInfo;

            private:
                SoldatInfo* base;
                std::vector<Client>* clients;

            public:
                // Indexers, throws std::ouf_of_range if client is out of range
                Client& operator[](int client)
                {
                    base->ThrowOnEmpty();
                    return clients->at(client);
                }

                const Client& operator[](int client) const
                {
                    base->ThrowOnEmpty();
                    return clients->at(client);
                }

                // Iterator interface, supports iterators, const_iterators, reverse_iterators and const_reverse_iterators
                iterator begin()
                {
                    base->ThrowOnEmpty();
                    return clients->begin();
                }

                const_iterator begin() const
                {
                    base->ThrowOnEmpty();
                    return clients->begin();
                }

                iterator end()
                {
                    base->ThrowOnEmpty();
                    return clients->end();
                }

                const_iterator end() const
                {
                    base->ThrowOnEmpty();
                    return clients->end();
                }

                reverse_iterator rbegin()
                {
                    base->ThrowOnEmpty();
                    return clients->rbegin();
                }

                const_reverse_iterator rbegin() const
                {
                    base->ThrowOnEmpty();
                    return clients->rbegin();
                }

                reverse_iterator rend()
                {
                    base->ThrowOnEmpty();
                    return clients->rend();
                }

                const_reverse_iterator rend() const
                {
                    base->ThrowOnEmpty();
                    return clients->rend();
                }
        } Players, Specs;


        // The following methods are static auxiliary methods

        // Returns a string representation of a given gamemode
        // Throws std::out_of_range on invalid subscript
        static std::string GamemodeToStr(int gamemode)
        {
            static const char *gamemodes[] = {
                "Deathmatch",
                "Pointmatch",
                "Teammatch",
                "Capture the Flag",
                "Rambomatch",
                "Infiltration",
                "Hold the Flag"
            };

            if (gamemode < 0 || gamemode > 6)
                throw std::out_of_range("invalid gamemode subscript");

            return gamemodes[gamemode];
        }

        // Returns a string representation of a given team
        // Throws std::out_of_range on invalid subscript
        static std::string TeamToStr(int team)
        {
            static const char *teams[] = {
                "None",
                "Alpha",
                "Bravo",
                "Charlie",
                "Delta",
                "Spectator"
            };

            if (team < 0 || team > 4)
                throw std::out_of_range("invalid team subscript");

            return teams[team];
        }

        // Converts ticks to seconds
        static int TicksToSecs(int ticks)
        {
            return (ticks / 60);
        }

        // Converts ticks to minutes
        static int TicksToMins(int ticks)
        {
            return (TicksToSecs(ticks) / 60);
        }
};

#endif

Visual Basic

Private Type RefreshPacket
    PlayerName(31) As String
    Team(31) As Byte
    Kills(31) As Long
    Deaths(31) As Long
    Ping(31) As Byte
    Number(31) As Byte
    IP(31, 3) As Byte
    TeamScore(3) As Long
    MapName As String
    TimeLimit As Double
    CurrentTime  As Double
    KillLimit As Long
    GameStyle As Byte
End Type

Private Function parseRefreshPacket(packet() As Byte, Optional offset As Byte = 0) As RefreshPacket
    Dim i As Integer
    Dim k As Integer
    Dim j As Integer
    Dim length As Byte
    i = offset
    With parseRefreshPacket
        For k = 0 To 31
            length = packet(i)
            i = i + 1
            For j = 0 To 23
                .PlayerName(k) = .PlayerName(k) + Chr(packet(i))
                i = i + 1
            Next j
            .PlayerName(k) = Left(.PlayerName(k), length)
        Next k
        For k = 0 To 31
           .Team(k) = packet(i)
            i = i + 1
        Next k
        For k = 0 To 31
            .Kills(k) = packet(i) + (packet(i + 1) * 256&)
            i = i + 2
        Next k
        For k = 0 To 31
            .Deaths(k) = packet(i) + (packet(i + 1) * 256&)
            i = i + 2
        Next k
        For k = 0 To 31
            .Ping(k) = packet(i)
            i = i + 1
        Next k
        For k = 0 To 31
            .Number(k) = packet(i)
            i = i + 1
        Next k
        For k = 0 To 31
            For j = 0 To 3
                .IP(k, j) = packet(i)
                i = i + 1
            Next j
        Next k
        For k = 0 To 3
            .TeamScore(k) = packet(i) + (packet(i + 1) * 256&)
            i = i + 2
        Next k
        length = packet(i)
        i = i + 1
        For k = 0 To 15
            .MapName = .MapName + Chr(packet(i))
            i = i + 1
        Next k
        .MapName = Left(.MapName, length)
        For k = 0 To 3
            .TimeLimit = .TimeLimit + (packet(i) * (256& ^ k))
            i = i + 1
        Next k
        For k = 0 To 3
            .CurrentTime = .CurrentTime + (packet(i) * (256& ^ k))
            i = i + 1
        Next k
        .KillLimit = packet(i) + (packet(i + 1) * 256&)
        i = i + 2
        .GameStyle = packet(i)
    End With
End Function

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
    Static refresh As Boolean
    Dim tempstring As String
    Dim Buffer() As Byte
    dim packet as RefreshPacket
    Winsock1.GetData Buffer
    tempstring = StrConv(Buffer, vbUnicode)
    If Left(tempstring, 7) = "REFRESH" Then
        If Len(tempstring) = 9 Then
            refresh = True
        Else
            packet = parseRefreshPacket(Buffer, 9)
        End If
    ElseIf refresh And UBound(Buffer) = 1187 Then
        packet = parseRefreshPacket(Buffer)
        refresh = False
    Else
        Text1.Text = Text1.Text & tempstring
    End If
   
    Text1.SelStart = Len(Text1.Text)
    'todo: output the refresh packet data somewhere
End Sub

PHP

<?php
    function GetSoldatInfo(&$sock) {
        if (!$sock) return false;
        $info = array(
            'gamemode'    => 0,
            'teammode'    => false,
            'pointmode'    => false,
            'players'     => 0,
            'spectators'     => 0,
            'map'        => '',
            'timelimit'    => 0,
            'currenttime'    => 0,
            'timeleft'    => 0,
            'limit'        => 0,
            'player'    => array(),
            'spectator'    => array(),
            'team'        => array()
        );

        // Temporary array for players
        $players = array();
        for ($i = 0; $i < 32; $i++) {
            $players[$i] = array(
                'name'         => '',
                'ip'         => '',
                'id'         => 0,
                'kills'        => 0,
                'deaths'    => 0,
                'team'        => 0,
                'ping'        => 0
            );
        }

        // Get player names
        for ($i = 0; $i < 32; $i++) {
            $data = fread($sock, 25);
            $len = ord($data[0]);
            $players[$i]['name'] = substr($data, 1, $len);
        }

        // Get player teams
        for ($i = 0; $i < 32; $i++) {
            $data = fread($sock, 1);
            $players[$i]['team'] = ord($data);
        }

        // Get player kills
        for ($i = 0; $i < 32; $i++) {
            $data = unpack("v", fread($sock, 2));
            $kills = $data[1];
            $players[$i]['kills'] = $kills;
        }

        // Get player deaths
        for ($i = 0; $i < 32; $i++) {
            $data = unpack("v", fread($sock, 2));
            $deaths = $data[1];
            $players[$i]['deaths'] = $deaths;
        }

        // Get player pings
        for ($i = 0; $i < 32; $i++) {
            $data = fread($sock, 1);
            $players[$i]['ping'] = ord($data);
        }

        // Get player IDs
        for ($i = 0; $i < 32; $i++) {
            $data = fread($sock, 1);
            $players[$i]['id'] = ord($data);
        }

        // Get player IPs
        for ($i = 0; $i < 32; $i++) {
            $data = unpack("N", fread($sock, 4));
            $players[$i]['ip'] = long2ip($data[1]);
        }

        // Get team scores
        for ($i = 1; $i < 5; $i++) {
            $data = unpack("v", fread($sock, 2));
            $score = $data[1];
            $info['team'][$i] = $score;
        }

        // Get map name
        $data = fread($sock, 17);
        $len = ord($data[0]);
        $info['map'] = substr($data, 1, $len);

        // Get time limit & current time, and form timeleft
        $data = unpack("V", fread($sock, 4));
        $timelimit = $data[1];
        $info['timelimit'] = $timelimit;
        $data = unpack("V", fread($sock, 4));
        $currenttime = $data[1];
        $info['currenttime'] = $timelimit - $currenttime;

        $timeleft = $currenttime;
        $temp = (floor($timeleft / 60) % 60);
        $info['timeleft'] = floor($timeleft / 3600) . ':' . ($temp < 10 ? '0' : '') . $temp;

        // Get kill limit
        $data = unpack("v", fread($sock, 2));
        $limit = $data[1];
        $info['limit'] = $limit;

        // Get gamestyle
        $data = fread($sock, 1);
        $gamestyle = ord($data);
        $info['gamemode'] = $gamestyle;
        if ($gamestyle == 2 || $gamestyle == 3 || $gamestyle == 5 || $gamestyle == 6) {
            $info['teammode'] = true;
            if ($gamestyle != 2) { $info['pointmode'] = true; }
        }
        if ($gamestyle != 2) {
            if ($gamestyle != 3 && $gamestyle != 5) {
                unset($info['team'][1]);
                unset($info['team'][2]);
            }
            unset($info['team'][3]);
            unset($info['team'][4]);
        }

        foreach ($players as $player) {
            if ($player['team'] < 5) {
                $info['players']++;
                $info['player'][] = $player;
            }
            else if ($player['team'] == 5) {
                $info['spectators']++;
                $info['spectator'][] = $player;
            }
        }

        return $info;
    }
?>


Example Usage

<?php
    $s = @fsockopen("localhost", 23073);
    if ($s) {
        $info = array();
        fputs($s, "password\n");
        fputs($s, "REFRESH\n");
        while(1) {
            $data = trim(fgets($s, 1024));
            if (!$data) break;
            else if ($data == "REFRESH") {
                $info = GetSoldatInfo($s);
                break;
            }
        }
        fclose($s);

        // Make sure the REFRESH packet was received
        if ($info) {
            // You can now use the $info array to check different stuff from server, check the function for the array's elements
            // Remember that the gamemode value is a number, so to get the string do something like:
            $gamemodes = array(
                0 => 'Deathmatch',
                1 => 'Pointmatch',
                2 => 'Teammatch',
                3 => 'Capture The Flag',
                4 => 'Rambomatch',
                5 => 'Infiltration',
                6 => 'Hold The Flag'
            );
            $gamemode_str = $gamemodes[$info['gamemode']];
        }
    }
?>

Ruby

socket = TCPsocket.open(server, port)
socket.puts(password)

while data=socket.gets.strip
  if data =~ /REFRESH/
    refresh = ""
    socket.read(1188, refresh)
    info = refresh.unpack("CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24CA24C32S32S32C32C32C32C32C32C32S4CA16l2SC")      names = Array.new(32, "")
      teams = Array.new(32, 0)
      kills = Array.new(32, 0)
      deaths = Array.new(32, 0)
      pings = Array.new(32, 0)
      numbers = Array.new(32, 0)
      ips =Array.new(32, Array.new(4, 0))
      teamscore = Array.new(4, 0)
      mapname = ""
      timelimit = 0
      currenttime = 0
      killlimit = 0
      gamestyle = 0
      32.times do |i|
        names[i] = info[2*i+1].to_s[0, info[2*i]]
        teams[i] = info[i+64]
        kills[i] = info[i+96]
        deaths[i] = info[i+128]
        pings[i] = info[i+160]
        numbers[i] = info[i+192]
        4.times do |j|
          ips[i][j] = info[(i-1)*4+j+224]
        end
      end
      4.times do |i|
        teamscore[i] = info[i+352]
      end
      mapname = info[357][0,info[356]]
      timelimit = info[358]
      currenttime = info[359]
      killlimit = info[360]
      gamestyle = info[361]
  end
end


TCL

proc Soldat:ParseRefresh packet {
    if { [string length $packet] != 1188 } {
        return
    }

    binary scan $packet "@800c32s32s32c32c32I32s4ca16iisc" teams kills deaths pings numbers ips scores map_len map timelimit timeleft limit gamemode

    set players {}
    set specs {}

    for { set i 0 } { $i < 32 } { incr i } {
        set team [lindex $teams $i]
        if {$team != -1} {
            binary scan $packet "@[expr $i*25]ca24" name_len name
            set ip [lindex $ips $i]
            set ip [join [list [expr ($ip >> 24) & 0xFF] [expr ($ip >> 16) & 0xFF] [expr ($ip >> 8) & 0xFF] [expr $ip & 0xFF]] .]

            array set player [list id      [expr [lindex $numbers $i] & 0xFF] \
                name    [string range $name 0 [expr $name_len-1]] \
                team    $team \
                kills   [expr [lindex $kills $i] & 0xFFFF] \
                deaths  [expr [lindex $deaths $i] & 0xFFFF] \
                ping    [expr [lindex $pings $i] & 0xFF] \
                ip      $ip]

            if {$team < 5} {
                lappend players [array get player]
            } else {
                lappend specs [array get player]
            }
        }
    }

    for { set i 0 } { $i < 4 } { incr i } {
        set scores [lreplace $scores $i $i [expr [lindex $scores $i] & 0xFFFF]]
    }

    array set info [list gamemode    $gamemode \
        num_players [llength $players] \
        num_specs   [llength $specs] \
        map         [string range $map 0 [expr $map_len-1]] \
        scores      $scores \
        limit       $limit \
        timelimit   [expr $timelimit / 3600] \
        timeleft    [expr $timeleft / 60] \
        players     $players \
        specs       $specs]

    return [array get info]
}

D

/* This is an example usage of the refresh packet in the D programming language.
 * It's a _VERY_ simple client that connects and echo's server actions to the console.
 * The refresh packet is parsed to the "Server" struct. Use this for anything you want
 */

import std.stdio;
import std.socket;
import std.socketstream;
import std.string;
import std.thread;
import std.c.time;

class Soldatserver
{
  const int REFRESH_INT = 3;
  // Raw structure for the refresh packet
  align(1) struct RawRefresh
  {
    // Player's name and the length
    align(1) struct Playername
    {
      byte len;
      char[24] name;
    }
    
    // Player's IP
    align(1) union IP
    {
      uint ilong;
      ubyte piece[4];
    }
    
    // Map and length (needed to slice)
    align(1) struct Map
    {
      byte len;
      char[16] map;
    }

    Playername[32] name;
    ubyte[32] team;
    ushort[32] kills;
    ushort[32] deaths;
    ubyte[32] ping;
    ubyte[32] number;
    IP[32] ip;
    
    ushort teamscore[4];
    Map map;
    uint timelimit;
    uint currenttime;
    ushort killlimit;
    byte gamestyle;  
  }
  
  enum Modes { Deathmatch, Pointmatch, Teammatch, CaptureTheFlag, Rambomatch, Infiltration, HoldTheFlag};
  enum Teams { None, Alpha, Bravo, Charlie, Delta, Spectator };
  enum Stats { Name, Kills, Deaths, Team, Ping, IP, ID };
  
  // Structure to replace the original raw refresh, for better usage
  struct Server
  {
    struct Player
    {
      char[] name;
      ushort kills, deaths;
      ubyte ping, number;
      ubyte[] IP;
      ubyte ID;
      ubyte team;
    }
    
    Player[32] players;
    char[] map;
    ushort[4] scores;
    ushort killlimit;
    uint timelimit, currenttime;
    byte gamestyle;
  }
  
  
  
  char[] host, pass;
  int port;
  
  InternetHost ih;
  TcpSocket sock;
  InternetAddress ia;
  SocketStream stream;
  Thread t;
  Server server;
  
  // Constructor
  this(char[] host_a, int port_a, char[] pass_a, bool autoConnect = true)
  {
    host = host_a;
    port = port_a;
    pass = pass_a;
    t = new Thread(&refresh); // Create the Thread but don't start it yet
    
    if (autoConnect)
      connect();
  }
  
  int connect()
  {
    ih = new InternetHost;
    
    // Resolve hostname
    if (!ih.getHostByName(host))
    {
      writefln("Unable to resolve hostname for \"%s\"", host);
      return 1;
    }
    
    ia = new InternetAddress(ih.addrList[0], port);
    sock = new TcpSocket();
    
    writefln("Connecting to %s:%d...", host, port);
    
    try
    {
      sock.connect(ia); // Connect!
    }
    catch (SocketException e)
    {
      // Duh, something went horribly wrong!
      writefln("Can't connect to %s:%d... wrong Port?", host, port);
      writefln("Socket error code: %d", e.errorCode);
      return 1;
    }
    
    stream = new SocketStream(sock); // Socket stream to read from
    
    writefln("Connected!\n~~~~~~~~~~~~~~~~~~~~~~~~~");
    
    sock.send(pass ~ "\r\n"); // log in
    mainLoop(); // Jump in the main loop
    
    return 0;
  }
  
  
  protected:
    
  int refresh() // our refresh thread - could return a void, tho
  {
    while (true)
    {
      sock.send("REFRESH\r\n");
      sleep(REFRESH_INT);
    }
    return 0;
  }
  
  void mainLoop()
  {
    char[] buffer;
    byte fd;
    t.start(); // start our thread
    
    while (!stream.eof()) // keep on truckin' till we disconnect
    {
      buffer = stream.readLine();
      chomp(buffer); // remove trailing \r\n
      
      if (buffer == "REFRESH")
        update();
      else
        printf("%s\n", toStringz(buffer));
    }
  }
  
  void update()
  {
    byte fd;
    
    RawRefresh info;
    stream.read(fd); // Because readLine reads the line + \r, but leaves out \n, we must catch this first
    stream.readBlock(&info, info.sizeof);
    
    server = typeof(server).init; // reset the struct
    
    server.map = info.map.map[0..info.map.len];
    server.killlimit = info.killlimit;
    server.timelimit = info.timelimit;
    server.currenttime = info.currenttime;
    server.gamestyle = info.gamestyle;
    
    server.scores[] = info.teamscore;

    for (int i = 0; i < 32; i++)
    {
      if (info.team[i] != 255)
      {
        int place = info.number[i];
        server.players[place].name = info.name[i].name[0..info.name[i].len];
        server.players[place].kills = info.kills[i];
        server.players[place].deaths = info.deaths[i];
        server.players[place].ping = info.ping[i];
        server.players[place].ID = info.number[i];
        server.players[place].IP = info.ip[i].piece[];
        server.players[place].team = info.team[i];
      }
    }
  }
}

int main()
{
  Soldatserver ss = new Soldatserver("localhost", 23073, "yourpass");
  return 0;
}


C#

        public class RefreshPacket
        {    
            public string[] PlayerName = new string[32];
            public byte[] Team = new byte[32];
            public long[] Kills = new long[32];
            public long[] Deaths = new long[32];
            public byte[] Ping = new byte[32];
            public byte[] Number = new byte[32];
            public byte[,] IP = new byte[32, 4];
            public long[] TeamScore = new long[4];
            public string MapName;
            public long TimeLimit;
            public long CurrentTime;
            public long KillLimit;
            public byte GameStyle;
        }

        private RefreshPacket parseRefreshPacket(byte[] packet, int offset)
        {
            int i = offset;
            byte length;
            RefreshPacket tempPacket = new RefreshPacket();

            for (int k = 0; k < 32; k++)
            {
                length = packet[i];
                i++;
                for (int j = 0; j < 24; j++)
                {
                    tempPacket.PlayerName[k] += Convert.ToChar(packet[i]);
                    i++;
                }
                tempPacket.PlayerName[k] = left(tempPacket.PlayerName[k], length);
            }
            for (int k = 0; k < 32; k++)
            {
                tempPacket.Team[k] = packet[i];
                i++;
            }
            for (int k = 0; k < 32; k++)
            {
                tempPacket.Kills[k] = packet[i] + (packet[i + 1] * 256);
                i += 2;
            }
            for (int k = 0; k < 32; k++)
            {
                tempPacket.Deaths[k] = packet[i] + (packet[i + 1] * 256);
                i += 2;
            }
            for (int k = 0; k < 32; k++)
            {
                tempPacket.Ping[k] = packet[i];
                i++;
            }
            for (int k = 0; k < 32; k++)
            {
                tempPacket.Number[k] = packet[i];
                i++;
            }
            for (int k = 0; k < 32; k++)
            {
                for (int j = 0; j < 4; j++)
                {
                    tempPacket.IP[k, j] = packet[i];
                    i++;
                }
            }
            for (int k = 0; k < 4; k++)
            {
                tempPacket.TeamScore[k] = packet[i] + (packet[i + 1] * 256);
                i += 2;
            }
            length = packet[i];
            i++;
            for (int k = 0; k < 16; k++)
            {
                tempPacket.MapName += Convert.ToChar(packet[i]);
                i++;
            }
            tempPacket.MapName = left(tempPacket.MapName, length);
            for (int k = 0; k < 4; k++)
            {
                //You can use getTime() to return the time limit as a string in "MM:SS" format.
                tempPacket.TimeLimit += Convert.ToInt64(packet[i] * (Math.Pow(256, k)));
                i++;
            }
            for (int k = 0; k < 4; k++)
            {
                //You can use getTime() to return the time left as a string in "MM:SS" format.
                tempPacket.CurrentTime += Convert.ToInt64(packet[i] * (Math.Pow(256, k)));
                i++;
            }
            tempPacket.KillLimit = packet[i] + (packet[i + 1] * 256);
            i += 2;
            tempPacket.GameStyle = packet[i];
           
            
            return tempPacket;
        }


        private string left(string word, int x)
        {
            if (x <= word.Length)
                word = word.Remove(x);
            return word;
        }

        private string getTime(long ticks)
        {
            string time;
            time = getMinutes(ticks) + ':' + getSeconds(ticks);
            return time;
        }

        private string getMinutes(long ticks)
        {
            int x;
            string minutes;
            x = ((Convert.ToInt32(ticks) / 60) / 60);
            minutes = Convert.ToString(x);
            return minutes;
        }

        private string getSeconds(long ticks)
        {
            int x;
            string seconds;
            x = ((Convert.ToInt32(ticks) / 60) % 60);
            seconds = Convert.ToString(x);
            if (seconds.Length == 1)
                seconds = '0' + seconds;
            return seconds;
        }


Haskell

module Main where

{-
 - As with almost every code ever written in Haskell, this can surely be cut down to
 - less than half of it's current size, but for demonstration purpose I'll leave it 
 - as is :)
 - 
 - The Data.Binary module is not part of the current GHC distribution (GHC 6.10.3).
 - However, it's in the HackageDB on Haskell.org:
 - http://hackage.haskell.org/package/binary-0.5.0.1
 -}

import System.IO
import Control.Exception
import Control.Monad
import qualified Data.ByteString.Lazy.Char8 as B8
import Data.Binary.Get -- Sadly not part of the standart GHC distribution, see above
import Data.Word
import Data.List
import Network

-- Hardcoded login data
serverHost = "localhost"
serverPort = 23073
serverPass = "lol"

-- A team
data Team = None | Alpha | Bravo | Charlie | Delta | Spectator
          deriving (Show, Read, Eq, Ord)

-- Gamestyles
data GameStyle = Deathmatch | Pointmatch | Teammatch | CaptureTheFlag | Rambomatch  | Infiltration | HoldTheFlag
               deriving (Show, Read, Eq, Ord)

-- A single player
data Player = Player { nick   :: String,
                       kills  :: Int,
                       deaths :: Int,
                       team   :: Team,
                       id     :: Word8,
                       ping   :: Word8,
                       ip     :: [Word8] }
            deriving (Show, Read, Eq, Ord)

-- Everything combined into functional awesomeness
data Server = Server { currentMap  :: String,
                       gameStyle   :: GameStyle,
                       players     :: [Player],
                       teamScores  :: [Int],
                       killLimit   :: Int,
                       timeLimit   :: Int,
                       currentTime :: Int }
            deriving (Show, Read, Eq, Ord)
            
main :: IO ()
main = withSocketsDo $ do -- withSocketsDo initializes Winsock for Windows but is completly harmless for Unix
   state <- connect serverHost serverPort serverPass -- Using the Maybe type, did we succeed in connecting?
   case state of
      Just handle -> refresh handle >> disconnect handle -- Yes, refresh and disconnect
      Nothing     -> putStrLn $ "Could not connect to the server. Please check\n" ++ -- Bawww!
                                "your connection details and the password."

connect :: String -> Int -> String -> IO (Maybe Handle)
connect host port pass = do
   h <- try $ connectTo host (PortNumber $ fromIntegral port) :: IO (Either IOException Handle) -- try connecting
   case h of
      Left e       -> return Nothing -- Fail
      Right handle -> do
         hSetBuffering handle NoBuffering -- Disable buffering
         f <- hGetLine handle             -- Server greeting us?
         if f == "Soldat Admin Connection Established.\r" -- Yep.
            then do 
               hPutStrLn handle pass -- Send the password
               result <- hGetLine handle -- check the result
               if result == "Welcome, you are in command of the server now.\r" 
                  then do replicateM_ 2 (hGetLine handle) -- Yay, we'Re in. Get rid of those 2 lines telling us what to do
                          return $ Just handle -- return the handle into the IO Monad and stop evaluating
                  else return Nothing -- Fail again
            else return Nothing -- Triple Fail

disconnect :: Handle -> IO ()
disconnect handle = hClose handle -- Cut the wire

refresh :: Handle -> IO ()
refresh handle = do 
   hPutStrLn handle "REFRESH" -- REFRESH!
   result <- hGetLine handle  -- Refresh?
   
   if result == "REFRESH\r"
      then do refreshData <- B8.hGet handle 1188 -- REFRESH! Get it!
              print $ runGet readServerInfo refreshData -- Parse it! Print it!
      else return () -- Fail :(

readServerInfo :: Get Server
readServerInfo = do
   names       <- replicateM 32 $ parseOneString 24 -- list of names
   teams       <- replicateM 32 getWord8            -- list of teams
   kills       <- replicateM 32 getWord16le         -- list of kills
   deaths      <- replicateM 32 getWord16le         -- list of deaths...
   pings       <- replicateM 32 getWord8            -- pings of lists
   ids         <- replicateM 32 getWord8            -- of ids list
   ips         <- replicateM 32 parseOneIP          -- IPS!
   teamscores  <- replicateM 4 getWord16le          -- team scores..
   mapname     <- parseOneString 16                 -- Zeh map
   timelimit   <- getWord32le                       -- time limit
   currenttime <- getWord32le                       -- time again, just not limit
   killlimit   <- getWord16le                       -- kills limit
   gamestyle   <- getWord8                          -- gamestyle!
   
   -- Because the name is the only reliable source that can not be 0 or empty for a valid player,
   -- we're using it to count how many players we have
   let numPlayers = length $ filter (not . B8.null) names
   -- Turn it into a list am BAM!
   let players = take numPlayers $ zipWith7 zipPlayers names teams kills deaths pings ids ips
   
   -- Return all that junk
   return Server { currentMap  = B8.unpack mapname,
                   gameStyle   = numToGamestyle gamestyle,
                   players     = players,
                   teamScores  = map fromIntegral teamscores,
                   killLimit   = fromIntegral killlimit,
                   timeLimit   = fromIntegral timelimit,
                   currentTime = fromIntegral currenttime }
                   
   -- I don't feel like commenting more :(
   -- Straight forward if you know a bit of Haskell.
 where zipPlayers n t k d p di pi = 
          Player { nick    = B8.unpack n,
                   kills   = fromIntegral k,
                   deaths  = fromIntegral d,
                   team    = numToTeam t,
                   Main.id = di,
                   ping    = p,
                   ip      = pi }
                   
       parseOneString n = do 
          len <- fmap fromIntegral getWord8
          str <- getLazyByteString n
          return $ B8.take len str
          
       parseOneIP = replicateM 4 getWord8
       
       numToGamestyle n = case n of
          0 -> Deathmatch
          1 -> Pointmatch
          2 -> Teammatch
          3 -> CaptureTheFlag
          4 -> Rambomatch
          5 -> Infiltration
          6 -> HoldTheFlag
          
       numToTeam n = case n of
          0 -> None
          1 -> Alpha
          2 -> Bravo
          3 -> Charlie
          4 -> Delta
          5 -> Spectator


Perl

#!/usr/bin/perl

# Since it's perl it can most likely be reduced to half its current size, but I'll 
# leave it verbose and heavily commented since I know most of you hate perl and the least I can do is explain how it works. 
# By jrgp - 4/22/2011

use strict;
use IO::Socket; 
use Socket;

# Kill output buffering for the shell output
BEGIN { $| = 1 }

# Holders for our stuff
my @players;
my $server_info = {};
my $server_version;
my $have_version = 0;
my @team_names = qw(None Alpha Bravo Charlie Delta Spectator);
my @mode_names = qw(DM PM TM CTF RM INF HTF);

# "Feed me a stray kitten!"
sub parse_refresh {
	my $handle = shift;
	my ($i, $sbuff, $len, $buff);

	# Get player names
	for ($i = 0; $i < 32; $i++) {

		# Get length of name
		recv($handle, $sbuff, 1, '');
		$len = unpack('W', $sbuff);

		# Get name using length
		recv($handle, $buff, $len, '');

		# Skip filler
		recv($handle, $sbuff, 24 - $len, '');
		
		# Start player reference
		$players[$i] = {};

		# Save name
		$players[$i]->{'name'} = $buff;

	}

	# Get player teams
	for ($i = 0; $i < 32; $i++) {
		recv($handle, $sbuff, 1, '');
		$players[$i]->{'team'} = unpack('W', $sbuff);
	}
	
	# Get player kills
	for ($i = 0; $i < 32; $i++) {
		recv($handle, $sbuff, 2, '');
		$players[$i]->{'kills'} = unpack('S', $sbuff);
	}
	
	# Get player deaths
	for ($i = 0; $i < 32; $i++) {
		recv($handle, $sbuff, 2, '');
		$players[$i]->{'deaths'} = unpack('S', $sbuff);
	}
	
	# Get player pings
	for ($i = 0; $i < 32; $i++) {
		recv($handle, $sbuff, 1, '');
		$players[$i]->{'ping'} = unpack('C', $sbuff);
	}
	
	# Get player IDs
	for ($i = 0; $i < 32; $i++) {
		recv($handle, $sbuff, 1, '');
		$players[$i]->{'id'} = unpack('C', $sbuff);
	}

	# Get player IPs
	for ($i = 0; $i < 32; $i++) {
		recv($handle, $sbuff, 4, '');
		$players[$i]->{'ip'} = join('.', unpack('CCCC', $sbuff));
	}

	# red team score
	recv($handle, $sbuff, 2, '');
	$server_info->{'score_alpha'} = unpack('S', $sbuff);

	# blue team score
	recv($handle, $sbuff, 2, '');
	$server_info->{'score_bravo'} = unpack('S', $sbuff);

	# charlie score
	recv($handle, $sbuff, 2, '');
	$server_info->{'score_charlie'} = unpack('S', $sbuff);

	# delta score
	recv($handle, $sbuff, 2, '');
	$server_info->{'score_delta'} = unpack('S', $sbuff);
	
	# map name len
	recv($handle, $sbuff, 1, '');
	$len  = unpack('W', $sbuff);
	recv($handle, $buff, $len, '');
	$server_info->{'map'} = $buff;
	recv($handle, $sbuff, 16 - $len, '');

	# Time limit
	recv($handle, $sbuff, 4, '');
	$server_info->{'time_limit'} = unpack('L', $sbuff);
	recv($handle, $sbuff, 4, '');
	$server_info->{'current_time'} = unpack('L', $sbuff);
	
	# Kill limit
	recv($handle, $sbuff, 2, '');
	$server_info->{'kill_limit'} = unpack('S', $sbuff);

	# Mode
	recv($handle, $sbuff, 1, '');
	$server_info->{'game_mode'} = $mode_names[unpack('W', $sbuff)];


}

my ($line, $buff);

# Connect
my $sock = new IO::Socket::INET (
	PeerAddr => 'server',
	PeerPort => '69',
	Proto => 'tcp')
|| die "Couldn't connect"; 

# Login 
print $sock "myPW\n";

# Request
print $sock "REFRESH\n";

# One char at a time since perl doesn't have fgets or something
while (recv($sock, $buff, 1, '') eq '') {

	# Reached the end of a line?
	if ($buff eq "\n") {

		# The goods?
		if ($line eq "REFRESH\r") {
			parse_refresh($sock);
			last;	# Only want it once for now
		}
		elsif ($have_version == 0 && $line =~ m/^Server Version: ([0-9\.]+)/){
			$have_version = 1;
			$server_version = $1;
		}

		# Kill line buffer
		$line = "";
	}

	# No; append to current line buffer
	else {
		$line .= $buff;
	}
}


# Kill socket handle
close $sock;

# Debug shiz


print "Version: $server_version\n";
print "Map: ".$server_info->{'map'}."\n\n";
print "Mode: ".$server_info->{'game_mode'}."\n\n";
print "Alpha Score: ".$server_info->{'score_alpha'}."\n";
print "Bravo Score: ".$server_info->{'score_bravo'}."\n\n";

print "Players:\n";
foreach my $player (@players) {

	# Skip empty player slots
	next if ($player->{'name'} eq '');

	print 
		"Player ID ".$player->{'id'}." (T: ".
		$team_names[$player->{'team'}].
		" K: ".$player->{'kills'}." D: ".
		$player->{'deaths'}." C: ".
		$player->{'caps'}." P:".$player->{'ping'}." ".
		"IP: ".$player->{'ip'}.
		"): ".$player->{'name'};


	print "\n";
}

Python

#!/usr/bin/python

# jrgp - 9/1/2013


import socket
import re
from struct import unpack

def refreshParse(sock):

    print 'parsing'

    players = {}
    info = {}
    
    for i in range(0, 32):
        players[i] = {}
        nameLength = unpack('B', sock.recv(1))[0]
        players[i]['name'] = sock.recv(nameLength)
        sock.recv(24 - nameLength)

    for i in range(0, 32):
        players[i]['team'] = unpack('B', sock.recv(1))[0]

    for i in range(0, 32):
        players[i]['kills'] = unpack('H', sock.recv(2))[0]

    for i in range(0, 32):
        players[i]['deaths'] = unpack('H', sock.recv(2))[0]

    for i in range(0, 32):
        players[i]['ping'] = unpack('B', sock.recv(1))[0]

    for i in range(0, 32):
        players[i]['id'] = unpack('B', sock.recv(1))[0]

    for i in range(0, 32):
        players[i]['ip'] = '.'.join([str(v) for v in unpack('BBBB', sock.recv(4))])

    info['score'] = {
        'alpha': unpack('H', sock.recv(2))[0],
        'bravo': unpack('H', sock.recv(2))[0],
        'charlie': unpack('H', sock.recv(2))[0],
        'delta': unpack('H', sock.recv(2))[0],
    }

    mapLength = unpack('B', sock.recv(1))[0]
    info['map'] = sock.recv(mapLength)
    sock.recv(16 - mapLength)

    info['timeLimit'] = unpack('i', sock.recv(4))[0]
    info['currentTime'] = unpack('i', sock.recv(4))[0]
    info['killLimit'] = unpack('H', sock.recv(2))[0]
    info['mode'] = unpack('B', sock.recv(1))[0]

    print 'players: %s' % [v for k, v in players.iteritems() if v['name'] != '']
    print 'info: %s' % info

    info['players'] = players
    return info

pw = 'niggabit.com'
ip = 'localhost'
port = 23073

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, port))

buf = ''

while True:
    try:
        data = s.recv(1)
    except Exception as e:
        break

    if not data:
        break

    buf = buf + data

    if re.search('\r?\n$', buf):
        if buf == 'Soldat Admin Connection Established.\r\n':
            print 'connected'
            s.send('%s\n' % pw)
        elif buf == 'Welcome, you are in command of the server now.\r\n':
            print 'authed'
            s.send('REFRESH\n')
        elif buf == 'REFRESH\r\n':
            print 'refresh packet inbound'
            refreshParse(s)
        else:
            print buf

        buf = ''

s.close()


See also


External Links