Download | Plain Text | Line Numbers
<?php
/* vim: set expandtab tabstop=2 shiftwidth=2 softtabstop=2 foldmethod=marker: */
/*
* Gameserver Query Protocol Class
* Copyright (C) 2004 Manuel Mausz ( manuel @ clanserver.eu )
* ClanServer - Just Gaming!
* http://www.clanserver.eu
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* {{{ Description
* Gameserver Query Protocol Class
*
* Queries different gameservers and returns the current
* status information. supports several query protocols.
*
* Supported Protocols:
* For further information: http://www.int64.org/protocols/
* - Half-Life/Half-Life: Source ("HalfLife")
* - All Seeing Eye ("AllSeeingEye")
* - Quake 1/Quake World ("Quake1")
* - Quake 2 ("Quake2")
* - Quake 3 ("Quake3")
* - Doom 3/Quake 4/Enemy Territory: Quake Wars ("Doom3")
* - GameSpy/UT ("GameSpy")
* - GameSpy 2 ("GameSpy2")
* - GameSpy 3 ("GameSpy3")
* - GameSpy 4 ("GameSpy4")
*
* Users:
* $this->SetProtocol("protocol")
* ... protocol to use for query
*
* $this->SetIpPort("ip:port:queryport")
* ... ip, port and queryport to use for query
* the queryport is optional
*
* $this->SetLocalQuery(0 or 1)
* ... set localhost query (used for halflife query)
*
* $this->SetRequestData(array("infotype1", "infotype2", ...))
* ... data to request from server. must be type array
* use the alias "FullInfo" for full information
*
* array = $this->GetData()
* ... request the data from gameserver
* data will be returned as type array
*
* bool = $this->ERROR
* ... TRUE if an error occur, FALSE otherwise
*
* string = $this->ERRSTR
* ... contains the error message as string
*
* Advanced Users:
* $this->SetSocketTimeOut(int seconds, int microseconds)
* ... sets socket timeout. default: 0s, 50000ms
*
* $this->SetSocketLoopTimeOut(int microseconds)
* ... sets loop timeout in microseconds. default: 1000
*
* Debug Modus:
* define("DEBUG", TRUE)
* ... enables debug modus
*
* AutoDetection:
* $this->SetProtocol("AutoDetect")
* ... must be set to "AutoDetect"
* $this->SetProtocols(array("protocol1", "protocol2", ...))
* ... protocols to use for autodetection
* ... NOTE: GameSpyPortPlus10 is an alias for GameSpy but uses QueryPort+10
*
* Developers:
* This class contains an autodetect-query-protocol-engine
* If you want to add your own query, you will have to name your methodes like:
* _YourNameMain() ... main methode. will be called first from engine
* _YourNameString1() ... sub methode. must be called from main methode
* will request string1 from gameserver (eg: _YourNameDetails)
* _YourNameAutoDetect() ... will be called from autodetection engine before _YourNameMain
* normally used for setting correct query port
* if you don't want autodetection, don't define this methode
* _YourNameFullInfo() ... wrapper for requestdatatype "FullInfo"
*
}}} */
/* {{{ ToDo:
* - HalfLife/HalfLifeSource
* - Sometimes query comes in fragments prefixed by an id
* syntax: "<id>2" -> 02, 12, 22, ... (not sure about that)
* this behavior is only recognized in _HalfLifeRules()
* -> append fragments depending on their queryid
* - GameSpy
* - If one player name contains the string deliminater "\", the query will probably stop working
* - Protocol is fragmental and will send a queryid followed by a number at the end
* -> append fragments depending on their queryid
* - Better Error Handling!
}}} */
class GSQuery
{
// {{{ global variable definitions
var $ERROR;
var $ERRSTR;
var $DEBUG;
var $_ip;
var $_port;
var $_queryport;
var $_fullinfo;
var $_localquery;
var $_socket;
var $_stimeout;
var $_mstimeout;
var $_looptimeout;
var $_protocol;
var $_gettypes;
var $_protocols;
// }}}
// {{{ GSQuery() - main class constructor
function GSQuery()
{
$this->ERROR = FALSE;
$this->ERRSTR = "";
$this->DEBUG = FALSE;
$this->_ip = 0;
$this->_port = 0;
$this->_queryport = 0;
$this->_fullinfo = 0;
$this->_localquery = 0;
$this->_socket = 0;
$this->_stimeout = 0;
$this->_mstimeout = 50000;
$this->_looptimeout = 1000;
$this->_protocol = "";
$this->_gettypes = array();
$this->_protocols = array();
$this->_globalvars = array();
if (defined('DEBUG') && DEBUG)
$this->DEBUG = TRUE;
set_error_handler(array(&$this, "_ErrorHandler"));
return TRUE;
}
// }}}
// {{{ SetProtocol() - sets query protocol to use
// @param string $arg1 protocol to use for gameserver query
// @return bool always TRUE
// @access public
function SetProtocol($string)
{
$this->_protocol = $string;
trigger_error("<b>Set Protocol</b>: ".$string);
return TRUE;
}
// }}}
// {{{ SetIpPort() - sets ip, port and optional queryport
// @param string $arg1 format: "ip:port" or "ip:port:queryport"
// @return bool always TRUE
// @access public
function SetIpPort($string)
{
if (substr_count($string, ":") == 2)
list($this->_ip, $this->_port, $this->_queryport) = explode(":", $string);
else
{
list($this->_ip, $this->_port) = explode(":", $string);
$this->_queryport = 0;
}
$this->_port = intval($this->_port);
$this->_queryport = intval($this->_queryport);
trigger_error("<b>Set Gameserver IP</b>: ".$this->_ip);
trigger_error("<b>Set Gameserver Port</b>: ".$this->_port);
if ($this->_queryport != 0)
trigger_error("<b>Set Gameserver QueryPort</b>: ".$this->_queryport);
return TRUE;
}
// }}}
// {{{ SetRequestData() - sets data which query will request from gameserver
// @param array $arg1 data to request from gameserver
// @return bool always TRUE
// @access public
function SetRequestData($gettypes)
{
if (!is_array($gettypes))
{
$this->_SoftError("SetRequestData(): argument 1 is not an array");
return FALSE;
}
$this->_gettypes = $gettypes;
if ($this->_gettypes[0] == "FullInfo")
$this->_fullinfo = 1;
foreach ($this->_gettypes as $type)
trigger_error("<b>Set RequestData</b>: ".$type);
return TRUE;
}
// }}}
// {{{ SetProtocols() - set autodetect protocols
// @param array $arg1 protocols to autodetect
// @return bool always TRUE
// @access public
function SetProtocols($protocols)
{
if (!is_array($protocols))
{
$this->_SoftError("SetProtocols(): argument 1 is not an array");
return FALSE;
}
$this->_protocols = $protocols;
foreach ($this->_protocols as $type)
trigger_error("<b>Set AutoDetect Protocols</b>: ".$type);
return TRUE;
}
// }}}
// {{{ SetLocalQuery() - set local query (used for halflife query)
// @param int 0 or 1
// @return bool always TRUE
// @access public
function SetLocalQuery($enable)
{
$this->_localquery = $enable;
trigger_error("<b>Set Local Query</b>: ".$this->_localquery);
return TRUE;
}
// }}}
// {{{ SetSocketTimeOut() - sets an optional socket timeout
// @param int $arg1 socket timeout in seconds
// int $arg2 socket timeout in microseconds
// @return bool always TRUE
// @access public
function SetSocketTimeOut($stimeout, $mstimeout)
{
$this->_stimeout = $stimeout;
$this->_mstimeout = $mstimeout;
trigger_error("<b>Set Socket Timeout Seconds</b>: ".$this->_stimeout);
trigger_error("<b>Set Socket Timeout MicroSeconds</b>: ".$this->_mstimeout);
return TRUE;
}
// }}}
// {{{ SetSocketLoopTimeOut() - sets an optional socket loop timeout
// @param int $arg1 socket loop timeout in microseconds
// @return bool always TRUE
// @access public
function SetSocketLoopTimeOut($timeout)
{
$this->_looptimeout = $timeout;
trigger_error("<b>Set Socket Loop Timeout</b>: ".$this->_looptimeout);
return TRUE;
}
// }}}
// {{{ GetData() - requests data from gameserver and returns an array
// @return array requested data as array in format: $array["data1"][...]
// bool FALSE if an error occur
// @access public
function GetData()
{
$recvdata = array();
$this->_CheckSettings();
if ($this->ERROR)
return FALSE;
if ($this->_fullinfo && $this->_protocol != "AutoDetect")
{
$recvdata = call_user_func(array(&$this, "_".$this->_protocol."FullInfo"));
$recvdata["Protocol"] = $this->_protocol;
}
else
$recvdata = call_user_func(array(&$this, "_".$this->_protocol."Main"));
$this->_CleanUP();
return $recvdata;
}
// }}}
// {{{ _GetMicroTime() - returns current timestamp as microseconds
// @return float timestamp as micrososeconds
// @access protected
function _GetMicroTime()
{
list($usec, $sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
}
// }}}
// {{{ _CleanUp() - class clean up
// @return bool always TRUE
// @access protected
function _CleanUp()
{
restore_error_handler();
return TRUE;
}
// }}}
// {{{ _CheckSettings() - check for valid settings
// @return bool TRUE if no error occur
// FALSE otherwise
// @access protected
function _CheckSettings()
{
$type = "";
// check available protocol
if ($this->_protocol == "")
{
$this->_SoftError("No protocol set");
return FALSE;
}
if (!is_callable(array(&$this, "_".$this->_protocol."Main")))
{
$this->_SoftError("\"".$this->_protocol."\" protocol not available");
return FALSE;
}
// check available methodes for this protocol
if (empty($this->_gettypes))
{
$this->_SoftError("No requesttypes set");
return FALSE;
}
if ($this->_protocol != "AutoDetect")
{
foreach ($this->_gettypes as $type)
{
if ($type != "RCon")
{
if (!is_callable(array(&$this, "_".$this->_protocol.$type)))
{
$this->_SoftError("SetRequestData() Type: \"".$type."\" now known in Protocol: \"".$this->_protocol."\"");
return FALSE;
}
}
}
}
// check ip and port
if ($this->_ip == "" || $this->_port == "")
{
$this->_SoftError("No IP or Port set");
return FALSE;
}
if (!preg_match("/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/", gethostbyname($this->_ip)))
{
$this->_SoftError("Wrong Gameserver IP Format");
return FALSE;
}
if (!is_integer($this->_port) || $this->_port <= 0 || 65536 < $this->_port)
{
$this->_SoftError("Wrong Gameserver Port Format");
return FALSE;
}
if ($this->_queryport != 0 && (!is_integer($this->_queryport) || $this->_queryport <= 0 || 65536 < $this->_queryport))
{
$this->_SoftError("Wrong Gameserver QueryPort Format");
return FALSE;
}
// check and calculate socket loop timeout
if ($this->_looptimeout < 500) $this->_looptimeout = 500;
if ($this->_looptimeout > 2000) $this->_looptimeout = 2000;
$this->_looptimeout = doubleval($this->_looptimeout / 1000.0);
return TRUE;
}
// }}}
// {{{ _ErrorHandler() - main php error handler
// @param -> reference to php documentation
// @return bool always TRUE
// stops script if error number is E_USER_ERROR
// @access protected
function _ErrorHandler($errno, $errstr, $errfile, $errline)
{
switch ($errno) {
case E_USER_ERROR:
echo "<b>[FATAL]</b> ".$errstr." in <b>".$errfile."</b> on line <b>".$errline."</b><br />\n";
exit(1);
break;
case E_USER_WARNING:
echo "<b>[ERROR]</b> ".$errstr." in <b>".$errfile."</b> on line <b>".$errline."</b><br />\n";
break;
case E_USER_NOTICE:
if ($this->DEBUG)
echo "<b>[DEBUG]</b> ".$errstr."</b>\n";
break;
case E_NOTICE:
echo "<b>[NOTICE]</b> ".$errstr." in <b>".$errfile."</b> on line <b>".$errline."</b><br />\n";
break;
default:
echo "<b>[UNKNOWN]</b> ".$errstr." in <b>".$errfile."</b> on line <b>".$errline."</b><br />\n";
break;
}
return TRUE;
}
// }}}
// {{{ _SoftError() - soft error handler
// @param string $arg1 error message
// @return bool always TRUE
// @access proteced
function _SoftError($errstr)
{
$this->ERROR = ($errstr === false || strlen($errstr) === 0) ? FALSE : TRUE;
$this->ERRSTR = $errstr;
return TRUE;
}
// }}}
// {{{ _CreateUDPSocket() - creates an udp socket
// @return bool always TRUE
// stops script if an error occur
// @access protected
function _CreateUDPSocket()
{
if ($this->_queryport == 0)
$this->_queryport = $this->_port;
if (!$this->_socket = fsockopen("udp://".$this->_ip, $this->_queryport, $errnr, $errstr))
{
trigger_error("Could not create socket (".$errstr.")", E_USER_ERROR);
}
socket_set_blocking($this->_socket, TRUE);
socket_set_timeout($this->_socket, $this->_stimeout, $this->_mstimeout);
return TRUE;
}
// }}}
// {{{ _SendSocket() - write data to socket
// @param string $arg1 string to write to socket
// @return bool always TRUE
// stops script if an error occur
// @access protected
function _SendSocket($string)
{
if (!fwrite($this->_socket, $string, strlen($string)))
trigger_error("Could not send data", E_USER_ERROR);
return TRUE;
}
// }}}
// {{{ _GetSocketData() - read data from socket
// @return string data received from socket buffer
// bool FALSE and triggers _SoftError() if an error occur
// @access protected
function _GetSocketData()
{
$recv = "";
$socketstatus = array();
$start = $this->_GetMicroTime();
do
{
$recv .= fgetc($this->_socket);
$socketstatus = socket_get_status($this->_socket);
if ($this->_GetMicroTime() > ($start + $this->_looptimeout))
{
$this->_CloseUDPSocket();
$this->_SoftError("Connection to server timeout out");
return FALSE;
}
}
while ($socketstatus["unread_bytes"]);
if ($recv == "")
$this->_SoftError("Nothing received from server");
return $recv;
}
// }}}
// {{{ _GetSocketDataNr() - read multiple times from socket
// @param int $arg1 number how often _GetSocketData() will be called
// @return array one array element for every received data
// triggers _SoftError() if no data will be received
// @access protected
function _GetSocketDataNr($nr)
{
$recv = "";
$data = array();
for ($i = 0; $i < $nr; $i++)
{
$recv = $this->_GetSocketData();
if ($recv != "")
array_push($data, $recv);
}
if (count($data))
$this->_SoftError(FALSE);
return $data;
}
// }}}
// {{{ _CloseUDPSocket() - close an udp socket
// @return bool always TRUE
// stops script if socket cannot be closed
// @access protected
function _CloseUDPSocket()
{
if (!fclose($this->_socket))
trigger_error("Could not close socket", E_USER_ERROR);
return TRUE;
}
// }}}
// {{{ _CheckQueryHeader() - checks for query header
// @param string $arg1 string to search in. string will be shortened automatically
// string $arg2 string to search for
// string $arg3 will be set to the snippet-string off $arg1
// @return bool TRUE if $arg2 was found
// FALSE otherwise
// @access protected
function _CheckQueryHeader(&$data, $header, &$snippet)
{
$offset = 0;
$snippet = "";
if ($this->ERROR)
return FALSE;
if (($offset = strpos($data, $header)) !== FALSE)
{
$snippet = substr($data, 0, $offset);
$data = substr($data, $offset + strlen($header));
return TRUE;
}
$this->_SoftError("No query header found in received data");
return FALSE;
}
// }}}
// {{{ _CheckQueryFooter() - checks for query footer
// @param string $arg1 string to search in. string will be shortened automatically
// string $arg2 string to search for
// string $arg3 will be set to the snippet-string off $arg1
// @return bool TRUE if $arg2 was found
// FALSE otherwise
// @access protected
function _CheckQueryFooter(&$data, $footer, &$snippet)
{
$offset = 0;
$snippet = "";
if ($this->ERROR)
return FALSE;
if (($offset = strpos($data, $footer)) !== FALSE)
{
$snippet = substr($data, $offset + strlen($footer));
$data = substr($data, 0, $offset);
return TRUE;
}
$this->_SoftError("No query footer found in received data");
return FALSE;
}
// }}}
// {{{ _GetCharacterTerminatedString() - get a character-terminated-string
// @param string $arg1 string to search in. string will be shortened automatically
// @param char $arg2 character to search for
// @return string first character-terminated string
// @access protected
function _GetCharacterTerminatedString(&$data, $chr)
{
$str = "";
$counter = 0;
while ((strlen($data) > $counter) && ($data{$counter++} != $chr))
$str .= $data{$counter-1};
$data = substr($data, strlen($str) + 1);
return $str;
}
// }}}
// {{{ _GetDelimitedVariables() - splits a string delimated by a character
// @param string $arg1 string to search in
// @return array splitted array. format: $data["before_character"] = after_character
// @access protected
function _GetDelimitedVariables(&$data, $delimiter)
{
$name = "";
$value = "";
$vars = array();
$name = strtok($data, $delimiter);
$value = strtok($delimiter);
while (strlen($name))
{
$vars[$name] = $value;
$name = strtok($delimiter);
$value = strtok($delimiter);
}
return $vars;
}
// }}}
// {{{ _GetByteAsChr() - get one byte as character
// @param string $arg1 string to search in. string will be shortened automatically
// @return string first byte of string as ascii character
// bool FALSE if length of $arg1 is zero
// @access protected
function _GetByteAsChr(&$data)
{
$str = "";
if (!strlen($data))
return FALSE;
$str = $data{0};
$data = substr($data, 1);
return $str;
}
// }}}
// {{{ _GetByteAsAscii() - get one byte as ascii value
// @param string $arg1 string to search in. string will be shortened automatically
// @return string first byte of string as type ascii value
// @access protected
function _GetByteAsAscii(&$data)
{
$str = "";
$str = ord($this->_GetByteAsChr($data));
return $str;
}
// }}}
// {{{ _GetStringByLength() - get a string by length
// @param string $arg1 string to snip. string will be shortened automatically
// int $arg2 length to snip off
// @return string snippet string
// @access protected
function _GetStringByLength(&$data, $length)
{
$str = "";
$str = substr($data, 0, $length);
$data = substr($data, strlen($str));
return $str;
}
// }}}
// {{{ _GetInt16AsInt() - get int16 value
// @param string $arg1 string to search in. string will be shortened automatically
// @return int corresponding int16 value
// bool FALSE if length of $arg1 is too short
// @access proteced
function _GetInt16AsInt(&$data)
{
$str = "";
if (strlen($data) < 2)
return FALSE;
$str = $this->_GetByteAsChr($data).$this->_GetByteAsChr($data);
$str = unpack('sint', $str);
return $str["int"];
}
// }}}
// {{{ _GetInt32AsInt() - get int32 value
// @param string $arg1 string to search in. string will be shortened automatically
// @return int corresponding int32 value
// bool FALSE if length of $arg1 is too short
// @access proteced
function _GetInt32AsInt(&$data)
{
$str = "";
if (strlen($data) < 4)
return FALSE;
$str = $this->_GetByteAsChr($data).$this->_GetByteAsChr($data).$this->_GetByteAsChr($data).$this->_GetByteAsChr($data);
$str = unpack('iint', $str);
return $str["int"];
}
// }}}
// {{{ _GetFloat32AsFloat() - get float32 value
// @param string $arg1 string to search in. string will be shortened automatically
// @return float corresponding float32 value
// bool FALSE if length of $arg1 is too short
// @access proteced
function _GetFloat32AsFloat(&$data)
{
$str = "";
if (strlen($data) < 4)
return FALSE;
$str = $this->_GetByteAsChr($data).$this->_GetByteAsChr($data).$this->_GetByteAsChr($data).$this->_GetByteAsChr($data);
$str = unpack('fint', $str);
return $str["int"];
}
// }}}
// {{{ _GetLongAsLong() - get long value
// @param string $arg1 string to search in. string will be shortened automatically
// @return long corresponding long value
// bool FALSE if length of $arg1 is too short
// @access proteced
function _GetLongAsLong(&$data)
{
$str = "";
if (strlen($data) < 4)
return FALSE;
$str = $this->_GetByteAsChr($data).$this->_GetByteAsChr($data).$this->_GetByteAsChr($data).$this->_GetByteAsChr($data);
$str = unpack('llong', $str);
return $str["long"];
}
// }}}
// {{{ _HexDump() - dump bytes as hex
// @param string $arg1 data to dump
// @return bool TRUE
// @access proteced
function _HexDump($data)
{
echo "Length: ".strlen($data)."\n";
$cache = "";
for ($i = 0; $i < strlen($data); $i++)
{
if ($i % 16 == 0)
printf("%08x ", $i);
elseif ($i % 8 == 0)
echo " ";
else
echo " ";
$cache .= $data{$i};
printf("%02x", ord($data{$i}));
if (strlen($cache) == 16 || $i == strlen($data) - 1)
{
if (strlen($cache) < 16)
{
$shift = "";
for ($j = strlen($cache); $j < 16; $j++)
{
if ($j % 8 == 0)
$shift .= " ";
$shift .= " ";
}
echo substr($shift, 0, strlen($shift) - 1);
if (strlen($shift) < 3*8)
echo " ";
}
echo " |";
for ($j = 0; $j < strlen($cache); $j++)
{
$chr = $cache{$j};
if (ord($chr) < ord("\x20") || ord($chr) > ord("\x7E"))
$chr = ".";
echo htmlentities($chr);
}
echo "|";
echo "\n";
$cache = "";
}
}
return TRUE;
}
// }}}
// {{{ _AutoDetectMain() - main methode of the autodetect routine
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _AutoDetectMain()
{
$protocol = "";
$subproto = "";
$data = array();
// first some checks
if (empty($this->_protocols))
{
$this->_SoftError("No autodetect protocols set");
return FALSE;
}
foreach ($this->_protocols as $protocol)
{
if (!is_callable(array(&$this, "_".$protocol."Main")))
{
$this->_SoftError("\"".$protocol."\" protocol not available");
return FALSE;
}
if (!is_callable(array(&$this, "_".$protocol."AutoDetect")))
{
$this->_SoftError("No Autodetection method available for Protocol: \"".$protocol."\"");
return FALSE;
}
foreach ($this->_gettypes as $subproto)
{
if (!is_callable(array(&$this, "_".$protocol.$subproto)))
{
$this->_SoftError("SetRequestData() Type: \"".$subproto."\" now known in Protocol: \"".$protocol."\"");
return FALSE;
}
}
}
if ($this->_queryport != 0)
{
$this->_SoftError("Never set a queryport if you use autodetection");
return FALSE;
}
trigger_error("<b>Starting AutoDetection</b>");
foreach ($this->_protocols as $type)
{
call_user_func(array(&$this, "_".$type."AutoDetect"));
$autodetect = new GSQuery();
$autodetect->SetProtocol($type);
$autodetect->SetIpPort($this->_ip.":".$this->_port.":".$this->_queryport);
$autodetect->SetRequestData($this->_gettypes);
$autodetect->SetSocketTimeOut($this->_stimeout, $this->_mstimeout);
$autodetect->SetSocketLoopTimeOut($this->_looptimeout);
$data = $autodetect->GetData();
$this->ERROR = $autodetect->ERROR;
$this->ERRSTR = $autodetect->ERRSTR;
if ($autodetect->ERROR == FALSE)
break;
}
return $data;
}
// }}}
// {{{ Query Protocol definitions
// {{{ Query Protocol: HalfLife
// {{{ _HalfLifeMain() - Query Protocol: HalfLife - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _HalfLifeMain()
{
$challenge = "\xFF\xFF\xFF\xFF";
$data = array();
$this->_CreateUDPSocket();
if (!$this->_localquery)
$challenge = call_user_func(array(&$this, "_".$this->_protocol."Challenge"));
foreach ($this->_gettypes as $type)
{
$data[$type] = call_user_func(array(&$this, "_".$this->_protocol.$type), $challenge);
if ($this->ERROR)
return FALSE;
}
$this->_CloseUDPSocket();
return $data;
}
/// }}}
// {{{ Query Protocol definitions
// {{{ Query Protocol: HalfLife
// {{{ _HalfLifeRecv() - Query Protocol: HalfLife - Type: Recv
// @access protected
function _HalfLifeRecv($query)
{
$this->_SendSocket($query);
$recv = "";
$packets = array();
$packetcnt = 1;
$compressed = false;
for ($i = 0; $i < $packetcnt; $i++)
{
$recv = implode("", $this->_GetSocketDataNr(1));
# split packet
if (substr($recv, 0, 4) == "\xFE\xFF\xFF\xFF")
{
$recv = substr($recv, 4);
$requestid = $this->_GetLongAsLong($recv); # maybe check too?
$compressed = (($requestid & (int)(1 << 31)) == (int)(1 << 31)) ? true : false;
if (isset($this->_globalvars["ProtocolVersion"]) && $this->_globalvars["ProtocolVersion"] == 48)
{
$tmp = $this->_GetByteAsAscii($recv);
$packetcnt = $tmp & 0xF;
$packetnum = ($tmp >> 4) & 0xF;
}
else
{
$packetcnt = $this->_GetByteAsAscii($recv);
$packetnum = $this->_GetByteAsAscii($recv);
}
# only in tf2 and newer source engines. should be harcoded?!
if (substr($recv, 0, 2) == "\xE0\x04")
$this->_GetInt16AsInt($recv);
if ($compressed)
{
$realsize = $this->_GetInt32AsInt($recv);
$realcrc32 = $this->_GetInt32AsInt($recv);
}
$packets[$packetnum] = $recv;
}
# single packet
elseif (substr($recv, 0, 4) == "\xFF\xFF\xFF\xFF")
{
$packets[0] = $recv;
}
# unknown
else
{
$this->_SoftError("Unknown header in half-life packet");
return FALSE;
}
}
$recv = implode("", $packets);
if ($compressed)
$recv = bzdecompress($recv);
return $recv;
}
/// }}}
// {{{ _HalfLifeDetails() - Query Protocol: HalfLife - Type: Server Details
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _HalfLifeDetails()
{
$recv = "";
$prefix = "";
$data = array();
$tmp = array();
$this->_SendSocket("\xFF\xFF\xFF\xFFTSource Engine Query\x00");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\xFF\xFF\xFF\xFF", $prefix))
return FALSE;
# Half-Life reply
if ($recv{0} == "m")
{
$recv = substr($recv, 1);
$tmp = explode(":", $this->_GetCharacterTerminatedString($recv, "\x00"));
$data["Ip"] = $tmp[0];
$data["Port"] = $tmp[1];
$data["Hostname"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$data["Map"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$data["GameDir"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$data["GameDesc"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$data["PlayerCount"] = $this->_GetByteAsAscii($recv);
$data["MaxPlayers"] = $this->_GetByteAsAscii($recv);
$data["ProtocolVersion"] = $this->_GetByteAsAscii($recv);
$data["ServerType"] = $this->_GetByteAsChr($recv);
$data["ServerOS"] = $this->_GetByteAsChr($recv);
$data["Password"] = $this->_GetByteAsAscii($recv);
$data["Modded"] = $this->_GetByteAsAscii($recv);
if ($data["Modded"])
{
$data["ModWebsite"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$data["ModDownloadServer"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$this->_GetCharacterTerminatedString($recv, "\x00");
$data["ModVersion"] = $this->_GetInt32AsInt($recv);
$data["ModSize"] = $this->_GetInt32AsInt($recv);
$data["ModServerSideOnly"] = $this->_GetByteAsAscii($recv);
$data["ModCustomDLL"] = $this->_GetByteAsAscii($recv);
}
$data["Secure"] = $this->_GetByteAsAscii($recv);
}
# Half-Life Source reply
elseif ($recv{0} == "I")
{
$recv = substr($recv, 1);
$data["ProtocolVersion"] = $this->_GetByteAsAscii($recv);
$this->_globalvars["ProtocolVersion"] = $data["ProtocolVersion"];
$data["Hostname"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$data["Map"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$data["GameDir"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$data["GameDesc"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$data["SteamAppID"] = $this->_GetInt16AsInt($recv);
$data["PlayerCount"] = $this->_GetByteAsAscii($recv);
$data["MaxPlayers"] = $this->_GetByteAsAscii($recv);
$data["BotCount"] = $this->_GetByteAsAscii($recv);
$data["ServerType"] = $this->_GetByteAsChr($recv);
$data["ServerOS"] = $this->_GetByteAsChr($recv);
$data["Password"] = $this->_GetByteAsAscii($recv);
$data["Secure"] = $this->_GetByteAsAscii($recv);
}
else
$this->_SoftError("Unknown reply from HalfLife");
return $data;
}
// }}}
// {{{ _HalfLifeChallenge() - Query Protocol: HalfLife - get challenge string for quering
// @return string challenge string
// @access protected
function _HalfLifeChallenge()
{
$recv = "";
$prefix = "";
$challenge = "";
// there's a bug in new hlds protocol which fucks up the challenge query.
// so fallback to A2S_PLAYER with -1 as challenge id */
//$this->_SendSocket("\xFF\xFF\xFF\xFFW");
$this->_SendSocket("\xFF\xFF\xFF\xFFV\xFF\xFF\xFF\xFF");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\xFF\xFF\xFF\xFFA", $prefix))
return FALSE;
$challenge = substr($recv, 0, 4);
return $challenge;
}
// }}}
// {{{ _HalfLifeRules() - Query Protocol: HalfLife - Type: Server Rules
// @param string challenge string
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _HalfLifeRules($challenge)
{
$prefix = "";
$recv = $this->_HalfLifeRecv("\xFF\xFF\xFF\xFFV".$challenge);
if (!$this->_CheckQueryHeader($recv, "\xFF\xFF\xFF\xFFE", $prefix))
return FALSE;
$data["RuleCount"] = $this->_GetInt16AsInt($recv);
for ($i = 0; $i < $data["RuleCount"]; $i++)
$data[$this->_GetCharacterTerminatedString($recv, "\x00")] = $this->_GetCharacterTerminatedString($recv, "\x00");
return $data;
}
// }}}
// {{{ _HalfLifePlayers() - Query Protocol: HalfLife - Type: Players
// @param string challenge string
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _HalfLifePlayers($challenge)
{
$prefix = "";
$recv = $this->_HalfLifeRecv("\xFF\xFF\xFF\xFFU".$challenge);
if (!$this->_CheckQueryHeader($recv, "\xFF\xFF\xFF\xFFD", $prefix))
return FALSE;
$data["PlayerCount"] = $this->_GetByteAsAscii($recv);
$data["Players"] = array();
for ($i = 0; $i < $data["PlayerCount"]; $i++)
{
$player = array();
$player["Number"] = $this->_GetByteAsAscii($recv);
$player["Name"] = $this->_GetCharacterTerminatedString($recv, "\x00");
$player["Score"] = $this->_GetInt32AsInt($recv);
$player["Time"] = round($this->_GetFloat32AsFloat($recv), 0) + 82800;
if ($player["Name"] == "" && $player["Score"] == 0 && $player["Time"] == 82800)
$player["Number"] = $player["Time"] = 0;
array_push($data["Players"], $player);
}
return $data;
}
// }}}
// {{{ _HalfLifeAutoDetect() - Query Protocol: HalfLife - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _HalfLifeAutoDetect()
{
$this->_queryport = $this->_port;
return TRUE;
}
// }}}
// {{{ _HalfLifeFullInfo() - Query Protocol: HalfLife - Type: Request Full Server Info
// @return array decoded data received from gameserver
// @access protected
function _HalfLifeFullInfo()
{
$this->SetRequestData(array("Details", "Rules", "Players"));
return $this->_HalfLifeMain();
}
// }}}
// }}}
// {{{ Query Protocol: AllSeeingEye
// {{{ _AllSeeingEyeMain() - Query Protocol: AllSeeingEye - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _AllSeeingEyeMain()
{
$recv = "";
$prefix = "";
$tmp = array();
$data = array();
$this->_CreateUDPSocket();
$this->_SendSocket("s");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "EYE1", $prefix))
return FALSE;
$tmp["Details"] = $this->_AllSeeingEyeDetails($recv);
$tmp["Players"] = $this->_AllSeeingEyePlayers($recv);
if ($this->ERROR)
return FALSE;
$this->_CloseUDPSocket();
foreach ($this->_gettypes as $type)
$data[$type] = $tmp[$type];
return $data;
}
// }}}
// {{{ _AllSeeingEyeDetails() - Query Protocol: AllSeeingEye - Type: Server Details
// @return array decoded data received from gameserver
// @access protected
function _AllSeeingEyeDetails(&$recv)
{
$tmp = "";
$data = array();
$data["GameName"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
$data["Port"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
$data["HostName"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
$data["GameTyp"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
$data["Map"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
$data["Version"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
$data["Password"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
$data["PlayerCount"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
$data["MaxPlayers"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
while (($tmp = $this->_GetByteAsAscii($recv)) != "1")
$data[$this->_GetStringByLength($recv, $tmp - 1)] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
return $data;
}
// }}}
// {{{ _AllSeeingEyePlayers() - Query Protocol: AllSeeingEye - Type: Players
// @return array decoded data received from gameserver
// @access protected
function _AllSeeingEyePlayers(&$recv)
{
$tmp = "";
$counter = 0;
$players = array();
if (strlen($recv) == 0)
return $players;
while(strlen($recv) > 0)
{
$tmp = $this->_GetByteAsAscii($recv);
if ($tmp & 1)
$players[$counter]["Name"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
if ($tmp & 2)
$players[$counter]["Team"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
if ($tmp & 4)
$players[$counter]["Skin"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
if ($tmp & 8)
$players[$counter]["Score"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
if ($tmp & 16)
$players[$counter]["Ping"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
if ($tmp & 32)
$players[$counter]["Time"] = $this->_GetStringByLength($recv, $this->_GetByteAsAscii($recv) - 1);
$counter++;
}
return $players;
}
// }}}
// {{{ _AllSeeingEyeAutoDetect() - Query Protocol: AllSeeingEye - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _AllSeeingEyeAutoDetect()
{
$this->_queryport = $this->_port + 123;
return TRUE;
}
// }}}
// {{{ _AllSeeingEyeFullInfo() - Query Protocol: AllSeeingEye - Type: Request Full Server Info
// @return array decoded data received from gameserver
// @access protected
function _AllSeeingEyeFullInfo()
{
$this->SetRequestData(array("Details", "Players"));
return $this->_AllSeeingEyeMain();
}
// }}}
// }}}
// {{{ Query Protocol: Quake1
// {{{ _Quake1Main() - Query Protocol: Quake1 - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _Quake1Main()
{
$recv = "";
$prefix = "";
$tmp = array();
$data = array();
$this->_CreateUDPSocket();
$this->_SendSocket("\xFF\xFF\xFF\xFFstatus");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\xFF\xFF\xFF\xFFn\\", $prefix))
return FALSE;
$tmp["Details"] = substr($recv, 0, strpos($recv, "\n"))."\\";
$tmp["Players"] = substr($recv, strpos($recv,"\n") + 1);
$tmp["Players"] = substr($tmp["Players"], 0, strlen($tmp["Players"]) - 1);
foreach ($this->_gettypes as $type)
{
$data[$type] = call_user_func(array(&$this, "_".$this->_protocol.$type), $tmp[$type]);
if ($this->ERROR)
return FALSE;
}
$this->_CloseUDPSocket();
return $data;
}
// }}}
// {{{ _Quake1Details() - Query Protocol: Quake1 - Type: Server Details
// @return array decoded data received from gameserver
// @access protected
function _Quake1Details($recv)
{
$data = array();
$data = $this->_GetDelimitedVariables($recv, "\\");
return $data;
}
// }}}
// {{{ _Quake1Players() - Query Protocol: Quake1 - Type: Players
// @return array decoded data received from gameserver
// @access protected
function _Quake1Players($recv)
{
$counter = 0;
$data = array();
$player = array();
$players = array();
if (strlen($recv) == 0)
return $players;
$data = explode("\n", $recv);
foreach ($data as $line)
{
if (strlen($line) == 0)
continue;
if (preg_match("/^([-0-9]+) ([-0-9]+) ([-0-9]+) ([-0-9]+) \"(.*)\" \"(.*)\" ([-0-9]+) ([-0-9]+)$/i", $line, $player))
{
$players[$counter]["Number"] = $player[1];
$players[$counter]["Score"] = $player[2];
$players[$counter]["Time"] = $player[3];
$players[$counter]["Ping"] = $player[4];
$players[$counter]["Name"] = $player[5];
$players[$counter]["Skin"] = $player[6];
$players[$counter]["Score1"] = $player[7];
$players[$counter]["Score2"] = $player[8];
$counter++;
}
else
$this->_SoftError("Unknown players data format received in Protocol: \"".$this->_protocol."\"");
}
return $players;
}
// }}}
// {{{ _Quake1AutoDetect() - Query Protocol: Quake1 - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _Quake1AutoDetect()
{
$this->_queryport = $this->_port;
return TRUE;
}
// }}}
// {{{ _Quake1FullInfo() - Query Protocol: Quake1 - Type: Request Full Server Info
// @return array decoded data received from gameserver
function _Quake1FullInfo()
{
$this->SetRequestData(array("Details", "Players"));
return $this->_Quake1Main();
}
// }}}
// }}}
// {{{ Query Protocol: Quake2
// {{{ _Quake2Main() - Query Protocol: Quake2 - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _Quake2Main()
{
$recv = "";
$prefix = "";
$tmp = array();
$data = array();
$this->_CreateUDPSocket();
$this->_SendSocket("\xFF\xFF\xFF\xFFstatus");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\xFF\xFF\xFF\xFFprint\x0A", $prefix))
return FALSE;
$tmp["Details"] = substr($recv, 0, strpos($recv, "\n"))."\\";
$tmp["Players"] = substr($recv, strpos($recv,"\n") + 1);
$tmp["Players"] = substr($tmp["Players"], 0, strlen($tmp["Players"]) - 1);
foreach ($this->_gettypes as $type)
{
$data[$type] = call_user_func(array(&$this, "_".$this->_protocol.$type), $tmp[$type]);
if ($this->ERROR)
return FALSE;
}
$this->_CloseUDPSocket();
return $data;
}
// }}}
// {{{ _Quake2Details() - Query Protocol: Quake2 - Type: Server Details
// @return array decoded data received from gameserver
// @access protected
function _Quake2Details($recv)
{
$data = array();
$data = $this->_GetDelimitedVariables($recv, "\\");
return $data;
}
// }}}
// {{{ _Quake2Players() - Query Protocol: Quake2 - Type: Players
// @return array decoded data received from gameserver
// @access protected
function _Quake2Players($recv)
{
$counter = 0;
$data = array();
$player = array();
$players = array();
if (strlen($recv) == 0)
return $players;
$data = explode("\n", $recv);
foreach ($data as $line)
{
if (strlen($line) == 0)
continue;
if (preg_match("/^([-0-9]+) ([-0-9]+) \"(.*)\"$/i", $line, $player))
{
$players[$counter]["Score"] = $player[1];
$players[$counter]["Ping"] = $player[2];
$players[$counter]["Name"] = $player[3];
$counter++;
}
else
$this->_SoftError("Unknown players data format received in Protocol: \"".$this->_protocol."\"");
}
return $players;
}
// }}}
// {{{ _Quake2AutoDetect() - Query Protocol: Quake2 - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _Quake2AutoDetect()
{
$this->_queryport = $this->_port;
return TRUE;
}
// }}}
// {{{ _Quake2FullInfo() - Query Protocol: Quake2 - Type: Request Full Server Info
// @return array decoded data received from gameserver
function _Quake2FullInfo()
{
$this->SetRequestData(array("Details", "Players"));
return $this->_Quake2Main();
}
// }}}
// }}}
// {{{ Query Protocol: Quake3
// {{{ _Quake3Main() - Query Protocol: Quake3 - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _Quake3Main()
{
$recv = "";
$prefix = "";
$tmp = array();
$data = array();
$this->_CreateUDPSocket();
$this->_SendSocket("\xFF\xFF\xFF\xFFgetstatus\x0A");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\xFF\xFF\xFF\xFFstatusResponse\x0A\\", $prefix))
return FALSE;
$tmp["Details"] = substr($recv, 0, strpos($recv, "\n"))."\\";
$tmp["Players"] = substr($recv, strpos($recv,"\n") + 1);
$tmp["Players"] = substr($tmp["Players"], 0, strlen($tmp["Players"]) - 1);
foreach ($this->_gettypes as $type)
{
$data[$type] = call_user_func(array(&$this, "_".$this->_protocol.$type), $tmp[$type]);
if ($this->ERROR)
return FALSE;
}
$this->_CloseUDPSocket();
return $data;
}
// }}}
// {{{ _Quake3Details() - Query Protocol: Quake3 - Type: Server Details
// @return array decoded data received from gameserver
// @access protected
function _Quake3Details($recv)
{
$data = array();
$data = $this->_GetDelimitedVariables($recv, "\\");
return $data;
}
// }}}
// {{{ _Quake3Players() - Query Protocol: Quake3 - Type: Players
// @return array decoded data received from gameserver
// @access protected
function _Quake3Players($recv)
{
$data = array();
$player = array();
$players = array();
if (strlen($recv) == 0)
return $players;
$data = explode("\n", $recv);
foreach ($data as $line)
{
if (strlen($line) == 0)
continue;
if (preg_match("/^([-0-9]+) ([-0-9]+) \"(.*)\"$/i", $line, $player))
array_push($players, array("frags" => $player[1], "ping" => $player[2], "name" => $player[3]));
elseif (preg_match("/^([-0-9]+) ([-0-9]+) ([-0-9]+) \"(.*)\"$/i", $line, $player))
array_push($players, array("frags" => $player[1], "ping" => $player[2], "deaths" => $player[3], "name" => $player[4]));
else
$this->_SoftError("Unknown players data format received in Protocol: \"".$this->_protocol."\"");
}
return $players;
}
// }}}
// {{{ _Quake3AutoDetect() - Query Protocol: Quake3 - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _Quake3AutoDetect()
{
$this->_queryport = $this->_port;
return TRUE;
}
// }}}
// {{{ _Quake3FullInfo() - Query Protocol: Quake3 - Type: Request Full Server Info
// @return array decoded data received from gameserver
// @access protected
function _Quake3FullInfo()
{
$this->SetRequestData(array("Details", "Players"));
return $this->_Quake3Main();
}
// }}}
// }}}
// {{{ Query Protocol: Doom3
// {{{ _Doom3Main() - Query Protocol: Doom3 - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _Doom3Main($protocol = 1)
{
$recv = "";
$prefix = "";
$tmp = array();
$data = array();
$this->_CreateUDPSocket();
$this->_SendSocket("\xFF\xFFgetInfo\x00\x00\x00\x00\x00");
$recv = implode("", $this->_GetSocketDataNr(5));
if (!$this->_CheckQueryHeader($recv, "\xFF\xFFinfoResponse\x00", $prefix))
return FALSE;
/* dirty hack to determine protocol version */
if ($tmp2 = strstr($recv, "si_version\x00"))
{
/* strip off si_version */
$this->_GetCharacterTerminatedString($tmp2, "\x00");
$version = $this->_GetCharacterTerminatedString($tmp2, "\x00");
if (strpos(strtolower($version), "doom") !== false)
$protocol = 1;
elseif (strpos(strtolower($version), "pray") !== false)
$protocol = 2;
elseif (strpos(strtolower($version), "quake4") !== false)
$protocol = 3;
elseif (strpos(strtolower($version), "etqw") !== false)
$protocol = 4;
}
if ($protocol == 4) /* ETQW */
$taskid = $this->_GetLongAsLong($recv);
$challenge = $this->_GetLongAsLong($recv);
$this->_protoversion = $this->_GetLongAsLong($recv);
$this->_protoversion = sprintf("%u.%u", $this->_protoversion >> 16, $this->_protoversion & 0xFFFF);
if ($protocol == 4) /* ETQW */
$this->_infosize = $this->_GetLongAsLong($recv);
$tmp["Details"] = $this->_Doom3Details($recv, $protocol);
if (!isset($tmp["Details"]["queryprotocol"]))
$tmp["Details"]["queryprotocol"] = $this->_protoversion;
$tmp["Players"] = $this->_Doom3Players($recv, $protocol);
$tmp["Details"]["osmask"] = $this->_GetLongAsLong($recv);
if ($protocol == 4) /* ETQW */
{
$tmp["Details"]["ranked"] = $this->_GetByteAsAscii($recv);
$tmp["Details"]["timeleft"] = $this->_GetInt32AsInt($recv);
$tmp["Details"]["gamestate"] = $this->_GetByteAsAscii($recv);
$tmp["Details"]["servertype"] = $this->_GetByteAsAscii($recv);
if ($tmp["Details"]["servertype"] == 0) /* regular server */
$tmp["Details"]["interested_clients"] = $this->_GetByteAsAscii($recv);
elseif ($tmp["Details"]["servertype"] == 0) /* tv server */
{
$tmp["Details"]["tv_numClients"] = $this->_GetByteAsAscii($recv);
$tmp["Details"]["tv_maxClients"] = $this->_GetByteAsAscii($recv);
}
}
if ($this->ERROR)
return FALSE;
$this->_CloseUDPSocket();
foreach ($this->_gettypes as $type)
$data[$type] = $tmp[$type];
return $data;
}
// }}}
// {{{ _Doom3Details() - Query Protocol: Doom3 - Type: Server Details
// @return array decoded data received from gameserver
// @access protected
function _Doom3Details(&$recv, $protocol = 1)
{
$key = "";
$data = array();
while (($key = $this->_GetCharacterTerminatedString($recv, "\x00")) !== FALSE)
{
$value = $this->_GetCharacterTerminatedString($recv, "\x00");
if ($key == "" && $value == "")
break;
$data[$key] = $value;
}
if ($protocol == 4) /* ETQW */
{
if (isset($data["si_map"]) && strpos($data["si_map"], ".entities") !== FALSE)
$data["si_map"] = substr($data["si_map"], 0, -9);
}
return $data;
}
// }}}
// {{{ _Doom3Players() - Query Protocol: Doom3 - Type: Players
// @return array decoded data received from gameserver
// @access protected
function _Doom3Players(&$recv, $protocol = 1)
{
$tmp = "";
$players = array();
if (strlen($recv) == 0)
return $players;
while (($number = $this->_GetByteAsAscii($recv)) != 32)
{
$players[$number]["Ping"] = $this->_GetByteAsAscii($recv) + $this->_GetByteAsAscii($recv) * 256;
if ($protocol != 4) /* ETQW */
$players[$number]["Rate"] = $this->_GetLongAsLong($recv);
$players[$number]["Name"] = $this->_GetCharacterTerminatedString($recv, "\x00");
if ($protocol == 4) /* ETQW */
{
$players[$number]["ClanTagPos"] = ($this->_GetByteAsAscii($recv) == 1) ? 1 : 0;
$players[$number]["ClanTag"] = $this->_GetCharacterTerminatedString($recv, "\x00");
}
if ($protocol == 3) /* Quake4 */
$players[$number]["Clantag"] = $this->_GetCharacterTerminatedString($recv, "\x00");
elseif ($protocol == 4) /* ETQW */
$players[$number]["Bot"] = ($this->_GetByteAsAscii($recv) == 1) ? 1 : 0;
}
return $players;
}
// }}}
// {{{ _Doom3AutoDetect() - Query Protocol: Doom3 - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _Doom3AutoDetect()
{
$this->_queryport = $this->_port;
return TRUE;
}
// }}}
// {{{ _Doom3FullInfo() - Query Protocol: Doom3 - Type: Request Full Server Info
// @return array decoded data received from gameserver
// @access protected
function _Doom3FullInfo($protocol = 1)
{
$this->SetRequestData(array("Details", "Players"));
return $this->_Doom3Main($protocol);
}
// }}}
// }}}
// {{{ Query Protocol: GameSpy
// {{{ _GameSpyMain() - Query Protocol: GameSpy - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpyMain()
{
$data = array();
$this->_CreateUDPSocket();
foreach ($this->_gettypes as $type)
{
$data[$type] = call_user_func(array(&$this, "_".$this->_protocol.$type));
if ($this->ERROR)
return FALSE;
}
$this->_CloseUDPSocket();
return $data;
}
// }}}
// {{{ _GameSpyDetails() - Query Protocol: GameSpy - Type: Server Details
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpyDetails()
{
$recv = "";
$suffix = "";
$data = array();
$this->_SendSocket("\\info\\");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryFooter($recv, "final\\", $suffix))
return FALSE;
$data = $this->_GetDelimitedVariables($recv, "\\");
return $data;
}
// }}}
// {{{ _GameSpyRules() - Query Protocol: GameSpy - Type: Server Rules
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpyRules()
{
$recv = "";
$suffix = "";
$data = array();
$this->_SendSocket("\\rules\\");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryFooter($recv, "final\\", $suffix))
return FALSE;
$data = $this->_GetDelimitedVariables($recv, "\\");
return $data;
}
// }}}
// {{{ _GameSpyPlayers() - Query Protocol: GameSpy - Type: Players
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpyPlayers()
{
$recv = "";
$suffix = "";
$type = "";
$tmp = "";
$counter = 0;
$key = 0;
$data = array();
$this->_SendSocket("\\players\\");
$recv = implode("", $this->_GetSocketDataNr(1));
while ($counter < 4)
{
if (!$this->_CheckQueryFooter($recv, "final\\", $suffix))
$recv .= implode("", $this->_GetSocketDataNr(1));
else
{
$recv .= "final\\";
break;
}
$counter++;
}
$recv = substr($recv, 1);
if (!$this->_CheckQueryFooter($recv, "final\\", $suffix))
return FALSE;
while(strlen($recv) > 0)
{
$tmp = $this->_GetCharacterTerminatedString($recv, "\\");
if (($counter = strrpos($tmp, "_")) !== FALSE)
{
$type = substr($tmp, 0, $counter);
$key = intval(substr($tmp, $counter+1));
$data[$key][$type] = $this->_GetCharacterTerminatedString($recv, "\\");
}
}
return $data;
}
// }}}
// {{{ _GameSpyAutoDetect() - Query Protocol: GameSpy - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _GameSpyAutoDetect($arg = 1)
{
$this->_queryport = $this->_port + 1;
return TRUE;
}
// }}}
// {{{ _GameSpyFullInfo() - Query Protocol: GameSpy - Type: Request Full Server Info
// @return array decoded data received from gameserver
// @access protected
function _GameSpyFullInfo()
{
$this->SetRequestData(array("Details", "Rules", "Players"));
return $this->_GameSpyMain();
}
// }}}
// {{{ _GameSpyPortPlus10Main() - Query Protocol: GameSpy Port+10 - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpyPortPlus10Main()
{
return $this->_GameSpyMain();
}
// }}}
// {{{ _GameSpyPortPlus10Details() - Query Protocol: GameSpy Port+10 - Type: Server Details
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpyPortPlus10Details()
{
return $this->_GameSpyDetails();
}
// }}}
// {{{ _GameSpyPortPlus10Rules() - Query Protocol: GameSpy Port+10 - Type: Server Rules
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpyPortPlus10Rules()
{
return $this->_GameSpyRules();
}
// }}}
// {{{ _GameSpyPortPlus10Players() - Query Protocol: GameSpy Port+10 - Type: Players
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpyPortPlus10Players()
{
return $this->_GameSpyPlayers();
}
// }}}
// {{{ _GameSpyPortPlus10AutoDetect() - Query Protocol: GameSpy Port+10 - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _GameSpyPortPlus10AutoDetect()
{
$this->_queryport = $this->_port + 10;
return TRUE;
}
// }}}
// {{{ _GameSpyPortPlus10FullInfo() - Query Protocol: GameSpy Port+10 - Type: Request Full Server Info
// @return array decoded data received from gameserver
// @access protected
function _GameSpyPortPlus10FullInfo()
{
return $this->_GameSpyFullInfo();
}
// }}}
// }}}
// {{{ Query Protocol: GameSpy2
// {{{ _GameSpy2Main() - Query Protocol: GameSpy2 - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy2Main()
{
$recv = "";
$prefix = "";
$tosend = "";
$data = array();
$tosend = "\xFE\xFD\x00\x04\x05\x06\x07";
$tosend .= (array_search("Details", $this->_gettypes) !== FALSE) ? "\xFF" : "\x00";
$tosend .= (array_search("Players", $this->_gettypes) !== FALSE) ? "\xFF" : "\x00";
$tosend .= (array_search("Teams", $this->_gettypes) !== FALSE) ? "\xFF" : "\x00";
$tosend .= "\x00\x00\x00";
$this->_CreateUDPSocket();
$this->_SendSocket($tosend);
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\x00\x04\x05\x06\x07", $prefix))
return FALSE;
if (substr($recv, 0, 8) == "splitnum")
$recv = substr($recv, 11);
foreach ($this->_gettypes as $type)
{
$data[$type] = call_user_func_array(array(&$this, "_".$this->_protocol.$type), array(&$recv));
if ($this->ERROR)
return FALSE;
}
$this->_CloseUDPSocket();
return $data;
}
// }}}
// {{{ _GameSpy2Details() - Query Protocol: GameSpy2 - Type: Server Details
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy2Details(&$recv)
{
$key = "";
$data = array();
while (($key = $this->_GetCharacterTerminatedString($recv, "\x00")) != "")
$data[$key] = $this->_GetCharacterTerminatedString($recv, "\x00");
if ($recv{0} == "\x00")
$recv = substr($recv, 1);
return $data;
}
// }}}
// {{{ _GameSpy2Players() - Query Protocol: GameSpy2 - Type: Players
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy2Players(&$recv)
{
$tmp = "";
$item = 0;
$counter = 0;
$keys = array();
$data = array();
if (strlen($recv) == 0)
return $data;
$data["PlayerCount"] = $this->_GetByteAsAscii($recv);
$tmp = $this->_GetByteAsAscii($recv);
if ($tmp == ord("p"))
$recv = chr($tmp).$recv;
else
$data["PlayerCount"] += $tmp;
while (($tmp = $this->_GetCharacterTerminatedString($recv, "\x00")) != "")
{
if (substr($tmp, strlen($tmp) - 1) == "_")
$tmp = substr($tmp, 0, strlen($tmp) - 1);
array_push($keys, $tmp);
}
while (($tmp = $this->_GetCharacterTerminatedString($recv, "\x00")) != "")
{
$data[$counter][$keys[$item]] = $tmp;
$item++;
if ($item == count($keys))
{
$item = 0;
$counter++;
}
}
return $data;
}
// }}}
// {{{ _GameSpy2Teams() - Query Protocol: GameSpy2 - Type: Teams
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy2Teams(&$recv)
{
$tmp = "";
$item = 0;
$counter = 0;
$keys = array();
$data = array();
if (strlen($recv) == 0)
return $data;
$numrules = $this->_GetByteAsAscii($recv);
while (($tmp = $this->_GetCharacterTerminatedString($recv, "\x00")) != "")
{
if (substr($tmp, strlen($tmp) - 1) == "_")
$tmp = substr($tmp, 0, strlen($tmp) - 1);
array_push($keys, $tmp);
}
while (($tmp = $this->_GetCharacterTerminatedString($recv, "\x00")) != "")
{
$data[$counter][$keys[$item]] = $tmp;
$item++;
if ($item == count($keys))
{
$item = 0;
$counter++;
}
}
return $data;
}
// }}}
// {{{ _GameSpy2AutoDetect() - Query Protocol: GameSpy2 - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _GameSpy2AutoDetect()
{
$this->_queryport = 23000;
return TRUE;
}
// }}}
// {{{ _GameSpy2FullInfo() - Query Protocol: GameSpy2 - Type: Request Full Server Info
// @return array decoded data received from gameserver
function _GameSpy2FullInfo()
{
$this->SetRequestData(array("Details", "Players", "Teams"));
return $this->_GameSpy2Main();
}
// }}}
// }}}
// {{{ Query Protocol: GameSpy3
// {{{ _GameSpy3Main() - Query Protocol: GameSpy3 - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy3Main($challenge = false)
{
$recv = "";
$recv_arr = array();
$recv_arr2 = array();
$prefix = "";
$tosend = "";
$data = array();
$end = 0;
$identifier = "\x04\x05\x06\x07";
$chall_str = "";
$this->_CreateUDPSocket();
if ($challenge)
{
$tosend = "\xFE\xFD\x09" . $identifier;
$this->_SendSocket($tosend);
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\x09" . $identifier, $prefix))
return FALSE;
$chall_str = pack("N", (int)$recv);
}
$tosend = "\xFE\xFD\x00" . $identifier . $chall_str;
$tosend .= (array_search("Details", $this->_gettypes) !== FALSE) ? "\xFF" : "\x00";
$tosend .= (array_search("Players", $this->_gettypes) !== FALSE) ? "\xFF" : "\x00";
$tosend .= (array_search("Teams", $this->_gettypes) !== FALSE) ? "\xFF" : "\x00";
$tosend .= "\x01";
$this->_SendSocket($tosend);
while(!$end)
{
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\x00" . $identifier, $prefix))
return FALSE;
$index = 0;
if (substr($recv, 0, 8) != "splitnum")
{
$end = 1;
}
else
{
$recv = substr($recv, 9);
$index = ord($recv{0});
if (ord($recv{0}) > ord("\x7F"))
{
$end = 1;
$index = count($recv_arr);
}
}
$recv_arr[$index] = substr($recv, 1);
}
$recv = "";
for ($i = 0; $i < count($recv_arr); $i++)
{
# we currently ignore the first byte
$recv_arr[$i] = substr($recv_arr[$i], 1);
$start = 0;
$end = strlen($recv_arr[$i]);
$recv_tmp = $recv_arr[$i];
# check head
$starttmp = $this->_GetCharacterTerminatedString($recv_tmp, "\x00");
if (substr($starttmp, -1) == "_")
$start = strlen($starttmp) + 2;
# check body
if (substr($recv_tmp, -2) != "\x00\x00")
{
$j = 0;
for($j = 2; (substr($recv_tmp, strlen($recv_tmp) - $j, 1) != "\x00" || $j == strlen($recv_tmp)); $j++);
$end = -$j + 1;
}
$recv .= substr($recv_arr[$i], $start, $end);
}
foreach ($this->_gettypes as $type)
{
$data[$type] = call_user_func_array(array(&$this, "_".$this->_protocol.$type), array(&$recv));
if ($this->ERROR)
return FALSE;
}
$this->_CloseUDPSocket();
return $data;
}
// }}}
// {{{ _GameSpy3Details() - Query Protocol: GameSpy2 - Type: Server Details
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy3Details(&$recv)
{
return $this->_GameSpy2Details($recv);
}
// }}}
// {{{ _GameSpy3Players() - Query Protocol: GameSpy3 - Type: Players
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy3Players(&$recv)
{
$data = $this->_GameSpy2Players($recv);
unset($data["PlayerCount"]);
return $data;
}
// }}}
// {{{ _GameSpy3Teams() - Query Protocol: GameSpy3 - Type: Teams
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy3Teams(&$recv)
{
$tmp = "";
$item = 0;
$counter = 0;
$data = array();
if (strlen($recv) == 0)
return $data;
while(strlen($recv))
{
$key = $this->_GetCharacterTerminatedString($recv, "\x00");
$this->_GetCharacterTerminatedString($recv, "\x00");
if (substr($key, -1) == "_")
$key = substr($key, 0, -1);
$counter = 0;
while (($tmp = $this->_GetCharacterTerminatedString($recv, "\x00")) != "")
{
$data[$counter][$key] = $tmp;
$counter++;
}
}
return $data;
}
// }}}
// {{{ _GameSpy3AutoDetect() - Query Protocol: GameSpy3 - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _GameSpy3AutoDetect()
{
$this->_queryport = $this->_port + 13333;
return TRUE;
}
// }}}
// {{{ _GameSpy3FullInfo() - Query Protocol: GameSpy3 - Type: Request Full Server Info
// @return array decoded data received from gameserver
// @access protected
function _GameSpy3FullInfo()
{
$this->SetRequestData(array("Details", "Players", "Teams"));
return $this->_GameSpy3Main(false);
}
// }}}
// }}}
// {{{ Query Protocol: GameSpy4
// {{{ _GameSpy4Main() - Query Protocol: GameSpy4 - Type: Main Methode
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy4Main()
{
return $this->_GameSpy3Main(true);
}
// }}}
// {{{ _GameSpy4Details() - Query Protocol: GameSpy2 - Type: Server Details
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy4Details(&$recv)
{
return $this->_GameSpy3Details($recv);
}
// }}}
// {{{ _GameSpy4Players() - Query Protocol: GameSpy4 - Type: Players
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy4Players(&$recv)
{
return $this->_GameSpy3Players($recv);
}
// }}}
// {{{ _GameSpy4Teams() - Query Protocol: GameSpy4 - Type: Teams
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _GameSpy4Teams(&$recv)
{
return $this->_GameSpy3Teams($recv);
}
// }}}
// {{{ _GameSpy4AutoDetect() - Query Protocol: GameSpy4 - Type: AutoDetect Methode
// @return bool always TRUE
// @access protected
function _GameSpy4AutoDetect()
{
return $this->_GameSpy3AutoDetect();
}
// }}}
// {{{ _GameSpy4FullInfo() - Query Protocol: GameSpy4 - Type: Request Full Server Info
// @return array decoded data received from gameserver
// @access protected
function _GameSpy4FullInfo()
{
$this->SetRequestData(array("Details", "Players", "Teams"));
return $this->_GameSpy3Main(true);
}
// }}}
// }}}
// }}}
}
/* {{{ Description
* RCon Query Protocol Class
*
* Adds RCon Support to the Gameserver Query Class
*
* Supported Protocols:
* - Half-Life ("HalfLife")
*
* Users:
* $this->SetRConPassword("rconpassword")
* ... rcon password to use for query
*
* $this->SetRConCommand("command")
* ... rcon command to send
*
* $this->SetRequestData(array("RCon"))
* ... "RCon" is the only available requestdata type
*
* Developers:
* This class extends the gameserver query protocol
* If you want to add your own rcon query, the only needed method must be named like:
* _RConYourNameMain() ... methode will be called automatically from engine
*
}}} */
class RCon extends GSQuery
{
// {{{ global variable definitions
var $_rconcommand;
var $_rconpassword;
// }}}
// {{{ RCon() - main class constructor
function RCon()
{
$_rconcommand = "";
$_rconpassword = "";
$this->GSQuery();
return TRUE;
}
// }}}
// {{{ SetProtocol() - sets query protocol to use
// @param string $arg1 protocol to use for gameserver query
// @return bool always TRUE
// @access public
function SetProtocol($string)
{
GSQuery::SetProtocol("RCon".$string);
return TRUE;
}
// }}}
// {{{ SetRConCommand() - sets rcon command to send
// @param string $arg1 rcon command to send to the gameserver
// @return bool always TRUE
// @access public
function SetRconCommand($string)
{
$this->_rconcommand = $string;
trigger_error("<b>Set RCon Command</b>: ".$string);
return TRUE;
}
// }}}
// {{{ SetRConPassword() - sets rcon password
// @param string $arg1 rcon password to use for rcon query
// @return bool always TRUE
// @access public
function SetRConPassword($string)
{
$this->_rconpassword = $string;
trigger_error("<b>Set RCon Password</b>: ".$string);
return TRUE;
}
// }}}
// {{{ _CheckSettings() - check for valid settings
// @return bool FALSE if an error occur
// TRUE otherwise
// @access protected
function _CheckSettings()
{
GSQuery::_CheckSettings();
if ($this->_rconcommand == "")
$this->_SoftError("No RCon Command set");
if ($this->_rconpassword == "")
$this->_SoftError("No RCon Password set");
if ($this->ERROR)
return FALSE;
return TRUE;
}
// }}}
// {{{ RCon Protocol: HalfLife
// {{{ _RConHalfLifeMain() - RCon Protocol: HalfLife
// @return array decoded data received from gameserver
// bool FALSE if an error occur
// @access protected
function _RConHalfLifeMain()
{
$recv = "";
$chnum = 0;
$data = array();
$this->_CreateUDPSocket();
$this->_SendSocket("\xFF\xFF\xFF\xFFchallenge rcon\x00");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\xFF\xFF\xFF\xFFchallenge rcon ", $prefix))
return FALSE;
$chnum = substr($recv, 0, strlen($recv) - 2);
$this->_SendSocket("\xFF\xFF\xFF\xFFrcon $chnum ".$this->_rconpassword." ".$this->_rconcommand."\n");
$recv = implode("", $this->_GetSocketDataNr(1));
if (!$this->_CheckQueryHeader($recv, "\xFF\xFF\xFF\xFF", $prefix))
return FALSE;
$data["RCon"] = substr($recv, 1, strlen($recv) - 3);
if ($data["RCon"] == "Bad rcon_password." || $data["RCon"] == "Bad challenge.")
{
$this->_SoftError("RCon Protocol: Half-Life - ".$data["RCon"]);
return FALSE;
}
$this->_CloseUDPSocket();
return $data;
}
// }}}
// }}}
}
echo "<pre>\n";
define("DEBUG", TRUE);
$query = new GSQuery();
#$query->SetProtocol("AutoDetect");
$query->SetProtocol("GameSpy3");
#$query->SetProtocol("Doom3");
#$query->SetProtocols(array("HalfLife", "Quake2", "Quake3", "GameSpy", "GameSpyPortPlus10", "GameSpy2", "GameSpy3", "GameSpy4", "AllSeeingEye", "Doom3"));
#$query->SetProtocols(array("Doom3"));
#$query->SetProtocols(array("Quake4"));
if (isset($_GET["queryport"]) && $_GET["queryport"] != "")
$query->SetIpPort($_GET["ip"].":".$_GET["port"].":".$_GET["queryport"]);
else
$query->SetIpPort($_GET["ip"].":".$_GET["port"]);
$query->SetRequestData(array("FullInfo"));
$query->SetSocketTimeOut(0, 100000);
$query->SetSocketLoopTimeOut(1000);
$data = $query->GetData();
print_r($data);
echo "</pre>\n";
?>