Difference between revisions of "Refresh"
(added python version) |
m |
||
Line 17: | Line 17: | ||
* Kill Limit | * Kill Limit | ||
* Gamestyle | * Gamestyle | ||
+ | |||
==Implementations== | ==Implementations== | ||
Line 789: | Line 790: | ||
?> | ?> | ||
</source> | </source> | ||
+ | |||
====Example Usage==== | ====Example Usage==== | ||
Line 873: | Line 875: | ||
end | end | ||
</source> | </source> | ||
+ | |||
===TCL=== | ===TCL=== | ||
Line 1,146: | Line 1,149: | ||
} | } | ||
</source> | </source> | ||
+ | |||
===C#=== | ===C#=== | ||
Line 1,284: | Line 1,288: | ||
} | } | ||
</source> | </source> | ||
+ | |||
===Haskell=== | ===Haskell=== | ||
Line 1,748: | Line 1,753: | ||
s.close() | s.close() | ||
</source> | </source> | ||
+ | |||
== See also == | == See also == | ||
* [[Refreshx|REFRESHX]] | * [[Refreshx|REFRESHX]] | ||
− | |||
== External Links == | == External Links == | ||
* [http://www.soldatforums.com/index.php?topic=212.0 Soldatforums REFRESH thread] | * [http://www.soldatforums.com/index.php?topic=212.0 Soldatforums REFRESH thread] | ||
+ | |||
+ | |||
+ | [[Category:Server]] |
Latest revision as of 21:35, 16 April 2018
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.
Contents
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