Refreshx

From Soldat Community Wiki
Revision as of 00:51, 30 September 2014 by ExHunter (talk | contribs) (Implementations)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The REFRESHX is a Raw command used in Soldat to give admin clients information about the server. It contains more information than REFRESH. The 1992 (as of 2.7.0e) byte-long REFRESHX-packet is sent right after REFRESHX\r\n.


Attention: The structure can change when a new version of Soldat comes.

Structure

  • Player (1888 Bytes)
    • Names (32*25 Bytes; first Byte (unsigned char) indicates length of the nick)
    • HWIDs (32*12 Bytes)
    • Teams (32*1 Byte; 0=DM Team, 1..4=Alpha..Delta, 5=Spectator)
    • Kills (32*2 Bytes; unsigned short)
    • Caps (32*1 Byte; unsigned char)
    • Deaths (32*2 Bytes; unsigned short)
    • Pings (32*4 Bytes; signed long)
    • IDs (32*1 Byte; unsigned char)
    • IPs (32*4 Bytes; each byte can be decoded as unsigned char then put together in the given order to a 0.0.0.0 format; or unpack as unsigned long (big endian) and use long2ip())
    • X Locations (32*4 Bytes; float)
    • Y Locations (32*4 Bytes; float)
  • Red Flag X Location (4 Bytes; float)
  • Red Flag Y Location (4 Bytes; float)
  • Blue Flag X Location (4 Bytes; float)
  • Blue Flag Y Location (4 Bytes; float)
  • Team Scores (4*2 Bytes; unsigned short)
  • Map Name (17 Bytes; first Byte (unsigned char) indicates length)
  • Time Limit (4 Bytes; unsigned long; in Ticks)
  • Time Left (4 Bytes; unsigned long; in Ticks)
  • Cap/Kill Limit (2 Bytes; unsigned short)
  • Gamestyle (1 Byte; unsigned char; 0=DM, 1=PM, 2=TM, 3=CTF, 4=RM, 5=INF, 6=HTF)
  • Max Players (1 Byte; unsigned char)
  • Max Spectators (1 Byte; unsigned char)
  • Game Passworded? (1 Byte; boolean)
  • Next Map (17 Bytes; first Byte (unsigned char) indicates length)

Implementations

PHP

<?php

/*
*  SOLDAT REFRESH AND REFRESHX PARSER
*    By: ramirez (c) 2007
*   http://devs.soldat.pl/wiki/index.php?title=Refreshx
*
*  UPDATE: added 2.7.0+ RefreshX support, by SyavX (01-06-2010)
                                           updated (06-03-2011)
*
*  Usage:
*    $info = ParseRefresh($packet);
*
*  Example:
*
*    <?php
*    include('soldat.refresh.php');
*    $socket = fsockopen('your.server.com', 23073);
*
*    fputs($socket, "PASSWORD\n");
*    fputs($socket, "REFRESH\n");
*    fputs($socket, "REFRESHX\n");
*
*    $version = null;
*    while ($data = trim(fgets($socket, 1024))) {
*        if (preg_match('/^Server Version: (.+)$/', $data, $match)) {
*            $version = $match[1];
*        }
*        else if ($data == 'REFRESH') {
*            $info = ParseRefresh(fread($socket, REFRESH_PACKET_SIZE), $version);
*            print_r($info);
*        }
*        else if ($data == 'REFRESHX') {
*            $info = ParseRefresh(fread($socket, RefreshXSize($version)), $version);
*            print_r($info);
*            break;
*        }
*    }
*
*    fclose($socket);
*    ?>
*/

define('REFRESH_PACKET_SIZE',  1188);

function RefreshXSize($version = '2.6.5')
{
    if ($version >= '2.7.0') {
        return 1992;
    }
    else if ($version >= '2.6.5') {
        return 1608;
    }
    else {
        return 1576;
    }
}

