Aus den meisten Gameservern lassen sich über einen Query-Port Daten zu ihrem Status auslesen. So kann man auf z.b. einer Webseite anzeigen lassen welche Map gerade gespielt wird oder wie viele Spieler auf dem Server sind. Es gibt im Internet Dienste die Gameserver dauerhaft überwachen und Statistiken erstellen, oft kann man dort auch die Daten eines Servers in fremde Webseiten einbinden. Wer nicht auf einen externen Dienst zurück greifen möchte kann sich selber ein Script erstellen um Gameserver abzufragen, das Problem dabei ist dass man wissen muss wie man mit dem Server sprechen muss.
Die Protokolle sind mit Ausnahme von Source oft gar nicht oder nur schlecht dokumentiert. Man muss also probieren oder den Server mit Tools wie HLSW abfragen und nebenbei den Netzwerk Traffic mitschneiden um zu sehen wie HLSW mit dem Server redet. Das ist sehr mühsam und Zeitaufwendig. Ich habe vor kurzem ein Webinterface für Gameserver geschrieben und dabei einige Protokolle gesammelt die ich hier jetzt zusammenfassen möchte.
Das Prinzip ist immer das gleiche, man sendet einen vorgegebenen Query-String an den Gameserver und erhält eine Antwort die man zur Darstellung zerlegen muss. Oft wird das Query einfach an den Gameport gesendet, bei manchen Gameservern gibt es auch einen extra Query-Port. Wenn man einmal ein Protokoll hat kann man damit gleich mehrere Spiele abfragen da es nur einige weinige Protokolle gibt die dann von mehreren Spielen genutzt werden.
Die Protokolle
Doom 3
Query
(int16) Header
(string) getInfo
FF FF 67 65 74 49 6E 66 6F ;ÿÿgetInfo
Response
(int16) Header
(string) infoResponse
(string) Schlüssel
(string) Wert
(string) Schlüssel
(string) Wert
(strings) Leerer Schlüssel/Wert (Ende von Schlüssel/Wert Paaren)
(byte) Spieler Nummer
(int16) Spieler Ping
(int16) Spieler Rate
(string) Unbekannt
(string) Unbekannt
(string) Spieler Name
(byte) Spieler #32 (Ende des Pakets?)
Gamespy
Query
(string) info
5C 69 6E 66 6F 5C ;\info\
Response
(string) Schlüssel
(string) Wert
(string) Schlüssel
(string) Wert
(string) Query ID (id.fragment)
(string) Finales Fragment
Gamespy 2
Query
(int64) Header
(byte) Trennzeichen
(string) Ping Wert (kann irgend etwas sein und dient zum prüfen ob die Antwort valide ist)
(byte) Gibt Server Info und Rules zurück wenn 0xFF, falls die Information nicht gewünscht ist kann stattdessen 0x00 gesendet werden.
(byte) Gibt Informationen zu den Spieler zurück wenn 0xFF, falls die Information nicht gewünscht ist kann stattdessen 0x00 gesendet werden.
(byte) Gibt Informationen zu den Teams zurück wenn 0xFF, falls die Information nicht gewünscht ist kann stattdessen 0x00 gesendet werden.
FE FD 00 43 4F 52 59 FF FF 00 ;þý.CORYÿÿ.
Response
(byte) Trennzeichen
(string) Ping Wert
(string) Schlüssel
(string) Wert
(strings) Leerer Schlüssel/Wert (Ende des Schlüssel/Wert Teils)
(byte) Anzahl der Spieler
(strings) Player Info Beschreibung
(string) Leerer string (Ende der Player Info Beschreibung)
(strings) Player Info
Quake3
Query
(int16) Header
(string) getstatus
FF FF FF FF 67 65 74 73 74 61 74 75 73 0A ;ÿÿÿÿgetstatus.
Response
(int32) Header
(string) statusResponse
(string) Schlüssel
(string) Wert
(string) Schlüssel
(string) Wert
(string) Player Info
(string) Player Info
Source
Query
(int16) Header
(byte) T (0x54)
(string) Source Engine Query
FF FF FF FF 54 53 6F 75 72 63 65 20 45 6E 67 69 ÿÿÿÿTSource Engi 6E 65 20 51 75 65 72 79 00 ne Query
Response
(byte) Header
(byte) Version
(string) Server Name
(string) Map
(string) Game Directory
(string) Game Beschreibung
(short) AppID
(byte) Anzahl Spieler
(byte) Max.Spieler
(byte) Anzahl Bots
(byte) Dedicated "d" oder SourceTV "p"
(byte) OS "l" für Linux, "w" für Windows
(byte) Passwort geschützter Server
(byte) VAC gesichert
(string) Game Version
Ein Beispiel in PHP
Zu erst benötigt man eine Klasse zur Kommunikation mit dem Server. Die Klasse enthält Methoden um einen Socket auf zu machen, Daten zu senden und zu lesen, außerdem gibt es dort Methoden um die Antwort auszuwerten.
class server_communication
{
var $socket = null;
var $ip = '';
var $port = 0;
var $network_protocol = 'tcp';
var $connected = false;
function __construct($ip, $port)
{
$this->ip = gethostbyname($ip);
$this->port = (int) $port;
if(!$this->connect())
{
die('No Connection');
}
}
function __destruct()
{
$this->close();
}
function close()
{
if(is_resource($this->socket))
{
fclose($this->socket);
}
}
function connect()
{
if($this->network_protocol == 'tcp')
{
if($this->socket = fsockopen($this->ip, $this->port, $errno, $error, 3))
{
$this->connected = true;
return true;
}
}
else
{
if($this->socket = fsockopen('udp://' . $this->ip, $this->port, $errno, $error, 3))
{
$this->connected = true;
return true;
}
}
}
function send($cmd)
{
if(is_resource($this->socket) && $this->connected)
{
fwrite($this->socket, $cmd, strlen($cmd));
}
}
function read($timeout = 3)
{
if(is_resource($this->socket) && $this->connected)
{
socket_set_timeout($this->socket, $timeout);
$reply = '';
do
{
$reply .= fgetc($this->socket);
$status = socket_get_status($this->socket);
}
while($status['unread_bytes']);
return $reply;
}
}
function get_byte()
{
return ord(fread($this->socket, 1));
}
function get_char()
{
return fread($this->socket, 1);
}
function get_int16()
{
if($data = fread($this->socket, 2))
{
$unpacked = unpack('sint', $data);
return $unpacked['int'];
}
}
function get_int32()
{
if($data = fread($this->socket, 4))
{
$unpacked = unpack('iint', $data);
return $unpacked['int'];
}
}
function get_float32()
{
if($data = fread($this->socket, 4))
{
$unpacked = unpack('fint', $data);
return $unpacked['int'];
}
}
function get_string()
{
$str = '';
while(($char = fread($this->socket, 1)) != chr(0))
{
$str .= $char;
}
return $str;
}
function get_string_part($len)
{
return fread($this->socket, (int) $len);
}
function remove_header()
{
$str = '';
while(($char = fread($this->socket, 1)) != chr(0))
{
if(ord($char) != 0)
{
return $char;
}
}
}
}
Jetzt kann man mit einer zweiten Klasse den Gameserver abfragen, in dem Beispiel wird ein Gameserver der das Gamespy Protokoll benutzt abgefragt.
class server_query extends server_communication
{
function gamespy()
{
$this->network_protocol = 'udp';
$this->send("\\info\\");
$reply = $this->read();
$reply = preg_split("#\\\#", $reply);
for($i=0; isset($reply[$i]); $i++)
{
if(!empty($reply[$i]))
{
$key = $reply[$i];
$i++;
$value = isset($reply[$i]) ? $reply[$i] : '';
$return[$key] = $value;
}
}
if(isset($return) && sizeof($return))
{
return $return;
}
}
}
Das ganze wird dann folgendermaßen aufgerufen:
$gameserver = new server_query($ip_adress, $port);
$data = $gameserver->gamespy();
print_r($data);
Welches Spiel verwendet welches Protokoll?
Eine weitere Herausforderung besteht darin rauszufeinden welches Protokoll ein bestimmtes Spiel benutzt. Auch hier hilft es wieder einfach den Netzwerk Trafic anzuschauen, wenn man einmal die Protokolle kennt lässt sich recht einfach erkennen was das Spiel spricht. Ich habe mal eine Liste mit Spielen zusammengestellt.
- ASE (All Seeing Eyes) Protokoll
- Soldat
- Doom Protokoll
- Enemy Territory: Quake Wars
- Prey
- Quake 4
- Gamespy1 Protokoll
- Battlefield 1942
- F.E.A.R
- Halo
- Medal of Honor Allied Assault
- Operation Flashpoint
- Unreal Tournament
- Americas Army
- Battlefield Vietnam
- Battlefield 2
- Battlefield 2142
- FarCry 2
- Unreal Tournament 3
- Arma2
- Crysis
- Minecraft
- Team Fortress Clasic
- Half-Life
- Alien Swarm
- Age of Chivalry
- Counter Strike 1.6
- Counter Strike Condition Zero
- Counter Strike: Source
- Counter Strike: Global Offensive
- Day of Defeat
- Day of Defeat: Source
- DayZ
- Garry's mod
- Half-Life 2
- Insurgency
- Left 4 dead
- Left 4 dead 2
- No More Room in Hell
- Synergy
- Rust
- Zombie Panic! Source
- Alien Arena
- Painkiller
- Quake 1
- Quake 2
- Call of Duty
- Call of Duty 2
- Call of Duty 4
- Call of Duty: World at War
- Nexuiz
- Open Area
- Quake 3
- Return to Castle Wolfenstein
- Soldier of Fortune 2
- Tremulous
- Urbanterror
- War§ow
- World of Padman
- GTA SA:MP
Ich habe auf gameserver-query.gq mal einen Dienst gebaut den ihr nutzen könnt um den Status von Gameservern auslesen zu können.
Kommentare
erstmal danke für deine Arbeit =) und die Veröffentlichung deines Codes.
Leider ist mir noch nicht ganz klar, wie ich dann Steam (speziell CS:GO) abfragen soll?! Deine Funktion ist auf Gamespy gepolt, Steam hat doch aber ebenfalls UDP als Abhorchstelle oder nicht? Einen Output bekomme ich jedenfalls nicht.
Vielleicht hast du eine Idee für mich =)
ich habe vor einiger Zeit mal eine Klasse gebastelt die alle Protokolle unterstützt die ich kenne. CS:GO sollte das Source Protokoll benutzen.
Die Klasse kannst du auf Github runterladen: https://github.com/Gameserveradmin/gameserver-status