01-08-2010, 11:37 AM
I know that everyone has their own method of doing things. I thought it would be a cool idea to to do it myself and add a bit more functionality to the script itself. I've never messed with sockets in PHP before but here is my take on it:
Using the Class:
The information that it retrieves is the following:
If you need any help, just leave a comment and I'll give you a hand.
Note:
This will not work on a web server unless you own your own because of the firewall that they use and the fact that the connection is UDP. This goes for all of these scripts. The firewall blocks the UDP packets the server returns because the connection in the PHP is not persistent.
PHP Code:
class serverQuery{
// Server Info
private $serverIp = NULL;
private $serverPort = NULL;
// Server Connection / Query
private $serverConnection = NULL;
private $serverResponse = NULL;
private $players = NULL;
// Server Information
private $serverInformation = array();
private $challengeCode = NULL;
private $playerData = array();
private $serverSettings = array();
public function __construct($server){
$server = explode(":", $server);
$this->serverIp = $server[0];
$this->serverPort = $server[1];
$this->queryServer();
$this->parseServerResponse();
}
public function queryServer(){
// Open a Connection to the Server
$this->serverConnection = pfsockopen("udp://" . $this->serverIp, $this->serverPort, $errorNumber, $errorString, 1);
stream_set_blocking($this->serverConnection, 1);
if(!$this->serverConnection){
return false;
} else {
// Send Query to Server For General Details
$this->serverResponse = substr($this->query("\xFF\xFF\xFF\xFF\x54Source Engine Query\0"), 2);
// Get the Challenge Code for a Player and Rules Query
$this->challengeCode = substr($this->query("\xFF\xFF\xFF\xFF\x57"), 1);
$this->players = $this->query("\xFF\xFF\xFF\xFF\x55" . $this->challengeCode);
$this->serverSettings = $this->parseRules($this->query("\xFF\xFF\xFF\xFF\x56" . $this->challengeCode));
fclose($this->serverConnection);
return true;
}
}
private function query($queryData){
// Query Function -- Removes 'Junk' [\xFF\xFF\xFF\xFF] -- Returns Data
fwrite($this->serverConnection, $queryData);
fread($this->serverConnection, 4);
$serverStatus = stream_get_meta_data($this->serverConnection);
return fread($this->serverConnection, $serverStatus['unread_bytes']);
}
public function parseServerResponse(){
/*
All of These Values are directory from the Developer Wiki
(http://developer.valvesoftware.com/wiki/Server_Queries)
*/
$this->serverInformation['hostname'] = trim(str_replace("\x01", "", $this->getString($this->serverResponse)));
$this->serverInformation['map'] = $this->getString($this->serverResponse);
$this->serverInformation['dir'] = $this->getString($this->serverResponse);
$this->serverInformation['description'] = $this->getString($this->serverResponse);
$this->serverInformation['appid'] = $this->getShort($this->serverResponse);
$this->serverInformation['players'] = $this->getByte($this->serverResponse);
$this->serverInformation['maxplayers'] = $this->getByte($this->serverResponse);
$this->serverInformation['bots'] = $this->getByte($this->serverResponse);
$this->serverInformation['type'] = (chr($this->getByte($this->serverResponse)) == "d") ? "Dedicated" : "Listen";
$this->serverInformation['os'] = (chr($this->getByte($this->serverResponse)) == "l") ? "Linux" : "Windows";
$this->serverInformation['pass'] = $this->getByte($this->serverResponse);
$this->serverInformation['vac'] = $this->getByte($this->serverResponse);
$this->serverInformation['version'] = $this->getString($this->serverResponse);
// Removes First Byte of Player Response and Uses The Next Byte as the Counter
$this->getByte($this->players);
$count = $this->getByte($this->players);
// Itterate Through and Get Cooresponding Values
for($x = 0; $x < $count; $x++){
$this->playerData[$x]['id'] = $this->getByte($this->players);
$this->playerData[$x]['name'] = $this->getString($this->players);
$this->playerData[$x]['kills'] = $this->getLong($this->players);
$playerTime = round($this->getFloat($this->players));
$this->playerData[$x]['time'] = round($playerTime / 3600) . ":" . round(($playerTime % 3600) / 60) . ":" . round($playerTime % 60);
}
return true;
}
private function getByte(&$source){
$byte = ord(substr($source, 0, 1));
$source = substr($source, 1);
return $byte;
}
private function getLong(&$source){
$long = unpack("L", substr($source, 0, 4));
$source = substr($source, 4);
return $long[1];
}
private function getShort(&$source){
$short = unpack("S", substr($source, 0, 2));
$source = substr($source, 2);
return $short[1];
}
private function getChar(&$source){
$char = substr($source, 0, 1);
$source = substr($source, 1);
return $char;
}
private function getFloat(&$source){
$float = unpack("f", substr($source, 0, 4));
$source = substr($source, 4);
return $float[1];
}
private function getString(&$source){
$string = "";
$loop = true;
while($loop){
$stringPart = $this->getChar($source);
if(ord($stringPart) != 0){
$string .= $stringPart;
} else {
$loop = false;
}
}
return $string;
}
private function parseRules($rules){
$newRules = array();
$this->getByte($rules);
if($this->getByte($rules) != 0){
$rules = explode(chr(0), $rules);
for($x = 1; $x < count($rules) - 1; $x += 2){
$newRules[$rules[$x]] = $rules[$x + 1];
}
}
return $newRules;
}
// Data access Functions
public function getServerInformation(){
return $this->serverInformation;
}
public function getPlayerInformation(){
return $this->playerData;
}
public function getServerSettings(){
return $this->serverSettings;
}
}
Using the Class:
PHP Code:
$serverQuery = new serverQuery($_GET['s']);
print_r(array("Server Information" => $serverQuery->getServerInformation(), "Player Information" => $serverQuery->getPlayerInformation(), "Server Settings" => $serverQuery->getServerSettings()));
The information that it retrieves is the following:
Code:
Array
(
[Server Information] => Array
(
[hostname] => SourceOP.com TF2 24/7 2fort [INSTANT RESPAWN]
[map] => ctf_2fort
[dir] => tf
[description] => Team Fortress
[appid] => 440
[players] => 33
[maxplayers] => 33
[bots] => 0
[type] => Dedicated
[os] => Linux
[pass] => 0
[vac] => 1
[version] => 1.0.7.8
)
[Player Information] => Array
(
[0] => Array
(
[id] => 1
[name] => Scofield
[kills] => 0
[time] => 0:12:19
)
[1] => Array
(
[id] => 2
[name] => Nazaire
[kills] => 13
[time] => 0:14:17
)
[2] => Array
(
[id] => 3
[name] => Cpt. Respawn
[kills] => 25
[time] => 1:44:32
)
[3] => Array
(
[id] => 4
[name] => You Shall Not Pass
[kills] => 35
[time] => 1:46:13
)
[4] => Array
(
[id] => 5
[name] => Jackass
[kills] => 21
[time] => 0:23:49
)
[5] => Array
(
[id] => 6
[name] => king.adnu
[kills] => 17
[time] => 1:38:37
)
[6] => Array
(
[id] => 7
[name] => lansky›
[kills] => 1
[time] => 0:4:46
)
[7] => Array
(
[id] => 8
[name] => Manhunt
[kills] => 3
[time] => 0:4:32
)
[8] => Array
(
[id] => 9
[name] => Sable
[kills] => 24
[time] => 1:11:18
)
[9] => Array
(
[id] => 10
[name] => Mashi
[kills] => 1
[time] => 0:5:45
)
[10] => Array
(
[id] => 11
[name] => å°éŠ€ç‹
[kills] => 15
[time] => 0:9:32
)
[11] => Array
(
[id] => 12
[name] => PQTHED420
[kills] => 8
[time] => 1:47:4
)
[12] => Array
(
[id] => 13
[name] => ScribbleNauts
[kills] => 13
[time] => 1:32:27
)
[13] => Array
(
[id] => 14
[name] => Bingell
[kills] => 24
[time] => 0:29:16
)
[14] => Array
(
[id] => 15
[name] => =UO= Gun-4-Hire
[kills] => 49
[time] => 1:39:13
)
[15] => Array
(
[id] => 16
[name] => ¿Super Cheese?
[kills] => 99
[time] => 1:57:29
)
[16] => Array
(
[id] => 17
[name] => The Dude
[kills] => 47
[time] => 2:7:31
)
[17] => Array
(
[id] => 18
[name] => Tony-C
[kills] => 0
[time] => 0:1:18
)
[18] => Array
(
[id] => 19
[name] => BDX
[kills] => 6
[time] => 0:7:27
)
[19] => Array
(
[id] => 20
[name] => Scooty Puff Sr.
[kills] => 116
[time] => 1:16:48
)
[20] => Array
(
[id] => 21
[name] => FF|Skyrider
[kills] => 18
[time] => 0:13:40
)
[21] => Array
(
[id] => 22
[name] => Volcoff
[kills] => 1
[time] => 1:32:23
)
[22] => Array
(
[id] => 23
[name] => Kurt
[kills] => 80
[time] => 1:21:37
)
[23] => Array
(
[id] => 24
[name] => raidian
[kills] => 9
[time] => 0:14:40
)
[24] => Array
(
[id] => 25
[name] => IceTea
[kills] => 13
[time] => 1:43:9
)
[25] => Array
(
[id] => 26
[name] => hurtmypony
[kills] => 69
[time] => 1:14:2
)
[26] => Array
(
[id] => 27
[name] => PMS ♥ SakurA 王女
[kills] => 84
[time] => 1:51:3
)
[27] => Array
(
[id] => 28
[name] => ho man...
[kills] => 4
[time] => 0:14:34
)
[28] => Array
(
[id] => 29
[name] => MR. CERVANTES
[kills] => 32
[time] => 1:18:45
)
[29] => Array
(
[id] => 30
[name] => Jenkus Invictus
[kills] => 14
[time] => 0:19:1
)
[30] => Array
(
[id] => 31
[name] => $tinky$hot The Grate
[kills] => 0
[time] => 0:10:34
)
[31] => Array
(
[id] => 0
[name] => Dr.Deflog
[kills] => 0
[time] => 0:0:15
)
)
[Server Settings] => Array
(
[DF_radio_on] => 0
[DF_jetpack_on] => 0
[DF_hook_on] => 0
[DF_sop_version] => SourceOP Internal Test Version 0.9.1
[tf_weapon_criticals] => 1
[tf_damage_disablespread] => 0
[tf_playergib] => 1
[tf_allow_player_use] => 0
[tf_force_holidays_off] => 0
[tf_birthday] => 0
[mp_tournament_stopwatch] => 1
[mp_windifference] => 0
[tf_arena_force_class] => 0
[tf_arena_change_limit] => 1
[tf_arena_override_cap_enable_time] => -1
[tf_arena_first_blood] => 1
[tf_gamemode_arena] => 0
[tf_gamemode_cp] => 0
[tf_gamemode_ctf] => 1
[tf_gamemode_payload] => 0
[tf_teamtalk] => 0
[tf_ctf_bonus_time] => 10
[tf_maxspeed] => 400
[tf_use_fixed_weaponspreads] => 0
[mp_respawnwavetime] => -20
[mp_tournament] => 0
[tf_arena_preround_time] => 10
[tf_arena_round_time] => 0
[tf_arena_max_streak] => 3
[tf_arena_use_queue] => 1
[mp_teams_unbalance_limit] => 1
[mp_maxrounds] => 6
[mp_winlimit] => 5
[mp_disable_respawn_times] => 0
[mp_autoteambalance] => 1
[mp_stalemate_enable] => 0
[mp_match_end_at_timelimit] => 0
[tf_overtime_nag] => 0
[sv_alltalk] => 0
[mp_timelimit] => 180
[mp_fraglimit] => 0
[nextlevel] =>
[sv_gravity] => 800
[sv_stopspeed] => 100
[sv_noclipaccelerate] => 5
[sv_noclipspeed] => 5
[sv_specaccelerate] => 5
[sv_specspeed] => 3
[sv_specnoclip] => 1
[sv_maxspeed] => 320
[sv_accelerate] => 10
[sv_airaccelerate] => 10
[sv_wateraccelerate] => 10
[sv_waterfriction] => 1
[sv_footsteps] => 1
[sv_rollspeed] => 200
[sv_rollangle] => 0
[sv_friction] => 4
[sv_bounce] => 0
[sv_stepsize] => 18
[r_VehicleViewDampen] => 1
[r_JeepViewDampenFreq] => 7.0
)
)
If you need any help, just leave a comment and I'll give you a hand.
Note:
This will not work on a web server unless you own your own because of the firewall that they use and the fact that the connection is UDP. This goes for all of these scripts. The firewall blocks the UDP packets the server returns because the connection in the PHP is not persistent.