Advertisement
masa-

Minecraft server Query in PHP (updated 2013-01-15)

Jan 15th, 2013
560
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 7.39 KB | None | 0 0
  1. <?php
  2.  
  3. // A simple version of server query.
  4. // Only returns the MOTD and the number of players and max players.
  5. // Should work even when enable-query=false in the server.properties
  6. function ping($host, $port = 25565, $timeout_s = 1, $timeout_us = 0)
  7. {
  8.     $errno = 0;
  9.     $errstr = "";
  10.  
  11.     //Set up our socket
  12.     $fp = @fsockopen("tcp://" . $host, $port, $errno, $errstr);
  13.  
  14.     if(!$fp)
  15.     {
  16.         //echo "no socket!<br />\n";
  17.         return array();
  18.     }
  19.  
  20.     //Send 0xFE: Server list ping
  21.     fwrite($fp, "\xFE");
  22.  
  23.     // Set the time out limit
  24.     stream_set_timeout($fp, $timeout_s, $timeout_us);
  25.     $arr = stream_get_meta_data($fp);
  26.  
  27.     if($arr['timed_out'])
  28.     {
  29.         //echo "timed out!<br />\n";
  30.         return array();
  31.     }
  32.  
  33.     //Read as much data as we can (max packet size: 241 bytes)
  34.     $d = fread($fp, 256);
  35.  
  36.     //Check we've got a 0xFF Disconnect
  37.     if($d[0] != "\xFF")
  38.     {
  39.         //echo "not a disconnect!<br />\n";
  40.         return array();
  41.     }
  42.  
  43.     //Remove the packet ident (0xFF) and the short containing the length of the string
  44.     $d = substr($d, 3);
  45.  
  46.     //Decode UCS-2 string
  47.     $d = mb_convert_encoding($d, 'auto', 'UCS-2');
  48.  
  49.     //Split into array
  50.     $d = explode("\xA7", $d);
  51.  
  52.     //Return an associative array of values
  53.     return array(
  54.             'motd'      => $d[0],
  55.             'players'   => intval($d[1]),
  56.             'max_players'   => intval($d[2])
  57.         );
  58. }
  59.  
  60.  
  61. // A verbose version of server query. Requires enable-query=true in server.properties
  62. function full_stat($host, $port = 25565, $timeout_s = 1, $timeout_us = 0)
  63. {
  64.     $errno = 0;
  65.     $errstr = "";
  66.  
  67.     //Set up our socket
  68.     $fp = @fsockopen("udp://" . $host, $port, $errno, $errstr);
  69.  
  70.     if(!$fp)
  71.     {
  72.         //echo "no socket!<br />\n";
  73.         return array();
  74.     }
  75.  
  76.     // Handshake:
  77.     // Get the challenge token; send 0xFE 0xFD 0x09 and a 4-byte session id
  78.     $str1 = "\xFE\xFD\x09\x00\x00\x00\x01"; // Arbitrary session id at the end (4 bytes) (we use 00 00 00 01 here)
  79.     fwrite($fp, $str1);
  80.  
  81.     // Set the time out limit
  82.     stream_set_timeout($fp, $timeout_s, $timeout_us);
  83.     $arr = stream_get_meta_data($fp);
  84.  
  85.     if($arr['timed_out'])
  86.     {
  87.         //echo "timed out!<br />\n";
  88.         return array();
  89.     }
  90.  
  91.     $resp = fread($fp, 4096);
  92.  
  93.     // Check that we got something back
  94.     if(strlen($resp) == 0)
  95.     {
  96.         //echo "empty response!<br />\n";
  97.         return array();
  98.     }
  99.  
  100.     // Check for a valid response
  101.     if($resp[0] != "\x09")
  102.     {
  103.         //echo "not a valid response!<br />\n";
  104.         return array();
  105.     }
  106.  
  107.     // Parse the challenge token from string to integer
  108.     $token = 0;
  109.     for($i = 5; $i < (strlen($resp) - 1); $i++)
  110.     {
  111.         $token *= 10;
  112.         $token += $resp[$i];
  113.     }
  114.  
  115.     // Divide the int32 into 4 bytes
  116.     $token_arr = array( 0 => ($token / (256*256*256)) % 256,
  117.                 1 => ($token / (256*256)) % 256,
  118.                 2 => ($token / 256) % 256,
  119.                 3 => ($token % 256)
  120.             );
  121.  
  122.     // Note that the challenge token is bound to your IP and port (as opposed to the [session ID]), and
  123.     // lasts up to 30 seconds. You read that right, it's up to; it's not "your token will expire
  124.     // after 30 seconds", it's "every token ever" is expired every 30 seconds. This means it's entirely
  125.     // possible that you may get a token and use it within the same second and have it expire.
  126.  
  127.     // Get the full version of server status
  128.     // Session ID and challenge tokens appended to magic header 0xFE 0xFD and command byte 0x00
  129.     // Payload padded to 8 bytes
  130.     $str = "\xFE\xFD\x00\x00\x00\x00\x01"
  131.         . chr($token_arr[0]) . chr($token_arr[1]) . chr($token_arr[2]) . chr($token_arr[3])
  132.         . "\x00\x00\x00\x00";
  133.     fwrite($fp, $str);
  134.  
  135.     $data = fread($fp, 4096);
  136.     $full_stat = substr($data, 16);     // Strip the crap from the start
  137.  
  138.     $tmp = explode("\x00\x01player_\x00\x00", $full_stat);  // First, split the payload in two parts
  139.     $keysvalues = explode("\x00", $tmp[0]);         // Divide the first part from every NULL-terminated string end into key1 val1 key2 val2...
  140.     unset($keysvalues[count($keysvalues) - 1]);     // Unset the last entry, because the are two 0x00 bytes at the end
  141.  
  142.     // Strip all the NULL-bytes from the end of the player list
  143.     $tmp = $tmp[1];
  144.     $i = strlen($tmp) - 1;
  145.     while($i >= 0)
  146.     {
  147.         if(ord($tmp[$i]) != 0)
  148.         {
  149.             break;
  150.         }
  151.         $i--;
  152.     }
  153.  
  154.     // Split the player information (if any)
  155.     if($i > 0)
  156.     {
  157.         $tmp = substr($tmp, 0, $i + 1);
  158.         $players = explode("\x00", $tmp);       // Explode the player information from the NULL-byte positions
  159.     }
  160.     else
  161.     {
  162.         $tmp = FALSE;
  163.     }
  164.  
  165.     // Arrange the key => value pairs into an array
  166.     $info = array();
  167.     for($i = 0; $i < count($keysvalues); $i += 2)
  168.     {
  169.         if($keysvalues[$i] == "")
  170.             break;
  171.  
  172.         $info[$keysvalues[$i]] = $keysvalues[$i + 1];
  173.     }
  174.  
  175.     // Collect all the information into one array
  176.     $full_stat = $info;
  177.     $full_stat['players'] = $players;
  178.  
  179.     return $full_stat;
  180. }
  181.  
  182. // -----------------------------------------------------------------------------------------
  183.  
  184. // These are here as an example and to test that the query works. You can edit the addresses to match
  185. // your server, and have the script dump the "raw" arrays with all the data.
  186. // If you only have one server, an easy example of usage would be:
  187. // $data = full_stat("some.addre.ss", 25565);
  188. // Just change the address and port to match your server.
  189. // You can add timeout values after the port, the default timeout is 1 second:
  190. // $data = full_stat("some.addre.ss", 25565, 0, 300000);
  191. // The first value is in seconds and the second is in microseconds. So the example above
  192. // would timeout after 300 000 microseconds = 300 milliseconds = 0.3 seconds
  193.  
  194. // Note that the full_stat() function will only work if the server has query enabled
  195. // ie. enable-query=true in server.properties file.
  196. // The ping() function should always work, as it uses the same method that the client does
  197. // in the server list menu (and thus only has the same data to display, MOTD and players/max players).
  198. // After you have your data, for example in the $data variable as in the example above, you
  199. // can then print what you want from it. The possible keys are easily seen if running
  200. // the test below, as it dumps the full arrays.
  201. // The keys are in brackets (for example [hostname]), so when you want
  202. // to print it, use some of PHP's print methods, for example echo:
  203. // echo $data['hostname'];
  204. // or printf:
  205. // printf("Hostname follows this example: %s\n", $data['hostname']);
  206.  
  207. // To recap: to test this script for your server:
  208. // 1. Save this script to your web server, for example as a test.php
  209. // 2. Edit the array below to include your server (address and port).
  210. // You can delete the rest of the example servers.
  211. // Use either full_stat, if you have enable-query=true, or ping, if you have enable-query=false.
  212. // Ping will of course work also when enable-query=true.
  213. // 3. Load the page in your web browser. If everything works, you should see an array with
  214. // some data about you server. You can use the keys shown in brackets to print only
  215. // the data you want in your final page.
  216. // echo $data['hostname']; if using the example above or
  217. // echo $servers['foo']['hostname']; if using the array below (foo is just a key/index in the array for the given server)
  218.  
  219. // Put your server(s) here to test the query:
  220. $servers = array(
  221.     'server1'   => full_stat("some.addre.ss", 25565),
  222.     'server2'   => full_stat("some.addre.ss", 25590),
  223.     'foo'       => full_stat("some.addre.ss", 25565, 2, 0), // An example with a 2 second timeout
  224.     'pingserver'    => ping("some.addre.ss", 25585, 2, 0)
  225. );
  226.  
  227. // Dump all the test data:
  228. echo "<pre>\n";
  229.  
  230. foreach($servers as $server => $data)
  231. {
  232.     echo "$server:\n";
  233.     print_r($data);
  234. }
  235.  
  236. echo "</pre>\n";
  237.  
  238. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement