_ip = $ip; $this->_port = $port; $this->_timeout = $phpUA["CONFIG"]["TIMEOUT"]; } function _connect() { if (!$this->_socket = @fsockopen("udp://" . $this->_ip, $this->_port, $errno, $errstr, $this->_timeout)) return false; return true; } function _disconnect() { if (!fclose($this->_socket)) return false; return true; } function _writeData($command) { // Messages are sent to the server by sending 4 consecutive bytes of 255 (32-bit integer -1) // and then the string command followed by a zero byte to terminate it if (!fwrite($this->_socket, "\xFF\xFF\xFF\xFF" . $command . "\x00")) return false; return true; } function _readData() { // The first byte of the datagram will tell us if it has been split or not. socket_set_timeout($this->_socket, $this->_timeout); $data = fread($this->_socket, 1); if (socket_timeout($this->_socket)) return false; switch (ord($data)) { case 255: // Just one datagram $status = socket_get_status($this->_socket); socket_set_timeout($this->_socket, $this->_timeout); $data .= fread($this->_socket, $status["unread_bytes"]); if (socket_timeout($this->_socket)) return false; break; case 254: // More than one datagram // This would have to be the only complicated part, thanks to the use of UDP. // This code was made possible thanks to an artical by "Montanus" entitled // "Managing Multiple UDP packet replies": http://dev.kquery.com/index.php?article=21 // We start by reading off the rest of the header. // I still want to know what the extra 4 bytes mean (apparently a counter). $status = socket_get_status($this->_socket); socket_set_timeout($this->_socket, $this->_timeout); fread($this->_socket, 7); if (socket_timeout($this->_socket)) return false; // The 9th byte tells us the datagram id and the total number of datagrams. socket_set_timeout($this->_socket, $this->_timeout); $data = fread($this->_socket, 1); if (socket_timeout($this->_socket)) return false; // We need to evaluate this in bits (so convert to binary) $bits = sprintf("%08b",ord($data)); // The low bits denote the total number of datagrams. (1-based) $count = bindec(substr($bits, -4)); // The high bits denote the current datagram id. $x = bindec(substr($bits, 0, 4)); // The rest is the datagram content. $status = socket_get_status($this->_socket); socket_set_timeout($this->_socket, $this->_timeout); $datagrams[$x] = fread($this->_socket, $status["unread_bytes"]); if (socket_timeout($this->_socket)) return false; // Repeat this process for each datagram. // We've already done the first one, so $i = 1 to start at the next. for ($i=1; $i<$count; $i++) { // Skip the header. socket_set_timeout($this->_socket, $this->_timeout); fread($this->_socket, 8); if (socket_timeout($this->_socket)) return false; // Evaluate the 9th byte. socket_set_timeout($this->_socket, $this->_timeout); $data = fread($this->_socket, 1); if (socket_timeout($this->_socket)) return false; $x = bindec(substr(sprintf("%08b",ord($data)), 0, 4)); // Read the datagram content. $status = socket_get_status($this->_socket); socket_set_timeout($this->_socket, $this->_timeout); $datagrams[$x] = fread($this->_socket, $status["unread_bytes"]); if (socket_timeout($this->_socket)) return false; } // Stick all of the datagrams together and pretend that it wasn't split. :) $data = ""; for ($i=0; $i<$count; $i++) { $data .= $datagrams[$i]; } break; } $this->_data = $data; return true; } function _getByte() { $data = substr($this->_data, 0, 1); $this->_data = substr($this->_data, 1); return ord($data); } function _getInt16() { $data = substr($this->_data, 0, 2); $this->_data = substr($this->_data, 2); $array = @unpack("Sshort", $data); return $array["short"]; } function _getInt32() { $data = substr($this->_data, 0, 4); $this->_data = substr($this->_data, 4); $array = @unpack("Lint", $data); return $array["int"]; } function _getFloat32() { $data = substr($this->_data, 0, 4); $this->_data = substr($this->_data, 4); $array = @unpack("ffloat", $data); return $array["float"]; } function _getString() { $data = ""; $byte = substr($this->_data, 0, 1); $this->_data = substr($this->_data, 1); while (ord($byte) != "0") { $data .= $byte; $byte = substr($this->_data, 0, 1); $this->_data = substr($this->_data, 1); } return $data; } // Game servers will answer the following messages: // The SDK allows the following query commands: // ping, details, players, rules, infostring, info function ping() { // "ping" // Server responds with the following packet: // (int32) -1 // (byte) ASCII 'j' (general acknowledgement, A2A_ACK) // (byte) 0 startBenchmark(__FILE__, __FUNCTION__); if (!$this->_connect()) return false; if (!$this->_writeData("ping")) return false; if (!$this->_readData()) return false; $this->_getInt32(); $ping = $this->_getByte(); $this->_getByte(); if (!$this->_disconnect()) return false; endBenchmark(__FILE__, __FUNCTION__); if ($ping == ord("j")) return true; else return false; } function info() { // "info" // (int32) -1 // (byte) ASCII 'm' ( S2A_INFO_DETAILED ) // (string) net address of server // (string) name of the host / server // (string) name of the map // (string) game directory (i.e. valve/) // (string) Game description (e.g. "half-life multiplay") // (byte) active client count // (byte) maximum clients allowed // (byte) protocol version startBenchmark(__FILE__, __FUNCTION__); $info = array(); if (!$this->_connect()) return false; if (!$this->_writeData("info")) return false; if (!$this->_readData()) return false; $this->_getByte(); $info["address"] = $this->_getString(); $info["hostname"] = $this->_getString(); $info["map"] = $this->_getString(); $info["mod_dir"] = $this->_getString(); $info["mod_name"] = $this->_getString(); $info["cur_players"] = $this->_getByte(); $info["max_players"] = $this->_getByte(); $info["protocol"] = $this->_getByte(); if (!$this->_disconnect()) return false; endBenchmark(__FILE__, __FUNCTION__); return $info; } function players() { // "players" // Server responds with the following packet: // (int32) -1 // (byte) ASCII 'D' (players response, S2A_PLAYER) // (byte) active client count // // for each active client // (byte) client number / index // (string) player name // (int32) client's frag total // (float32) client's total time in-game startBenchmark(__FILE__, __FUNCTION__); $players = array(); if (!$this->_connect()) return false; if (!$this->_writeData("players")) return false; if (!$this->_readData()) return false; $this->_getInt32(); $this->_getByte(); $count = $this->_getByte(); for ($i=0; $i<$count; $i++) { $players["id"][$i] = $this->_getByte(); $players["name"][$i] = $this->_getString(); $players["frags"][$i] = $this->_getInt32(); $time = $this->_getFloat32(); $minutes = floor($time / 60); $seconds = floor($time - ($minutes * 60)); $players["time"][$i] = sprintf("%02smin %02ssec", $minutes, $seconds); } if (!$this->_disconnect()) return false; endBenchmark(__FILE__, __FUNCTION__); return $players; } function rules() { // "rules" // (int32) -1 // (byte) ASCII 'E' (rules response, S2A_RULES) // (int16) number of rules // // for each rule // (string) rule name // (string) rule value startBenchmark(__FILE__, __FUNCTION__); $rules = array(); if (!$this->_connect()) return false; if (!$this->_writeData("rules")) return false; if (!$this->_readData()) return false; $this->_getInt32(); $this->_getByte(); $count = $this->_getInt16(); for ($i=0; $i<$count; $i++) { $rule = $this->_getString(); $value = $this->_getString(); $rules[$rule] = $value; } if (!$this->_disconnect()) return false; endBenchmark(__FILE__, __FUNCTION__); return $rules; } function details() { global $phpUA; // "details" // (int32) -1 // (byte) ASCII 'm' ( S2A_INFO_DETAILED ) // (string) net address of server // (string) name of the host / server // (string) name of the map // (string) game directory (i.e. valve/) // (string) Game description (e.g. "half-life multiplay") // (byte) active client count // (byte) maximum clients allowed // (byte) protocol version // (byte) type of server == 'l' for listen or 'd' for dedicated // (byte) os of server == 'w' for win32 or 'l' for linux // (byte) password on server == 1 or yes, 0, for no // (byte) is server running a mod? == 1 for yes, 0 for no startBenchmark(__FILE__, __FUNCTION__); $details = array(); if (!$this->_connect()) return false; if (!$this->_writeData("details")) return false; if (!$this->_readData()) return false; $this->_getInt32(); $this->_getByte(); $details["address"] = $this->_getString(); $details["hostname"] = $this->_getString(); $details["map"] = $this->_getString(); $details["mod_dir"] = $this->_getString(); $details["mod_name"] = $this->_getString(); $details["cur_players"] = $this->_getByte(); $details["max_players"] = $this->_getByte(); $details["protocol"] = $this->_getByte(); $server_type = $this->_getByte(); switch ($server_type) { case ord("l"): $details["server_type"] = $phpUA["LANGUAGE"]["Listen"]; break; case ord("d"): $details["server_type"] = $phpUA["LANGUAGE"]["Dedicated"]; break; } $server_os = $this->_getByte(); switch ($server_os) { case ord("w"): $details["server_os"] = "Windows"; break; case ord("l"): $details["server_os"] = "Linux"; break; } $password = $this->_getByte(); switch ($password) { case 0: $details["password"] = $phpUA["LANGUAGE"]["None"]; break; case 1: $details["password"] = $phpUA["LANGUAGE"]["Required"]; break; } $details["mod_enabled"] = $this->_getByte(); if ($details["mod_enabled"]) { // If the server is running mod byte was 1: // (string) URL for mod's "info" website // (string) URL for mod's download ftp server // (string) Empty string, unused (used to be HL version) // (int32) mod version # // (int32) mod download size ( in bytes, approx. ) // (byte) is the mod a server side only mod? 1 == yes, 0 == no // (byte) does this server require you to have a custom client side .dll ( client.dll )? 1 == yes, 0 == no. // // Protocol extension (SDK 2.3), sent whether or not the mod byte is 0 or 1 // (byte) server is a secure server == 1 for yes, 0 for no $details["mod_url"] = $this->_getString(); $details["mod_download"] = $this->_getString(); $this->_getString(); $details["mod_ver"] = $this->_getInt32(); $details["mod_size"] = $this->_getInt32(); $details["mod_serverside"] = $this->_getByte(); $details["mod_customdll"] = $this->_getByte(); $secure = $this->_getByte(); switch ($secure) { case 0: $details["secure"] = $phpUA["LANGUAGE"]["No"]; break; case 1: $details["secure"] = $phpUA["LANGUAGE"]["Yes"]; break; } } if (!$this->_disconnect()) return false; endBenchmark(__FILE__, __FUNCTION__); return $details; } function infostring() { // "infostring" (SDK 2.3) // (int32) -1 // (string) "infostringresponse" // (string) string containing key/value pairs delimited by the '\' character. startBenchmark(__FILE__, __FUNCTION__); $infostring = array(); if (!$this->_connect()) return false; if (!$this->_writeData("infostring")) return false; if (!$this->_readData()) return false; $this->_getInt32(); $this->_getString(); $string = $this->_getString(); $pair = explode("\\", $string); for ($i=1; $i"; } if (!$this->_disconnect()) return false; endBenchmark(__FILE__, __FUNCTION__); return $infostring; } } ?>