diff --git a/src/network/mcpe/ChunkRequestTask.php b/src/network/mcpe/ChunkRequestTask.php index d90e0cd32..7395da728 100644 --- a/src/network/mcpe/ChunkRequestTask.php +++ b/src/network/mcpe/ChunkRequestTask.php @@ -25,9 +25,11 @@ namespace pocketmine\network\mcpe; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\Compressor; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\protocol\LevelChunkPacket; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\serializer\ChunkSerializer; use pocketmine\scheduler\AsyncTask; use pocketmine\world\format\Chunk; @@ -68,8 +70,9 @@ class ChunkRequestTask extends AsyncTask{ public function onRun() : void{ $chunk = FastChunkSerializer::deserialize($this->chunk); $subCount = ChunkSerializer::getSubChunkCount($chunk); - $payload = ChunkSerializer::serialize($chunk, RuntimeBlockMapping::getInstance(), $this->tiles); - $this->setResult($this->compressor->compress(PacketBatch::fromPackets(LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $subCount, $payload))->getBuffer())); + $encoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $payload = ChunkSerializer::serialize($chunk, RuntimeBlockMapping::getInstance(), $encoderContext, $this->tiles); + $this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $subCount, $payload))->getBuffer())); } public function onError() : void{ diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index eab54915b..258e21ee9 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -42,6 +42,7 @@ use pocketmine\network\mcpe\cache\ChunkCache; use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\Compressor; use pocketmine\network\mcpe\compression\DecompressionException; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\convert\SkinAdapterSingleton; use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\encryption\DecryptionException; @@ -74,6 +75,7 @@ use pocketmine\network\mcpe\protocol\PlayStatusPacket; use pocketmine\network\mcpe\protocol\RemoveActorPacket; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\protocol\ServerboundPacket; use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket; use pocketmine\network\mcpe\protocol\SetActorDataPacket; @@ -158,6 +160,7 @@ class NetworkSession{ private bool $forceAsyncCompression = true; private PacketPool $packetPool; + private PacketSerializerContext $packetSerializerContext; private ?InventoryManager $invManager = null; @@ -185,6 +188,9 @@ class NetworkSession{ $this->compressor = $compressor; $this->packetPool = $packetPool; + //TODO: allow this to be injected + $this->packetSerializerContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $this->disposeHooks = new ObjectSet(); $this->connectTime = time(); @@ -335,7 +341,7 @@ class NetworkSession{ } try{ - foreach($stream->getPackets($this->packetPool, 500) as [$packet, $buffer]){ + foreach($stream->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){ try{ $this->handleDataPacket($packet, $buffer); }catch(PacketHandlingException $e){ @@ -361,7 +367,7 @@ class NetworkSession{ $timings->startTiming(); try{ - $stream = new PacketSerializer($buffer); + $stream = PacketSerializer::decoder($buffer, 0, $this->packetSerializerContext); try{ $packet->decode($stream); }catch(PacketDecodeException $e){ @@ -430,7 +436,7 @@ class NetworkSession{ }elseif($this->forceAsyncCompression){ $syncMode = false; } - $promise = $this->server->prepareBatch(PacketBatch::fromPackets(...$this->sendBuffer), $this->compressor, $syncMode); + $promise = $this->server->prepareBatch(PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer), $this->compressor, $syncMode); $this->sendBuffer = []; $this->queueCompressedNoBufferFlush($promise, $immediate); } diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php index 3638f04cb..1442a5bbf 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -24,7 +24,9 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; use pocketmine\network\mcpe\compression\Compressor; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\Server; use function spl_object_id; @@ -38,7 +40,8 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ } public function broadcastPackets(array $recipients, array $packets) : void{ - $stream = PacketBatch::fromPackets(...$packets); + //TODO: we should be using session-specific serializer contexts for this + $stream = PacketBatch::fromPackets(new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()), ...$packets); /** @var Compressor[] $compressors */ $compressors = []; diff --git a/src/network/mcpe/convert/RuntimeBlockMapping.php b/src/network/mcpe/convert/RuntimeBlockMapping.php index 95dbcb8fb..1185a02dd 100644 --- a/src/network/mcpe/convert/RuntimeBlockMapping.php +++ b/src/network/mcpe/convert/RuntimeBlockMapping.php @@ -29,6 +29,7 @@ use pocketmine\data\bedrock\LegacyBlockIdToStringIdMap; use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\SingletonTrait; use Webmozart\PathUtil\Path; @@ -52,7 +53,7 @@ final class RuntimeBlockMapping{ if($canonicalBlockStatesFile === false){ throw new AssumptionFailedError("Missing required resource file"); } - $stream = new PacketSerializer($canonicalBlockStatesFile); + $stream = PacketSerializer::decoder($canonicalBlockStatesFile, 0, new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary())); $list = []; while(!$stream->feof()){ $list[] = $stream->getNbtCompoundRoot(); @@ -66,7 +67,7 @@ final class RuntimeBlockMapping{ $legacyIdMap = LegacyBlockIdToStringIdMap::getInstance(); /** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */ $legacyStateMap = []; - $legacyStateMapReader = new PacketSerializer(file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "vanilla", "r12_to_current_block_map.bin"))); + $legacyStateMapReader = PacketSerializer::decoder(file_get_contents(Path::join(\pocketmine\RESOURCE_PATH, "vanilla", "r12_to_current_block_map.bin")), 0, new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary())); $nbtReader = new NetworkNbtSerializer(); while(!$legacyStateMapReader->feof()){ $id = $legacyStateMapReader->getString(); diff --git a/src/network/mcpe/protocol/serializer/PacketBatch.php b/src/network/mcpe/protocol/serializer/PacketBatch.php index 86b0a94d6..a1af98c35 100644 --- a/src/network/mcpe/protocol/serializer/PacketBatch.php +++ b/src/network/mcpe/protocol/serializer/PacketBatch.php @@ -42,8 +42,8 @@ class PacketBatch{ * @phpstan-return \Generator * @throws PacketDecodeException */ - public function getPackets(PacketPool $packetPool, int $max) : \Generator{ - $serializer = new PacketSerializer($this->buffer); + public function getPackets(PacketPool $packetPool, PacketSerializerContext $decoderContext, int $max) : \Generator{ + $serializer = PacketSerializer::decoder($this->buffer, 0, $decoderContext); for($c = 0; $c < $max and !$serializer->feof(); ++$c){ try{ $buffer = $serializer->getString(); @@ -64,10 +64,10 @@ class PacketBatch{ * * @return PacketBatch */ - public static function fromPackets(Packet ...$packets) : self{ - $serializer = new PacketSerializer(); + public static function fromPackets(PacketSerializerContext $context, Packet ...$packets) : self{ + $serializer = PacketSerializer::encoder($context); foreach($packets as $packet){ - $subSerializer = new PacketSerializer(); + $subSerializer = PacketSerializer::encoder($context); $packet->encode($subSerializer); $serializer->putString($subSerializer->getBuffer()); } diff --git a/src/network/mcpe/protocol/serializer/PacketSerializer.php b/src/network/mcpe/protocol/serializer/PacketSerializer.php index 1f2ad2278..9df467f1c 100644 --- a/src/network/mcpe/protocol/serializer/PacketSerializer.php +++ b/src/network/mcpe/protocol/serializer/PacketSerializer.php @@ -30,7 +30,6 @@ use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\NbtDataException; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\TreeRoot; -use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\PacketDecodeException; use pocketmine\network\mcpe\protocol\types\BoolGameRule; use pocketmine\network\mcpe\protocol\types\command\CommandOriginData; @@ -71,10 +70,20 @@ use function substr; class PacketSerializer extends BinaryStream{ private int $shieldItemRuntimeId; + private PacketSerializerContext $context; - public function __construct(string $buffer = "", int $offset = 0){ + protected function __construct(PacketSerializerContext $context, string $buffer = "", int $offset = 0){ parent::__construct($buffer, $offset); - $this->shieldItemRuntimeId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromStringId("minecraft:shield"); + $this->context = $context; + $this->shieldItemRuntimeId = $context->getItemDictionary()->fromStringId("minecraft:shield"); + } + + public static function encoder(PacketSerializerContext $context) : self{ + return new self($context); + } + + public static function decoder(string $buffer, int $offset, PacketSerializerContext $context) : self{ + return new self($context, $buffer, $offset); } /** @@ -248,9 +257,8 @@ class PacketSerializer extends BinaryStream{ $readExtraCrapInTheMiddle($this); $blockRuntimeId = $this->getVarInt(); - $extraData = new PacketSerializer($this->getString()); - $shieldItemRuntimeId = $this->shieldItemRuntimeId; - return (static function() use ($extraData, $id, $meta, $count, $blockRuntimeId, $shieldItemRuntimeId) : ItemStack{ + $extraData = self::decoder($this->getString(), 0, $this->context); + return (static function() use ($extraData, $id, $meta, $count, $blockRuntimeId) : ItemStack{ $nbtLen = $extraData->getLShort(); /** @var CompoundTag|null $compound */ @@ -283,7 +291,7 @@ class PacketSerializer extends BinaryStream{ } $shieldBlockingTick = null; - if($id === $shieldItemRuntimeId){ + if($id === $extraData->shieldItemRuntimeId){ $shieldBlockingTick = $extraData->getLLong(); } @@ -312,9 +320,9 @@ class PacketSerializer extends BinaryStream{ $writeExtraCrapInTheMiddle($this); $this->putVarInt($item->getBlockRuntimeId()); - $shieldItemRuntimeId = $this->shieldItemRuntimeId; - $this->putString((static function() use ($item, $shieldItemRuntimeId) : string{ - $extraData = new PacketSerializer(); + $context = $this->context; + $this->putString((static function() use ($item, $context) : string{ + $extraData = PacketSerializer::encoder($context); $nbt = $item->getNbt(); if($nbt !== null){ @@ -337,7 +345,7 @@ class PacketSerializer extends BinaryStream{ } $blockingTick = $item->getShieldBlockingTick(); - if($item->getId() === $shieldItemRuntimeId){ + if($item->getId() === $extraData->shieldItemRuntimeId){ $extraData->putLLong($blockingTick ?? 0); } return $extraData->getBuffer(); diff --git a/src/network/mcpe/protocol/serializer/PacketSerializerContext.php b/src/network/mcpe/protocol/serializer/PacketSerializerContext.php new file mode 100644 index 000000000..887adbc90 --- /dev/null +++ b/src/network/mcpe/protocol/serializer/PacketSerializerContext.php @@ -0,0 +1,39 @@ +itemDictionary = $itemDictionary; + } + + public function getItemDictionary() : ItemTypeDictionary{ return $this->itemDictionary; } +} diff --git a/src/network/mcpe/serializer/ChunkSerializer.php b/src/network/mcpe/serializer/ChunkSerializer.php index abf0e1b25..6408cd780 100644 --- a/src/network/mcpe/serializer/ChunkSerializer.php +++ b/src/network/mcpe/serializer/ChunkSerializer.php @@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\serializer; use pocketmine\block\tile\Spawnable; use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\utils\Binary; use pocketmine\utils\BinaryStream; use pocketmine\world\format\Chunk; @@ -52,8 +53,8 @@ final class ChunkSerializer{ return 0; } - public static function serialize(Chunk $chunk, RuntimeBlockMapping $blockMapper, ?string $tiles = null) : string{ - $stream = new PacketSerializer(); + public static function serialize(Chunk $chunk, RuntimeBlockMapping $blockMapper, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{ + $stream = PacketSerializer::encoder($encoderContext); $subChunkCount = self::getSubChunkCount($chunk); for($y = 0; $y < $subChunkCount; ++$y){ $layers = $chunk->getSubChunk($y)->getBlockLayers(); diff --git a/tests/phpstan/configs/l7-baseline.neon b/tests/phpstan/configs/l7-baseline.neon index a9eb81028..ff9421a41 100644 --- a/tests/phpstan/configs/l7-baseline.neon +++ b/tests/phpstan/configs/l7-baseline.neon @@ -591,7 +591,7 @@ parameters: path: ../../../src/network/mcpe/auth/ProcessLoginTask.php - - message: "#^Parameter \\#1 \\$buffer of class pocketmine\\\\network\\\\mcpe\\\\protocol\\\\serializer\\\\PacketSerializer constructor expects string, string\\|false given\\.$#" + message: "#^Parameter \\#1 \\$buffer of static method pocketmine\\\\network\\\\mcpe\\\\protocol\\\\serializer\\\\PacketSerializer\\:\\:decoder\\(\\) expects string, string\\|false given\\.$#" count: 1 path: ../../../src/network/mcpe/convert/RuntimeBlockMapping.php diff --git a/tests/phpunit/network/mcpe/protocol/DataPacketTest.php b/tests/phpunit/network/mcpe/protocol/DataPacketTest.php index 94bdcb83d..880be1e8f 100644 --- a/tests/phpunit/network/mcpe/protocol/DataPacketTest.php +++ b/tests/phpunit/network/mcpe/protocol/DataPacketTest.php @@ -24,7 +24,9 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol; use PHPUnit\Framework\TestCase; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; class DataPacketTest extends TestCase{ @@ -32,11 +34,13 @@ class DataPacketTest extends TestCase{ $pk = new TestPacket(); $pk->senderSubId = 3; $pk->recipientSubId = 2; - $serializer = new PacketSerializer(); + + $context = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $serializer = PacketSerializer::encoder($context); $pk->encode($serializer); $pk2 = new TestPacket(); - $pk2->decode(new PacketSerializer($serializer->getBuffer())); + $pk2->decode(PacketSerializer::decoder($serializer->getBuffer(), 0, $context)); self::assertSame($pk2->senderSubId, 3); self::assertSame($pk2->recipientSubId, 2); } diff --git a/tests/phpunit/network/mcpe/protocol/LoginPacketTest.php b/tests/phpunit/network/mcpe/protocol/LoginPacketTest.php index 7ddf34dbe..cdd20f840 100644 --- a/tests/phpunit/network/mcpe/protocol/LoginPacketTest.php +++ b/tests/phpunit/network/mcpe/protocol/LoginPacketTest.php @@ -24,18 +24,21 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\protocol; use PHPUnit\Framework\TestCase; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use function strlen; class LoginPacketTest extends TestCase{ public function testInvalidChainDataJsonHandling() : void{ - $stream = new PacketSerializer(); + $context = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $stream = PacketSerializer::encoder($context); $stream->putUnsignedVarInt(ProtocolInfo::LOGIN_PACKET); $payload = '{"chain":[]'; //intentionally malformed $stream->putInt(ProtocolInfo::CURRENT_PROTOCOL); - $stream2 = new PacketSerializer(); + $stream2 = PacketSerializer::encoder($context); $stream2->putLInt(strlen($payload)); $stream2->put($payload); $stream->putString($stream2->getBuffer()); @@ -43,6 +46,6 @@ class LoginPacketTest extends TestCase{ $pk = PacketPool::getInstance()->getPacket($stream->getBuffer()); $this->expectException(PacketDecodeException::class); - $pk->decode(new PacketSerializer($stream->getBuffer())); //bang + $pk->decode(PacketSerializer::decoder($stream->getBuffer(), 0, $context)); //bang } } diff --git a/tests/phpunit/network/mcpe/protocol/serializer/PacketBatchTest.php b/tests/phpunit/network/mcpe/protocol/serializer/PacketBatchTest.php index 28d01dc8c..da90c0c0a 100644 --- a/tests/phpunit/network/mcpe/protocol/serializer/PacketBatchTest.php +++ b/tests/phpunit/network/mcpe/protocol/serializer/PacketBatchTest.php @@ -24,9 +24,11 @@ declare(strict_types=1); namespace pocketmine\mcpe\protocol\serializer; use PHPUnit\Framework\TestCase; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\protocol\PacketDecodeException; use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\protocol\TestPacket; use function array_fill; @@ -34,21 +36,23 @@ class PacketBatchTest extends TestCase{ public function testDecodeTooBig() : void{ $limit = 10; - $write = PacketBatch::fromPackets(...array_fill(0, $limit + 1, new TestPacket())); + $decoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $write = PacketBatch::fromPackets($decoderContext, ...array_fill(0, $limit + 1, new TestPacket())); $read = new PacketBatch($write->getBuffer()); $this->expectException(PacketDecodeException::class); $readCount = 0; - foreach($read->getPackets(PacketPool::getInstance(), $limit) as $packet){ + foreach($read->getPackets(PacketPool::getInstance(), $decoderContext, $limit) as $packet){ $readCount++; } } public function testDecodeAtLimit() : void{ $limit = 10; - $write = PacketBatch::fromPackets(...array_fill(0, $limit, new TestPacket())); + $decoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $write = PacketBatch::fromPackets($decoderContext, ...array_fill(0, $limit, new TestPacket())); $read = new PacketBatch($write->getBuffer()); $readCount = 0; - foreach($read->getPackets(PacketPool::getInstance(), $limit) as $packet){ + foreach($read->getPackets(PacketPool::getInstance(), $decoderContext, $limit) as $packet){ $readCount++; } self::assertSame($limit, $readCount);