Difference between revisions of "Refreshx"
(Created page with "The '''REFRESHX''' is a Raw command used in Soldat to give admin clients information about the server. It contains more information t...") |
(→Implementations) |
||
Line 1,340: | Line 1,340: | ||
4 -> Delta | 4 -> Delta | ||
5 -> Spectator | 5 -> Spectator | ||
+ | </source> | ||
+ | |||
+ | ===Pascal=== | ||
+ | <source lang="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; | ||
</source> | </source> | ||
Latest revision as of 23:51, 29 September 2014
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.
Contents
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;