function ParseRefresh(&$packet, $version = '2.6.5') {
    if (strlen($packet) == REFRESH_PACKET_SIZE) {
        $refreshx = false;
    }
    else if (strlen($packet) == RefreshXSize($version)) {
        $refreshx = true;
    }
    else {
        return false;
    }

    $info = array(
        'gamemode'    => 0,
        'players'     => 0,
        'spectators'  => 0,
        'map'         => '',
        'timelimit'   => 0,
        'currenttime' => 0,
        'timeleft'    => 0,
        'limit'       => 0,
        'player'      => array(),
        'spectator'   => array(),
        'team'        => array()
    );

    if ($refreshx) {
        $info = array_merge($info, array(
            'maxplayers' => 0,
            'maxspecs'   => 0,
            'nextmap'    => '',
            'passworded' => false,
            'redflag'    => array(),
            'blueflag'   => array()
        ));
    }

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

        if ($refreshx) {
            $players[$i] = array_merge($players[$i], array(
                'x' => 0,
                'y' => 0
            ));
            if ($version >= '2.7.0') {
                $players[$i] = array_merge($players[$i], array('hwid' => ''));
            }
        }
    }
    
    if ($refreshx && $version >= '2.7.0') {
        /* 
        384 = (11 + 1 byte per hwid) * 32 players
        1184 = 800 + 384
        */
        $pos = 1184;
    }
     else {
        /*
        800 = (24 + 1 byte per name) * 32 players
        */
        $pos = 800;
    }

    $teams                            = unpack('C*', substr($packet, $pos, 32));  $pos += 32;
    $kills                            = unpack('v*', substr($packet, $pos, 64));  $pos += 64;
    if ($version >= '2.6.5') {  $caps = unpack('C*', substr($packet, $pos, 32));  $pos += 32;  }
    $deaths                           = unpack('v*', substr($packet, $pos, 64));  $pos += 64;
    if (!$refreshx)          { $pings = unpack('C*', substr($packet, $pos, 32));  $pos += 32;  }
    else                     { $pings = unpack('l*', substr($packet, $pos, 128)); $pos += 128; }
    $ids                              = unpack('C*', substr($packet, $pos, 32));  $pos += 32;
    $ips                              = unpack('N*', substr($packet, $pos, 128)); $pos += 128;
    if ($refreshx)           { $locs  = unpack('f*', substr($packet, $pos, 256)); $pos += 256; }
 
    for ($i = 0; $i < 32; $i++) {
        $players[$i]['name']   = substr($packet, $i*25+1, ord($packet[$i*25]));
        $players[$i]['team']   = $teams[$i+1];
        $players[$i]['kills']  = $kills[$i+1];
        $players[$i]['caps']   = $caps[$i+1];
        $players[$i]['deaths'] = $deaths[$i+1];
        $players[$i]['ping']   = $pings[$i+1];
        $players[$i]['id']     = $ids[$i+1];
        $players[$i]['ip']     = long2ip($ips[$i+1]);
 
        if ($refreshx) {
            $players[$i]['x'] = $locs[$i+1];
            $players[$i]['y'] = $locs[$i+33];
            if ($version >= '2.7.0') {
                $players[$i]['hwid'] = substr($packet, $i*12+801, 11);
            }
        }
    }

    if ($refreshx) {
        $data = unpack('f*', substr($packet, $pos, 16)); $pos += 16;
        $info['redflag']  = array('x' => $data[1], 'y' => $data[2]);
        $info['blueflag'] = array('x' => $data[3], 'y' => $data[4]);
    }

    $teams = unpack('v*', substr($packet, $pos, 8));            $pos += 8;
    $map   = unpack('Clen/A16name', substr($packet, $pos, 17)); $pos += 17;
    $time  = unpack('V*', substr($packet, $pos, 8));            $pos += 8;
    $limit = unpack('v', substr($packet, $pos, 2));                 $pos += 2;

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

    $info['team'] = $teams;
    $info['map'] = substr($map['name'], 0, $map['len']);
    $info['timelimit'] = $timelimit;
    $info['currenttime'] = $timelimit - $currenttime;
    $info['limit'] = $limit[1];
    $info['gamemode'] = ord($packet[$pos++]);

    if ($refreshx) {
        $data = unpack('C*', substr($packet, $pos, 4)); $pos += 4;
        $info['maxplayers'] = $data[1];
        $info['maxspecs']   = $data[2];
        $info['passworded'] = ($data[3] != 0 ? true : false);
        $info['nextmap']    = substr($packet, $pos, $data[4]);
    }

    if ($info['gamemode'] != 2) {
        if ($info['gamemode'] != 3 && $info['gamemode'] != 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;
}

    function checkServer($host, $port)
    {
     $host = gethostbyname ($host);
     $socket = @fsockopen($host, $port, $errno, $errstr, 0.3);
     if ($socket)
     {
        return TRUE;
        fclose($socket);
     }
     else
     {
        return FALSE;
        fclose($socket);
     }
    }

    function connectServer($host, $port, $pw)
    {
        $host = gethostbyname ($host);
        $socket = @fsockopen($host, $port);        
        if ($socket) {
            $info = array();
            fputs($socket, $pw . "\n");
            fputs($socket, "REFRESHX\n");

            $version = null;

            while ($data = trim(fgets($socket, 1024))) {
                if (preg_match('/^Server Version: (.+)$/', $data, $match)) {
                    $version = $match[1];
                }
                else if ($data == 'REFRESH') {
                    $info = ParseRefresh(fread($socket, REFRESH_PACKET_SIZE), $version);
                    return $info;
                }
                else if ($data == 'REFRESHX') {
                    $info = ParseRefresh(fread($socket, RefreshXSize($version)), $version);
                    return $info;
                    break;
                }
            }
        }
        fclose($socket);
    }

    function sendOnServer($host, $port, $pw, $com)
    {
        $host = gethostbyname($host);
        $socket = @fsockopen($host, $port);

        if ($socket) {
            fputs($socket, $pw . "\n");
            fputs($socket, "/" . $com . "\n");

            return TRUE;
        }
        else {
            return FALSE;
        }
        fclose($socket);
    }
?>


PHP Class

<?php
/**
 * Soldat Admin REFRESHX packet parser
 * 
 * Parses the REFRESHXpacket-data from SoldatServers
 * SOLDATSERVER VERSION 2.6.5+ ONLY
 * 
 * @author m!nus
 * 
 * Call parse() with your REFRESHX data as parameter
 * 
 * Offered Functions:
 * getBestPlayer
 * getBlueFlagX
 * getBlueFlagY
 * getFlags
 * getGameMode
 * getLimit
 * getMap
 * getMaxPlayers
 * getMaxSpectators
 * getNextMap
 * getPlayerCount
 * getPlayersFromTeam
 * getRedFlagX
 * getRedFlagY
 * getSpectatorCount
 * getTeams
 * getTimeLeft
 * getTimeLimit
 * getTimePassed
 * isTeamMode
*/

class REFRESHX
{
	
	// array $aPlayers Contains Player Information
	protected $aPlayers = array();
	
	// array $aTeams Contains Team Information
	protected $aTeams = array();
	
	// array $aGame Contains General Game Information
	protected $aGame = array();
	
	// const integer PacketLength REFRESHX packet length, this var is useful if your read from the server
	const PacketLength = 1608;
	
	// Team Constants
	// {
	const TEAM_DM		=  1; //= 0x000001; // Non-Teammatch
	const TEAM_ALPHA	=  2; //= 0x000010; // Alpha Team
	const TEAM_BRAVO	=  4; //= 0x000100; // Bravo Team
	const TEAM_CHARLIE	=  8; //= 0x001000; // Charlie Team
	const TEAM_DELTA	= 16; //= 0x010000; // Delta Team
	const TEAM_SPECT	= 32; //= 0x100000; // Spectators
	const TEAM_ALL		= 31; //= 0x011111; // All Players
	const TEAM_ALLSPECT	= 63; //= 0x111111; // All Players and Spectators
	// }
	
	// Game Modes
	// {
	const MODE_ERR	= -1; // An Error Occoured
	const MODE_DM	=  0; // DeathMatch
	const MODE_PM	=  1; // PointMatch
	const MODE_TM	=  2; // TeamMatch
	const MODE_CTF	=  3; // Capture The Flag
	const MODE_RM	=  4; // RamboMatch
	const MODE_INF	=  5; // Infiltration
	const MODE_HTF	=  6; // Hold The Flag
	// }
	
	// GameMode Returnselectors
	// {
	const GAMEMODE_FORMAT_INT		= 1; // Return as INT (example: 3)
	const GAMEMODE_FORMAT_SHORTSTR	= 2; // Return as long STRING (example: CTF)
	const GAMEMODE_FORMAT_STR		= 3; // Return as STRING (example: Capture The Flag)
	// }
	
	// Time Return Mode
	// {
	const TIMEMODE_TIMELEFT			= 1; // Time left
	const TIMEMODE_TIMELIMIT		= 2; // Time limit
	const TIMEMODE_TIMEPASSED		= 3; // Time passed
	// }
	
	// BestPlayer Return Mode
	// {
	const BESTPLAYER_KILLS			= 1; // Return Player with most Kills
	const BESTPLAYER_RATIO			= 2; // with best K:D Ratio
	const BESTPLAYER_DEATHS			= 3; // with least Kills
	// }
	
	
	
	/**
	 * Initializes information arrays
	*/
	public function __construct()
	{
		
		
		// Construct Team Array
		for($i = 0; $i <= 5; $i++)
		{
			$this->aTeams[$i] = array(
										'aPlayers'		=> array(),
										'iCaps'			=> 0,
										'iPlayerCount'	=> 0,
										'iKills'		=> 0,
										'iDeaths'		=> 0
									);
		}
		
		// Create Player Array
		for($i = 0; $i < 32; $i++)
		{
			$this->aPlayers[$i] = array(
										'sName'			=> '',
										'iTeam'			=> 0,
										'iKills'		=> 0,
										'iCaps'			=> 0, // new since 2.6.5
										'iDeaths'		=> 0,
										'iRatio'		=> 0,
										'iPing'			=> 0,
										'iID'			=> 0,
										'sIP'			=> '0.0.0.0',
										'fLocX'			=> 0,
										'fLocY'			=> 0
									);
		}
		
		
		// Init Game Array
		$this->aGame = array(
								'sMap' 				=> 'map_error',
								'iTimelimitTicks' 	=> -1,
								'iTimeleftTicks' 	=> -1,
								'iTimepassedTicks' 	=> -1,
								'fTimelimit' 		=> -1,
								'fTimeleft'			=> -1,
								'fTimepassed' 		=> -1,
								'iMode'				=> self::MODE_ERR,
								'sMode'				=> 'Unknown',
								'sModeShort'		=> '???',
								'bTeamMode'			=> false,
								'iPlayerCount'		=> 0,
								'iSpecatorCount'	=> 0,
								'fRedFlagX'			=> 0,
								'fRedFlagY'			=> 0,
								'fBlueFlagX'		=> 0,
								'fBlueFlagY'		=> 0,
								'iMaxPlayers'		=> -1,
								'iMaxSpectators'	=> -1,
								'bPassworded'		=> false,
								'sNextMap'			=> 'map_error'
							);
	}
	
	/**
	 * Parses the REFRESHX Data
	 * 
	 * @param string $sData REFRESHX Data
	*/
	public function parse($sData)
	{
		
		// Set in-string Start Position
		$iStartPos = 0;
		
		
		
		// Name (25 Bytes)
		for($i = 0; $i < 32; $i++)
		{
			$this->aPlayers[$i]['sName'] = substr($sData, $iStartPos + 1, ord($sData{$iStartPos})<25?ord($sData{$iStartPos}):24 );
			$iStartPos += 25;
		}
		
		// Team (1 Byte)
		for($i = 0; $i < 32; $i++)
		{
			switch(ord($sData{$iStartPos}))
			{
				case 0:
				$this->aPlayers[$i]['iTeam'] = self::TEAM_DM;
				break;
				case 1:
				$this->aPlayers[$i]['iTeam'] = self::TEAM_ALPHA;
				break;
				case 2:
				$this->aPlayers[$i]['iTeam'] = self::TEAM_BRAVO;
				break;
				case 3:
				$this->aPlayers[$i]['iTeam'] = self::TEAM_CHARLIE;
				break;
				case 4:
				$this->aPlayers[$i]['iTeam'] = self::TEAM_DELTA;
				break;
				case 5:
				$this->aPlayers[$i]['iTeam'] = self::TEAM_SPECT;
				break;
			}
			
			$iStartPos += 1;
		}
		
		// Kills (2 Bytes)
		for($i = 0; $i < 32; $i++)
		{
			list(,$this->aPlayers[$i]['iKills']) = unpack('v', substr($sData, $iStartPos, 2) );
			$iStartPos += 2;
		}
		
		// Caps (1 Byte)
		for($i = 0; $i < 32; $i++)
		{
			$this->aPlayers[$i]['iCaps'] = ord( $sData[$iStartPos] );
			$iStartPos++;
		}
		
		// Deaths (2 Bytes)
		for($i = 0; $i < 32; $i++)
		{
			list(,$this->aPlayers[$i]['iDeaths']) = unpack('v', substr($sData, $iStartPos, 2) );
			$iStartPos += 2;
		}
		
		// calc K:D Ratio
		for($i = 0; $i < 32; $i++)
		{
			if($this->aPlayers[$i]['iDeaths'] == 0)
			{
				$this->aPlayers[$i]['fRatio'] = 0;
			}
			else
			{
				$this->aPlayers[$i]['fRatio'] = $this->aPlayers[$i]['iKills'] / $this->aPlayers[$i]['iDeaths'];
			}
		}
		
		// Ping (4 Byte)
		for($i = 0; $i < 32; $i++)
		{
			list(,$this->aPlayers[$i]['iPing']) = unpack('l', substr($sData, $iStartPos, 4) );
			$iStartPos += 4;
		}
		
		// ID (1 Byte)
		for($i = 0; $i < 32; $i++)
		{
			$sTmp = ord($sData{$iStartPos});
			$this->aPlayers[$i]['iID'] = $sTmp;
			$iStartPos += 1;
		}
		
		// IP (4 Bytes)
		for($i = 0; $i < 32; $i++)
		{
			list(,$this->aPlayers[$i]['sIP']) = unpack('N', substr($sData, $iStartPos, 4) );
			$this->aPlayers[$i]['sIP'] = long2ip($this->aPlayers[$i]['sIP']);
			$iStartPos += 4;
		}
		
		// Location: X (4 Bytes)
		for($i = 0; $i < 32; $i++)
		{
			list(,$this->aPlayers[$i]['fLocX']) = unpack('f', substr($sData, $iStartPos, 4) );
			$iStartPos += 4;
		}
		
		// Location: Y (4 Bytes)
		for($i = 0; $i < 32; $i++)
		{
			list(,$this->aPlayers[$i]['fLocY']) = unpack('f', substr($sData, $iStartPos, 4) );
			$iStartPos += 4;
		}
		
		
		for($i = 0; $i < 32; $i++)
		{
			if($this->aPlayers[$i]['iTeam'] <= 5 && $this->aPlayers[$i]['iTeam'] >= 0)
			{
				if($this->aPlayers[$i]['iID'] < 1 || $this->aPlayers[$i]['iID'] > 32)
				{
					// Empty Slot => Remove from Array
					unset($this->aPlayers[$i]);
					continue;
				}
				// Add Reference to PlayerArray to Team Array
				$this->aTeams[ $this->aPlayers[$i]['iTeam'] ]['aPlayers'][$i] = &$this->aPlayers[$i];
				
				// Increase Team's Playercount
				$this->aTeams[ $this->aPlayers[$i]['iTeam'] ]['iPlayerCount']++;
				
				// Increase Team's Point and Deathcount
				$this->aTeams[ $this->aPlayers[$i]['iTeam'] ]['iKills'] += $this->aPlayers[$i]['iKills'];
				$this->aTeams[ $this->aPlayers[$i]['iTeam'] ]['iDeaths'] += $this->aPlayers[$i]['iDeaths'];
				
				// Increase Total Player- and Spectatorcount
				if($this->aPlayers[$i]['iTeam'] <= 4 && $this->aPlayers[$i]['iTeam'] >= 0)
				{
					$this->aGame['iPlayerCount']++;
				}
				else
				{
					$this->aGame['iSpectatorCount']++;
				}
			}
			else
			{
				// Empty Slot => Remove from Array
				unset($this->aPlayers[$i]);
			}
		}
		
		// Flag Positions (4x4 Bytes)
		list(,$this->aGame['fRedFlagX'], $this->aGame['fRedFlagY'],
			$this->aGame['fBlueFlagX'], $this->aGame['fBlueFlagY']) = unpack('f*', substr($sData, $iStartPos, 16) );
		$iStartPos += 16;
		
		// Parse Team Info
		for($i = 1; $i < 5; $i++)
		{
			// Team Caps (2 Bytes)
			list(,$this->aTeams[$i]['iCaps']) = unpack('v', substr($sData, $iStartPos, 2) );
			$iStartPos += 2;
		}
		
		// Mapname (17 Bytes; 1 Byte length, max 16 Bytes Name)
		$this->aGame['sMap'] = substr($sData, $iStartPos + 1, ord($sData{$iStartPos}) );
		$iStartPos += 17;
		
		// Timelimit (4 Bytes) - in ticks (1 sec = 60 ticks)
		list(,$this->aGame['iTimelimitTicks']) = unpack('V', substr($sData, $iStartPos, 4) );
		$this->aGame['fTimelimit'] = ($this->aGame['iTimelimitTicks'] / 60); // Convert to Seconds
		$iStartPos += 4;
		
		// Timeleft (4 Bytes) - in ticks (1 sec = 60 ticks)
		list(,$this->aGame['iTimeleftTicks']) = unpack('V', substr($sData, $iStartPos, 4) );
		$this->aGame['fTimeleft'] = ($this->aGame['iTimeleftTicks'] / 60); // Convert to Seconds
		$iStartPos += 4;
		
		// Timepassed - just calc
		$this->aGame['fTimepassed'] = $this->aGame['fTimelimit'] - $this->aGame['fTimeleft'];
		$this->aGame['iTimepassedTicks'] = $this->aGame['iTimelimitTicks'] - $this->aGame['iTimeleftTicks'];
		
		// Cap/Kill-Limit (2 Bytes)
		list(,$this->aGame['iLimit']) = unpack('v', substr($sData, $iStartPos, 2) );
		$iStartPos += 2;
		
		// Gamemode (1 Byte)
		$this->aGame['iMode'] = ord($sData{$iStartPos});
		$iStartPos++;
		// 0 DM, 1 PM, 2 TM, 3 CTF, 4 RM, 5 INF, 6 HTF
		
		// Gamemode
		switch($this->aGame['iMode'])
		{
			case self::MODE_DM: // Deathmatch
			$this->aGame['sMode'] = 'Deathmatch';
			$this->aGame['sModeShort'] = 'DM';
			$this->aGame['bTeamMode'] = false;
			break;
			
			case self::MODE_PM: // Pointmatch
			$this->aGame['sMode'] = 'Pointmatch';
			$this->aGame['sModeShort'] = 'PM';
			$this->aGame['bTeamMode'] = false;
			break;
			
			case self::MODE_TM: // Teammatch
			$this->aGame['sMode'] = 'Teammatch';
			$this->aGame['sModeShort'] = 'TM';
			$this->aGame['bTeamMode'] = true; // Gamemode is a Teammode
			break;
			
			case self::MODE_CTF: // Capture The Flag
			$this->aGame['sMode'] = 'Capture The Flag';
			$this->aGame['sModeShort'] = 'CTF';
			$this->aGame['bTeamMode'] = true; // Gamemode is a Teammode
			break;
			
			case self::MODE_RM: // Rambomatch
			$this->aGame['sMode'] = 'Rambomatch';
			$this->aGame['sModeShort'] = 'RM';
			$this->aGame['bTeamMode'] = false;
			break;
			
			case self::MODE_INF: // Infiltration
			$this->aGame['sMode'] = 'Infiltration';
			$this->aGame['sModeShort'] = 'INF';
			$this->aGame['bTeamMode'] = true; // Gamemode is a Teammode
			break;
			
			case self::MODE_HTF: // Hold  The Flag
			$this->aGame['sMode'] = 'Hold The Flag';
			$this->aGame['sModeShort'] = 'HTF';
			$this->aGame['bTeamMode'] = true; // Gamemode is a Teammode
			break;
			
			default:
			$this->aGame['sMode'] = 'Unknown';
			$this->aGame['sModeShort'] = '???';
			$this->aGame['bTeamMode'] = false;
		}
		
		// Max Players (1 Byte)
		$this->aGame['iMaxPlayers'] = ord($sData{$iStartPos});
		$iStartPos++;
		
		// Max Spectators (1 Byte)
		$this->aGame['iMaxSpectators'] = ord($sData{$iStartPos});
		$iStartPos++;
		
		// Is Passworded? (1 Byte)
		$this->aGame['bPassworded'] = ($sData{$iStartPos} != 1) ? false : true;
		$iStartPos++;
		
		// Next Map (17 Bytes)
		$this->aGame['sNextMap'] = substr($sData, $iStartPos + 1, ord($sData{$iStartPos}) );
		$iStartPos += 17;
		
		
	} // public function parse($sData)
	
	
	/**
	 * Returns an array with all Players from the given Teams.
	 * 
	 * Team constants can be ORed.
	 * 
	 * @param integer $iFromTeam Team constant(s)
	 * @return &array Array of Players (reference)
	*/
	public function getPlayersFromTeam($iFromTeam = null)
	{
		if($iFromTeam == null)
		{
			$iFromTeam = self::TEAM_ALL;
		}
		$aReturn = array();
		
		foreach($this->aPlayers as $iIndex => $aPlayer)
		{
			if($iFromTeam & $aPlayer['iTeam']) // 0x110011 & 0x010000 != 0
			{
				$aReturn[$iIndex] = &$aPlayer;
			}
		}
		
		return $aReturn;
	} // public function getPlayers($aFromTeam = self::TEAM_ALL)
	
	/**
	 * Returns an array with Teams.
	 * 
	 * Team constants can be ORed.
	 * 
	 * @param integer $iFromTeam Team constant(s)
	 * @return &array Array of Teams (reference)
	*/
	public function getTeams($iFromTeam = null)
	{
		if($iFromTeam == null)
		{
			$iFromTeam = self::TEAM_ALL;
		}
		$aReturn = array();
		
		if($iFromTeam & self::TEAM_ALPHA)
		{
			$aReturn[self::TEAM_APLHA] = &$this->aTeams[self::TEAM_APLHA];
		}
		if($iFromTeam & self::TEAM_BRAVO)
		{
			$aReturn[self::TEAM_BRAVO] = &$this->aTeams[self::TEAM_BRAVO];
		}
		if($iFromTeam & self::TEAM_CHARLIE)
		{
			$aReturn[self::TEAM_CHARLIE] = &$this->aTeams[self::TEAM_CHARLIE];
		}
		if($iFromTeam & self::TEAM_DELTA)
		{
			$aReturn[self::TEAM_DELTA] = &$this->aTeams[self::TEAM_DELTA];
		}
		
		return $aReturn;
	} // public function getTeams($aFromTeam = self::TEAM_ALL)
	
	
	/**
	 * Returns the Gamemode
	 * 
	 * @param integer $iType Possible values: REFRFESHX::GAMEMODE_FORMAT_INT, REFRFESHX::GAMEMODE_FORMAT_SHORTSTR, REFRFESHX::GAMEMODE_FORMAT_STR
	 * @return string/integer Game Mode
	*/
	public function getGameMode($iType = null)
	{
		if($iType == null)
		{
			$iType = self::GAMEMODE_FORMAT_INT;
		}
		
		if($iType == self::GAMEMODE_FORMAT_INT)
		{
			return $this->aGame['iMode'];
		}
		if($iType == self::GAMEMODE_FORMAT_SHORTSTR)
		{
			return $this->aGame['sModeShort'];
		}
		if($iType == self::GAMEMODE_FORMAT_STR)
		{
			return $this->aGame['sMode'];
		}
	} // public function getGameMode($iType = null)
	
	
	/**
	 * Returns true if the Server is running a Teambased mode (TM, CTF, INF, HTF)
	 * 
	 * @return integer Time left
	*/
	public function isTeamMode()
	{
		return $this->aGame['bTeamMode'];
	} // public function isTeamMode()
	
	/**
	 * Returns time left on the Server
	 * 
	 * @param bool $bAsTicks return as Ticks(integer) instead of Seconds(float)
	 * @return integer/float Time left
	*/
	public function getTimeLeft($bAsTicks = false)
	{
		return $bAsTicks?$this->aGame['iTimeleftTicks']:$this->aGame['fTimeleft'];
	} // public function getTimeLeft()
	
	/**
	 * Returns time passed on the Server
	 * 
	 * @param bool $bAsTicks return as Ticks(integer) instead of Seconds(float)
	 * @return integer/float Time passed
	*/
	public function getTimePassed($bAsTicks = false)
	{
		return $bAsTicks?$this->aGame['iTimepassedTicks']:$this->aGame['fTimepassed'];
	} // public function getTimePassed()
	
	/**
	 * Returns time left on the Server
	 * 
	 * @param bool $bAsTicks return as Ticks(integer) instead of Seconds(float)
	 * @return integer/float Time left
	*/
	public function getTimeLimit($bAsTicks = false)
	{
		return $bAsTicks?$this->aGame['iTimelimitTicks']:$this->aGame['fTimelimit'];
	} // public function getTimeLimit()
	
	/**
	 * Returns Cap/Kill Limit
	 * @return string Map Name
	*/
	public function getLimit()
	{
		return $this->aGame['iLimit'];
	} // public function getLimit()
	
	/**
	 * Returns current Map on the Server
	 * @return string Map Name
	*/
	public function getMap()
	{
		return $this->aGame['sMap'];
	} // public function getMap()
	
	/**
	 * Returns next-to-be-played Map
	 * @return string Next Map Name
	*/
	public function getNextMap()
	{
		return $this->aGame['sNextMap'];
	} // public function getNextMap()
	
	/**
	 * Returns the best Player on the Server
	 * 
	 * @param integer $iBestPlayer Possible values: REFRFESHX::BESTPLAYER_KILLS, REFRFESHX::BESTPLAYER_RATIO, REFRFESHX::BESTPLAYER_DEATHS
	 * @return array Playerarray
	*/
	public function getBestPlayer($iBestPlayer = null)
	{
		if($iBestPlayer == null)
		{
			$iBestPlayer = self::BESTPLAYER_KILLS;
		}
		
		$iBestTilNowKills = -1;
		$iBestTilNowRatio = -1;
		$iBestTilNowDeaths = 2147483647;
		
		foreach($this->aPlayers as $iID => $aPlayer)
		{
			if(self::TEAM_ALL & $aPlayer['iTeam']) // if not spectator
			{
				switch($iBestPlayer)
				{
					case self::BESTPLAYER_KILLS:
						if($aPlayer['iKills'] > $iBestTilNowKills)
						{
							$iBestTilNowID = $iID;
							$iBestTilNowKills = $aPlayer['iKills'];
						}
						if($aPlayer['iKills'] == $iBestTilNowKills && $this->aPlayers[$iBestTilNowID]['iDeaths'] > $aPlayer['iDeaths'])
						{
							$iBestTilNowID = $iID;
						}
					break;
				
					case self::BESTPLAYER_RATIO:
						if($aPlayer['fRatio'] > $iBestTilNowRatio)
						{
							$iBestTilNowID = $iID;
							$iBestTilNowRatio = $aPlayer['fRatio'];
						}
						if($aPlayer['fRatio'] == $iBestTilNowRatio && $this->aPlayers[$iBestTilNowID]['iKills'] < $aPlayer['iKills'])
						{
							$iBestTilNowID = $iID;
						}
					break;
				
					case self::BESTPLAYER_DEATHS:
						if($aPlayer['iDeaths'] < $iBestTilNowDeaths)
						{
							$iBestTilNowID = $iID;
							$iBestTilNowDeaths = $aPlayer['iDeaths'];
						}
						if($aPlayer['iDeaths'] == $iBestTilNowDeaths && $this->aPlayers[$iBestTilNowID]['iKills'] < $aPlayer['iKills'])
						{
							$iBestTilNowID = $iID;
						}
					break;
				}
			}
		}
		
		return $this->aPlayers[$iBestTilNowID];
	}
	
	/**
	 * Returns Flag Positions
	 * 
	 * @return array RedX,RedY,BlueX,BlueY array indices
	*/
	public function getFlags()
	{
		return array($this->aGame['fRedFlagX'], $this->aGame['fRedFlagY'],
					$this->aGame['fBlueFlagX'], $this->aGame['fBlueFlagY']);
	} // public function getFlags()
	
	/**
	 * Returns Red Flag X Position
	 * 
	 * @return float RedX
	*/
	public function getRedFlagX()
	{
		return $this->aGame['fRedFlagX'];
	} // public function getRedFlagX()
	
	/**
	 * Returns Red Flag Y Position
	 * 
	 * @return float RedY
	*/
	public function getRedFlagY()
	{
		return $this->aGame['fRedFlagY'];
	} // public function getRedFlagY()
	
	/**
	 * Returns Blue Flag X Position
	 * 
	 * @return float BlueX
	*/
	public function getBlueFlagX()
	{
		return $this->aGame['fBlueFlagX'];
	} // public function getBlueFlagX()
	
	/**
	 * Returns Blue Flag Y Position
	 * 
	 * @return float BlueY
	*/
	public function getBlueFlagY()
	{
		return $this->aGame['fBlueFlagY'];
	} // public function getBlueFlagY()
	
	/**
	 * Returns Number of maximal Players
	 * 
	 * @return integer MaxPlayers
	*/
	public function getMaxPlayers()
	{
		return $this->aGame['iMaxPlayers'];
	} // public function getMaxPlayers()
	
	/**
	 * Returns Number of maximal Spectators
	 * 
	 * @return integer MaxSpectators
	*/
	public function getMaxSpectators()
	{
		return $this->aGame['iMaxSpectators'];
	} // public function getMaxSpectators()
	
	/**
	 * Returns Number of current Players
	 * 
	 * @return integer current Number of Players
	*/
	public function getPlayerCount()
	{
		return $this->aGame['iPlayerCount'];
	} // public function getPlayerCount()
	
	/**
	 * Returns Number of maximal Spectators
	 * 
	 * @return integer current Number of Spectators
	*/
	public function getSpectatorCount()
	{
		return $this->aGame['iSpectatorCount'];
	} // public function getSpectatorCount()
	
}

?>

Ruby (Possibly not working)

socket = TCPsocket.open(server, port)
socket.puts(password)
 
while data=socket.gets.strip
  if data =~ /REFRESHX/
    refreshx = ""
    socket.read(1608, refreshx)
    info = refreshx.unpack("CA24"*32 + "C32S32C32S32L32C32" + "C32"*4 + "e32e32eeeeS4CA16llSCCCCCA16")
      names = Array.new(32, "")
      teams = Array.new(32, 0)
      kills = Array.new(32, 0)
      flags = Array.new(32, 0) #<-- NEW
      deaths = Array.new(32, 0)
      pings = Array.new(32, 0) #<-- integer
      numbers = Array.new(32, 0)
      ips =Array.new(32, Array.new(4, 0))
      X = Array.new(32, 0) #<-- NEW
      Y = Array.new(32, 0) #<-- NEW
      RedFlagX = 0 #<-- NEW
      RedFlagY = 0 #<-- NEW
      BlueFlagX = 0 #<-- NEW
      BlueFlagY = 0 #<-- NEW
      teamscore = Array.new(4, 0)
      mapname = ""
      timelimit = 0
      currenttime = 0
      killlimit = 0
      gamestyle = 0
      MaxPlayers = 0 #<-- NEW
      MaxSpectators = 0 #<-- NEW
      Passworded = 0 #<-- NEW
      NextMap= "" #<-- NEW
      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]
        flags[i] = info[i+128]
        deaths[i] = info[i+160]
        pings[i] = info[i+192]
        numbers[i] = info[i+224]
        4.times do |j|
          ips[i][j] = info[(i-1)*4+j+256]
        end
      end
      32.times do |i|
        X[i] = info[i+384] #<-- NEW
        Y[i] = info[i+416] #<-- NEW
      end
      RedFlagX = info[448] #<-- NEW
      RedFlagY = info[449] #<-- NEW
      BlueFlagX = info[450] #<-- NEW
      BlueFlagY = info[451] #<-- NEW
      4.times do |i|
        teamscore[i] = info[i+452]
      end
      mapname = info[457][0,info[456]]
      timelimit = info[458]
      currenttime = info[459]
      killlimit = info[460]
      gamestyle = info[461]
      MaxPlayers = info[462] #<-- NEW
      MaxSpectators = info[463] #<-- NEW
      Passworded = info[464] #<-- NEW
      NextMap= info[466][0,info[465]] #<-- NEW
  end
