Flawedspirit icon

Minecraft Server Query Class

Flawedspirit | PRO | 02/27/14 06:13:36 PM UTC | 0 ⭐ | 270 👁️ | Never ⏰ | []
PHP |

4.87 KB

|

None

|

0 👍

/

0 👎

<?php
 
class ServerQueryException extends Exception {}
 
class ServerQuery {
    const TYPE_CHALLENGE = 0x09;
    const TYPE_QUERY = 0x00;
 
    private $socket;
    private $players;
    private $info;
 
    public function connect($addr, $port = 25565, $timeout = 3) {
        if(!is_int($timeout) || $timeout < 0) {
            throw new InvalidArgumentException('Timeout must be a positive integer.');
        }
 
        if(!is_int($port) || $port < 1 || $port > 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);
    }
}
 
?>

Comments