diff --git a/changelogs/4.9.md b/changelogs/4.9.md new file mode 100644 index 000000000..34b861657 --- /dev/null +++ b/changelogs/4.9.md @@ -0,0 +1,14 @@ +**For Minecraft: Bedrock Edition 1.19.30** + +### Note about API versions +Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps. +Plugin developers should **only** update their required API to this version if you need the changes in this build. + +**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do. + +# 4.9.0 +Released 20th September 2022. + +## General +- Added support for Minecraft: Bedrock Edition 1.19.30. +- Removed support for older versions. diff --git a/composer.json b/composer.json index fa630de86..4bed6daac 100644 --- a/composer.json +++ b/composer.json @@ -34,8 +34,8 @@ "adhocore/json-comment": "^1.1", "fgrosse/phpasn1": "^2.3", "netresearch/jsonmapper": "^4.0", - "pocketmine/bedrock-data": "~1.10.0+bedrock-1.19.20", - "pocketmine/bedrock-protocol": "~12.2.0+bedrock-1.19.20", + "pocketmine/bedrock-data": "~1.11.0+bedrock-1.19.30", + "pocketmine/bedrock-protocol": "~13.0.0+bedrock-1.19.30", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/classloader": "^0.2.0", diff --git a/composer.lock b/composer.lock index b2e7b44d7..b4b27c130 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "82c3c4f98020c30f8ba31f11211abdc3", + "content-hash": "0f6c845836d4ec6f221415d2f9dd1fc5", "packages": [ { "name": "adhocore/json-comment", @@ -245,16 +245,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "1.10.0+bedrock-1.19.20", + "version": "1.11.0+bedrock-1.19.30", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "43610f6749f22d15ede6b60ed5402bdeff47453e" + "reference": "2cca1d48421db20740ffd1ce67b40b91acc61187" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/43610f6749f22d15ede6b60ed5402bdeff47453e", - "reference": "43610f6749f22d15ede6b60ed5402bdeff47453e", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2cca1d48421db20740ffd1ce67b40b91acc61187", + "reference": "2cca1d48421db20740ffd1ce67b40b91acc61187", "shasum": "" }, "type": "library", @@ -265,22 +265,22 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.19.20" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.19.30" }, - "time": "2022-08-09T17:44:22+00:00" + "time": "2022-09-20T18:19:22+00:00" }, { "name": "pocketmine/bedrock-protocol", - "version": "12.2.0+bedrock-1.19.21", + "version": "13.0.0+bedrock-1.19.30", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "d1b3e83f77e2c6628b64793485260cddc55d92e3" + "reference": "94de2221676ca717587e1ff4e45445c24ada1749" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/d1b3e83f77e2c6628b64793485260cddc55d92e3", - "reference": "d1b3e83f77e2c6628b64793485260cddc55d92e3", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/94de2221676ca717587e1ff4e45445c24ada1749", + "reference": "94de2221676ca717587e1ff4e45445c24ada1749", "shasum": "" }, "require": { @@ -312,9 +312,9 @@ "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", "support": { "issues": "https://github.com/pmmp/BedrockProtocol/issues", - "source": "https://github.com/pmmp/BedrockProtocol/tree/12.2.0+bedrock-1.19.21" + "source": "https://github.com/pmmp/BedrockProtocol/tree/bedrock-1.19.30" }, - "time": "2022-08-24T18:47:37+00:00" + "time": "2022-09-20T18:35:00+00:00" }, { "name": "pocketmine/binaryutils", @@ -926,16 +926,16 @@ }, { "name": "ramsey/uuid", - "version": "4.5.0", + "version": "4.5.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "ef842484ba57f163c6d465ab744bfecb872a11d4" + "reference": "a161a26d917604dc6d3aa25100fddf2556e9f35d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/ef842484ba57f163c6d465ab744bfecb872a11d4", - "reference": "ef842484ba57f163c6d465ab744bfecb872a11d4", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/a161a26d917604dc6d3aa25100fddf2556e9f35d", + "reference": "a161a26d917604dc6d3aa25100fddf2556e9f35d", "shasum": "" }, "require": { @@ -1004,7 +1004,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.5.0" + "source": "https://github.com/ramsey/uuid/tree/4.5.1" }, "funding": [ { @@ -1016,7 +1016,7 @@ "type": "tidelift" } ], - "time": "2022-09-15T01:44:53+00:00" + "time": "2022-09-16T03:22:46+00:00" }, { "name": "symfony/polyfill-php81", diff --git a/src/PocketMine.php b/src/PocketMine.php index f4f9a198d..b2e9a57a7 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -177,10 +177,10 @@ namespace pocketmine { --------------------------------------- ! WARNING ! --------------------------------------- - You're using PHP 8.0 with JIT enabled. This provides significant performance improvements. + You're using PHP with JIT enabled. This provides significant performance improvements. HOWEVER, it is EXPERIMENTAL, and has already been seen to cause weird and unexpected bugs. Proceed with caution. - If you want to report any bugs, make sure to mention that you are using PHP 8.0 with JIT. + If you want to report any bugs, make sure to mention that you have enabled PHP JIT. To turn off JIT, change `opcache.jit` to `0` in your php.ini file. ------------------------------------------------------------------------------------------- diff --git a/src/VersionInfo.php b/src/VersionInfo.php index a97ab2640..b3acd4d99 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,7 +31,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "4.8.2"; + public const BASE_VERSION = "4.9.1"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "beta"; diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index 90b3f659f..cd1f96a5e 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -227,7 +227,7 @@ final class EntityFactory{ */ public function createFromData(World $world, CompoundTag $nbt) : ?Entity{ try{ - $saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier"); + $saveId = $nbt->getTag("identifier") ?? $nbt->getTag("id"); $func = null; if($saveId instanceof StringTag){ $func = $this->creationFuncs[$saveId->getValue()] ?? null; @@ -248,7 +248,7 @@ final class EntityFactory{ public function injectSaveId(string $class, CompoundTag $saveData) : void{ if(isset($this->saveNames[$class])){ - $saveData->setTag("id", new StringTag($this->saveNames[$class])); + $saveData->setTag("identifier", new StringTag($this->saveNames[$class])); }else{ throw new \InvalidArgumentException("Entity $class is not registered"); } diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index 7556e843d..5c39f3bec 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -186,6 +186,10 @@ class ItemEntity extends Entity{ return true; } + public function canSaveWithChunk() : bool{ + return !$this->item->isNull() && parent::canSaveWithChunk(); + } + public function saveNBT() : CompoundTag{ $nbt = parent::saveNBT(); $nbt->setTag("Item", $this->item->nbtSerialize()); diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 6f9286920..156f65978 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -56,6 +56,7 @@ use pocketmine\network\mcpe\handler\LoginPacketHandler; use pocketmine\network\mcpe\handler\PacketHandler; use pocketmine\network\mcpe\handler\PreSpawnPacketHandler; use pocketmine\network\mcpe\handler\ResourcePacksPacketHandler; +use pocketmine\network\mcpe\handler\SessionStartPacketHandler; use pocketmine\network\mcpe\handler\SpawnResponsePacketHandler; use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket; @@ -164,6 +165,7 @@ class NetworkSession{ */ private \SplQueue $compressedQueue; private bool $forceAsyncCompression = true; + private bool $enableCompression = false; //disabled until handshake completed private PacketSerializerContext $packetSerializerContext; @@ -196,17 +198,10 @@ class NetworkSession{ $this->connectTime = time(); - $this->setHandler(new LoginPacketHandler( + $this->setHandler(new SessionStartPacketHandler( $this->server, $this, - function(PlayerInfo $info) : void{ - $this->info = $info; - $this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET); - $this->logger->setPrefix($this->getLogPrefix()); - }, - function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{ - $this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey); - } + fn() => $this->onSessionStartSuccess() )); $this->manager->add($this); @@ -221,6 +216,24 @@ class NetworkSession{ return $this->logger; } + private function onSessionStartSuccess() : void{ + $this->logger->debug("Session start handshake completed, awaiting login packet"); + $this->flushSendBuffer(true); + $this->enableCompression = true; + $this->setHandler(new LoginPacketHandler( + $this->server, + $this, + function(PlayerInfo $info) : void{ + $this->info = $info; + $this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET); + $this->logger->setPrefix($this->getLogPrefix()); + }, + function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{ + $this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey); + } + )); + } + protected function createPlayer() : void{ $this->server->createPlayer($this, $this->info, $this->authenticated, $this->cachedOfflinePlayerData)->onCompletion( \Closure::fromCallable([$this, 'onPlayerCreated']), @@ -335,18 +348,22 @@ class NetworkSession{ } } - Timings::$playerNetworkReceiveDecompress->startTiming(); - try{ - $stream = new PacketBatch($this->compressor->decompress($payload)); - }catch(DecompressionException $e){ - $this->logger->debug("Failed to decompress packet: " . base64_encode($payload)); - throw PacketHandlingException::wrap($e, "Compressed packet batch decode error"); - }finally{ - Timings::$playerNetworkReceiveDecompress->stopTiming(); + if($this->enableCompression){ + Timings::$playerNetworkReceiveDecompress->startTiming(); + try{ + $decompressed = $this->compressor->decompress($payload); + }catch(DecompressionException $e){ + $this->logger->debug("Failed to decompress packet: " . base64_encode($payload)); + throw PacketHandlingException::wrap($e, "Compressed packet batch decode error"); + }finally{ + Timings::$playerNetworkReceiveDecompress->stopTiming(); + } + }else{ + $decompressed = $payload; } try{ - foreach($stream->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){ + foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){ if($packet === null){ $this->logger->debug("Unknown packet: " . base64_encode($buffer)); throw new PacketHandlingException("Unknown packet received"); @@ -451,7 +468,14 @@ class NetworkSession{ }elseif($this->forceAsyncCompression){ $syncMode = false; } - $promise = $this->server->prepareBatch(PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer), $this->compressor, $syncMode); + + $batch = PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer); + if($this->enableCompression){ + $promise = $this->server->prepareBatch($batch, $this->compressor, $syncMode); + }else{ + $promise = new CompressBatchPromise(); + $promise->resolve($batch->getBuffer()); + } $this->sendBuffer = []; $this->queueCompressedNoBufferFlush($promise, $immediate); } diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index 5ed60745c..b1f5f1f38 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -43,7 +43,9 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction; use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; +use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor; use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient; +use pocketmine\network\mcpe\protocol\types\recipe\StringIdMetaItemDescriptor; use pocketmine\player\GameMode; use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; @@ -110,7 +112,7 @@ class TypeConverter{ public function coreItemStackToRecipeIngredient(Item $itemStack) : RecipeIngredient{ if($itemStack->isNull()){ - return new RecipeIngredient(0, 0, 0); + return new RecipeIngredient(null, 0); } if($itemStack->hasAnyDamageValue()){ [$id, ] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), 0); @@ -118,15 +120,25 @@ class TypeConverter{ }else{ [$id, $meta] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), $itemStack->getMeta()); } - return new RecipeIngredient($id, $meta, $itemStack->getCount()); + return new RecipeIngredient(new IntIdMetaItemDescriptor($id, $meta), $itemStack->getCount()); } public function recipeIngredientToCoreItemStack(RecipeIngredient $ingredient) : Item{ - if($ingredient->getId() === 0){ + $descriptor = $ingredient->getDescriptor(); + if($descriptor === null){ return VanillaItems::AIR(); } - [$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($ingredient->getId(), $ingredient->getMeta()); - return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount()); + if($descriptor instanceof IntIdMetaItemDescriptor){ + [$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($descriptor->getId(), $descriptor->getMeta()); + return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount()); + } + if($descriptor instanceof StringIdMetaItemDescriptor){ + $intId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromStringId($descriptor->getId()); + [$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($intId, $descriptor->getMeta()); + return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount()); + } + + throw new \LogicException("Unsupported conversion of recipe ingredient to core item stack"); } public function coreItemStackToNet(Item $itemStack) : ItemStack{ diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 7a8caabfe..53bcd0345 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -51,7 +51,6 @@ use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\ActorEventPacket; use pocketmine\network\mcpe\protocol\ActorPickRequestPacket; -use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\BlockActorDataPacket; use pocketmine\network\mcpe\protocol\BlockPickRequestPacket; @@ -664,10 +663,6 @@ class InGamePacketHandler extends PacketHandler{ return true; //this is a broken useless packet, so we don't use it } - public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{ - return true; //no longer used, but the client still sends it for flight changes - } - public function handleBlockActorData(BlockActorDataPacket $packet) : bool{ $pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ()); if($pos->distanceSquared($this->player->getLocation()) > 10000){ diff --git a/src/network/mcpe/handler/SessionStartPacketHandler.php b/src/network/mcpe/handler/SessionStartPacketHandler.php new file mode 100644 index 000000000..687422de2 --- /dev/null +++ b/src/network/mcpe/handler/SessionStartPacketHandler.php @@ -0,0 +1,76 @@ +getProtocolVersion(); + if(!$this->isCompatibleProtocol($protocolVersion)){ + $this->session->sendDataPacket(PlayStatusPacket::create($protocolVersion < ProtocolInfo::CURRENT_PROTOCOL ? PlayStatusPacket::LOGIN_FAILED_CLIENT : 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->session->disconnect( + $this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_disconnect_incompatibleProtocol((string) $protocolVersion)), + false + ); + + return true; + } + + //TODO: we're filling in the defaults to get pre-1.19.30 behaviour back for now, but we should explore the new options in the future + $this->session->sendDataPacket(NetworkSettingsPacket::create( + NetworkSettingsPacket::COMPRESS_EVERYTHING, + CompressionAlgorithm::ZLIB, + false, + 0, + 0 + )); + ($this->onSuccess)(); + + return true; + } + + protected function isCompatibleProtocol(int $protocolVersion) : bool{ + return $protocolVersion === ProtocolInfo::CURRENT_PROTOCOL; + } +} diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index ddb221692..33a50ac56 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -58,7 +58,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ * Sometimes this gets changed when the MCPE-layer protocol gets broken to the point where old and new can't * communicate. It's important that we check this to avoid catastrophes. */ - private const MCPE_RAKNET_PROTOCOL_VERSION = 10; + private const MCPE_RAKNET_PROTOCOL_VERSION = 11; private const MCPE_RAKNET_PACKET_ID = "\xfe"; diff --git a/src/world/World.php b/src/world/World.php index 549a0f501..7c8522ae2 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -2492,7 +2492,7 @@ class World implements ChunkManager{ continue; } if($entity === null){ - $saveIdTag = $nbt->getTag("id") ?? $nbt->getTag("identifier"); + $saveIdTag = $nbt->getTag("identifier") ?? $nbt->getTag("id"); $saveId = ""; if($saveIdTag instanceof StringTag){ $saveId = $saveIdTag->getValue();