Advertisement
Flawedspirit

Minecraft Server Query Class

Feb 27th, 2014
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 4.87 KB | None | 0 0
  1. <?php
  2.  
  3. class ServerQueryException extends Exception {}
  4.  
  5. class ServerQuery {
  6.     const TYPE_CHALLENGE = 0x09;
  7.     const TYPE_QUERY = 0x00;
  8.  
  9.     private $socket;
  10.     private $players;
  11.     private $info;
  12.  
  13.     public function connect($addr, $port = 25565, $timeout = 3) {
  14.         if(!is_int($timeout) || $timeout < 0) {
  15.             throw new InvalidArgumentException('Timeout must be a positive integer.');
  16.         }
  17.  
  18.         if(!is_int($port) || $port < 1 || $port > 65535) {
  19.             throw new InvalidArgumentException('Port must be an integer between 1 and 65535.');
  20.         }
  21.  
  22.         $this -> socket = @fsockopen('udp://' . $addr, (int)$port, $errno, $errstr, $timeout);
  23.  
  24.         if($errno || $this -> socket === false) {
  25.             throw new ServerQueryException('Could not create socket: ' . $errstr);
  26.         }
  27.  
  28.         stream_set_timeout($this -> socket, $timeout);
  29.         stream_set_blocking($this -> socket, true);
  30.  
  31.         try {
  32.             $challenge = $this -> getChallenge();
  33.             $this -> getStatus($challenge);
  34.         } catch(ServerQueryException $ex) {
  35.             // Dispose of socket and close the connection
  36.             fclose($this -> socket);
  37.             throw new ServerQueryException($ex -> getMessage());
  38.         }
  39.  
  40.         fclose($this -> socket);
  41.     }
  42.  
  43.     public function getServerInfo() {
  44.         return isset($this -> info) ? $this -> info : false;
  45.     }
  46.  
  47.     public function getPlayerList() {
  48.         return isset($this -> players) ? $this -> players : false;
  49.     }
  50.  
  51.     private function getChallenge() {
  52.         $data = $this -> query(self::TYPE_CHALLENGE);
  53.  
  54.         if($data === false) {
  55.             throw new ServerQueryException('Failed to receive challenge token.');
  56.         }
  57.  
  58.         return pack('N', $data);
  59.     }
  60.  
  61.     private function getStatus($challenge) {
  62.         $data = $this -> query(self::TYPE_QUERY, $challenge . pack('c*', 0x00, 0x00, 0x00, 0x00));
  63.  
  64.         if(!$data) {
  65.             throw new ServerQueryException('Failed to get server status.');
  66.         }
  67.  
  68.         $lastKey = '';
  69.         $info = array();
  70.  
  71.         $data = substr($data, 11);
  72.         $data = explode("\x00\x00\x01player_\x00\x00", $data);
  73.  
  74.         if(count($data) !== 2) {
  75.             throw new ServerQueryException('Failed to parse server status.');
  76.         }
  77.  
  78.         $players = substr($data[1], 0, -2);
  79.         $data = explode("\x00", $data[0]);
  80.  
  81.         $validKeys = array(
  82.             'game_id'    => 'Game',
  83.             'hostname'   => 'Server Name',
  84.             'gametype'   => 'Game Type',
  85.             'version'    => 'Version',
  86.             'plugins'    => 'Plugins', //This will never actually display anything; it's hardcoded to ""
  87.             'map'        => 'Default World',
  88.             'numplayers' => 'Players',
  89.             'maxplayers' => 'Max Players',
  90.             'hostport'   => 'Server Port',
  91.             'hostip'     => 'Server Address'
  92.         );
  93.  
  94.         foreach($data as $key => $value) {
  95.             if(~$key & 1) {
  96.                 if(!array_key_exists($value, $validKeys)) {
  97.                     $lastKey = false;
  98.                     continue;
  99.                 }
  100.  
  101.                 $lastKey = $validKeys[$value];
  102.                 $info[$lastKey] = '';
  103.             } else if($lastKey != false) {
  104.                 $info[$lastKey] = $value;
  105.             }
  106.         }
  107.  
  108.         // Check integers
  109.         $info['Players']     = intval($info['Players']);
  110.         $info['Max Players'] = intval($info['Max Players']);
  111.         $info['Server Port'] = intval($info['Server Port']);
  112.  
  113.         // Parse installed plugins, if any
  114.         if($info['Plugins']) {
  115.             $data = explode(": ", $info['Plugins'], 2);
  116.  
  117.             $info['Raw_Plugins'] = $info['Plugins'];
  118.             $info['Software'] = $data[0];
  119.  
  120.             if(count($data) == 2) {
  121.                 $info['Plugins'] = explode(", ", $data[1]);
  122.             }
  123.         } else {
  124.             $info['Software'] = 'Vanilla Server';
  125.         }
  126.  
  127.         $this -> info = $info;
  128.  
  129.         if($players) {
  130.             $this -> players = explode("\x00", $players);
  131.         }
  132.     }
  133.  
  134.     private function query($command, $append = "") {
  135.         $command = pack('c*', 0xFE, 0xFD, $command, 0x01, 0x02, 0x03, 0x04) . $append;
  136.         $length = strlen($command);
  137.  
  138.         // Compare number of bytes sent with number of bytes returned by fwrite
  139.         // If there is a mismatch, then we have a problem
  140.         if($length !== fwrite($this -> socket, $command, $length)) {
  141.             throw new ServerQueryException('Failed to write to socket.');
  142.         }
  143.  
  144.         $data = fread($this -> socket, 2048);
  145.  
  146.         if($data === false) {
  147.             throw new ServerQueryException('Failed to read from socket.');
  148.         }
  149.  
  150.         if(strlen($data) < 5 || $data[0] != $command[2]) {
  151.             return false;
  152.         }
  153.  
  154.         return substr($data, 5);
  155.     }
  156. }
  157.  
  158. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement