server = $server; $this->sleeper = new SleeperNotifier(); $this->rakLib = new RakLibServer( $this->server->getLogger(), \pocketmine\COMPOSER_AUTOLOADER_PATH, new InternetAddress($this->server->getIp(), $this->server->getPort(), 4), (int) $this->server->getProperty("network.max-mtu-size", 1492), self::MCPE_RAKNET_PROTOCOL_VERSION, $this->sleeper ); $this->interface = new ServerHandler($this->rakLib, $this); } public function start() : void{ $this->server->getTickSleeper()->addNotifier($this->sleeper, function() : void{ while($this->interface->handlePacket()); }); $this->rakLib->start(PTHREADS_INHERIT_CONSTANTS); //HACK: MainLogger needs constants for exception logging } public function setNetwork(Network $network) : void{ $this->network = $network; } public function tick() : void{ if(!$this->rakLib->isRunning() and !$this->rakLib->isShutdown()){ $e = $this->rakLib->getCrashInfo(); if($e !== null){ throw $e; } throw new \Exception("RakLib Thread crashed without crash information"); } } public function closeSession(string $identifier, string $reason) : void{ if(isset($this->sessions[$identifier])){ $session = $this->sessions[$identifier]; unset($this->identifiers[spl_object_hash($session)]); unset($this->sessions[$identifier]); $session->onClientDisconnect($reason); } } public function close(NetworkSession $session, string $reason = "unknown reason") : void{ if(isset($this->identifiers[$h = spl_object_hash($session)])){ unset($this->sessions[$this->identifiers[$h]]); $this->interface->closeSession($this->identifiers[$h], $reason); unset($this->identifiers[$h]); } } public function shutdown() : void{ $this->server->getTickSleeper()->removeNotifier($this->sleeper); $this->interface->shutdown(); } public function openSession(string $identifier, string $address, int $port, int $clientID) : void{ $session = new NetworkSession($this->server, $this, $address, $port); $this->sessions[$identifier] = $session; $this->identifiers[spl_object_hash($session)] = $identifier; } public function handleEncapsulated(string $identifier, EncapsulatedPacket $packet, int $flags) : void{ if(isset($this->sessions[$identifier])){ if($packet->buffer === "" or $packet->buffer{0} !== self::MCPE_RAKNET_PACKET_ID){ return; } //get this now for blocking in case the player was closed before the exception was raised $session = $this->sessions[$identifier]; $address = $session->getIp(); $port = $session->getPort(); $buf = substr($packet->buffer, 1); try{ $session->handleEncoded($buf); }catch(BadPacketException $e){ $logger = $this->server->getLogger(); $logger->error("Bad packet from $address $port: " . $e->getMessage()); //intentionally doesn't use logException, we don't want spammy packet error traces to appear in release mode $logger->debug("Origin: " . Utils::cleanPath($e->getFile()) . "(" . $e->getLine() . ")"); foreach(Utils::printableTrace($e->getTrace()) as $frame){ $logger->debug($frame); } $session->disconnect("Packet processing error"); $this->interface->blockAddress($address, 5); } } } public function blockAddress(string $address, int $timeout = 300) : void{ $this->interface->blockAddress($address, $timeout); } public function unblockAddress(string $address) : void{ $this->interface->unblockAddress($address); } public function handleRaw(string $address, int $port, string $payload) : void{ $this->server->handlePacket($this, $address, $port, $payload); } public function sendRawPacket(string $address, int $port, string $payload) : void{ $this->interface->sendRaw($address, $port, $payload); } public function addRawPacketFilter(string $regex) : void{ $this->interface->addRawPacketFilter($regex); } public function notifyACK(string $identifier, int $identifierACK) : void{ } public function setName(string $name) : void{ $info = $this->server->getQueryInformation(); $this->interface->sendOption("name", implode(";", [ "MCPE", rtrim(addcslashes($name, ";"), '\\'), ProtocolInfo::CURRENT_PROTOCOL, ProtocolInfo::MINECRAFT_VERSION_NETWORK, $info->getPlayerCount(), $info->getMaxPlayerCount(), $this->rakLib->getServerId(), $this->server->getName(), GameMode::toString($this->server->getGamemode()) ]) . ";" ); } public function setPortCheck(bool $name) : void{ $this->interface->sendOption("portChecking", $name); } public function handleOption(string $option, string $value) : void{ if($option === "bandwidth"){ $v = unserialize($value); $this->network->addStatistics($v["up"], $v["down"]); } } public function putPacket(NetworkSession $session, string $payload, bool $immediate = true) : void{ if(isset($this->identifiers[$h = spl_object_hash($session)])){ $identifier = $this->identifiers[$h]; $pk = new EncapsulatedPacket(); $pk->buffer = self::MCPE_RAKNET_PACKET_ID . $payload; $pk->reliability = PacketReliability::RELIABLE_ORDERED; $pk->orderChannel = 0; $this->interface->sendEncapsulated($identifier, $pk, ($immediate ? RakLib::PRIORITY_IMMEDIATE : RakLib::PRIORITY_NORMAL)); } } public function updatePing(string $identifier, int $pingMS) : void{ if(isset($this->sessions[$identifier])){ $this->sessions[$identifier]->updatePing($pingMS); } } }