65535) { throw new InvalidArgumentException('Port must be an integer between 1 and 65535.'); } $this -> socket = @fsockopen('udp://' . $addr, (int)$port, $errno, $errstr, $timeout); if($errno || $this -> socket === false) { throw new ServerQueryException('Could not create socket: ' . $errstr); } stream_set_timeout($this -> socket, $timeout); stream_set_blocking($this -> socket, true); try { $challenge = $this -> getChallenge(); $this -> getStatus($challenge); } catch(ServerQueryException $ex) { // Dispose of socket and close the connection fclose($this -> socket); throw new ServerQueryException($ex -> getMessage()); } fclose($this -> socket); } public function getServerInfo() { return isset($this -> info) ? $this -> info : false; } public function getPlayerList() { return isset($this -> players) ? $this -> players : false; } private function getChallenge() { $data = $this -> query(self::TYPE_CHALLENGE); if($data === false) { throw new ServerQueryException('Failed to receive challenge token.'); } return pack('N', $data); } private function getStatus($challenge) { $data = $this -> query(self::TYPE_QUERY, $challenge . pack('c*', 0x00, 0x00, 0x00, 0x00)); if(!$data) { throw new ServerQueryException('Failed to get server status.'); } $lastKey = ''; $info = array(); $data = substr($data, 11); $data = explode("\x00\x00\x01player_\x00\x00", $data); if(count($data) !== 2) { throw new ServerQueryException('Failed to parse server status.'); } $players = substr($data[1], 0, -2); $data = explode("\x00", $data[0]); $validKeys = array( 'game_id' => 'Game', 'hostname' => 'Server Name', 'gametype' => 'Game Type', 'version' => 'Version', 'plugins' => 'Plugins', //This will never actually display anything; it's hardcoded to "" 'map' => 'Default World', 'numplayers' => 'Players', 'maxplayers' => 'Max Players', 'hostport' => 'Server Port', 'hostip' => 'Server Address' ); foreach($data as $key => $value) { if(~$key & 1) { if(!array_key_exists($value, $validKeys)) { $lastKey = false; continue; } $lastKey = $validKeys[$value]; $info[$lastKey] = ''; } else if($lastKey != false) { $info[$lastKey] = $value; } } // Check integers $info['Players'] = intval($info['Players']); $info['Max Players'] = intval($info['Max Players']); $info['Server Port'] = intval($info['Server Port']); // Parse installed plugins, if any if($info['Plugins']) { $data = explode(": ", $info['Plugins'], 2); $info['Raw_Plugins'] = $info['Plugins']; $info['Software'] = $data[0]; if(count($data) == 2) { $info['Plugins'] = explode(", ", $data[1]); } } else { $info['Software'] = 'Vanilla Server'; } $this -> info = $info; if($players) { $this -> players = explode("\x00", $players); } } private function query($command, $append = "") { $command = pack('c*', 0xFE, 0xFD, $command, 0x01, 0x02, 0x03, 0x04) . $append; $length = strlen($command); // Compare number of bytes sent with number of bytes returned by fwrite // If there is a mismatch, then we have a problem if($length !== fwrite($this -> socket, $command, $length)) { throw new ServerQueryException('Failed to write to socket.'); } $data = fread($this -> socket, 2048); if($data === false) { throw new ServerQueryException('Failed to read from socket.'); } if(strlen($data) < 5 || $data[0] != $command[2]) { return false; } return substr($data, 5); } } ?>