From 9931c1d50aad0397dab4f7da073436f53697d447 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 30 Nov 2021 18:46:29 +0000 Subject: [PATCH] Protocol changes for 1.18.0 --- composer.json | 1 + composer.lock | 3 +- src/pocketmine/Player.php | 1 + src/pocketmine/PocketMine.php | 11 ++++ src/pocketmine/level/format/Chunk.php | 64 ++++++++++++++++++- .../level/format/io/ChunkRequestTask.php | 9 +-- .../network/mcpe/protocol/ProtocolInfo.php | 6 +- .../network/mcpe/protocol/StartGamePacket.php | 4 ++ .../network/mcpe/protocol/SubChunkPacket.php | 12 +++- src/pocketmine/resources/vanilla | 2 +- 10 files changed, 100 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 400c32846..ab792210b 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "require": { "php": "^8.0", "php-64bit": "*", + "ext-chunkutils2": "^0.3.1", "ext-ctype": "*", "ext-curl": "*", "ext-date": "*", diff --git a/composer.lock b/composer.lock index dd293362e..e948d9abf 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": "f13c4ed7311b99ac07dca31d2f77ad1d", + "content-hash": "2d144524b177c9e7f699ba7366c16354", "packages": [ { "name": "adhocore/json-comment", @@ -2761,6 +2761,7 @@ "platform": { "php": "^8.0", "php-64bit": "*", + "ext-chunkutils2": "^0.3.1", "ext-ctype": "*", "ext-curl": "*", "ext-date": "*", diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index d1dbe7891..7721b4013 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -2279,6 +2279,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $pk->itemTable = ItemTypeDictionary::getInstance()->getEntries(); $pk->playerMovementSettings = new PlayerMovementSettings(PlayerMovementType::LEGACY, 0, false); $pk->serverSoftwareVersion = sprintf("%s %s", \pocketmine\NAME, \pocketmine\VERSION); + $pk->blockPaletteChecksum = 0; //we don't bother with this (0 skips verification) - the preimage is some dumb stringified NBT, not even actual NBT $this->dataPacket($pk); $this->sendDataPacket(new AvailableActorIdentifiersPacket()); diff --git a/src/pocketmine/PocketMine.php b/src/pocketmine/PocketMine.php index be7fa241e..2c6c1fdd8 100644 --- a/src/pocketmine/PocketMine.php +++ b/src/pocketmine/PocketMine.php @@ -74,6 +74,7 @@ namespace pocketmine { } $extensions = [ + "chunkutils2" => "PocketMine ChunkUtils v2", "curl" => "cURL", "ctype" => "ctype", "date" => "Date", @@ -115,6 +116,16 @@ namespace pocketmine { } } + $chunkutils2_version = phpversion("chunkutils2"); + $wantedVersionLock = "0.3"; + $wantedVersionMin = "$wantedVersionLock.0"; + if($chunkutils2_version !== false && ( + version_compare($chunkutils2_version, $wantedVersionMin) < 0 || + preg_match("/^" . preg_quote($wantedVersionLock, "/") . "\.\d+(?:-dev)?$/", $chunkutils2_version) === 0 //lock in at ^0.2, optionally at a patch release + )){ + $messages[] = "chunkutils2 ^$wantedVersionMin is required, while you have $chunkutils2_version."; + } + if(extension_loaded("pocketmine")){ $messages[] = "The native PocketMine extension is no longer supported."; } diff --git a/src/pocketmine/level/format/Chunk.php b/src/pocketmine/level/format/Chunk.php index a80b41f0f..2055862c6 100644 --- a/src/pocketmine/level/format/Chunk.php +++ b/src/pocketmine/level/format/Chunk.php @@ -28,6 +28,7 @@ namespace pocketmine\level\format; use pocketmine\block\BlockFactory; use pocketmine\entity\Entity; +use pocketmine\level\biome\Biome; use pocketmine\level\Level; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; @@ -35,13 +36,20 @@ use pocketmine\nbt\tag\StringTag; use pocketmine\Player; use pocketmine\tile\Spawnable; use pocketmine\tile\Tile; +use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\Binary; use pocketmine\utils\BinaryStream; +use pocketmine\world\format\PalettedBlockArray; use function array_fill; use function array_filter; +use function array_flip; use function array_values; use function assert; use function chr; use function count; +use function file_get_contents; +use function is_array; +use function json_decode; use function ord; use function pack; use function str_repeat; @@ -841,10 +849,21 @@ class Chunk{ public function networkSerialize() : string{ $result = ""; $subChunkCount = $this->getSubChunkSendCount(); + + //TODO: HACK! fill in fake subchunks to make up for the new negative space client-side + for($y = 0; $y < 4; ++$y){ + $result .= chr(8); //subchunk version 8 + $result .= chr(0); //0 layers - client will treat this as all-air + } for($y = 0; $y < $subChunkCount; ++$y){ $result .= $this->subChunks[$y]->networkSerialize(); } - $result .= $this->biomeIds . chr(0); //border block array count + + //TODO: right now we don't support 3D natively, so we just 3Dify our 2D biomes so they fill the column + $encodedBiomePalette = $this->networkSerializeBiomesAsPalette(); + $result .= str_repeat($encodedBiomePalette, 25); + + $result .= chr(0); //border block array count //Border block entry format: 1 byte (4 bits X, 4 bits Z). These are however useless since they crash the regular client. foreach($this->tiles as $tile){ @@ -856,6 +875,49 @@ class Chunk{ return $result; } + private function networkSerializeBiomesAsPalette() : string{ + /** @var string[]|null $biomeIdMap */ + static $biomeIdMap = null; + if($biomeIdMap === null){ + $biomeIdMapRaw = file_get_contents(\pocketmine\RESOURCE_PATH . '/vanilla/biome_id_map.json'); + if($biomeIdMapRaw === false) throw new AssumptionFailedError(); + $biomeIdMapDecoded = json_decode($biomeIdMapRaw, true); + if(!is_array($biomeIdMapDecoded)) throw new AssumptionFailedError(); + $biomeIdMap = array_flip($biomeIdMapDecoded); + } + $biomePalette = new PalettedBlockArray($this->getBiomeId(0, 0)); + for($x = 0; $x < 16; ++$x){ + for($z = 0; $z < 16; ++$z){ + $biomeId = $this->getBiomeId($x, $z); + if(!isset($biomeIdMap[$biomeId])){ + //make sure we aren't sending bogus biomes - the 1.18.0 client crashes if we do this + $biomeId = Biome::OCEAN; + } + for($y = 0; $y < 16; ++$y){ + $biomePalette->set($x, $y, $z, $biomeId); + } + } + } + + $biomePaletteBitsPerBlock = $biomePalette->getBitsPerBlock(); + $encodedBiomePalette = + chr(($biomePaletteBitsPerBlock << 1) | 1) . //the last bit is non-persistence (like for blocks), though it has no effect on biomes since they always use integer IDs + $biomePalette->getWordArray(); + + //these LSHIFT by 1 uvarints are optimizations: the client expects zigzag varints here + //but since we know they are always unsigned, we can avoid the extra fcall overhead of + //zigzag and just shift directly. + $biomePaletteArray = $biomePalette->getPalette(); + if($biomePaletteBitsPerBlock !== 0){ + $encodedBiomePalette .= Binary::writeUnsignedVarInt(count($biomePaletteArray) << 1); + } + foreach($biomePaletteArray as $p){ + $encodedBiomePalette .= Binary::writeUnsignedVarInt($p << 1); + } + + return $encodedBiomePalette; + } + /** * Fast-serializes the chunk for passing between threads * TODO: tiles and entities diff --git a/src/pocketmine/level/format/io/ChunkRequestTask.php b/src/pocketmine/level/format/io/ChunkRequestTask.php index 945ae324a..9cf66ce2b 100644 --- a/src/pocketmine/level/format/io/ChunkRequestTask.php +++ b/src/pocketmine/level/format/io/ChunkRequestTask.php @@ -47,21 +47,18 @@ class ChunkRequestTask extends AsyncTask{ /** @var int */ protected $compressionLevel; - /** @var int */ - private $subChunkCount; - public function __construct(Level $level, int $chunkX, int $chunkZ, Chunk $chunk){ $this->levelId = $level->getId(); $this->compressionLevel = $level->getServer()->networkCompressionLevel; - $this->chunk = $chunk->networkSerialize(); + $this->chunk = $chunk->fastSerialize(); $this->chunkX = $chunkX; $this->chunkZ = $chunkZ; - $this->subChunkCount = $chunk->getSubChunkSendCount(); } public function onRun(){ - $pk = LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $this->subChunkCount, $this->chunk); + $chunk = Chunk::fastDeserialize($this->chunk); + $pk = LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $chunk->getSubChunkSendCount() + 4, $chunk->networkSerialize()); $batch = new BatchPacket(); $batch->addPacket($pk); diff --git a/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php b/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php index 94012727b..e43718e02 100644 --- a/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php +++ b/src/pocketmine/network/mcpe/protocol/ProtocolInfo.php @@ -37,11 +37,11 @@ interface ProtocolInfo{ */ /** Actual Minecraft: PE protocol version */ - public const CURRENT_PROTOCOL = 471; + public const CURRENT_PROTOCOL = 475; /** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */ - public const MINECRAFT_VERSION = 'v1.17.40'; + public const MINECRAFT_VERSION = 'v1.18.0'; /** Version number sent to clients in ping responses. */ - public const MINECRAFT_VERSION_NETWORK = '1.17.40'; + public const MINECRAFT_VERSION_NETWORK = '1.18.0'; public const LOGIN_PACKET = 0x01; public const PLAY_STATUS_PACKET = 0x02; diff --git a/src/pocketmine/network/mcpe/protocol/StartGamePacket.php b/src/pocketmine/network/mcpe/protocol/StartGamePacket.php index df3de9e1e..d702732df 100644 --- a/src/pocketmine/network/mcpe/protocol/StartGamePacket.php +++ b/src/pocketmine/network/mcpe/protocol/StartGamePacket.php @@ -182,6 +182,8 @@ class StartGamePacket extends DataPacket{ /** @var string */ public $serverSoftwareVersion; + public int $blockPaletteChecksum; + protected function decodePayload(){ $this->entityUniqueId = $this->getEntityUniqueId(); $this->entityRuntimeId = $this->getEntityRuntimeId(); @@ -265,6 +267,7 @@ class StartGamePacket extends DataPacket{ $this->multiplayerCorrelationId = $this->getString(); $this->enableNewInventorySystem = $this->getBool(); $this->serverSoftwareVersion = $this->getString(); + $this->blockPaletteChecksum = $this->getLLong(); } protected function encodePayload(){ @@ -346,6 +349,7 @@ class StartGamePacket extends DataPacket{ $this->putString($this->multiplayerCorrelationId); $this->putBool($this->enableNewInventorySystem); $this->putString($this->serverSoftwareVersion); + $this->putLLong($this->blockPaletteChecksum); } public function handle(NetworkSession $session) : bool{ diff --git a/src/pocketmine/network/mcpe/protocol/SubChunkPacket.php b/src/pocketmine/network/mcpe/protocol/SubChunkPacket.php index 35d4be5f4..ade9163d4 100644 --- a/src/pocketmine/network/mcpe/protocol/SubChunkPacket.php +++ b/src/pocketmine/network/mcpe/protocol/SubChunkPacket.php @@ -39,8 +39,9 @@ class SubChunkPacket extends DataPacket{ private string $data; private int $requestResult; private ?SubChunkPacketHeightMapInfo $heightMapData = null; + private ?int $usedBlobHash = null; - public static function create(int $dimension, int $subChunkX, int $subChunkY, int $subChunkZ, string $data, int $requestResult, ?SubChunkPacketHeightMapInfo $heightMapData) : self{ + public static function create(int $dimension, int $subChunkX, int $subChunkY, int $subChunkZ, string $data, int $requestResult, ?SubChunkPacketHeightMapInfo $heightMapData, ?int $usedBlobHash) : self{ $result = new self; $result->dimension = $dimension; $result->subChunkX = $subChunkX; @@ -49,6 +50,7 @@ class SubChunkPacket extends DataPacket{ $result->data = $data; $result->requestResult = $requestResult; $result->heightMapData = $heightMapData; + $result->usedBlobHash = $usedBlobHash; return $result; } @@ -66,6 +68,8 @@ class SubChunkPacket extends DataPacket{ public function getHeightMapData() : ?SubChunkPacketHeightMapInfo{ return $this->heightMapData; } + public function getUsedBlobHash() : ?int{ return $this->usedBlobHash; } + protected function decodePayload() : void{ $this->dimension = $this->getVarInt(); $this->subChunkX = $this->getVarInt(); @@ -81,6 +85,7 @@ class SubChunkPacket extends DataPacket{ SubChunkPacketHeightMapType::ALL_TOO_LOW => SubChunkPacketHeightMapInfo::allTooLow(), default => throw new \UnexpectedValueException("Unknown heightmap data type $heightMapDataType") }; + $this->usedBlobHash = $this->getBool() ? $this->getLLong() : null; } protected function encodePayload() : void{ @@ -101,6 +106,11 @@ class SubChunkPacket extends DataPacket{ $this->putByte(SubChunkPacketHeightMapType::DATA); $heightMapData->write($this); } + $usedBlobHash = $this->usedBlobHash; + $this->putBool($usedBlobHash !== null); + if($usedBlobHash !== null){ + $this->putLLong($usedBlobHash); + } } public function handle(NetworkSession $handler) : bool{ diff --git a/src/pocketmine/resources/vanilla b/src/pocketmine/resources/vanilla index f29b7be8f..482c679aa 160000 --- a/src/pocketmine/resources/vanilla +++ b/src/pocketmine/resources/vanilla @@ -1 +1 @@ -Subproject commit f29b7be8fa3046d2ee4c6421485b97b3f5b07774 +Subproject commit 482c679aa5ed0b81c088c2b1ff0b8110a94c8a6c