From cc2addfe92879f12ba8f5b49465b580c76b6b9bd Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Pueyo Date: Tue, 23 Apr 2013 00:58:26 +0200 Subject: [PATCH] Implemented Query protocol --- src/API/ServerAPI.php | 7 ++- src/config.php | 2 +- src/network/Query.php | 128 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 src/network/Query.php diff --git a/src/API/ServerAPI.php b/src/API/ServerAPI.php index e6acb7204..d548f0703 100644 --- a/src/API/ServerAPI.php +++ b/src/API/ServerAPI.php @@ -34,6 +34,7 @@ class ServerAPI{ private $apiList = array(); private $asyncCnt = 0; private $rcon; + private $query; public static function request(){ return self::$serverRequest; @@ -91,7 +92,7 @@ class ServerAPI{ console("[INFO] Loading properties..."); $this->config = new Config(DATA_PATH . "server.properties", CONFIG_PROPERTIES, array( - "server-name" => "Minecraft Server", + "server-name" => "Minecraft PE Server", "description" => "Server made using PocketMine-MP", "motd" => "Welcome @username to this server!", "server-invisible" => false, @@ -114,6 +115,7 @@ class ServerAPI{ "generator-settings" => "", "level-name" => false, "server-id" => false, + "enable-query" => false, "enable-rcon" => false, "rcon.password" => substr(base64_encode(Utils::getRandomBytes(20, false)), 3, 10), "rcon.port" => 19132, @@ -348,6 +350,9 @@ class ServerAPI{ if($this->getProperty("enable-rcon") === true){ $this->rcon = new RCON($this->getProperty("rcon.password", ""), $this->getProperty("rcon.port", 19132)); } + if($this->getProperty("enable-query") === true){ + $this->query = new Query(); + } $this->server->init(); unregister_tick_function(array($this->server, "tick")); diff --git a/src/config.php b/src/config.php index bf51384f5..cbf769de4 100644 --- a/src/config.php +++ b/src/config.php @@ -46,6 +46,6 @@ define("TEST_MD5", "1e0d28177b73dfd09f922502fe767bec"); define("MAJOR_VERSION", "Alpha_1.3dev"); define("CURRENT_STRUCTURE", 5); define("CURRENT_PROTOCOL", 9); -define("CURRENT_MINECRAFT_VERSION", "v0.6.1 alpha"); +define("CURRENT_MINECRAFT_VERSION", "0.6.1"); define("CURRENT_API_VERSION", 6); define("CURRENT_PHP_VERSION", "5.5"); \ No newline at end of file diff --git a/src/network/Query.php b/src/network/Query.php new file mode 100644 index 000000000..f27632a95 --- /dev/null +++ b/src/network/Query.php @@ -0,0 +1,128 @@ +workers = array(); + $this->password = (string) $password; + console("[INFO] Starting GS4 status listener"); + $this->threads = (int) max(1, $threads); + $this->clientsPerThread = (int) max(1, $clientsPerThread); + $this->server = ServerAPI::request(); + $addr = $this->server->api->getProperty("server-ip"); + $port = $this->server->api->getProperty("server-port"); + console("[INFO] Setting query port to $port"); + $this->server->addHandler("server.unknownpacket", array($this, "packetHandler"), 50); + $this->server->schedule(20 * 30, array($this, "regenerateToken"), array(), true); + $this->regenerateToken(); + $this->regenerateInfo(); + console("[INFO] Query running on $addr:$port"); + } + + public function regenerateInfo(){ + $str = ""; + $plist = "PocketMine-MP ".MAJOR_VERSION; + $pl = $this->server->api->plugin->getList(); + if(count($pl) > 0){ + $plist .= ":"; + foreach($pl as $p){ + $plist .= " ".str_replace(array(";", ":", " "), array("", "", "_"), $p["name"])." ".str_replace(array(";", ":", " "), array("", "", "_"), $p["version"]).";"; + } + $plist = substr($plist, 0, -1); + } + $KVdata = array( + "splitnum" => chr(128), + "hostname" => $this->server->name, + "gametype" => "SMP", + "game_id" => "MINECRAFTPE", + "version" => CURRENT_MINECRAFT_VERSION, + "plugins" => $plist, + "map" => $this->server->mapName, + "numplayers" => count($this->server->clients), + "maxplayers" => $this->server->maxClients, + "hostport" => $this->server->api->getProperty("server-port"), + "hostip" => $this->server->api->getProperty("server-ip") + ); + foreach($KVdata as $key => $value){ + $str .= $key."\x00".$value."\x00"; + } + $str .= "\x00\x01player_\x00\x00"; + foreach($this->server->clients as $player){ + if($player->username != ""){ + $str .= $player->username."\x00"; + } + } + $str .= "\x00"; + $this->longData = $str; + $this->timeout = microtime(true) + 5; + } + + public function regenerateToken(){ + $this->token = Utils::readInt("\x00".Utils::getRandomBytes(3, false)); + } + + public function packetHandler(&$packet, $event){ + if($event !== "server.unknownpacket"){ + return; + } + $magic = substr($packet["raw"], 0, 2); + $offset = 2; + if($magic !== "\xfe\xfd"){ + return; + } + $type = ord($packet["raw"]{2}); + ++$offset; + $sessionID = Utils::readInt(substr($packet["raw"], $offset, 4)); + $offset += 4; + $payload = substr($packet["raw"], $offset); + switch($type){ + case 9: //Handshake + $this->server->send(9, chr(9).Utils::writeInt($sessionID).$this->token."\x00", true, $packet["ip"], $packet["port"]); + break; + case 0: //Stat + $token = Utils::readInt(substr($payload, 0, 4)); + if($token !== $this->token){ + break; + } + if(strlen($payload) === 8){ + if($this->timeout < microtime(true)){ + $this->regenerateInfo(); + } + $this->server->send(0, chr(0).Utils::writeInt($sessionID).$this->longData, true, $packet["ip"], $packet["port"]); + }else{ + $this->server->send(0, chr(0).Utils::writeInt($sessionID).$this->server->name."\x00SMP\x00".$this->server->mapName."\x00".count($this->server->clients)."\x00".$this->server->maxClients."\x00".Utils::writeLShort($this->server->api->getProperty("server-port")).$this->server->api->getProperty("server-ip")."\x00", true, $packet["ip"], $packet["port"]); + } + break; + } + return true; + } + +}