From 9a0a8a55b1c750d1443bd247811ead52fd3f63f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:17:41 +0000 Subject: [PATCH 1/2] Bump shivammathur/setup-php in the github-actions group (#6787) --- .github/workflows/discord-release-notify.yml | 2 +- .github/workflows/draft-release-pr-check.yml | 2 +- .github/workflows/draft-release.yml | 2 +- .github/workflows/main.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index e15134fdb..76780d633 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v5 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.35.3 + uses: shivammathur/setup-php@2.35.4 with: php-version: 8.2 diff --git a/.github/workflows/draft-release-pr-check.yml b/.github/workflows/draft-release-pr-check.yml index cf6036968..9905d843d 100644 --- a/.github/workflows/draft-release-pr-check.yml +++ b/.github/workflows/draft-release-pr-check.yml @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v5 - name: Setup PHP - uses: shivammathur/setup-php@2.35.3 + uses: shivammathur/setup-php@2.35.4 with: php-version: 8.2 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 052635234..6edf05dfb 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -87,7 +87,7 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.35.3 + uses: shivammathur/setup-php@2.35.4 with: php-version: ${{ env.PHP_VERSION }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9d5f282bb..4a4efc2e3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v5 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.35.3 + uses: shivammathur/setup-php@2.35.4 with: php-version: 8.3 tools: php-cs-fixer:3.75 From fa5cc3301c8bf46f4d5b1bcb2d8944d517a3b880 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Tue, 2 Sep 2025 18:36:00 +0100 Subject: [PATCH 2/2] Strip unnecessary NBT from network items (#6790) TypeConverter: Strip unnecessary NBT from clientbound items Item containers like shulker boxes, or chests with block entity data obtained with ctrl+middle click, will often have extremely large NBT payloads. This problem gets exponentially worse in cases where it's possible to nest inventories, as in #4665. We can't easily address this at the core level, because tiles are not able to exist within items (due to position requirement) so we don't have a good way to avoid this useless NBT in the first place. However, we can strip it before the item is sent to the client, which dramatically reduces the network costs of such items, as well as removing any reason the client could have to send such enormous items to the server. A fully loaded shulker box with written books now takes only 5-6 KB on the wire instead of ~1 MB, which is a substantial improvement. Dealing with this in a less hacky way is currently blocked on #6147. --- src/network/mcpe/convert/TypeConverter.php | 90 +++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index e886b2b8b..a6520ce12 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\convert; +use pocketmine\block\tile\Container; use pocketmine\block\VanillaBlocks; use pocketmine\crafting\ExactRecipeIngredient; use pocketmine\crafting\MetaWildcardRecipeIngredient; @@ -31,10 +32,16 @@ use pocketmine\crafting\TagWildcardRecipeIngredient; use pocketmine\data\bedrock\BedrockDataFiles; use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\ItemTypeNames; +use pocketmine\data\SavedDataLoadingException; use pocketmine\item\Item; use pocketmine\item\VanillaItems; +use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\NBT; use pocketmine\nbt\NbtException; use pocketmine\nbt\tag\CompoundTag; +use pocketmine\nbt\tag\ListTag; +use pocketmine\nbt\tag\Tag; +use pocketmine\nbt\TreeRoot; use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode; @@ -52,11 +59,13 @@ use pocketmine\utils\SingletonTrait; use pocketmine\world\format\io\GlobalBlockStateHandlers; use pocketmine\world\format\io\GlobalItemDataHandlers; use function get_class; +use function hash; class TypeConverter{ use SingletonTrait; private const PM_ID_TAG = "___Id___"; + private const PM_FULL_NBT_HASH_TAG = "___FullNbtHash___"; private const RECIPE_INPUT_WILDCARD_META = 0x7fff; @@ -197,6 +206,85 @@ class TypeConverter{ return new ExactRecipeIngredient($result); } + /** + * Strips unnecessary block actor NBT from items that have it. + * This tag can potentially be extremely large, and is not read by the client anyway. + */ + protected function stripBlockEntityNBT(CompoundTag $tag) : bool{ + if(($tag->getTag(Item::TAG_BLOCK_ENTITY_TAG)) !== null){ + //client doesn't use this tag, so it's fine to delete completely + $tag->removeTag(Item::TAG_BLOCK_ENTITY_TAG); + return true; + } + return false; + } + + /** + * Strips non-viewable data from shulker boxes and similar blocks + * The lore for shulker boxes only requires knowing the type & count of items and possibly custom name + * We don't need to, and should not allow, sending nested inventories across the network. + */ + protected function stripContainedItemNonVisualNBT(CompoundTag $tag) : bool{ + if( + ($blockEntityInventoryTag = $tag->getTag(Container::TAG_ITEMS)) !== null && + $blockEntityInventoryTag instanceof ListTag && + $blockEntityInventoryTag->getTagType() === NBT::TAG_Compound && + $blockEntityInventoryTag->count() > 0 + ){ + $stripped = new ListTag(); + + /** @var CompoundTag $itemTag */ + foreach($blockEntityInventoryTag as $itemTag){ + try{ + $containedItem = Item::nbtDeserialize($itemTag); + $customName = $containedItem->getCustomName(); + $containedItem->clearNamedTag(); + $containedItem->setCustomName($customName); + $stripped->push($containedItem->nbtSerialize()); + }catch(SavedDataLoadingException){ + continue; + } + } + $tag->setTag(Container::TAG_ITEMS, $stripped); + return true; + } + return false; + } + + /** + * Computes a hash of an item's server-side NBT. + * This is baked into an item's network NBT to make sure the client doesn't try to stack items with the same network + * NBT but different server-side NBT. + */ + protected function hashNBT(Tag $tag) : string{ + $encoded = (new LittleEndianNbtSerializer())->write(new TreeRoot($tag)); + return hash('sha256', $encoded, binary: true); + } + + /** + * TODO: HACK! + * Creates a copy of an item's NBT with non-viewable data stripped. + * This is a pretty yucky hack that's mainly needed because of inventories inside blockitems containing blockentity + * data. There isn't really a good way to deal with this due to the way tiles currently require a position, + * otherwise we could just keep a copy of the tile context and ask it for persistent vs network NBT as needed. + * Unfortunately, making this nice will require significant BC breaks, so this will have to do for now. + */ + protected function cleanupUnnecessaryItemNBT(CompoundTag $original) : CompoundTag{ + $tag = clone $original; + $anythingStripped = false; + foreach([ + $this->stripContainedItemNonVisualNBT($tag), + $this->stripBlockEntityNBT($tag) + ] as $stripped){ + $anythingStripped = $anythingStripped || $stripped; + } + + if($anythingStripped){ + $tag->setByteArray(self::PM_FULL_NBT_HASH_TAG, $this->hashNBT($original)); + } + return $tag; + } + public function coreItemStackToNet(Item $itemStack) : ItemStack{ if($itemStack->isNull()){ return ItemStack::null(); @@ -205,7 +293,7 @@ class TypeConverter{ if($nbt->count() === 0){ $nbt = null; }else{ - $nbt = clone $nbt; + $nbt = $this->cleanupUnnecessaryItemNBT($nbt); } $idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack);