├── LICENSE ├── README.md └── query.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Chris Churchwell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP-Minecraft-Query 2 | =================== 3 | 4 | A simple PHP class to connect to a minecraft server and get data about the server using the Query Protocol. 5 | 6 | To connect to a server, Query must be activated on the minecraft server by setting `enable-query=true` in the server's server.properties. 7 | 8 | This script connects to the server over UDP. 9 | 10 | ## Examples 11 | 12 | ### As a Regular PHP Library: 13 | ```php 14 | require_once('query.php'); 15 | 16 | $server = new Query('some.minecraftserver.com'); 17 | //or new Query('some.minecraftserver.com', $port, $timeout); 18 | 19 | if ($server->connect()) 20 | { 21 | $info = $server->get_info(); 22 | print_r($info); 23 | } 24 | 25 | ``` 26 | 27 | ### As a CodeIgniter Library: 28 | ```php 29 | $settings = array( 30 | 'host' => 'some.minecraftserver.com', //required 31 | 'port' => 25565, //optional 32 | 'timeout' = 3, //optional 33 | ); 34 | $this->load->library('query', $settings); 35 | 36 | $this->query->connect(); 37 | 38 | if ($this->query->is_connected()) { 39 | $info = $this->query->get_info(); 40 | print_r($info); 41 | } 42 | ``` 43 | 44 | ### get_info() result 45 | Result is an array: here is the dump: 46 | ``` 47 | Array 48 | ( 49 | [hostname] => Server MOTD 50 | [gametype] => SMP 51 | [game_id] => MINECRAFT 52 | [version] => 1.7.2 53 | [plugins] => 54 | [map] => world 55 | [numplayers] => 2 56 | [maxplayers] => 20 57 | [hostport] => 25565 58 | [hostip] => 127.0.1.1 59 | [players] => Array 60 | ( 61 | [0] => player1 62 | [1] => player2 63 | ) 64 | 65 | ) 66 | ``` 67 | -------------------------------------------------------------------------------- /query.php: -------------------------------------------------------------------------------- 1 | host = $host; 30 | $this->port = $port; 31 | $this->timeout = $timeout; 32 | 33 | if (is_array($host)) 34 | { 35 | $this->host = $host['host']; 36 | $this->port = empty($host['port'])?$port:$host['port']; 37 | $this->timeout = empty($host['timeout'])?$timeout:$host['timeout']; 38 | $auto_connect = empty($host['auto_connect'])?$auto_connect:$host['auto_connect']; 39 | } 40 | 41 | if ($auto_connect === true) { 42 | $this->connect(); 43 | } 44 | 45 | } 46 | 47 | /** 48 | * Returns the description of the last error produced. 49 | * 50 | * @return String - Last error string. 51 | */ 52 | public function get_error() { 53 | return $this->errstr; 54 | } 55 | 56 | /** 57 | * Checks whether or not the current connection is established. 58 | * 59 | * @return boolean - True if connected; false otherwise. 60 | */ 61 | public function is_connected() { 62 | if (empty($this->token)) return false; 63 | return true; 64 | } 65 | 66 | /** 67 | * Disconnects! 68 | * duh 69 | */ 70 | public function disconnect() { 71 | if ($this->socket) { 72 | fclose($this->socket); 73 | } 74 | } 75 | 76 | /** 77 | * Connects to the host via UDP with the provided credentials. 78 | * @return boolean - true if successful, false otherwise. 79 | */ 80 | public function connect() 81 | { 82 | $this->socket = fsockopen( 'udp://' . $this->host, $this->port, $errno, $errstr, $this->timeout ); 83 | 84 | if (!$this->socket) 85 | { 86 | $this->errstr = $errstr; 87 | return false; 88 | } 89 | 90 | stream_set_timeout( $this->socket, $this->timeout ); 91 | stream_set_blocking( $this->socket, true ); 92 | 93 | return $this->get_challenge(); 94 | 95 | } 96 | 97 | /** 98 | * Authenticates with the host server and saves the authentication token to a class var. 99 | * 100 | * @return boolean - True if succesfull; false otherwise. 101 | */ 102 | private function get_challenge() 103 | { 104 | if (!$this->socket) 105 | { 106 | return false; 107 | } 108 | 109 | //build packet to get challenge. 110 | $packet = pack("c3N", 0xFE, 0xFD, Query::TYPE_HANDSHAKE, Query::SESSION_ID); 111 | 112 | //write packet 113 | if ( fwrite($this->socket, $packet, strlen($packet)) === FALSE) { 114 | $this->errstr = "Unable to write to socket"; 115 | return false; 116 | } 117 | 118 | //read packet. 119 | $response = fread($this->socket, 2056); 120 | 121 | if (empty($response)) { 122 | $this->errstr = "Unable to authenticate connection"; 123 | return false; 124 | } 125 | 126 | $response_data = unpack("c1type/N1id/a*token", $response); 127 | 128 | if (!isset($response_data['token']) || empty($response_data['token'])) { 129 | $this->errstr = "Unable to authenticate connection."; 130 | return false; 131 | } 132 | 133 | $this->token = $response_data['token']; 134 | 135 | return true; 136 | 137 | } 138 | 139 | /** 140 | * Gets all the info from the server. 141 | * 142 | * @return boolean|array - Returns the data in an array, or false if there was an error. 143 | */ 144 | public function get_info() 145 | { 146 | if (!$this->is_connected()) { 147 | $this->errstr = "Not connected to host"; 148 | return false; 149 | } 150 | //build packet to get info 151 | $packet = pack("c3N2", 0xFE, 0xFD, Query::TYPE_STAT, Query::SESSION_ID, $this->token); 152 | 153 | //add the full stat thingy. 154 | $packet = $packet . pack("c4", 0x00, 0x00, 0x00, 0x00); 155 | 156 | //write packet 157 | if (!fwrite($this->socket, $packet, strlen($packet))) { 158 | $this->errstr = "Unable to write to socket."; 159 | return false; 160 | } 161 | 162 | //read packet header 163 | $response = fread($this->socket, 16); 164 | //$response = stream_get_contents($this->socket); 165 | 166 | // first byte is type. next 4 are id. dont know what the last 11 are for. 167 | $response_data = unpack("c1type/N1id", $response); 168 | 169 | //read the rest of the stream. 170 | $response = fread($this->socket, 2056); 171 | 172 | //split the response into 2 parts. 173 | $payload = explode ( "\x00\x01player_\x00\x00" , $response); 174 | 175 | $info_raw = explode("\x00", rtrim($payload[0], "\x00")); 176 | 177 | //extract key->value chunks from info 178 | $info = array(); 179 | foreach (array_chunk($info_raw, 2) as $pair) { 180 | list($key, $value) = $pair; 181 | //strip possible color format codes from hostname 182 | if ($key == "hostname") { 183 | $value = $this->strip_color_codes($value); 184 | } 185 | $info[$key] = $value; 186 | } 187 | 188 | //get player data. 189 | $players_raw = rtrim($payload[1], "\x00"); 190 | $players = array(); 191 | if (!empty($players_raw)) { 192 | $players = explode("\x00", $players_raw); 193 | } 194 | 195 | //attach player data to info for simplicity 196 | $info['players'] = $players; 197 | 198 | return $info; 199 | } 200 | 201 | /** 202 | * Clears Minecraft color codes from a string. 203 | * 204 | * @param String $string - the string to remove the codes from 205 | * @return String - a clean string. 206 | */ 207 | public function strip_color_codes($string) { 208 | return preg_replace('/[\x00-\x1F\x80-\xFF]./', '', $string); 209 | } 210 | 211 | } 212 | --------------------------------------------------------------------------------