end

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 Control.Applicative
import Network
import Unsafe.Coerce

-- 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,
                       caps   :: Int,
                       deaths :: Int,
                       team   :: Team,
                       userId :: Word8,
                       ping   :: Word32,
                       ip     :: [Word8],
                       coords :: (Float, Float) }
            deriving (Show, Read, Eq, Ord)

-- Everything combined into functional awesomeness
data Server = Server { currentMap  :: String,
                       nextMap     :: String,
                       gameStyle   :: GameStyle,
                       players     :: [Player],
                       maxPlayers  :: Int,
                       maxSpecs    :: Int,
                       teamScores  :: [Int],
                       killLimit   :: Int,
                       timeLimit   :: Int,
                       currentTime :: Int,
                       redFlagXY   :: (Float, Float),
                       bluFlagXY   :: (Float, Float),
                       hasPassword :: 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 "REFRESHX" -- REFRESH!
   result <- hGetLine handle  -- Refresh?
   
   if result == "REFRESHX\r"
      then do refreshData <- B8.hGet handle 1608 -- 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
   caps        <- replicateM 32 getWord8
   deaths      <- replicateM 32 getWord16le         -- list of deaths...
   pings       <- replicateM 32 getWord32le            -- pings of lists
   ids         <- replicateM 32 getWord8            -- of ids list
   ips         <- replicateM 32 parseOneIP          -- IPS!
   xs          <- replicateM 32 getWord32le
   ys          <- replicateM 32 getWord32le
   redXY       <- replicateM 2 getWord32le
   bluXY       <- replicateM 2 getWord32le
   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!
   maxP        <- getWord8
   maxSpec     <- getWord8
   pass        <- getWord8                          -- BUG? This is always 0x01 (True).
                                                    -- Might be server related, as...
   nextmap     <- parseOneString 16                 -- ...this next map here reads very well, uncorrupted.

   -- 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 $ getZipList $ zipPlayers 
                    <$> ZipList names  -- Because the player informations
                    <*> ZipList teams  -- are split up all over the place
                    <*> ZipList kills  -- in about 10 different arrays,
                    <*> ZipList caps   -- we have to use ZipList.
                    <*> ZipList deaths -- zipWith7 worked well for REFRESH
                    <*> ZipList pings  -- But as for the case of REFRESHXH
                    <*> ZipList ids    -- we need more lists to zip!
                    <*> ZipList ips    -- This one actually scales much better
                    <*> ZipList xs     -- And looks kind of awesome, too!
                    <*> ZipList ys
   
   -- Return all that junk
   return Server { currentMap  = B8.unpack mapname,
                   nextMap     = B8.unpack nextmap,
                   gameStyle   = numToGamestyle gamestyle,
                   players     = players,
                   maxPlayers  = fromIntegral maxP,
                   maxSpecs    = fromIntegral maxSpec,
                   teamScores  = map fromIntegral teamscores,
                   killLimit   = fromIntegral killlimit,
                   timeLimit   = fromIntegral timelimit,
                   currentTime = fromIntegral currenttime,
                   redFlagXY   = (floatify $ redXY !! 0, floatify $ redXY !! 1),
                   bluFlagXY   = (floatify $ bluXY !! 0, floatify $ bluXY !! 1),
                   hasPassword = fromIntegral pass }
                   
   -- I don't feel like commenting more :(
   -- Straight forward if you know a bit of Haskell.
 where zipPlayers n t k c d p di pi x y = 
          Player { nick    = B8.unpack n,
                   kills   = fromIntegral k,
                   caps    = fromIntegral c,
                   deaths  = fromIntegral d,
                   team    = numToTeam t,
                   userId  = di,
                   ping    = p,
                   ip      = pi,
                   coords  = (floatify x, floatify y) }
                   
       floatify n = unsafeCoerce n :: Float     
       
       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

Pascal

type
  playerX = record
    namelen: byte;
    name: Array[1..24] of char;
      tagidlen: byte;
      tagid: Array[1..11] of char;
    team: byte;
    kills: word;
    caps: byte;
    deaths: word;
    ping: cardinal;
    id: byte;
    ip: Array[1..4] of byte;
    x: single;
    y: single;
end;

type
  refreshX = record
    players: Array[0..31] of playerX;
    redflagx: single;
    redflagy: single;
    blueflagx: single;
    blueflagy: single;
    alphascore: word;
    bravoscore: word;
    charliescore: word;
    deltascore: word;
    currentmaplen: byte;
    currentmap: Array[1..16] of char;
    timelimit: longint;
    timeleft: longint;
    limit: byte;
    gamestyle: byte;
    maxplayers: byte;
    maxspectators: byte;
    password: byte;
    nextmaplen: byte;
    nextmap: Array[1..16] of char;
end;

var
  packet: ^refreshX;

procedure RefreshX(buffer: array of byte);
var
  i: byte;
begin
  GetMem(packet,sizeof(refreshX));
  for i := 0 to 31 do begin
    move(buffer[i*25],packet^.players[i].namelen, 1);
    move(buffer[i*25+1],packet^.players[i].name, 24);
    move(buffer[i+800],packet^.players[i].tagidlen, 1);
    move(buffer[i*12+801], packet^.players[i].tagid, 11);
    move(buffer[i+1184], packet^.players[i].team, 1);
    move(buffer[i*2+1216], packet^.players[i].kills, 2);
    move(buffer[i+1280], packet^.players[i].caps, 1);
    move(buffer[i*2+1312], packet^.players[i].deaths, 2);
    move(buffer[i*4+1376], packet^.players[i].ping, 4);
    move(buffer[i+1504], packet^.players[i].id, 1);
    move(buffer[i*4+1536], packet^.players[i].ip, 4);
    move(buffer[i*4+1664], packet^.players[i].x, 4);
    move(buffer[i*4+1792], packet^.players[i].y, 4);
  end;
  move(buffer[1920], packet^.redflagx, 4);
  move(buffer[1924], packet^.redflagy, 4);
  move(buffer[1928], packet^.blueflagx, 4);
  move(buffer[1932], packet^.blueflagy, 4);
  move(buffer[1936], packet^.alphascore, 2);
  move(buffer[1938], packet^.bravoscore, 2);
  move(buffer[1940], packet^.charliescore, 2);
  move(buffer[1942], packet^.deltascore, 2);
  move(buffer[1944], packet^.currentmaplen, 1);
  move(buffer[1945], packet^.currentmap, 16);
  move(buffer[1961], packet^.timelimit, 4);
  move(buffer[1965], packet^.timeleft, 4);
  move(buffer[1969], packet^.limit, 2);
  move(buffer[1971], packet^.gamestyle, 1);
  move(buffer[1972], packet^.maxplayers, 1);
  move(buffer[1973], packet^.maxspectators, 1);
  move(buffer[1974], packet^.password, 1);
  move(buffer[1975], packet^.nextmaplen, 1);
  move(buffer[1976], packet^.nextmap, 16);
end;

See also