Initial mass migration to session handlers

This introduces several new session handlers, splitting up session handling into several new states:

- Login: Only allows handling the LoginPacket. This is the only time LoginPacket can be sent, and it'll be discarded when sent at any other time.
- Resource packs: Handles only the resource packs sequence (downloading packs and such). This is the only time ResourcePackClientResponse and ResourcePackChunkRequest will be handled.
- Pre-spawn: Only chunk radius requests are accepted during this state.

SimpleNetworkHandler handles all the "rest" of the logic that hasn't yet been separated out into their own dedicated handlers. There's also a NullNetworkHandler which discards all packets while it's active.

This solves a large number of issues with the security of the login sequence. It solves a range of possible DoS attacks and crashes, while also allowing great code simplification and cleanup.
This commit is contained in:
Dylan K. Taylor 2018-07-20 17:09:04 +01:00
parent 97a1483f75
commit f626b9e8a0
8 changed files with 409 additions and 213 deletions

View File

@ -114,26 +114,16 @@ use pocketmine\network\mcpe\protocol\MobEffectPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\ResourcePackChunkDataPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket;
use pocketmine\network\mcpe\protocol\ResourcePackDataInfoPacket;
use pocketmine\network\mcpe\protocol\ResourcePacksInfoPacket;
use pocketmine\network\mcpe\protocol\ResourcePackStackPacket;
use pocketmine\network\mcpe\protocol\RespawnPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket;
use pocketmine\network\mcpe\protocol\SetTitlePacket;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\TransferPacket;
use pocketmine\network\mcpe\protocol\types\CommandData;
use pocketmine\network\mcpe\protocol\types\CommandEnum;
use pocketmine\network\mcpe\protocol\types\CommandParameter;
use pocketmine\network\mcpe\protocol\types\ContainerIds;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\UpdateAttributesPacket;
use pocketmine\network\mcpe\protocol\UpdateBlockPacket;
@ -143,7 +133,6 @@ use pocketmine\permission\PermissibleBase;
use pocketmine\permission\PermissionAttachment;
use pocketmine\permission\PermissionAttachmentInfo;
use pocketmine\plugin\Plugin;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\tile\ItemFrame;
use pocketmine\tile\Spawnable;
use pocketmine\tile\Tile;
@ -182,9 +171,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
/** @var NetworkSession */
protected $networkSession;
/** @var int */
protected $protocol = -1;
/**
* @var int
* Last measurement of player's latency in milliseconds.
@ -993,7 +979,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
protected function doFirstSpawn(){
$this->spawned = true;
$this->sendPlayStatus(PlayStatusPacket::PLAYER_SPAWN);
$this->networkSession->onSpawn();
if($this->hasPermission(Server::BROADCAST_CHANNEL_USERS)){
$this->server->getPluginManager()->subscribeToPermission(Server::BROADCAST_CHANNEL_USERS, $this);
@ -1799,59 +1785,17 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handleLogin(LoginPacket $packet) : bool{
if($this->loggedIn){
return false;
}
$this->protocol = $packet->protocol;
if($packet->protocol !== ProtocolInfo::CURRENT_PROTOCOL){
if($packet->protocol < ProtocolInfo::CURRENT_PROTOCOL){
$this->sendPlayStatus(PlayStatusPacket::LOGIN_FAILED_CLIENT, true);
}else{
$this->sendPlayStatus(PlayStatusPacket::LOGIN_FAILED_SERVER, true);
}
//This pocketmine disconnect message will only be seen by the console (PlayStatusPacket causes the messages to be shown for the client)
$this->close("", $this->server->getLanguage()->translateString("pocketmine.disconnect.incompatibleProtocol", [$packet->protocol ?? "unknown"]), false);
return true;
}
if(!self::isValidUserName($packet->username)){
$this->close("", "disconnectionScreen.invalidName");
return true;
}
$this->username = TextFormat::clean($packet->username);
$this->displayName = $this->username;
$this->iusername = strtolower($this->username);
if($packet->locale !== null){
$this->locale = $packet->locale;
}
$this->randomClientId = $packet->clientId;
$this->uuid = UUID::fromString($packet->clientUUID);
$this->rawUUID = $this->uuid->toBinary();
$skin = new Skin(
$packet->clientData["SkinId"],
base64_decode($packet->clientData["SkinData"] ?? ""),
base64_decode($packet->clientData["CapeData"] ?? ""),
$packet->clientData["SkinGeometryName"] ?? "",
base64_decode($packet->clientData["SkinGeometry"] ?? "")
);
$this->setSkin($packet->skin);
if(!$skin->isValid()){
$this->close("", "disconnectionScreen.invalidSkin");
return true;
}
$this->setSkin($skin);
$this->server->getPluginManager()->callEvent($ev = new PlayerPreLoginEvent($this, "Plugin reason"));
if($ev->isCancelled()){
@ -1884,13 +1828,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true;
}
public function sendPlayStatus(int $status, bool $immediate = false){
$pk = new PlayStatusPacket();
$pk->status = $status;
$pk->protocol = $this->protocol;
$this->sendDataPacket($pk, $immediate);
}
public function onVerifyCompleted(LoginPacket $packet, ?string $error, bool $signedByMojang) : void{
if($this->closed){
return;
@ -1935,64 +1872,12 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
$this->sendPlayStatus(PlayStatusPacket::LOGIN_SUCCESS);
$this->loggedIn = true;
$this->server->onPlayerLogin($this);
$pk = new ResourcePacksInfoPacket();
$manager = $this->server->getResourcePackManager();
$pk->resourcePackEntries = $manager->getResourceStack();
$pk->mustAccept = $manager->resourcePacksRequired();
$this->dataPacket($pk);
$this->networkSession->onLoginSuccess();
}
public function handleResourcePackClientResponse(ResourcePackClientResponsePacket $packet) : bool{
switch($packet->status){
case ResourcePackClientResponsePacket::STATUS_REFUSED:
//TODO: add lang strings for this
$this->close("", "You must accept resource packs to join this server.", true);
break;
case ResourcePackClientResponsePacket::STATUS_SEND_PACKS:
$manager = $this->server->getResourcePackManager();
foreach($packet->packIds as $uuid){
$pack = $manager->getPackById($uuid);
if(!($pack instanceof ResourcePack)){
//Client requested a resource pack but we don't have it available on the server
$this->close("", "disconnectionScreen.resourcePack", true);
$this->server->getLogger()->debug("Got a resource pack request for unknown pack with UUID " . $uuid . ", available packs: " . implode(", ", $manager->getPackIdList()));
return false;
}
$pk = new ResourcePackDataInfoPacket();
$pk->packId = $pack->getPackId();
$pk->maxChunkSize = 1048576; //1MB
$pk->chunkCount = (int) ceil($pack->getPackSize() / $pk->maxChunkSize);
$pk->compressedPackSize = $pack->getPackSize();
$pk->sha256 = $pack->getSha256();
$this->dataPacket($pk);
}
break;
case ResourcePackClientResponsePacket::STATUS_HAVE_ALL_PACKS:
$pk = new ResourcePackStackPacket();
$manager = $this->server->getResourcePackManager();
$pk->resourcePackStack = $manager->getResourceStack();
$pk->mustAccept = $manager->resourcePacksRequired();
$this->dataPacket($pk);
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:
$this->_actuallyConstruct();
break;
default:
return false;
}
return true;
}
protected function _actuallyConstruct(){
public function _actuallyConstruct(){
$namedtag = $this->server->getOfflinePlayerData($this->username); //TODO: make this async
if(($level = $this->server->getLevelByName($namedtag->getString("Level", "", true))) === null){
@ -2021,50 +1906,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return;
}
$spawnPosition = $this->getSpawn();
$pk = new StartGamePacket();
$pk->entityUniqueId = $this->id;
$pk->entityRuntimeId = $this->id;
$pk->playerGamemode = Player::getClientFriendlyGamemode($this->gamemode);
$pk->playerPosition = $this->getOffsetPosition($this);
$pk->pitch = $this->pitch;
$pk->yaw = $this->yaw;
$pk->seed = -1;
$pk->dimension = DimensionIds::OVERWORLD; //TODO: implement this properly
$pk->worldGamemode = Player::getClientFriendlyGamemode($this->server->getGamemode());
$pk->difficulty = $this->level->getDifficulty();
$pk->spawnX = $spawnPosition->getFloorX();
$pk->spawnY = $spawnPosition->getFloorY();
$pk->spawnZ = $spawnPosition->getFloorZ();
$pk->hasAchievementsDisabled = true;
$pk->time = $this->level->getTime();
$pk->eduMode = false;
$pk->rainLevel = 0; //TODO: implement these properly
$pk->lightningLevel = 0;
$pk->commandsEnabled = true;
$pk->levelId = "";
$pk->worldName = $this->server->getMotd();
$this->dataPacket($pk);
$this->level->sendTime($this);
$this->sendAttributes(true);
$this->sendCommandData();
$this->sendSettings();
$this->sendPotionEffects($this);
$this->sendData($this);
$this->sendAllInventories();
$this->inventory->sendCreativeContents();
$this->inventory->sendHeldItem($this);
$this->networkSession->getInterface()->putPacket($this, $this->server->getCraftingManager()->getCraftingDataPacket());
$this->server->addOnlinePlayer($this);
$this->server->sendFullPlayerListData($this);
$this->server->getLogger()->info($this->getServer()->getLanguage()->translateString("pocketmine.player.logIn", [
TextFormat::AQUA . $this->username . TextFormat::WHITE,
$this->networkSession->getIp(),
@ -2075,6 +1916,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
round($this->y, 4),
round($this->z, 4)
]));
$this->server->addOnlinePlayer($this);
}
protected function initHumanData() : void{
@ -2128,7 +1971,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* @return bool
*/
public function chat(string $message) : bool{
if(!$this->spawned or !$this->isAlive()){
if(!$this->isAlive()){
return false;
}
@ -2172,7 +2015,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->sendPosition($this, null, null, MovePlayerPacket::MODE_RESET);
$this->server->getLogger()->debug("Got outdated pre-teleport movement from " . $this->getName() . ", received " . $newPos . ", expected " . $this->asVector3());
//Still getting movements from before teleport, ignore them
}elseif((!$this->isAlive() or !$this->spawned) and $newPos->distanceSquared($this) > 0.01){
}elseif((!$this->isAlive()) and $newPos->distanceSquared($this) > 0.01){
$this->sendPosition($this, null, null, MovePlayerPacket::MODE_RESET);
$this->server->getLogger()->debug("Reverted movement of " . $this->getName() . " due to not alive or not spawned, received " . $newPos . ", locked at " . $this->asVector3());
}else{
@ -2204,7 +2047,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handleEntityEvent(EntityEventPacket $packet) : bool{
if(!$this->spawned or !$this->isAlive()){
if(!$this->isAlive()){
return true;
}
$this->doCloseInventory();
@ -2232,7 +2075,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* @return bool
*/
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
if(!$this->spawned or !$this->isAlive()){
if(!$this->isAlive()){
return false;
}
@ -2594,7 +2437,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handleMobEquipment(MobEquipmentPacket $packet) : bool{
if(!$this->spawned or !$this->isAlive()){
if(!$this->isAlive()){
return true;
}
@ -2644,7 +2487,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handlePlayerAction(PlayerActionPacket $packet) : bool{
if(!$this->spawned or (!$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_RESPAWN and $packet->action !== PlayerActionPacket::ACTION_DIMENSION_CHANGE_REQUEST)){
if(!$this->isAlive() and $packet->action !== PlayerActionPacket::ACTION_RESPAWN and $packet->action !== PlayerActionPacket::ACTION_DIMENSION_CHANGE_REQUEST){
return true;
}
@ -2668,7 +2511,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
$this->stopSleep();
break;
case PlayerActionPacket::ACTION_RESPAWN:
if(!$this->spawned or $this->isAlive() or !$this->isOnline()){
if($this->isAlive() or !$this->isOnline()){
break;
}
@ -2781,7 +2624,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handleAnimate(AnimatePacket $packet) : bool{
if(!$this->spawned or !$this->isAlive()){
if(!$this->isAlive()){
return true;
}
@ -2805,7 +2648,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* @return bool if the item was dropped or if the item was null
*/
public function dropItem(Item $item) : bool{
if(!$this->spawned or !$this->isAlive()){
if(!$this->isAlive()){
return false;
}
@ -2854,7 +2697,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handleBlockEntityData(BlockEntityDataPacket $packet) : bool{
if(!$this->spawned or !$this->isAlive()){
if(!$this->isAlive()){
return true;
}
$this->doCloseInventory();
@ -2890,7 +2733,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
public function handleItemFrameDropItem(ItemFrameDropItemPacket $packet) : bool{
if(!$this->spawned or !$this->isAlive()){
if(!$this->isAlive()){
return true;
}
@ -2917,25 +2760,6 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
return true;
}
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
$manager = $this->server->getResourcePackManager();
$pack = $manager->getPackById($packet->packId);
if(!($pack instanceof ResourcePack)){
$this->close("", "disconnectionScreen.resourcePack", true);
$this->server->getLogger()->debug("Got a resource pack chunk request for unknown pack with UUID " . $packet->packId . ", available packs: " . implode(", ", $manager->getPackIdList()));
return false;
}
$pk = new ResourcePackChunkDataPacket();
$pk->packId = $pack->getPackId();
$pk->chunkIndex = $packet->chunkIndex;
$pk->data = $pack->getPackChunk(1048576 * $packet->chunkIndex, 1048576);
$pk->progress = (1048576 * $packet->chunkIndex);
$this->dataPacket($pk);
return true;
}
public function handleBookEdit(BookEditPacket $packet) : bool{
/** @var WritableBook $oldBook */
$oldBook = $this->inventory->getItem($packet->inventorySlot);
@ -3732,7 +3556,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* @return bool
*/
public function doCloseWindow(int $windowId) : bool{
if(!$this->spawned or $windowId === 0){
if($windowId === 0){
return false;
}
@ -3844,7 +3668,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
}
}
protected function sendAllInventories(){
public function sendAllInventories(){
foreach($this->windowIndex as $id => $inventory){
$inventory->sendContents($this);
}

View File

@ -25,11 +25,15 @@ namespace pocketmine\network\mcpe;
use pocketmine\event\server\DataPacketReceiveEvent;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\network\mcpe\handler\LoginSessionHandler;
use pocketmine\network\mcpe\handler\PreSpawnSessionHandler;
use pocketmine\network\mcpe\handler\ResourcePacksSessionHandler;
use pocketmine\network\mcpe\handler\SessionHandler;
use pocketmine\network\mcpe\handler\SimpleSessionHandler;
use pocketmine\network\mcpe\protocol\DataPacket;
use pocketmine\network\mcpe\protocol\DisconnectPacket;
use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\NetworkInterface;
use pocketmine\Player;
use pocketmine\Server;
@ -60,7 +64,7 @@ class NetworkSession{
$this->ip = $ip;
$this->port = $port;
$this->setHandler(new SimpleSessionHandler($player));
$this->setHandler(new LoginSessionHandler($player, $this));
}
public function getInterface() : NetworkInterface{
@ -144,4 +148,29 @@ class NetworkSession{
}
$this->interface->close($this->player, $notify ? $reason : "");
}
//TODO: onEnableEncryption() step
public function onLoginSuccess() : void{
$pk = new PlayStatusPacket();
$pk->status = PlayStatusPacket::LOGIN_SUCCESS;
$this->sendDataPacket($pk);
$this->setHandler(new ResourcePacksSessionHandler($this->server, $this->player, $this));
}
public function onResourcePacksDone() : void{
$this->player->_actuallyConstruct();
$this->setHandler(new PreSpawnSessionHandler($this->server, $this->player, $this));
}
public function onSpawn() : void{
$pk = new PlayStatusPacket();
$pk->status = PlayStatusPacket::PLAYER_SPAWN;
$this->sendDataPacket($pk);
//TODO: split this up even further
$this->setHandler(new SimpleSessionHandler($this->player));
}
}

View File

@ -0,0 +1,89 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\Player;
/**
* Handles the initial login phase of the session. This handler is used as the initial state.
*/
class LoginSessionHandler extends SessionHandler{
/** @var Player */
private $player;
/** @var NetworkSession */
private $session;
public function __construct(Player $player, NetworkSession $session){
$this->player = $player;
$this->session = $session;
}
public function handleLogin(LoginPacket $packet) : bool{
if(!$this->isCompatibleProtocol($packet->protocol)){
$pk = new PlayStatusPacket();
$pk->status = $packet->protocol < ProtocolInfo::CURRENT_PROTOCOL ?
PlayStatusPacket::LOGIN_FAILED_CLIENT : PlayStatusPacket::LOGIN_FAILED_SERVER;
$pk->protocol = $packet->protocol;
$this->session->sendDataPacket($pk, true);
//This pocketmine disconnect message will only be seen by the console (PlayStatusPacket causes the messages to be shown for the client)
$this->player->close(
"",
$this->player->getServer()->getLanguage()->translateString("pocketmine.disconnect.incompatibleProtocol", [$packet->protocol]),
false
);
return true;
}
if(!Player::isValidUserName($packet->username)){
$this->player->close("", "disconnectionScreen.invalidName");
return true;
}
if($packet->skin === null or !$packet->skin->isValid()){
$this->player->close("", "disconnectionScreen.invalidSkin");
return true;
}
if($this->player->handleLogin($packet)){
if($this->session->getHandler() === $this){ //when login verification is disabled, the handler will already have been replaced
$this->session->setHandler(new NullSessionHandler()); //drop packets received during login verification
}
return true;
}
return false;
}
protected function isCompatibleProtocol(int $protocolVersion) : bool{
return $protocolVersion === ProtocolInfo::CURRENT_PROTOCOL;
}
}

View File

@ -0,0 +1,31 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
/**
* Handler which simply ignores all packets received.
*/
class NullSessionHandler extends SessionHandler{
}

View File

@ -0,0 +1,99 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\Player;
use pocketmine\Server;
/**
* Handler used for the pre-spawn phase of the session.
*/
class PreSpawnSessionHandler extends SessionHandler{
/** @var Server */
private $server;
/** @var Player */
private $player;
/** @var NetworkSession */
private $session;
public function __construct(Server $server, Player $player, NetworkSession $session){
$this->player = $player;
$this->server = $server;
$this->session = $session;
}
public function setUp() : void{
$spawnPosition = $this->player->getSpawn();
$pk = new StartGamePacket();
$pk->entityUniqueId = $this->player->getId();
$pk->entityRuntimeId = $this->player->getId();
$pk->playerGamemode = Player::getClientFriendlyGamemode($this->player->getGamemode());
$pk->playerPosition = $this->player->getOffsetPosition($this->player);
$pk->pitch = $this->player->pitch;
$pk->yaw = $this->player->yaw;
$pk->seed = -1;
$pk->dimension = DimensionIds::OVERWORLD; //TODO: implement this properly
$pk->worldGamemode = Player::getClientFriendlyGamemode($this->server->getGamemode());
$pk->difficulty = $this->player->getLevel()->getDifficulty();
$pk->spawnX = $spawnPosition->getFloorX();
$pk->spawnY = $spawnPosition->getFloorY();
$pk->spawnZ = $spawnPosition->getFloorZ();
$pk->hasAchievementsDisabled = true;
$pk->time = $this->player->getLevel()->getTime();
$pk->eduMode = false;
$pk->rainLevel = 0; //TODO: implement these properly
$pk->lightningLevel = 0;
$pk->commandsEnabled = true;
$pk->levelId = "";
$pk->worldName = $this->server->getMotd();
$this->session->sendDataPacket($pk);
$this->player->getLevel()->sendTime($this->player);
$this->player->sendAttributes(true);
$this->player->sendCommandData();
$this->player->sendSettings();
$this->player->sendPotionEffects($this->player);
$this->player->sendData($this->player);
$this->player->sendAllInventories();
$this->player->getInventory()->sendCreativeContents();
$this->player->getInventory()->sendHeldItem($this->player);
$this->session->getInterface()->putPacket($this->player, $this->server->getCraftingManager()->getCraftingDataPacket());
$this->server->sendFullPlayerListData($this->player);
}
public function handleRequestChunkRadius(RequestChunkRadiusPacket $packet) : bool{
$this->player->setViewDistance($packet->radius);
return true;
}
}

View File

@ -0,0 +1,128 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\ResourcePackChunkDataPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket;
use pocketmine\network\mcpe\protocol\ResourcePackDataInfoPacket;
use pocketmine\network\mcpe\protocol\ResourcePacksInfoPacket;
use pocketmine\network\mcpe\protocol\ResourcePackStackPacket;
use pocketmine\Player;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\Server;
/**
* Handler used for the resource packs sequence phase of the session. This handler takes care of downloading resource
* packs to the client.
*/
class ResourcePacksSessionHandler extends SessionHandler{
/** @var Server */
private $server;
/** @var Player */
private $player;
/** @var NetworkSession */
private $session;
public function __construct(Server $server, Player $player, NetworkSession $session){
$this->server = $server;
$this->player = $player;
$this->session = $session;
}
public function setUp() : void{
$pk = new ResourcePacksInfoPacket();
$manager = $this->server->getResourcePackManager();
$pk->resourcePackEntries = $manager->getResourceStack();
$pk->mustAccept = $manager->resourcePacksRequired();
$this->session->sendDataPacket($pk);
}
public function handleResourcePackClientResponse(ResourcePackClientResponsePacket $packet) : bool{
switch($packet->status){
case ResourcePackClientResponsePacket::STATUS_REFUSED:
//TODO: add lang strings for this
$this->player->close("", "You must accept resource packs to join this server.", true);
break;
case ResourcePackClientResponsePacket::STATUS_SEND_PACKS:
$manager = $this->server->getResourcePackManager();
foreach($packet->packIds as $uuid){
$pack = $manager->getPackById($uuid);
if(!($pack instanceof ResourcePack)){
//Client requested a resource pack but we don't have it available on the server
$this->player->close("", "disconnectionScreen.resourcePack", true);
$this->server->getLogger()->debug("Got a resource pack request for unknown pack with UUID " . $uuid . ", available packs: " . implode(", ", $manager->getPackIdList()));
return false;
}
$pk = new ResourcePackDataInfoPacket();
$pk->packId = $pack->getPackId();
$pk->maxChunkSize = 1048576; //1MB
$pk->chunkCount = (int) ceil($pack->getPackSize() / $pk->maxChunkSize);
$pk->compressedPackSize = $pack->getPackSize();
$pk->sha256 = $pack->getSha256();
$this->session->sendDataPacket($pk);
}
break;
case ResourcePackClientResponsePacket::STATUS_HAVE_ALL_PACKS:
$pk = new ResourcePackStackPacket();
$manager = $this->server->getResourcePackManager();
$pk->resourcePackStack = $manager->getResourceStack();
$pk->mustAccept = $manager->resourcePacksRequired();
$this->session->sendDataPacket($pk);
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:
$this->session->onResourcePacksDone();
break;
default:
return false;
}
return true;
}
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
$manager = $this->server->getResourcePackManager();
$pack = $manager->getPackById($packet->packId);
if(!($pack instanceof ResourcePack)){
$this->player->close("", "disconnectionScreen.resourcePack", true);
$this->server->getLogger()->debug("Got a resource pack chunk request for unknown pack with UUID " . $packet->packId . ", available packs: " . implode(", ", $manager->getPackIdList()));
return false;
}
$pk = new ResourcePackChunkDataPacket();
$pk->packId = $pack->getPackId();
$pk->chunkIndex = $packet->chunkIndex;
$pk->data = $pack->getPackChunk(1048576 * $packet->chunkIndex, 1048576);
$pk->progress = (1048576 * $packet->chunkIndex);
$this->session->sendDataPacket($pk);
return true;
}
}

View File

@ -42,7 +42,6 @@ use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\LabTablePacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
@ -53,8 +52,6 @@ use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\ResourcePackChunkRequestPacket;
use pocketmine\network\mcpe\protocol\ResourcePackClientResponsePacket;
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
use pocketmine\network\mcpe\protocol\SetLocalPlayerAsInitializedPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
@ -77,18 +74,10 @@ class SimpleSessionHandler extends SessionHandler{
$this->player = $player;
}
public function handleLogin(LoginPacket $packet) : bool{
return $this->player->handleLogin($packet);
}
public function handleClientToServerHandshake(ClientToServerHandshakePacket $packet) : bool{
return false; //TODO
}
public function handleResourcePackClientResponse(ResourcePackClientResponsePacket $packet) : bool{
return $this->player->handleResourcePackClientResponse($packet);
}
public function handleText(TextPacket $packet) : bool{
if($packet->type === TextPacket::TYPE_CHAT){
return $this->player->chat($packet->message);
@ -207,10 +196,6 @@ class SimpleSessionHandler extends SessionHandler{
return false; //TODO
}
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
return $this->player->handleResourcePackChunkRequest($packet);
}
public function handlePlayerSkin(PlayerSkinPacket $packet) : bool{
return $this->player->changeSkin($packet->skin, $packet->newSkinName, $packet->oldSkinName);
}

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
#include <rules/DataPacket.h>
use pocketmine\entity\Skin;
use pocketmine\network\mcpe\handler\SessionHandler;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\MainLogger;
@ -52,6 +53,8 @@ class LoginPacket extends DataPacket{
public $serverAddress;
/** @var string */
public $locale;
/** @var Skin|null */
public $skin;
/** @var array (the "chain" index contains one or more JWTs) */
public $chainData = [];
@ -136,7 +139,15 @@ class LoginPacket extends DataPacket{
$this->clientId = $this->clientData["ClientRandomId"] ?? null;
$this->serverAddress = $this->clientData["ServerAddress"] ?? null;
$this->locale = $this->clientData["LanguageCode"] ?? null;
$this->locale = $this->clientData["LanguageCode"] ?? "en_US";
$this->skin = new Skin(
$this->clientData["SkinId"] ?? "",
base64_decode($this->clientData["SkinData"] ?? ""),
base64_decode($this->clientData["CapeData"] ?? ""),
$this->clientData["SkinGeometryName"] ?? "",
base64_decode($this->clientData["SkinGeometry"] ?? "")
);
}
protected function encodePayload() : void{