66 private const MCPE_RAKNET_PROTOCOL_VERSION = 11;
68 private const MCPE_RAKNET_PACKET_ID =
"\xfe";
73 private int $rakServerId;
77 private array $sessions = [];
82 private int $sleeperNotifierId;
88 public function __construct(
98 $this->packetBroadcaster = $packetBroadcaster;
99 $this->entityEventBroadcaster = $entityEventBroadcaster;
100 $this->typeConverter = $typeConverter;
102 $this->rakServerId = mt_rand(0, PHP_INT_MAX);
104 $sleeperEntry = $this->
server->getTickSleeper()->addNotifier(
function() :
void{
105 Timings::$connection->startTiming();
107 while($this->eventReceiver->handle($this));
109 Timings::$connection->stopTiming();
112 $this->sleeperNotifierId = $sleeperEntry->getNotifierId();
115 $mainToThreadBuffer =
new ThreadSafeArray();
117 $threadToMainBuffer =
new ThreadSafeArray();
120 $this->
server->getLogger(),
125 $this->
server->getConfigGroup()->getPropertyInt(YmlServerProperties::NETWORK_MAX_MTU_SIZE, 1492),
126 self::MCPE_RAKNET_PROTOCOL_VERSION,
138 $this->
server->getLogger()->debug(
"Waiting for RakLib to start...");
140 $this->rakLib->startAndWait();
144 $this->
server->getLogger()->debug(
"RakLib booted successfully");
147 public function setNetwork(
Network $network) : void{
148 $this->network = $network;
152 if(!$this->rakLib->isRunning()){
153 $e = $this->rakLib->getCrashInfo();
157 throw new \Exception(
"RakLib Thread crashed without crash information");
162 if(isset($this->sessions[$sessionId])){
163 $session = $this->sessions[$sessionId];
164 unset($this->sessions[$sessionId]);
165 $session->onClientDisconnect(match($reason){
166 DisconnectReason::CLIENT_DISCONNECT => KnownTranslationFactory::pocketmine_disconnect_clientDisconnect(),
167 DisconnectReason::PEER_TIMEOUT => KnownTranslationFactory::pocketmine_disconnect_error_timeout(),
168 DisconnectReason::CLIENT_RECONNECT => KnownTranslationFactory::pocketmine_disconnect_clientReconnect(),
169 default =>
"Unknown RakLib disconnect reason (ID $reason)"
174 public function close(
int $sessionId) : void{
175 if(isset($this->sessions[$sessionId])){
176 unset($this->sessions[$sessionId]);
177 $this->interface->closeSession($sessionId);
182 $this->
server->getTickSleeper()->removeNotifier($this->sleeperNotifierId);
183 $this->rakLib->quit();
186 public function onClientConnect(
int $sessionId,
string $address,
int $port,
int $clientID) : void{
189 $this->network->getSessionManager(),
192 $this->packetBroadcaster,
193 $this->entityEventBroadcaster,
195 $this->typeConverter,
199 $this->sessions[$sessionId] = $session;
202 public function onPacketReceive(
int $sessionId,
string $packet) : void{
203 if(isset($this->sessions[$sessionId])){
204 if($packet ===
"" || $packet[0] !== self::MCPE_RAKNET_PACKET_ID){
205 $this->sessions[$sessionId]->getLogger()->debug(
"Non-FE packet received: " . base64_encode($packet));
209 $session = $this->sessions[$sessionId];
210 $address = $session->getIp();
211 $buf = substr($packet, 1);
212 $name = $session->getDisplayName();
214 $session->handleEncoded($buf);
215 }
catch(PacketHandlingException $e){
216 $logger = $session->getLogger();
218 $session->disconnectWithError(
219 reason:
"Bad packet: " . $e->getMessage(),
220 disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error_badPacket()
223 $logger->debug(implode(
"\n", Utils::printableExceptionInfo($e)));
225 $this->interface->blockAddress($address, 5);
226 }
catch(\Throwable $e){
228 $this->server->getLogger()->emergency(
"Crash occurred while handling a packet from session: $name");
234 public function blockAddress(
string $address,
int $timeout = 300) : void{
235 $this->interface->blockAddress($address, $timeout);
239 $this->interface->unblockAddress($address);
242 public function onRawPacketReceive(
string $address,
int $port,
string $payload) : void{
243 $this->network->processRawPacket($this, $address, $port, $payload);
246 public function sendRawPacket(
string $address,
int $port,
string $payload) : void{
247 $this->interface->sendRaw($address, $port, $payload);
251 $this->interface->addRawPacketFilter($regex);
254 public function onPacketAck(
int $sessionId,
int $identifierACK) : void{
255 if(isset($this->sessions[$sessionId])){
256 $this->sessions[$sessionId]->handleAckReceipt($identifierACK);
260 public function setName(
string $name) : void{
261 $info = $this->
server->getQueryInformation();
263 $this->interface->setName(implode(
";",
266 rtrim(addcslashes($name,
";"),
'\\'),
267 ProtocolInfo::CURRENT_PROTOCOL,
268 ProtocolInfo::MINECRAFT_VERSION_NETWORK,
269 $info->getPlayerCount(),
270 $info->getMaxPlayerCount(),
272 $this->server->getName(),
273 match($this->
server->getGamemode()){
274 GameMode::SURVIVAL =>
"Survival",
275 GameMode::ADVENTURE =>
"Adventure",
276 default =>
"Creative"
282 public function setPortCheck(
bool $name) : void{
283 $this->interface->setPortCheck($name);
286 public function setPacketLimit(
int $limit) : void{
287 $this->interface->setPacketsPerTickLimit($limit);
290 public function onBandwidthStatsUpdate(
int $bytesSentDiff,
int $bytesReceivedDiff) : void{
291 $this->network->getBandwidthTracker()->add($bytesSentDiff, $bytesReceivedDiff);
294 public function putPacket(
int $sessionId,
string $payload,
bool $immediate =
true, ?
int $receiptId =
null) : void{
295 if(isset($this->sessions[$sessionId])){
296 $pk =
new EncapsulatedPacket();
297 $pk->buffer = self::MCPE_RAKNET_PACKET_ID . $payload;
298 $pk->reliability = PacketReliability::RELIABLE_ORDERED;
299 $pk->orderChannel = 0;
300 $pk->identifierACK = $receiptId;
302 $this->interface->sendEncapsulated($sessionId, $pk, $immediate);
306 public function onPingMeasure(
int $sessionId,
int $pingMS) : void{
307 if(isset($this->sessions[$sessionId])){
308 $this->sessions[$sessionId]->updatePing($pingMS);