Das hier ist ein Tutorial für ein PHP Server-Query Script das Informationen von einem HL2/CS:S Server auslesen und anzeigen kann.
Vor einiger Zeit fragte sich Chrisber, ob es möglich ist, einen HL² Server zu kontaktieren und Informationen auszulesen.
Da ich vor langer zeit über so ein Script Script im Web gestolpert bin, habe ich es mal rausgewühlt.
Ein Script, was nur halb funktionierte und kaum eine Funktionierende Funktion hatte.
Nach und nach haben wir beide noch Funktionen hinzugefügt, geändert und verbessert.
Hier mal ein Beispiel wie das Ergebnis ausschaut: http://www.server.hsfighter.net
Um nur die Informationen zu bekommen (z.B. Servername, Map usw)
Um Player vom Server ab zu fragen (z.B. Playername, Frags usw.), muss man eine Art „Ticket“ anfordern. Dieses Ticket ist # eine begrenzte Zeit gültig und ermöglicht es die Informationen zu bekommen.
Serversettings sind ähnlich wie die Player, auch mit einem Ticket
OK, soweit zum Prinzip.
Code:
Ich gehe mal davon aus das PHP Kentnisse vorhanden sind.
Wir wollen das ganze in einer Klasse zusammenfassen, damit man es später leicht anpassen und für verschiedene Server verwenden kann.1)
class HLServerAbfrage { }
Jetzt werden wir erst mal unsere wichtigsten Variablen deklarieren und einige schon mit Werten füllen da wir diese Werte dann nachher zu unserem Gameserver Senden:
var $server_address; var $ip; var $port; var $fp; var $challenge; var $serverinfo; var $playerlist; var $cvarlist; var $A2S_SERVERQUERY_GETCHALLENGE = "\x55\xFF\xFF\xFF\xFF"; // challenge var $A2S_INFO = "TSource Engine Query\x00"; // info var $A2S_PLAYER = "\x55"; // player var $A2S_RULES = "\x56"; // rules
Jetzt kommen wir zu den Funktionen die wir brauchen:
function hlserver($server_address = 0) { list($this->ip, $this->port) = explode(":", $server_address); }
function connect() { $this->fp = fsockopen("udp://".$this->ip, $this->port, $errno, $errstr, 3); if (!$this->fp) { $Fehler = 1; } }
Damit stellen wir eine und Verbindung zu unserem Server her und speichern sie in der Variablen “$this→fp“.
Die Funktion fsockopen
brauch dazu die beiden Variablen mit der IP und dem PORT.
function send_strcmd($strcmd) { fwrite($this->fp, sprintf('%c%c%c%c%s%c', 0xFF, 0xFF, 0xFF, 0xFF, $strcmd, 0x00)); }
Wir benutzten die Funktion fwrite
.
Wir brauchen uns mit dem Rest nicht auseinander setzten, nur die Variable “$strcmd ist noch interessant.
Damit werden später die Daten übergeben, die wir zum Server schicken.
Jetzt brauchen wir Funktionen, die die Daten auswertet die uns der Server zurück schick. Man muss sich das folgendermaßen vorstellen:
Der Server schickt uns eine Kette mit Informationen, die alle hintereinander kommen. Die müssen wir an bestimmten stellen trennen, damit wir die Informationen unterteilen können.
Bsp. Server schickt uns das: „de_dust25161“
Da sind folgende Informationen enthalten: Map, Max Player, Player auf dem Server und Anzahl der Bots.
Diese müssen jetzt getrennt werden!
// 1 Byte vom Server holen function get_byte() { return ord(fread($this->fp, 1)); } // 1 Zeichen (1 Byte) vom Server holen function get_char() { return fread($this->fp, 1); } // einen int16-Wert (2 Bytes) vom Server holen function get_int16() { $unpacked = unpack('sint', fread($this->fp, 2)); return $unpacked["int"]; } // einen int32-Wert (4 Bytes) vom Server holen function get_int32() { $unpacked = unpack('iint', fread($this->fp, 4)); return $unpacked["int"]; } // einen float32-Wert (4 Bytes) vom Server holen function get_float32() { $unpacked = unpack('fint', fread($this->fp, 4)); return $unpacked["int"]; } // einen String vom Server holen function get_string() { $str = ''; while(($char = fread($this->fp, 1)) != chr(0)) { $str .= $char; } return $str; } // 4 bytes von der challenge holen function get_4(){ return fread($this->fp, 4); }
Die Informationen die wir auswerten, sind von verschiedenen Datentypen. Mit den oben genannten Funktionen kann man sie auswerten. Darauf werde ich erst mal nicht genauer eingehen, ich denke das ist selbsterklärend.
Nun brauchen wir eine Funktion, um ein Ticket anzufordern:
function challenge() { $this->connect(); $this->send_strcmd($this->A2S_SERVERQUERY_GETCHALLENGE); $this->get_int32(); $this->get_byte(); $challenge = $this->get_4(); fclose($this->fp); return $challenge; }
Wir benutzen unsere Verbindungsfunktionen und übergeben den Wert “W“ (\x57) den wir vorher bei der Deklaration in die Variable “A2S_SERVERQUERY_GETCHALLENGE“ geschrieben haben.
Wir haben HEX-Zeichen für das W genommen!
Also mit anderen Worten schicken wir ein “W“ (\x57) zum Server.
Darauf antwortet der Server mit einem 4byte großen Datenpaket.
Deswegen benutzen wir die Funktion “get_4“ die wir vorher geschrieben haben.
Zum Schluss trennen wir die Verbindung. Die Funktion gibt die Ticketnummer aus.
Jetzt brauchen wir die Funktionen um den Server die Informationen zu entlocken, die wir wollen.
Fangen wir mit den Server Informationen an:
function infos() { $this->connect(); $this->send_strcmd($this -> A2S_INFO); $this->get_int32(); $this->get_byte(); $this->serverinfo["network_version"] = $this->get_byte(); $this->serverinfo["name"] = $this->get_string(); $this->serverinfo["map"] = $this->get_string(); $this->serverinfo["directory"] = $this->get_string(); $this->serverinfo["discription"]= $this->get_string(); $this->serverinfo["steam_id"] = $this->get_int16(); $this->serverinfo["players"] = $this->get_byte(); $this->serverinfo["maxplayers"] = $this->get_byte(); $this->serverinfo["bot"] = $this->get_byte(); $this->serverinfo["dedicated"] = $this->get_char(); $this->serverinfo["os"] = $this->get_char(); $this->serverinfo["password"] = $this->get_byte(); $this->serverinfo["secure"] = $this->get_byte(); $this->serverinfo["version"] = $this->get_string(); fclose($this->fp); return $this->serverinfo; }
Wir schicken wieder einen Wert zum Server (TSource Engine Query) der in der Variable “A2S_INFO“ gespeichert ist.
Danach rufen wir unseren Auswertungsfunktionen auf, um die Daten die der Server schickt auszuwerten.
Zum Schluss speichern wir das ganze im Serverinfo Array.
Jetzt noch eine Funktion um die Player, Frags usw… vom Server abfragen:
function players() { $challenge = $this->challenge(); $this->connect(); $this->send_strcmd($this->A2S_PLAYER.$challenge); $this->get_int32(); $this->get_byte(); $playercount = $this->get_byte(); for($i=1; $i <= $playercount; $i++) { $this->playerlist["index"][$i] = $this->get_byte(); $this->playerlist["name"][$i] = $this->get_string(); $this->playerlist["frags"][$i] = $this->get_int32(); $this->playerlist["time"][$i] = date('H:i:s', round($this->get_float32(), 0)+82800); } fclose($this->fp); return $this->playerlist; }
Wir machen das wieder so wie bei den Infos, nun hängen wir allerdings noch die Ticketnummer (challenge) an und rufen die Funktion auf um sie zu bekommen.
function cvars() { $challenge = $this->challenge(); $this->connect(); $this->send_strcmd($this->A2S_RULES.$challenge); $this->get_int32(); $this->get_byte(); $cvarcount = $this->get_int16(); for($i=1; $i <= $cvarcount; $i++) { $this->cvarlist[$this->get_string()] = $this->get_string(); } fclose($this->fp); return $this->cvarlist; }
Das ist das Gleiche wie bei den Playern, nur die Auswertfunktionen sind anders.
Das war es auch schon.
<?php /** * ============================================================================= * This class will get infos from a HL2 server over php * * @author HSFighter * @special thx to Chrisber * @version 1.1.0 * @package PHP Server-Query Script * @link http://sourceserver.info * * @version 1.1.0: serverstatus.class.php by hsfighter $ * ============================================================================= */ class HLServerAbfrage { var $server_address; var $ip; var $port; var $fp; var $challenge; var $serverinfo; var $playerlist; var $cvarlist; var $A2S_SERVERQUERY_GETCHALLENGE = "\x55\xFF\xFF\xFF\xFF"; // challenge var $A2S_INFO = "TSource Engine Query\x00"; // info var $A2S_PLAYER = "\x55"; // player var $A2S_RULES = "\x56"; // rules // IP und PORT trennen function hlserver($server_address = 0) { list($this->ip, $this->port) = explode(":", $server_address); } // Verbindung zum Server aufbauen function connect() { $this->fp = fsockopen("udp://".$this->ip, $this->port, $errno, $errstr, 3); if (!$this->fp) { $Fehler = 1; } } // String-Command" senden function send_strcmd($strcmd) { fwrite($this->fp, sprintf('%c%c%c%c%s%c', 0xFF, 0xFF, 0xFF, 0xFF, $strcmd, 0x00)); } // 1 Byte vom Server holen function get_byte() { return ord(fread($this->fp, 1)); } // 1 Zeichen (1 Byte) vom Server holen function get_char() { return fread($this->fp, 1); } // einen int16-Wert (2 Bytes) vom Server holen function get_int16() { $unpacked = unpack('sint', fread($this->fp, 2)); return $unpacked["int"]; } // einen int32-Wert (4 Bytes) vom Server holen function get_int32() { $unpacked = unpack('iint', fread($this->fp, 4)); return $unpacked["int"]; } // einen float32-Wert (4 Bytes) vom Server holen function get_float32() { $unpacked = unpack('fint', fread($this->fp, 4)); return $unpacked["int"]; } // einen String vom Server holen function get_string() { $str = ''; while(($char = fread($this->fp, 1)) != chr(0)) { $str .= $char; } return $str; } // 4 bytes von der challenge holen function get_4() { return fread($this->fp, 4); } // Challenger vom Server holen function challenge() { $this->connect(); $this->send_strcmd($this->A2S_SERVERQUERY_GETCHALLENGE); $this->get_int32(); $this->get_byte(); $challenge = $this->get_4(); fclose($this->fp); return $challenge; } // Infos vom Server holen function infos() { $this->connect(); $this->send_strcmd($this -> A2S_INFO); $this->get_int32(); $this->get_byte(); $this->serverinfo["network_version"] = $this->get_byte(); $this->serverinfo["name"] = $this->get_string(); $this->serverinfo["map"] = $this->get_string(); $this->serverinfo["directory"] = $this->get_string(); $this->serverinfo["discription"]= $this->get_string(); $this->serverinfo["steam_id"] = $this->get_int16(); $this->serverinfo["players"] = $this->get_byte(); $this->serverinfo["maxplayers"] = $this->get_byte(); $this->serverinfo["bot"] = $this->get_byte(); $this->serverinfo["dedicated"] = $this->get_char(); $this->serverinfo["os"] = $this->get_char(); $this->serverinfo["password"] = $this->get_byte(); $this->serverinfo["secure"] = $this->get_byte(); $this->serverinfo["version"] = $this->get_string(); fclose($this->fp); return $this->serverinfo; } // Player-Liste vom Server holen function players() { $challenge = $this->challenge(); $this->connect(); $this->send_strcmd($this->A2S_PLAYER.$challenge); $this->get_int32(); $this->get_byte(); $playercount = $this->get_byte(); for($i=1; $i <= $playercount; $i++) { $this->playerlist["index"][$i] = $this->get_byte(); $this->playerlist["name"][$i] = $this->get_string(); $this->playerlist["frags"][$i] = $this->get_int32(); $this->playerlist["time"][$i] = date('H:i:s', round($this->get_float32(), 0)+82800); } fclose($this->fp); return $this->playerlist; } // Rules-Liste (CVARs) vom Server holen function cvars() { $challenge = $this->challenge(); $this->connect(); $this->send_strcmd($this->A2S_RULES.$challenge); $this->get_int32(); $this->get_byte(); $cvarcount = $this->get_int16(); for($i=1; $i <= $cvarcount; $i++) { $this->cvarlist[$this->get_string()] = $this->get_string(); } fclose($this->fp); return $this->cvarlist; } } ?>
Nun muss nur noch auf die Klasse zugegriffen werden:
<?php // Server Angaben $ip = '176.57.137.35'; $port = '27015'; $mappic_path = 'http://meine-hp.de/mappics/css/'; // Klasse Laden include ("serverstatus.class.php"); // Verbindung zum Server herstellen $verbindung = new HLServerAbfrage; $verbindung -> hlserver($ip.':'.$port); // Serverinfos abfragen $infos = $verbindung->infos(); $infos["os"] = str_replace("l", 'Linux', $infos["os"]); $infos["os"] = str_replace("w", 'Windows', $infos["os"]); $infos["password"] = str_replace("1", 'Yes', $infos["password"]); $infos["password"] = str_replace("0", 'No', $infos["password"]); $infos["secure"] = str_replace("1", 'VAC2', $infos["secure"]); $infos["secure"] = str_replace("0", 'None', $infos["secure"]); $infos["dedicated"] = str_replace("l", 'Listen', $infos["dedicated"]); $infos["dedicated"] = str_replace("d", 'Dedicated', $infos["dedicated"]); $infos["dedicated"] = str_replace("p", 'SourceTV', $infos["dedicated"]); // Serverinfos abfragen echo 'Name: '.$infos["name"].'<br>'; echo 'IP: '.$ip.'<br>'; echo 'PORT: '.$port.'<br>'; echo 'OS: '.$infos["os"].'<br>'; echo 'Dedicated: '.$infos["dedicated"].'<br>'; echo 'Version: '.$infos["version"].'<br>'; echo 'Discription: '.$infos["discription"].'<br>'; echo 'Map: '.$infos["map"].'<br>'; echo 'Players: '.$infos["players"].'<br>'; echo 'Max. Players: '.$infos["maxplayers"].'<br>'; echo 'Bots: '.$infos["bot"].'<br>'; echo 'VAC: '.$infos["secure"].'<br>'; echo 'Password: '.$infos["password"].'<br>'; echo '<IMG src="'.$mappic_path.$infos["map"].'.jpg" border=0><br>'; // Playerinfos abfragen $players = $verbindung->players(); if (isSet($players)){ arsort ($players["frags"]); // Player nach Frags sortieren $x = 0; foreach($players["frags"] as $key => $value) { $x++; Echo '#'.$x.' -- '; // Nummer Echo ''.$players["index"][$key].' -- '; // Index Echo ''.$players["name"][$key].' -- '; // Name Echo ''.$players["frags"][$key].' -- '; // Frags Echo ''.$players["time"][$key].' -- '; // Zeit Echo '<br>'; } }else{ echo "server leer"; } // Cvarinfos abfragen $nextmap = 'Unknow!!!'; $cvars = $verbindung->cvars(); // Schleife um das Array aus zu lesen foreach($cvars as $key => $value) { // Prüfung des Key's. if ($key == 'amx_nextmap')$nextmap = $value; if ($key == 'cm_nextmap')$nextmap = $value; if ($key == 'am_nextmap')$nextmap = $value; if ($key == 'mp_nextmap')$nextmap = $value; if ($key == 'mani_nextmap')$nextmap = $value; // Anzeige aller Cvars!!! Echo ''.$key.' = '.$value.'<br>'; } Echo '<br>Next map is: <b>'.$nextmap.'</b><br>'; ?>
Danke auch an Chrisber. Ohne ihn hätte ich dieses Tut nicht schrieben können.
Auf dieses Tutorial darf frei Verlinkt werden.
So lange es hier im Wiki auf sourceserver.info verbleibt.
Wenn ihr das Tutorial oder Teile davon wo anders verwenden möchtet, bitte eine PM an HSFighter!