From 9825e3abdd43f13f54aa359cce5020c5e303541c Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Thu, 24 Oct 2024 12:33:17 +0100 Subject: [PATCH 01/10] Create team-pr-auto-approve.yml --- .github/workflows/team-pr-auto-approve.yml | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/team-pr-auto-approve.yml diff --git a/.github/workflows/team-pr-auto-approve.yml b/.github/workflows/team-pr-auto-approve.yml new file mode 100644 index 00000000..a582be32 --- /dev/null +++ b/.github/workflows/team-pr-auto-approve.yml @@ -0,0 +1,42 @@ +#Due to GitHub awkwardness, it's not easy to reduce the review requirement for collaborators. +#Our policy is that 2 collaborators should be aware of every change. +#For outside PRs, this means 2 collaborator reviews. +#For PRs made by collaborators, this means 1 reviewer + the author. +#We trust that collaborators don't need as much oversight. +name: Auto approve collaborator PRs + +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + - ready_for_review + +permissions: + pull-requests: write + +jobs: + approve: + name: Auto approve + runs-on: ubuntu-latest + + steps: + - name: Check if PR author has write access + id: check-permission + uses: actions-cool/check-user-permission@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + require: write + username: ${{ github.event.pull_request.user.login }} + #technically this would be fine for dependabot but generally bots don't count as team members + check-bot: true + + #TODO: Some way to avoid unnecessary repeated reviews would be nice here + + - name: Approve PR if authorized + if: steps.check-permission.outputs.require-result == 'true' && steps.check-permission.outputs.check-result == 'false' + uses: juliangruber/approve-pull-request-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + number: ${{ github.event.pull_request.number }} From 6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459 Mon Sep 17 00:00:00 2001 From: Dries C Date: Thu, 24 Oct 2024 17:45:43 +0200 Subject: [PATCH 02/10] Protocol changes for 1.21.40 (#277) --- src/CraftingDataPacket.php | 4 +- src/InventoryContentPacket.php | 10 +-- src/InventorySlotPacket.php | 10 +-- src/MobEffectPacket.php | 4 +- src/MovementEffectPacket.php | 65 +++++++++++++++++++ src/PacketHandlerDefaultImplTrait.php | 8 +++ src/PacketHandlerInterface.php | 4 ++ src/PacketPool.php | 2 + src/PlayerAuthInputPacket.php | 44 ++++++------- src/ProtocolInfo.php | 8 ++- src/ResourcePacksInfoPacket.php | 22 +------ src/SetMovementAuthorityPacket.php | 47 ++++++++++++++ src/UpdatePlayerGameTypePacket.php | 4 +- ...ovementType.php => MovementEffectType.php} | 8 +-- src/types/PlayerAction.php | 3 +- src/types/PlayerAuthInputFlags.php | 5 ++ src/types/PlayerMovementSettings.php | 8 +-- src/types/ServerAuthMovementMode.php | 23 +++++++ src/types/camera/CameraPreset.php | 28 +++++++- src/types/camera/CameraSetInstruction.php | 6 ++ src/types/inventory/ContainerIds.php | 1 - src/types/login/ClientData.php | 9 +++ .../resourcepacks/ResourcePackInfoEntry.php | 9 ++- 23 files changed, 254 insertions(+), 78 deletions(-) create mode 100644 src/MovementEffectPacket.php create mode 100644 src/SetMovementAuthorityPacket.php rename src/types/{PlayerMovementType.php => MovementEffectType.php} (63%) create mode 100644 src/types/ServerAuthMovementMode.php diff --git a/src/CraftingDataPacket.php b/src/CraftingDataPacket.php index f04a1c6b..93d812e8 100644 --- a/src/CraftingDataPacket.php +++ b/src/CraftingDataPacket.php @@ -36,7 +36,7 @@ class CraftingDataPacket extends DataPacket implements ClientboundPacket{ public const ENTRY_FURNACE = 2; public const ENTRY_FURNACE_DATA = 3; public const ENTRY_MULTI = 4; - public const ENTRY_SHULKER_BOX = 5; + public const ENTRY_USER_DATA_SHAPELESS = 5; public const ENTRY_SHAPELESS_CHEMISTRY = 6; public const ENTRY_SHAPED_CHEMISTRY = 7; public const ENTRY_SMITHING_TRANSFORM = 8; @@ -76,7 +76,7 @@ protected function decodePayload(PacketSerializer $in) : void{ $recipeType = $in->getVarInt(); $this->recipesWithTypeIds[] = match($recipeType){ - self::ENTRY_SHAPELESS, self::ENTRY_SHULKER_BOX, self::ENTRY_SHAPELESS_CHEMISTRY => ShapelessRecipe::decode($recipeType, $in), + self::ENTRY_SHAPELESS, self::ENTRY_USER_DATA_SHAPELESS, self::ENTRY_SHAPELESS_CHEMISTRY => ShapelessRecipe::decode($recipeType, $in), self::ENTRY_SHAPED, self::ENTRY_SHAPED_CHEMISTRY => ShapedRecipe::decode($recipeType, $in), self::ENTRY_FURNACE, self::ENTRY_FURNACE_DATA => FurnaceRecipe::decode($recipeType, $in), self::ENTRY_MULTI => MultiRecipe::decode($recipeType, $in), diff --git a/src/InventoryContentPacket.php b/src/InventoryContentPacket.php index 1e7e5f64..6e42e260 100644 --- a/src/InventoryContentPacket.php +++ b/src/InventoryContentPacket.php @@ -26,18 +26,18 @@ class InventoryContentPacket extends DataPacket implements ClientboundPacket{ /** @var ItemStackWrapper[] */ public array $items = []; public FullContainerName $containerName; - public int $dynamicContainerSize; + public ItemStackWrapper $storage; /** * @generate-create-func * @param ItemStackWrapper[] $items */ - public static function create(int $windowId, array $items, FullContainerName $containerName, int $dynamicContainerSize) : self{ + public static function create(int $windowId, array $items, FullContainerName $containerName, ItemStackWrapper $storage) : self{ $result = new self; $result->windowId = $windowId; $result->items = $items; $result->containerName = $containerName; - $result->dynamicContainerSize = $dynamicContainerSize; + $result->storage = $storage; return $result; } @@ -48,7 +48,7 @@ protected function decodePayload(PacketSerializer $in) : void{ $this->items[] = $in->getItemStackWrapper(); } $this->containerName = FullContainerName::read($in); - $this->dynamicContainerSize = $in->getUnsignedVarInt(); + $this->storage = $in->getItemStackWrapper(); } protected function encodePayload(PacketSerializer $out) : void{ @@ -58,7 +58,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putItemStackWrapper($item); } $this->containerName->write($out); - $out->putUnsignedVarInt($this->dynamicContainerSize); + $out->putItemStackWrapper($this->storage); } public function handle(PacketHandlerInterface $handler) : bool{ diff --git a/src/InventorySlotPacket.php b/src/InventorySlotPacket.php index b98af415..c75627fb 100644 --- a/src/InventorySlotPacket.php +++ b/src/InventorySlotPacket.php @@ -24,18 +24,18 @@ class InventorySlotPacket extends DataPacket implements ClientboundPacket{ public int $windowId; public int $inventorySlot; public FullContainerName $containerName; - public int $dynamicContainerSize; + public ItemStackWrapper $storage; public ItemStackWrapper $item; /** * @generate-create-func */ - public static function create(int $windowId, int $inventorySlot, FullContainerName $containerName, int $dynamicContainerSize, ItemStackWrapper $item) : self{ + public static function create(int $windowId, int $inventorySlot, FullContainerName $containerName, ItemStackWrapper $storage, ItemStackWrapper $item) : self{ $result = new self; $result->windowId = $windowId; $result->inventorySlot = $inventorySlot; $result->containerName = $containerName; - $result->dynamicContainerSize = $dynamicContainerSize; + $result->storage = $storage; $result->item = $item; return $result; } @@ -44,7 +44,7 @@ protected function decodePayload(PacketSerializer $in) : void{ $this->windowId = $in->getUnsignedVarInt(); $this->inventorySlot = $in->getUnsignedVarInt(); $this->containerName = FullContainerName::read($in); - $this->dynamicContainerSize = $in->getUnsignedVarInt(); + $this->storage = $in->getItemStackWrapper(); $this->item = $in->getItemStackWrapper(); } @@ -52,7 +52,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putUnsignedVarInt($this->windowId); $out->putUnsignedVarInt($this->inventorySlot); $this->containerName->write($out); - $out->putUnsignedVarInt($this->dynamicContainerSize); + $out->putItemStackWrapper($this->storage); $out->putItemStackWrapper($this->item); } diff --git a/src/MobEffectPacket.php b/src/MobEffectPacket.php index dcf3f864..b10485ef 100644 --- a/src/MobEffectPacket.php +++ b/src/MobEffectPacket.php @@ -69,7 +69,7 @@ protected function decodePayload(PacketSerializer $in) : void{ $this->amplifier = $in->getVarInt(); $this->particles = $in->getBool(); $this->duration = $in->getVarInt(); - $this->tick = $in->getLLong(); + $this->tick = $in->getUnsignedVarLong(); } protected function encodePayload(PacketSerializer $out) : void{ @@ -79,7 +79,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putVarInt($this->amplifier); $out->putBool($this->particles); $out->putVarInt($this->duration); - $out->putLLong($this->tick); + $out->putUnsignedVarLong($this->tick); } public function handle(PacketHandlerInterface $handler) : bool{ diff --git a/src/MovementEffectPacket.php b/src/MovementEffectPacket.php new file mode 100644 index 00000000..642015dc --- /dev/null +++ b/src/MovementEffectPacket.php @@ -0,0 +1,65 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\types\MovementEffectType; + +class MovementEffectPacket extends DataPacket implements ClientboundPacket{ + public const NETWORK_ID = ProtocolInfo::MOVEMENT_EFFECT_PACKET; + + private int $actorRuntimeId; + private MovementEffectType $effectType; + private int $duration; + private int $tick; + + /** + * @generate-create-func + */ + public static function create(int $actorRuntimeId, MovementEffectType $effectType, int $duration, int $tick) : self{ + $result = new self; + $result->actorRuntimeId = $actorRuntimeId; + $result->effectType = $effectType; + $result->duration = $duration; + $result->tick = $tick; + return $result; + } + + public function getActorRuntimeId() : int{ return $this->actorRuntimeId; } + + public function getEffectType() : MovementEffectType{ return $this->effectType; } + + public function getDuration() : int{ return $this->duration; } + + public function getTick() : int{ return $this->tick; } + + protected function decodePayload(PacketSerializer $in) : void{ + $this->actorRuntimeId = $in->getActorRuntimeId(); + $this->effectType = MovementEffectType::fromPacket($in->getUnsignedVarInt()); + $this->duration = $in->getUnsignedVarInt(); + $this->tick = $in->getUnsignedVarLong(); + } + + protected function encodePayload(PacketSerializer $out) : void{ + $out->putActorRuntimeId($this->actorRuntimeId); + $out->putUnsignedVarInt($this->effectType->value); + $out->putUnsignedVarInt($this->duration); + $out->putUnsignedVarLong($this->tick); + } + + public function handle(PacketHandlerInterface $handler) : bool{ + return $handler->handleMovementEffect($this); + } +} diff --git a/src/PacketHandlerDefaultImplTrait.php b/src/PacketHandlerDefaultImplTrait.php index 4f658842..9523649c 100644 --- a/src/PacketHandlerDefaultImplTrait.php +++ b/src/PacketHandlerDefaultImplTrait.php @@ -829,4 +829,12 @@ public function handleCameraAimAssist(CameraAimAssistPacket $packet) : bool{ public function handleContainerRegistryCleanup(ContainerRegistryCleanupPacket $packet) : bool{ return false; } + + public function handleMovementEffect(MovementEffectPacket $packet) : bool{ + return false; + } + + public function handleSetMovementAuthority(SetMovementAuthorityPacket $packet) : bool{ + return false; + } } diff --git a/src/PacketHandlerInterface.php b/src/PacketHandlerInterface.php index 0a606333..ca966659 100644 --- a/src/PacketHandlerInterface.php +++ b/src/PacketHandlerInterface.php @@ -421,4 +421,8 @@ public function handleServerboundDiagnostics(ServerboundDiagnosticsPacket $packe public function handleCameraAimAssist(CameraAimAssistPacket $packet) : bool; public function handleContainerRegistryCleanup(ContainerRegistryCleanupPacket $packet) : bool; + + public function handleMovementEffect(MovementEffectPacket $packet) : bool; + + public function handleSetMovementAuthority(SetMovementAuthorityPacket $packet) : bool; } diff --git a/src/PacketPool.php b/src/PacketPool.php index ab095cc2..e9a4be85 100644 --- a/src/PacketPool.php +++ b/src/PacketPool.php @@ -235,6 +235,8 @@ public function __construct(){ $this->registerPacket(new ServerboundDiagnosticsPacket()); $this->registerPacket(new CameraAimAssistPacket()); $this->registerPacket(new ContainerRegistryCleanupPacket()); + $this->registerPacket(new MovementEffectPacket()); + $this->registerPacket(new SetMovementAuthorityPacket()); } public function registerPacket(Packet $packet) : void{ diff --git a/src/PlayerAuthInputPacket.php b/src/PlayerAuthInputPacket.php index 28cd658a..0a7a0cf3 100644 --- a/src/PlayerAuthInputPacket.php +++ b/src/PlayerAuthInputPacket.php @@ -14,6 +14,7 @@ namespace pocketmine\network\mcpe\protocol; +use pocketmine\math\Vector2; use pocketmine\math\Vector3; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\InputMode; @@ -26,7 +27,6 @@ use pocketmine\network\mcpe\protocol\types\PlayerBlockActionStopBreak; use pocketmine\network\mcpe\protocol\types\PlayerBlockActionWithBlockInfo; use pocketmine\network\mcpe\protocol\types\PlayMode; -use function assert; use function count; class PlayerAuthInputPacket extends DataPacket implements ServerboundPacket{ @@ -42,7 +42,7 @@ class PlayerAuthInputPacket extends DataPacket implements ServerboundPacket{ private int $inputMode; private int $playMode; private int $interactionMode; - private ?Vector3 $vrGazeDirection = null; + private Vector2 $interactRotation; private int $tick; private Vector3 $delta; private ?ItemInteractionData $itemInteractionData = null; @@ -52,6 +52,7 @@ class PlayerAuthInputPacket extends DataPacket implements ServerboundPacket{ private ?PlayerAuthInputVehicleInfo $vehicleInfo = null; private float $analogMoveVecX; private float $analogMoveVecZ; + private Vector3 $cameraOrientation; /** * @generate-create-func @@ -68,7 +69,7 @@ private static function internalCreate( int $inputMode, int $playMode, int $interactionMode, - ?Vector3 $vrGazeDirection, + Vector2 $interactRotation, int $tick, Vector3 $delta, ?ItemInteractionData $itemInteractionData, @@ -77,6 +78,7 @@ private static function internalCreate( ?PlayerAuthInputVehicleInfo $vehicleInfo, float $analogMoveVecX, float $analogMoveVecZ, + Vector3 $cameraOrientation, ) : self{ $result = new self; $result->position = $position; @@ -89,7 +91,7 @@ private static function internalCreate( $result->inputMode = $inputMode; $result->playMode = $playMode; $result->interactionMode = $interactionMode; - $result->vrGazeDirection = $vrGazeDirection; + $result->interactRotation = $interactRotation; $result->tick = $tick; $result->delta = $delta; $result->itemInteractionData = $itemInteractionData; @@ -98,6 +100,7 @@ private static function internalCreate( $result->vehicleInfo = $vehicleInfo; $result->analogMoveVecX = $analogMoveVecX; $result->analogMoveVecZ = $analogMoveVecZ; + $result->cameraOrientation = $cameraOrientation; return $result; } @@ -106,7 +109,6 @@ private static function internalCreate( * @param int $inputMode @see InputMode * @param int $playMode @see PlayMode * @param int $interactionMode @see InteractionMode - * @param Vector3|null $vrGazeDirection only used when PlayMode::VR * @param PlayerBlockAction[]|null $blockActions Blocks that the client has interacted with */ public static function create( @@ -120,7 +122,7 @@ public static function create( int $inputMode, int $playMode, int $interactionMode, - ?Vector3 $vrGazeDirection, + Vector2 $interactRotation, int $tick, Vector3 $delta, ?ItemInteractionData $itemInteractionData, @@ -128,13 +130,9 @@ public static function create( ?array $blockActions, ?PlayerAuthInputVehicleInfo $vehicleInfo, float $analogMoveVecX, - float $analogMoveVecZ + float $analogMoveVecZ, + Vector3 $cameraOrientation ) : self{ - if($playMode === PlayMode::VR and $vrGazeDirection === null){ - //yuck, can we get a properly written packet just once? ... - throw new \InvalidArgumentException("Gaze direction must be provided for VR play mode"); - } - $realInputFlags = $inputFlags & ~((1 << PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST) | (1 << PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION) | (1 << PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS)); if($itemStackRequest !== null){ $realInputFlags |= 1 << PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST; @@ -160,7 +158,7 @@ public static function create( $inputMode, $playMode, $interactionMode, - $vrGazeDirection?->asVector3(), + $interactRotation, $tick, $delta, $itemInteractionData, @@ -168,7 +166,8 @@ public static function create( $blockActions, $vehicleInfo, $analogMoveVecX, - $analogMoveVecZ + $analogMoveVecZ, + $cameraOrientation ); } @@ -224,9 +223,7 @@ public function getInteractionMode() : int{ return $this->interactionMode; } - public function getVrGazeDirection() : ?Vector3{ - return $this->vrGazeDirection; - } + public function getInteractRotation() : Vector2{ return $this->interactRotation; } public function getTick() : int{ return $this->tick; @@ -257,6 +254,8 @@ public function getAnalogMoveVecX() : float{ return $this->analogMoveVecX; } public function getAnalogMoveVecZ() : float{ return $this->analogMoveVecZ; } + public function getCameraOrientation() : Vector3{ return $this->cameraOrientation; } + public function hasFlag(int $flag) : bool{ return ($this->inputFlags & (1 << $flag)) !== 0; } @@ -272,9 +271,7 @@ protected function decodePayload(PacketSerializer $in) : void{ $this->inputMode = $in->getUnsignedVarInt(); $this->playMode = $in->getUnsignedVarInt(); $this->interactionMode = $in->getUnsignedVarInt(); - if($this->playMode === PlayMode::VR){ - $this->vrGazeDirection = $in->getVector3(); - } + $this->interactRotation = $in->getVector2(); $this->tick = $in->getUnsignedVarLong(); $this->delta = $in->getVector3(); if($this->hasFlag(PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION)){ @@ -300,6 +297,7 @@ protected function decodePayload(PacketSerializer $in) : void{ } $this->analogMoveVecX = $in->getLFloat(); $this->analogMoveVecZ = $in->getLFloat(); + $this->cameraOrientation = $in->getVector3(); } protected function encodePayload(PacketSerializer $out) : void{ @@ -313,10 +311,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putUnsignedVarInt($this->inputMode); $out->putUnsignedVarInt($this->playMode); $out->putUnsignedVarInt($this->interactionMode); - if($this->playMode === PlayMode::VR){ - assert($this->vrGazeDirection !== null); - $out->putVector3($this->vrGazeDirection); - } + $out->putVector2($this->interactRotation); $out->putUnsignedVarLong($this->tick); $out->putVector3($this->delta); if($this->itemInteractionData !== null){ @@ -337,6 +332,7 @@ protected function encodePayload(PacketSerializer $out) : void{ } $out->putLFloat($this->analogMoveVecX); $out->putLFloat($this->analogMoveVecZ); + $out->putVector3($this->cameraOrientation); } public function handle(PacketHandlerInterface $handler) : bool{ diff --git a/src/ProtocolInfo.php b/src/ProtocolInfo.php index 97a42c84..177a8cb0 100644 --- a/src/ProtocolInfo.php +++ b/src/ProtocolInfo.php @@ -32,11 +32,11 @@ private function __construct(){ */ /** Actual Minecraft: PE protocol version */ - public const CURRENT_PROTOCOL = 729; + public const CURRENT_PROTOCOL = 748; /** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */ - public const MINECRAFT_VERSION = 'v1.21.30'; + public const MINECRAFT_VERSION = 'v1.21.40'; /** Version number sent to clients in ping responses. */ - public const MINECRAFT_VERSION_NETWORK = '1.21.30'; + public const MINECRAFT_VERSION_NETWORK = '1.21.40'; public const LOGIN_PACKET = 0x01; public const PLAY_STATUS_PACKET = 0x02; @@ -253,4 +253,6 @@ private function __construct(){ public const SERVERBOUND_DIAGNOSTICS_PACKET = 0x13b; public const CAMERA_AIM_ASSIST_PACKET = 0x13c; public const CONTAINER_REGISTRY_CLEANUP_PACKET = 0x13d; + public const MOVEMENT_EFFECT_PACKET = 0x13e; + public const SET_MOVEMENT_AUTHORITY_PACKET = 0x13f; } diff --git a/src/ResourcePacksInfoPacket.php b/src/ResourcePacksInfoPacket.php index dfdbffa4..f8a40ff8 100644 --- a/src/ResourcePacksInfoPacket.php +++ b/src/ResourcePacksInfoPacket.php @@ -26,25 +26,17 @@ class ResourcePacksInfoPacket extends DataPacket implements ClientboundPacket{ public bool $mustAccept = false; //if true, forces client to choose between accepting packs or being disconnected public bool $hasAddons = false; public bool $hasScripts = false; //if true, causes disconnect for any platform that doesn't support scripts yet - /** - * @var string[] - * @phpstan-var array - */ - public array $cdnUrls = []; /** * @generate-create-func * @param ResourcePackInfoEntry[] $resourcePackEntries - * @param string[] $cdnUrls - * @phpstan-param array $cdnUrls */ - public static function create(array $resourcePackEntries, bool $mustAccept, bool $hasAddons, bool $hasScripts, array $cdnUrls) : self{ + public static function create(array $resourcePackEntries, bool $mustAccept, bool $hasAddons, bool $hasScripts) : self{ $result = new self; $result->resourcePackEntries = $resourcePackEntries; $result->mustAccept = $mustAccept; $result->hasAddons = $hasAddons; $result->hasScripts = $hasScripts; - $result->cdnUrls = $cdnUrls; return $result; } @@ -57,13 +49,6 @@ protected function decodePayload(PacketSerializer $in) : void{ while($resourcePackCount-- > 0){ $this->resourcePackEntries[] = ResourcePackInfoEntry::read($in); } - - $this->cdnUrls = []; - for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; $i++){ - $packId = $in->getString(); - $cdnUrl = $in->getString(); - $this->cdnUrls[$packId] = $cdnUrl; - } } protected function encodePayload(PacketSerializer $out) : void{ @@ -74,11 +59,6 @@ protected function encodePayload(PacketSerializer $out) : void{ foreach($this->resourcePackEntries as $entry){ $entry->write($out); } - $out->putUnsignedVarInt(count($this->cdnUrls)); - foreach($this->cdnUrls as $packId => $cdnUrl){ - $out->putString($packId); - $out->putString($cdnUrl); - } } public function handle(PacketHandlerInterface $handler) : bool{ diff --git a/src/SetMovementAuthorityPacket.php b/src/SetMovementAuthorityPacket.php new file mode 100644 index 00000000..647954cc --- /dev/null +++ b/src/SetMovementAuthorityPacket.php @@ -0,0 +1,47 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\types\ServerAuthMovementMode; + +class SetMovementAuthorityPacket extends DataPacket implements ClientboundPacket{ + public const NETWORK_ID = ProtocolInfo::SET_MOVEMENT_AUTHORITY_PACKET; + + private ServerAuthMovementMode $mode; + + /** + * @generate-create-func + */ + public static function create(ServerAuthMovementMode $mode) : self{ + $result = new self; + $result->mode = $mode; + return $result; + } + + public function getMode() : ServerAuthMovementMode{ return $this->mode; } + + protected function decodePayload(PacketSerializer $in) : void{ + $this->mode = ServerAuthMovementMode::fromPacket($in->getByte()); + } + + protected function encodePayload(PacketSerializer $out) : void{ + $out->putByte($this->mode->value); + } + + public function handle(PacketHandlerInterface $handler) : bool{ + return $handler->handleSetMovementAuthority($this); + } +} diff --git a/src/UpdatePlayerGameTypePacket.php b/src/UpdatePlayerGameTypePacket.php index 770d8fa2..92651818 100644 --- a/src/UpdatePlayerGameTypePacket.php +++ b/src/UpdatePlayerGameTypePacket.php @@ -45,13 +45,13 @@ public function getTick() : int{ return $this->tick; } protected function decodePayload(PacketSerializer $in) : void{ $this->gameMode = $in->getVarInt(); $this->playerActorUniqueId = $in->getActorUniqueId(); - $this->tick = $in->getUnsignedVarInt(); + $this->tick = $in->getUnsignedVarLong(); } protected function encodePayload(PacketSerializer $out) : void{ $out->putVarInt($this->gameMode); $out->putActorUniqueId($this->playerActorUniqueId); - $out->putUnsignedVarInt($this->tick); + $out->putUnsignedVarLong($this->tick); } public function handle(PacketHandlerInterface $handler) : bool{ diff --git a/src/types/PlayerMovementType.php b/src/types/MovementEffectType.php similarity index 63% rename from src/types/PlayerMovementType.php rename to src/types/MovementEffectType.php index 0c527822..0b1d35b6 100644 --- a/src/types/PlayerMovementType.php +++ b/src/types/MovementEffectType.php @@ -14,9 +14,9 @@ namespace pocketmine\network\mcpe\protocol\types; -final class PlayerMovementType{ +enum MovementEffectType : int{ + use PacketIntEnumTrait; - public const LEGACY = 0; //MovePlayerPacket - public const SERVER_AUTHORITATIVE_V1 = 1; //PlayerAuthInputPacket - public const SERVER_AUTHORITATIVE_V2_REWIND = 2; //PlayerAuthInputPacket + movement replay (used for server authoritative knockback) + case INVALID = -1; + case GLIDE_BOOST = 0; } diff --git a/src/types/PlayerAction.php b/src/types/PlayerAction.php index 30060a6a..ce4caca8 100644 --- a/src/types/PlayerAction.php +++ b/src/types/PlayerAction.php @@ -42,7 +42,7 @@ private function __construct(){ public const SET_ENCHANTMENT_SEED = 20; //no longer used public const START_SWIMMING = 21; public const STOP_SWIMMING = 22; - public const START_SPIN_ATTACK = 23; //no longer used + public const START_SPIN_ATTACK = 23; public const STOP_SPIN_ATTACK = 24; public const INTERACT_BLOCK = 25; public const PREDICT_DESTROY_BLOCK = 26; @@ -56,6 +56,7 @@ private function __construct(){ public const START_FLYING = 34; public const STOP_FLYING = 35; public const ACK_ACTOR_DATA = 36; + public const START_USING_ITEM = 37; //Backwards compatibility (blame @dktapps) public const CRACK_BREAK = 18; diff --git a/src/types/PlayerAuthInputFlags.php b/src/types/PlayerAuthInputFlags.php index 77ea7259..60809451 100644 --- a/src/types/PlayerAuthInputFlags.php +++ b/src/types/PlayerAuthInputFlags.php @@ -106,5 +106,10 @@ final class PlayerAuthInputFlags{ public const VERTICAL_COLLISION = 50; public const DOWN_LEFT = 51; public const DOWN_RIGHT = 52; + public const START_USING_ITEM = 53; + public const IS_CAMERA_RELATIVE_MOVEMENT_ENABLED = 54; + public const IS_ROT_CONTROLLED_BY_MOVE_DIRECTION = 55; + public const START_SPIN_ATTACK = 56; + public const STOP_SPIN_ATTACK = 57; } diff --git a/src/types/PlayerMovementSettings.php b/src/types/PlayerMovementSettings.php index f1948418..e073b6fc 100644 --- a/src/types/PlayerMovementSettings.php +++ b/src/types/PlayerMovementSettings.php @@ -18,26 +18,26 @@ final class PlayerMovementSettings{ public function __construct( - private int $movementType, + private ServerAuthMovementMode $movementType, private int $rewindHistorySize, private bool $serverAuthoritativeBlockBreaking ){} - public function getMovementType() : int{ return $this->movementType; } + public function getMovementType() : ServerAuthMovementMode{ return $this->movementType; } public function getRewindHistorySize() : int{ return $this->rewindHistorySize; } public function isServerAuthoritativeBlockBreaking() : bool{ return $this->serverAuthoritativeBlockBreaking; } public static function read(PacketSerializer $in) : self{ - $movementType = $in->getVarInt(); + $movementType = ServerAuthMovementMode::fromPacket($in->getVarInt()); $rewindHistorySize = $in->getVarInt(); $serverAuthBlockBreaking = $in->getBool(); return new self($movementType, $rewindHistorySize, $serverAuthBlockBreaking); } public function write(PacketSerializer $out) : void{ - $out->putVarInt($this->movementType); + $out->putVarInt($this->movementType->value); $out->putVarInt($this->rewindHistorySize); $out->putBool($this->serverAuthoritativeBlockBreaking); } diff --git a/src/types/ServerAuthMovementMode.php b/src/types/ServerAuthMovementMode.php new file mode 100644 index 00000000..dcd5b85d --- /dev/null +++ b/src/types/ServerAuthMovementMode.php @@ -0,0 +1,23 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types; + +enum ServerAuthMovementMode : int{ + use PacketIntEnumTrait; + + case LEGACY_CLIENT_AUTHORITATIVE_V1 = 0; + case SERVER_AUTHORITATIVE_V2 = 1; + case SERVER_AUTHORITATIVE_V3 = 2; +} diff --git a/src/types/camera/CameraPreset.php b/src/types/camera/CameraPreset.php index b1642329..fe8aec91 100644 --- a/src/types/camera/CameraPreset.php +++ b/src/types/camera/CameraPreset.php @@ -32,11 +32,15 @@ public function __construct( private ?float $yaw, private ?float $rotationSpeed, private ?bool $snapToTarget, + private ?Vector2 $horizontalRotationLimit, + private ?Vector2 $verticalRotationLimit, + private ?bool $continueTargeting, private ?Vector2 $viewOffset, private ?Vector3 $entityOffset, private ?float $radius, private ?int $audioListenerType, - private ?bool $playerEffects + private ?bool $playerEffects, + private ?bool $alignTargetAndCameraForward ){} public function getName() : string{ return $this->name; } @@ -57,6 +61,12 @@ public function getRotationSpeed() : ?float { return $this->rotationSpeed; } public function getSnapToTarget() : ?bool { return $this->snapToTarget; } + public function getHorizontalRotationLimit() : ?Vector2{ return $this->horizontalRotationLimit; } + + public function getVerticalRotationLimit() : ?Vector2{ return $this->verticalRotationLimit; } + + public function getContinueTargeting() : ?bool{ return $this->continueTargeting; } + public function getViewOffset() : ?Vector2{ return $this->viewOffset; } public function getEntityOffset() : ?Vector3{ return $this->entityOffset; } @@ -67,6 +77,8 @@ public function getAudioListenerType() : ?int{ return $this->audioListenerType; public function getPlayerEffects() : ?bool{ return $this->playerEffects; } + public function getAlignTargetAndCameraForward() : ?bool{ return $this->alignTargetAndCameraForward; } + public static function read(PacketSerializer $in) : self{ $name = $in->getString(); $parent = $in->getString(); @@ -77,11 +89,15 @@ public static function read(PacketSerializer $in) : self{ $yaw = $in->readOptional($in->getLFloat(...)); $rotationSpeed = $in->readOptional($in->getLFloat(...)); $snapToTarget = $in->readOptional($in->getBool(...)); + $horizontalRotationLimit = $in->readOptional($in->getVector2(...)); + $verticalRotationLimit = $in->readOptional($in->getVector2(...)); + $continueTargeting = $in->readOptional($in->getBool(...)); $viewOffset = $in->readOptional($in->getVector2(...)); $entityOffset = $in->readOptional($in->getVector3(...)); $radius = $in->readOptional($in->getLFloat(...)); $audioListenerType = $in->readOptional($in->getByte(...)); $playerEffects = $in->readOptional($in->getBool(...)); + $alignTargetAndCameraForward = $in->readOptional($in->getBool(...)); return new self( $name, @@ -93,11 +109,15 @@ public static function read(PacketSerializer $in) : self{ $yaw, $rotationSpeed, $snapToTarget, + $horizontalRotationLimit, + $verticalRotationLimit, + $continueTargeting, $viewOffset, $entityOffset, $radius, $audioListenerType, - $playerEffects + $playerEffects, + $alignTargetAndCameraForward ); } @@ -111,10 +131,14 @@ public function write(PacketSerializer $out) : void{ $out->writeOptional($this->yaw, $out->putLFloat(...)); $out->writeOptional($this->rotationSpeed, $out->putLFloat(...)); $out->writeOptional($this->snapToTarget, $out->putBool(...)); + $out->writeOptional($this->horizontalRotationLimit, $out->putVector2(...)); + $out->writeOptional($this->verticalRotationLimit, $out->putVector2(...)); + $out->writeOptional($this->continueTargeting, $out->putBool(...)); $out->writeOptional($this->viewOffset, $out->putVector2(...)); $out->writeOptional($this->entityOffset, $out->putVector3(...)); $out->writeOptional($this->radius, $out->putLFloat(...)); $out->writeOptional($this->audioListenerType, $out->putByte(...)); $out->writeOptional($this->playerEffects, $out->putBool(...)); + $out->writeOptional($this->alignTargetAndCameraForward, $out->putBool(...)); } } diff --git a/src/types/camera/CameraSetInstruction.php b/src/types/camera/CameraSetInstruction.php index b8512d10..6c8e9918 100644 --- a/src/types/camera/CameraSetInstruction.php +++ b/src/types/camera/CameraSetInstruction.php @@ -27,6 +27,7 @@ public function __construct( private ?CameraSetInstructionRotation $rotation, private ?Vector3 $facingPosition, private ?Vector2 $viewOffset, + private ?Vector3 $entityOffset, private ?bool $default ){} @@ -42,6 +43,8 @@ public function getFacingPosition() : ?Vector3{ return $this->facingPosition; } public function getViewOffset() : ?Vector2{ return $this->viewOffset; } + public function getEntityOffset() : ?Vector3{ return $this->entityOffset; } + public function getDefault() : ?bool{ return $this->default; } public static function read(PacketSerializer $in) : self{ @@ -51,6 +54,7 @@ public static function read(PacketSerializer $in) : self{ $rotation = $in->readOptional(fn() => CameraSetInstructionRotation::read($in)); $facingPosition = $in->readOptional($in->getVector3(...)); $viewOffset = $in->readOptional($in->getVector2(...)); + $entityOffset = $in->readOptional($in->getVector3(...)); $default = $in->readOptional($in->getBool(...)); return new self( @@ -60,6 +64,7 @@ public static function read(PacketSerializer $in) : self{ $rotation, $facingPosition, $viewOffset, + $entityOffset, $default ); } @@ -71,6 +76,7 @@ public function write(PacketSerializer $out) : void{ $out->writeOptional($this->rotation, fn(CameraSetInstructionRotation $v) => $v->write($out)); $out->writeOptional($this->facingPosition, $out->putVector3(...)); $out->writeOptional($this->viewOffset, $out->putVector2(...)); + $out->writeOptional($this->entityOffset, $out->putVector3(...)); $out->writeOptional($this->default, $out->putBool(...)); } } diff --git a/src/types/inventory/ContainerIds.php b/src/types/inventory/ContainerIds.php index fa36c231..a1486dee 100644 --- a/src/types/inventory/ContainerIds.php +++ b/src/types/inventory/ContainerIds.php @@ -31,6 +31,5 @@ private function __construct(){ public const FIXED_INVENTORY = 123; public const UI = 124; public const CONTAINER_ID_REGISTRY = 125; - public const CONTAINER_ID_REGISTRY_INVENTORY = 126; } diff --git a/src/types/login/ClientData.php b/src/types/login/ClientData.php index 2a87601a..45877bdf 100644 --- a/src/types/login/ClientData.php +++ b/src/types/login/ClientData.php @@ -76,6 +76,12 @@ final class ClientData{ /** @required */ public string $LanguageCode; + /** @required */ + public int $MaxViewDistance; + + /** @required */ + public int $MemoryTier; + public bool $OverrideSkin; /** @@ -99,6 +105,9 @@ final class ClientData{ /** @required */ public string $PlatformOnlineId; + /** @required */ + public int $PlatformType; + public string $PlatformUserId = ""; //xbox-only, apparently /** @required */ diff --git a/src/types/resourcepacks/ResourcePackInfoEntry.php b/src/types/resourcepacks/ResourcePackInfoEntry.php index d673e031..0bb7b4c0 100644 --- a/src/types/resourcepacks/ResourcePackInfoEntry.php +++ b/src/types/resourcepacks/ResourcePackInfoEntry.php @@ -26,7 +26,8 @@ public function __construct( private string $contentId = "", private bool $hasScripts = false, private bool $isAddonPack = false, - private bool $isRtxCapable = false + private bool $isRtxCapable = false, + private string $cdnUrl = "" ){} public function getPackId() : string{ @@ -61,6 +62,8 @@ public function isAddonPack() : bool{ return $this->isAddonPack; } public function isRtxCapable() : bool{ return $this->isRtxCapable; } + public function getCdnUrl() : string{ return $this->cdnUrl; } + public function write(PacketSerializer $out) : void{ $out->putString($this->packId); $out->putString($this->version); @@ -71,6 +74,7 @@ public function write(PacketSerializer $out) : void{ $out->putBool($this->hasScripts); $out->putBool($this->isAddonPack); $out->putBool($this->isRtxCapable); + $out->putString($this->cdnUrl); } public static function read(PacketSerializer $in) : self{ @@ -83,6 +87,7 @@ public static function read(PacketSerializer $in) : self{ $hasScripts = $in->getBool(); $isAddonPack = $in->getBool(); $rtxCapable = $in->getBool(); - return new self($uuid, $version, $sizeBytes, $encryptionKey, $subPackName, $contentId, $hasScripts, $isAddonPack, $rtxCapable); + $cdnUrl = $in->getString(); + return new self($uuid, $version, $sizeBytes, $encryptionKey, $subPackName, $contentId, $hasScripts, $isAddonPack, $rtxCapable, $cdnUrl); } } From 68d818091f8d26ba82788f775d14572c11cca8b1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 26 Oct 2024 17:56:04 +0100 Subject: [PATCH 03/10] Ported protocol_info_generator codegen stuff to PHP this now takes a JSON file as input, as provided by protocol_info_dumper.py in bds-modding-devkit. --- tools/generate-protocol-info.php | 467 +++++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 tools/generate-protocol-info.php diff --git a/tools/generate-protocol-info.php b/tools/generate-protocol-info.php new file mode 100644 index 00000000..7c12ce36 --- /dev/null +++ b/tools/generate-protocol-info.php @@ -0,0 +1,467 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\tools\generate_protocol_info; + +use pocketmine\network\mcpe\protocol\ClientboundPacket; +use pocketmine\network\mcpe\protocol\DataPacket; +use pocketmine\network\mcpe\protocol\Packet; +use pocketmine\network\mcpe\protocol\PacketDecodeException; +use pocketmine\network\mcpe\protocol\PacketHandlerDefaultImplTrait; +use pocketmine\network\mcpe\protocol\PacketHandlerInterface; +use pocketmine\network\mcpe\protocol\PacketPool; +use pocketmine\network\mcpe\protocol\ServerboundPacket; +use function array_fill_keys; +use function asort; +use function ceil; +use function count; +use function dechex; +use function dirname; +use function file_exists; +use function file_get_contents; +use function file_put_contents; +use function fwrite; +use function implode; +use function is_array; +use function is_bool; +use function is_int; +use function is_string; +use function json_decode; +use function max; +use function preg_split; +use function scandir; +use function sprintf; +use function str_contains; +use function str_ends_with; +use function str_pad; +use function strlen; +use function strtoupper; +use function substr; +use const DIRECTORY_SEPARATOR; +use const JSON_THROW_ON_ERROR; +use const PHP_EOL; +use const PREG_SPLIT_DELIM_CAPTURE; +use const PREG_SPLIT_NO_EMPTY; +use const SORT_NUMERIC; +use const STDERR; +use const STR_PAD_LEFT; + +const DATA_PACKET_TEMPLATE = <<<'CODE' + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +class %s extends DataPacket{ + public const NETWORK_ID = ProtocolInfo::%s; + + /** + * @generate-create-func + */ + public static function create() : self{ + $result = new self; + //TODO: add fields + return $result; + } + + protected function decodePayload(PacketSerializer $in) : void{ + //TODO + } + + protected function encodePayload(PacketSerializer $out) : void{ + //TODO + } + + public function handle(PacketHandlerInterface $handler) : bool{ + return $handler->handle%s($this); + } +} + +CODE; + +const PACKET_HANDLER_TRAIT_TEMPLATE = <<<'CODE' + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +/** + * This trait provides default implementations for all packet handlers, so that you don't have to manually write + * handlers for every single packet even if you don't care about them. + * + * This file is automatically generated. Do not edit it manually. + */ +trait PacketHandlerDefaultImplTrait{ + +%s +} + +CODE; + +const PACKET_HANDLER_INTERFACE_TEMPLATE = <<<'CODE' + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +/** + * This class is an automatically generated stub. Do not edit it manually. + */ +interface PacketHandlerInterface{ +%s +} + +CODE; + +const PACKET_POOL_TEMPLATE = <<<'CODE' + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +use pocketmine\utils\Binary; +use pocketmine\utils\BinaryDataException; + +class PacketPool{ + protected static ?PacketPool $instance = null; + + public static function getInstance() : self{ + if(self::$instance === null){ + self::$instance = new self; + } + return self::$instance; + } + + /** @var \SplFixedArray */ + protected \SplFixedArray $pool; + + public function __construct(){ + $this->pool = new \SplFixedArray(%d); +%s + } + + public function registerPacket(Packet $packet) : void{ + $this->pool[$packet->pid()] = clone $packet; + } + + public function getPacketById(int $pid) : ?Packet{ + return isset($this->pool[$pid]) ? clone $this->pool[$pid] : null; + } + + /** + * @throws BinaryDataException + */ + public function getPacket(string $buffer) : ?Packet{ + $offset = 0; + return $this->getPacketById(Binary::readUnsignedVarInt($buffer, $offset) & DataPacket::PID_MASK); + } +} + +CODE; + +const PROTOCOL_INFO_TEMPLATE = <<<'CODE' + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +/** + * Version numbers and packet IDs for the current Minecraft PE protocol + */ +final class ProtocolInfo{ + + private function __construct(){ + //NOOP + } + + /** + * NOTE TO DEVELOPERS + * Do not waste your time or ours submitting pull requests changing game and/or protocol version numbers. + * Pull requests changing game and/or protocol version numbers will be closed. + * + * This file is generated automatically, do not edit it manually. + */ + + /** Actual Minecraft: PE protocol version */ + public const CURRENT_PROTOCOL = %d; + /** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */ + public const MINECRAFT_VERSION = '%s'; + /** Version number sent to clients in ping responses. */ + public const MINECRAFT_VERSION_NETWORK = '%s'; + +%s +} + +CODE; + +/** + * @return string[] + */ +function split_upper(string $string) : array{ + $split = preg_split('/([A-Z][^A-Z]*)/', $string, flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + if($split === false){ + throw new \Error("preg_split failed"); + } + return $split; +} + +function rchop(string $string, string $substring) : string{ + if(str_ends_with($string, $substring)){ + return substr($string, 0, -strlen($substring)); + } + return $string; +} + +/** + * @param string[] $packetToIdList + * @phpstan-param array $packetToIdList + */ +function generate_new_packet_stubs(array $packetToIdList, string $packetsDir) : void{ + foreach($packetToIdList as $name => $id){ + $packetFilePath = $packetsDir . DIRECTORY_SEPARATOR . $name . '.php'; + if(!file_exists($packetFilePath)){ + echo "!!! New packet: $name" . PHP_EOL; + $constName = strtoupper(implode('_', split_upper($name))); + $baseName = rchop($name, 'Packet'); + file_put_contents($packetFilePath, sprintf(DATA_PACKET_TEMPLATE, $name, $constName, $baseName)); + echo "Created stub class for $name at $packetFilePath" . PHP_EOL; + } + } +} + +/** + * @param string[] $packetToIdList + * @phpstan-param array $packetToIdList + */ +function check_removed_packets(array $packetToIdList, string $packetsDir) : void{ + $existing = scandir($packetsDir); + if($existing === false){ + return; + } + + //use ::class constants here so that they are checked for existence + $ignoredClasses = array_fill_keys([ + DataPacket::class, + PacketPool::class, + Packet::class, + PacketDecodeException::class, + PacketHandlerDefaultImplTrait::class, + PacketHandlerInterface::class, + ClientboundPacket::class, + ServerboundPacket::class, + ], true); + foreach($existing as $fileName){ + if(str_ends_with($fileName, ".php")){ + $packetName = substr($fileName, 0, -strlen(".php")); + if(!str_contains($packetName, "Packet") || isset($ignoredClasses["pocketmine\\network\\mcpe\\protocol\\" . $packetName])){ + continue; + } + if(!isset($packetToIdList[$packetName])){ + echo "!!! Removed packet: $packetName" . PHP_EOL; + } + } + } +} + +/** + * @param string[] $packetToIdList + * @phpstan-param array $packetToIdList + */ +function generate_protocol_info(array $packetToIdList, int $protocolVersion, int $major, int $minor, int $patch, int $revision, bool $beta, string $packetsDir) : void{ + $consts = ""; + $last = 0; + + foreach($packetToIdList as $name => $id){ + if($id !== $last + 1){ + $consts .= "\n"; + } + + $last = $id; + $consts .= sprintf( + "\tpublic const %s = %s;\n", + strtoupper(implode("_", split_upper($name))), + "0x" . str_pad(dechex($id), 2, "0", STR_PAD_LEFT) + ); + } + + $gameVersion = sprintf("v%d.%d.%d%s", $major, $minor, $patch, $beta ? ".$revision beta" : ""); + $gameVersionNetwork = sprintf("%d.%d.%d%s", $major, $minor, $patch, $beta ? ".$revision" : ""); + file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "ProtocolInfo.php", sprintf( + PROTOCOL_INFO_TEMPLATE, + $protocolVersion, + $gameVersion, + $gameVersionNetwork, + $consts + )); + + echo "Recreated ProtocolInfo" . PHP_EOL; +} + +/** + * @param string[] $packetToIdList + * @phpstan-param array $packetToIdList + */ +function generate_packet_pool(array $packetToIdList, string $packetsDir) : void{ + $entries = ""; + + foreach($packetToIdList as $name => $id){ + $entries .= sprintf("\n\t\t\$this->registerPacket(new %s());", $name); + } + + $poolSize = (int) (ceil(max($packetToIdList) / 256) * 256); + file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketPool.php", sprintf( + PACKET_POOL_TEMPLATE, + $poolSize, + $entries + )); + echo "Recreated PacketPool\n"; +} + +/** + * @param string[] $packetToIdList + * @phpstan-param array $packetToIdList + */ +function generate_packet_handler_classes(array $packetToIdList, string $packetsDir) : void{ + $interfaceFunctions = []; + $traitFunctions = []; + + foreach($packetToIdList as $name => $id){ + $baseName = rchop($name, "Packet"); + $interfaceFunctions[] = sprintf("\tpublic function handle%s(%s \$packet) : bool;", $baseName, $name); + $traitFunctions[] = sprintf("\tpublic function handle%s(%s \$packet) : bool{\n\t\treturn false;\n\t}", $baseName, $name); + } + + file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketHandlerInterface.php", sprintf( + PACKET_HANDLER_INTERFACE_TEMPLATE, + implode("\n\n", $interfaceFunctions) + )); + echo "Recreated PacketHandlerInterface" . PHP_EOL; + file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketHandlerDefaultImplTrait.php", sprintf( + PACKET_HANDLER_TRAIT_TEMPLATE, + implode("\n\n", $traitFunctions) + )); + echo "Recreated PacketHandlerDefaultImplTrait" . PHP_EOL; +} + +if(count($argv) < 2){ + fwrite(STDERR, "Please provide an input protocol_info.json file" . PHP_EOL); + exit(1); +} + +$rawData = file_get_contents($argv[1]); +if($rawData === false){ + fwrite(STDERR, "Couldn't read data from " . $argv[1] . PHP_EOL); + exit(1); +} + +try{ + $json = json_decode($rawData, associative: true, flags: JSON_THROW_ON_ERROR); +}catch(\JsonException $e){ + fwrite(STDERR, "Error decoding input file: " . $e->getMessage() . PHP_EOL); + exit(1); +} +if(!is_array($json) || count($json) !== 2 || !is_array($json["version"] ?? null) || !is_array($json["packets"] ?? null)){ + fwrite(STDERR, "Invalid input file, expected 2 objects: \"version\" and \"packets\"" . PHP_EOL); + exit(1); +} + +$versionInfo = $json["version"]; +$major = $versionInfo["major"] ?? null; +$minor = $versionInfo["minor"] ?? null; +$patch = $versionInfo["patch"] ?? null; +$revision = $versionInfo["revision"] ?? null; +$beta = $versionInfo["beta"] ?? null; +$protocolVersion = $versionInfo["protocol_version"] ?? null; +if(!is_int($major) || !is_int($minor) || !is_int($patch) || !is_int($revision) || !is_bool($beta) || !is_int($protocolVersion)){ + fwrite(STDERR, "Invalid version info, expected \"major\" (int), \"minor\" (int), \"patch\" (int), \"revision\" (int), \"beta\" (bool) and \"protocol_version\" (int)" . PHP_EOL); + exit(1); +} + +echo "Generating code basics for version $major.$minor.$patch.$revision " . ($beta ? "beta" : "") . " (protocol $protocolVersion)" . PHP_EOL; + +$packetToIdList = []; +foreach($json["packets"] as $name => $id){ + if(!is_string($name) || !is_int($id)){ + fwrite(STDERR, "Invalid packet entry \"$name\", expected string => int" . PHP_EOL); + exit(1); + } + $packetToIdList[$name] = $id; +} +asort($packetToIdList, SORT_NUMERIC); + +$packetsDir = dirname(__DIR__) . '/src/'; +generate_protocol_info($packetToIdList, $protocolVersion, $major, $minor, $patch, $revision, $beta, $packetsDir); +generate_packet_pool($packetToIdList, $packetsDir); +generate_packet_handler_classes($packetToIdList, $packetsDir); +check_removed_packets($packetToIdList, $packetsDir); +generate_new_packet_stubs($packetToIdList, $packetsDir); + +echo "Done" . PHP_EOL; From f98f21e4b3b9190d907954844f8994f242bdfa86 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 26 Oct 2024 18:25:14 +0100 Subject: [PATCH 04/10] tools: added a wrapper for all BedrockData generation tools this way it takes 1 command to do all that crap instead of 4 --- tools/update-from-bedrock-data.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tools/update-from-bedrock-data.php diff --git a/tools/update-from-bedrock-data.php b/tools/update-from-bedrock-data.php new file mode 100644 index 00000000..ba1f09f4 --- /dev/null +++ b/tools/update-from-bedrock-data.php @@ -0,0 +1,23 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +if(count($argv) < 2){ + fwrite(STDERR, "Required args: path to BedrockData" . PHP_EOL); + exit(1); +} + +passthru(PHP_BINARY . " " . __DIR__ . '/generate-protocol-info.php ' . escapeshellarg($argv[1] . '/protocol_info.json')); +passthru(PHP_BINARY . " " . __DIR__ . '/generate-entity-ids.php ' . escapeshellarg($argv[1] . '/entity_id_map.json')); +passthru(PHP_BINARY . " " . __DIR__ . '/generate-level-sound-ids.php ' . escapeshellarg($argv[1] . '/level_sound_id_map.json')); +passthru(PHP_BINARY . " " . __DIR__ . '/generate-command-parameter-types.php ' . escapeshellarg($argv[1] . '/command_arg_types.json')); From 3e049bde9856d704bfa84a1feec7f2b692281327 Mon Sep 17 00:00:00 2001 From: "Dylan T." Date: Fri, 15 Nov 2024 21:04:07 +0000 Subject: [PATCH 05/10] Use RestrictedActions auto approver from --- .github/workflows/team-pr-auto-approve.yml | 35 ++++++++++------------ 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/.github/workflows/team-pr-auto-approve.yml b/.github/workflows/team-pr-auto-approve.yml index a582be32..8f40b16e 100644 --- a/.github/workflows/team-pr-auto-approve.yml +++ b/.github/workflows/team-pr-auto-approve.yml @@ -13,30 +13,25 @@ on: - reopened - ready_for_review -permissions: - pull-requests: write - jobs: - approve: - name: Auto approve + dispatch: + name: Request approval runs-on: ubuntu-latest steps: - - name: Check if PR author has write access - id: check-permission - uses: actions-cool/check-user-permission@v2 + - name: Generate access token + id: generate-token + uses: actions/create-github-app-token@v1 with: - token: ${{ secrets.GITHUB_TOKEN }} - require: write - username: ${{ github.event.pull_request.user.login }} - #technically this would be fine for dependabot but generally bots don't count as team members - check-bot: true - - #TODO: Some way to avoid unnecessary repeated reviews would be nice here + app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }} + private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }} + owner: ${{ github.repository_owner }} + repositories: RestrictedActions - - name: Approve PR if authorized - if: steps.check-permission.outputs.require-result == 'true' && steps.check-permission.outputs.check-result == 'false' - uses: juliangruber/approve-pull-request-action@v2 + - name: Dispatch restricted action + uses: peter-evans/repository-dispatch@v3 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - number: ${{ github.event.pull_request.number }} + token: ${{ steps.generate-token.outputs.token }} + repository: ${{ github.repository_owner }}/RestrictedActions + event-type: auto_approve_collaborator_pr + client-payload: '{"repo": "${{ github.repository }}", "pull_request_id": "${{ github.event.pull_request.number }}" }' From 51c252069633cbd2aca4e5acf296fb2ff651a91c Mon Sep 17 00:00:00 2001 From: Dries C Date: Mon, 25 Nov 2024 20:18:46 +0100 Subject: [PATCH 06/10] Protocol changes for 1.21.50 --- src/CameraAimAssistPacket.php | 8 +- src/CameraAimAssistPresetsPacket.php | 79 ++++++++++ src/PacketHandlerDefaultImplTrait.php | 4 + src/PacketHandlerInterface.php | 2 + src/PlayerAuthInputPacket.php | 59 ++++---- src/ProtocolInfo.php | 7 +- src/ResourcePacksInfoPacket.php | 11 +- src/serializer/BitSet.php | 140 ++++++++++++++++++ src/types/LevelSoundEvent.php | 14 +- .../camera/CameraAimAssistCategories.php | 58 ++++++++ src/types/camera/CameraAimAssistCategory.php | 43 ++++++ .../CameraAimAssistCategoryBlockPriority.php | 43 ++++++ .../CameraAimAssistCategoryEntityPriority.php | 43 ++++++ .../CameraAimAssistCategoryPriorities.php | 82 ++++++++++ src/types/camera/CameraAimAssistPreset.php | 115 ++++++++++++++ .../CameraAimAssistPresetItemSettings.php | 43 ++++++ src/types/camera/CameraPreset.php | 16 +- src/types/entity/EntityIds.php | 1 + .../ItemStackResponseSlotInfo.php | 7 +- .../resourcepacks/ResourcePackInfoEntry.php | 9 +- tests/phpunit/BitSetTest.php | 41 +++++ 21 files changed, 783 insertions(+), 42 deletions(-) create mode 100644 src/CameraAimAssistPresetsPacket.php create mode 100644 src/serializer/BitSet.php create mode 100644 src/types/camera/CameraAimAssistCategories.php create mode 100644 src/types/camera/CameraAimAssistCategory.php create mode 100644 src/types/camera/CameraAimAssistCategoryBlockPriority.php create mode 100644 src/types/camera/CameraAimAssistCategoryEntityPriority.php create mode 100644 src/types/camera/CameraAimAssistCategoryPriorities.php create mode 100644 src/types/camera/CameraAimAssistPreset.php create mode 100644 src/types/camera/CameraAimAssistPresetItemSettings.php create mode 100644 tests/phpunit/BitSetTest.php diff --git a/src/CameraAimAssistPacket.php b/src/CameraAimAssistPacket.php index e15eab2b..8793f264 100644 --- a/src/CameraAimAssistPacket.php +++ b/src/CameraAimAssistPacket.php @@ -22,6 +22,7 @@ class CameraAimAssistPacket extends DataPacket implements ClientboundPacket{ public const NETWORK_ID = ProtocolInfo::CAMERA_AIM_ASSIST_PACKET; + private string $presetId; private Vector2 $viewAngle; private float $distance; private CameraAimAssistTargetMode $targetMode; @@ -30,8 +31,9 @@ class CameraAimAssistPacket extends DataPacket implements ClientboundPacket{ /** * @generate-create-func */ - public static function create(Vector2 $viewAngle, float $distance, CameraAimAssistTargetMode $targetMode, CameraAimAssistActionType $actionType) : self{ + public static function create(string $presetId, Vector2 $viewAngle, float $distance, CameraAimAssistTargetMode $targetMode, CameraAimAssistActionType $actionType) : self{ $result = new self; + $result->presetId = $presetId; $result->viewAngle = $viewAngle; $result->distance = $distance; $result->targetMode = $targetMode; @@ -39,6 +41,8 @@ public static function create(Vector2 $viewAngle, float $distance, CameraAimAssi return $result; } + public function getPresetId() : string{ return $this->presetId; } + public function getViewAngle() : Vector2{ return $this->viewAngle; } public function getDistance() : float{ return $this->distance; } @@ -48,6 +52,7 @@ public function getTargetMode() : CameraAimAssistTargetMode{ return $this->targe public function getActionType() : CameraAimAssistActionType{ return $this->actionType; } protected function decodePayload(PacketSerializer $in) : void{ + $this->presetId = $in->getString(); $this->viewAngle = $in->getVector2(); $this->distance = $in->getLFloat(); $this->targetMode = CameraAimAssistTargetMode::fromPacket($in->getByte()); @@ -55,6 +60,7 @@ protected function decodePayload(PacketSerializer $in) : void{ } protected function encodePayload(PacketSerializer $out) : void{ + $out->putString($this->presetId); $out->putVector2($this->viewAngle); $out->putLFloat($this->distance); $out->putByte($this->targetMode->value); diff --git a/src/CameraAimAssistPresetsPacket.php b/src/CameraAimAssistPresetsPacket.php new file mode 100644 index 00000000..4d6cdfe8 --- /dev/null +++ b/src/CameraAimAssistPresetsPacket.php @@ -0,0 +1,79 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\types\camera\CameraAimAssistCategories; +use pocketmine\network\mcpe\protocol\types\camera\CameraAimAssistPreset; +use function count; + +class CameraAimAssistPresetsPacket extends DataPacket implements ClientboundPacket{ + public const NETWORK_ID = ProtocolInfo::CAMERA_AIM_ASSIST_PRESETS_PACKET; + + /** @var CameraAimAssistCategories[] */ + private array $categories; + /** @var CameraAimAssistPreset[] */ + private array $presets; + + /** + * @generate-create-func + * @param CameraAimAssistCategories[] $categories + * @param CameraAimAssistPreset[] $presets + */ + public static function create(array $categories, array $presets) : self{ + $result = new self; + $result->categories = $categories; + $result->presets = $presets; + return $result; + } + + /** + * @return CameraAimAssistCategories[] + */ + public function getCategories() : array{ return $this->categories; } + + /** + * @return CameraAimAssistPreset[] + */ + public function getPresets() : array{ return $this->presets; } + + protected function decodePayload(PacketSerializer $in) : void{ + $this->categories = []; + for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){ + $this->categories[] = CameraAimAssistCategories::read($in); + } + + $this->presets = []; + for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){ + $this->presets[] = CameraAimAssistPreset::read($in); + } + } + + protected function encodePayload(PacketSerializer $out) : void{ + $out->putUnsignedVarInt(count($this->categories)); + foreach($this->categories as $category){ + $category->write($out); + } + + $out->putUnsignedVarInt(count($this->presets)); + foreach($this->presets as $preset){ + $preset->write($out); + } + } + + public function handle(PacketHandlerInterface $handler) : bool{ + return $handler->handleCameraAimAssistPresets($this); + } +} diff --git a/src/PacketHandlerDefaultImplTrait.php b/src/PacketHandlerDefaultImplTrait.php index 9523649c..9bb58fb0 100644 --- a/src/PacketHandlerDefaultImplTrait.php +++ b/src/PacketHandlerDefaultImplTrait.php @@ -837,4 +837,8 @@ public function handleMovementEffect(MovementEffectPacket $packet) : bool{ public function handleSetMovementAuthority(SetMovementAuthorityPacket $packet) : bool{ return false; } + + public function handleCameraAimAssistPresets(CameraAimAssistPresetsPacket $packet) : bool{ + return false; + } } diff --git a/src/PacketHandlerInterface.php b/src/PacketHandlerInterface.php index ca966659..70d7fa93 100644 --- a/src/PacketHandlerInterface.php +++ b/src/PacketHandlerInterface.php @@ -425,4 +425,6 @@ public function handleContainerRegistryCleanup(ContainerRegistryCleanupPacket $p public function handleMovementEffect(MovementEffectPacket $packet) : bool; public function handleSetMovementAuthority(SetMovementAuthorityPacket $packet) : bool; + + public function handleCameraAimAssistPresets(CameraAimAssistPresetsPacket $packet) : bool; } diff --git a/src/PlayerAuthInputPacket.php b/src/PlayerAuthInputPacket.php index 0a7a0cf3..c03f9ca6 100644 --- a/src/PlayerAuthInputPacket.php +++ b/src/PlayerAuthInputPacket.php @@ -16,6 +16,7 @@ use pocketmine\math\Vector2; use pocketmine\math\Vector3; +use pocketmine\network\mcpe\protocol\serializer\BitSet; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\InputMode; use pocketmine\network\mcpe\protocol\types\InteractionMode; @@ -38,7 +39,7 @@ class PlayerAuthInputPacket extends DataPacket implements ServerboundPacket{ private float $headYaw; private float $moveVecX; private float $moveVecZ; - private int $inputFlags; + private BitSet $inputFlags; private int $inputMode; private int $playMode; private int $interactionMode; @@ -53,6 +54,7 @@ class PlayerAuthInputPacket extends DataPacket implements ServerboundPacket{ private float $analogMoveVecX; private float $analogMoveVecZ; private Vector3 $cameraOrientation; + private Vector2 $rawMove; /** * @generate-create-func @@ -65,7 +67,7 @@ private static function internalCreate( float $headYaw, float $moveVecX, float $moveVecZ, - int $inputFlags, + BitSet $inputFlags, int $inputMode, int $playMode, int $interactionMode, @@ -79,6 +81,7 @@ private static function internalCreate( float $analogMoveVecX, float $analogMoveVecZ, Vector3 $cameraOrientation, + Vector2 $rawMove, ) : self{ $result = new self; $result->position = $position; @@ -101,11 +104,12 @@ private static function internalCreate( $result->analogMoveVecX = $analogMoveVecX; $result->analogMoveVecZ = $analogMoveVecZ; $result->cameraOrientation = $cameraOrientation; + $result->rawMove = $rawMove; return $result; } /** - * @param int $inputFlags @see PlayerAuthInputFlags + * @param BitSet $inputFlags @see PlayerAuthInputFlags * @param int $inputMode @see InputMode * @param int $playMode @see PlayMode * @param int $interactionMode @see InteractionMode @@ -118,7 +122,7 @@ public static function create( float $headYaw, float $moveVecX, float $moveVecZ, - int $inputFlags, + BitSet $inputFlags, int $inputMode, int $playMode, int $interactionMode, @@ -131,22 +135,18 @@ public static function create( ?PlayerAuthInputVehicleInfo $vehicleInfo, float $analogMoveVecX, float $analogMoveVecZ, - Vector3 $cameraOrientation + Vector3 $cameraOrientation, + Vector2 $rawMove ) : self{ - $realInputFlags = $inputFlags & ~((1 << PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST) | (1 << PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION) | (1 << PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS)); - if($itemStackRequest !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST; - } - if($itemInteractionData !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION; - } - if($blockActions !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS; - } - if($vehicleInfo !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE; + if($inputFlags->getLength() !== 65){ + throw new \InvalidArgumentException("Input flags must be 65 bits long"); } + $inputFlags->set(PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST, $itemStackRequest !== null); + $inputFlags->set(PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION, $itemInteractionData !== null); + $inputFlags->set(PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS, $blockActions !== null); + $inputFlags->set(PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE, $vehicleInfo !== null); + return self::internalCreate( $position, $pitch, @@ -154,7 +154,7 @@ public static function create( $headYaw, $moveVecX, $moveVecZ, - $realInputFlags, + $inputFlags, $inputMode, $playMode, $interactionMode, @@ -167,7 +167,8 @@ public static function create( $vehicleInfo, $analogMoveVecX, $analogMoveVecZ, - $cameraOrientation + $cameraOrientation, + $rawMove ); } @@ -198,7 +199,7 @@ public function getMoveVecZ() : float{ /** * @see PlayerAuthInputFlags */ - public function getInputFlags() : int{ + public function getInputFlags() : BitSet{ return $this->inputFlags; } @@ -256,9 +257,7 @@ public function getAnalogMoveVecZ() : float{ return $this->analogMoveVecZ; } public function getCameraOrientation() : Vector3{ return $this->cameraOrientation; } - public function hasFlag(int $flag) : bool{ - return ($this->inputFlags & (1 << $flag)) !== 0; - } + public function getRawMove() : Vector2{ return $this->rawMove; } protected function decodePayload(PacketSerializer $in) : void{ $this->pitch = $in->getLFloat(); @@ -267,20 +266,20 @@ protected function decodePayload(PacketSerializer $in) : void{ $this->moveVecX = $in->getLFloat(); $this->moveVecZ = $in->getLFloat(); $this->headYaw = $in->getLFloat(); - $this->inputFlags = $in->getUnsignedVarLong(); + $this->inputFlags = BitSet::read($in, 65); $this->inputMode = $in->getUnsignedVarInt(); $this->playMode = $in->getUnsignedVarInt(); $this->interactionMode = $in->getUnsignedVarInt(); $this->interactRotation = $in->getVector2(); $this->tick = $in->getUnsignedVarLong(); $this->delta = $in->getVector3(); - if($this->hasFlag(PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION)){ + if($this->inputFlags->get(PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION)){ $this->itemInteractionData = ItemInteractionData::read($in); } - if($this->hasFlag(PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST)){ + if($this->inputFlags->get(PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST)){ $this->itemStackRequest = ItemStackRequest::read($in); } - if($this->hasFlag(PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS)){ + if($this->inputFlags->get(PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS)){ $this->blockActions = []; $max = $in->getVarInt(); for($i = 0; $i < $max; ++$i){ @@ -292,12 +291,13 @@ protected function decodePayload(PacketSerializer $in) : void{ }; } } - if($this->hasFlag(PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE)){ + if($this->inputFlags->get(PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE)){ $this->vehicleInfo = PlayerAuthInputVehicleInfo::read($in); } $this->analogMoveVecX = $in->getLFloat(); $this->analogMoveVecZ = $in->getLFloat(); $this->cameraOrientation = $in->getVector3(); + $this->rawMove = $in->getVector2(); } protected function encodePayload(PacketSerializer $out) : void{ @@ -307,7 +307,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putLFloat($this->moveVecX); $out->putLFloat($this->moveVecZ); $out->putLFloat($this->headYaw); - $out->putUnsignedVarLong($this->inputFlags); + $this->inputFlags->write($out); $out->putUnsignedVarInt($this->inputMode); $out->putUnsignedVarInt($this->playMode); $out->putUnsignedVarInt($this->interactionMode); @@ -333,6 +333,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putLFloat($this->analogMoveVecX); $out->putLFloat($this->analogMoveVecZ); $out->putVector3($this->cameraOrientation); + $out->putVector2($this->rawMove); } public function handle(PacketHandlerInterface $handler) : bool{ diff --git a/src/ProtocolInfo.php b/src/ProtocolInfo.php index 177a8cb0..b373de66 100644 --- a/src/ProtocolInfo.php +++ b/src/ProtocolInfo.php @@ -32,11 +32,11 @@ private function __construct(){ */ /** Actual Minecraft: PE protocol version */ - public const CURRENT_PROTOCOL = 748; + public const CURRENT_PROTOCOL = 766; /** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */ - public const MINECRAFT_VERSION = 'v1.21.40'; + public const MINECRAFT_VERSION = 'v1.21.50'; /** Version number sent to clients in ping responses. */ - public const MINECRAFT_VERSION_NETWORK = '1.21.40'; + public const MINECRAFT_VERSION_NETWORK = '1.21.50'; public const LOGIN_PACKET = 0x01; public const PLAY_STATUS_PACKET = 0x02; @@ -255,4 +255,5 @@ private function __construct(){ public const CONTAINER_REGISTRY_CLEANUP_PACKET = 0x13d; public const MOVEMENT_EFFECT_PACKET = 0x13e; public const SET_MOVEMENT_AUTHORITY_PACKET = 0x13f; + public const CAMERA_AIM_ASSIST_PRESETS_PACKET = 0x140; } diff --git a/src/ResourcePacksInfoPacket.php b/src/ResourcePacksInfoPacket.php index f8a40ff8..9e1f434f 100644 --- a/src/ResourcePacksInfoPacket.php +++ b/src/ResourcePacksInfoPacket.php @@ -16,6 +16,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry; +use Ramsey\Uuid\UuidInterface; use function count; class ResourcePacksInfoPacket extends DataPacket implements ClientboundPacket{ @@ -26,17 +27,21 @@ class ResourcePacksInfoPacket extends DataPacket implements ClientboundPacket{ public bool $mustAccept = false; //if true, forces client to choose between accepting packs or being disconnected public bool $hasAddons = false; public bool $hasScripts = false; //if true, causes disconnect for any platform that doesn't support scripts yet + private UuidInterface $worldTemplateId; + private string $worldTemplateVersion; /** * @generate-create-func * @param ResourcePackInfoEntry[] $resourcePackEntries */ - public static function create(array $resourcePackEntries, bool $mustAccept, bool $hasAddons, bool $hasScripts) : self{ + public static function create(array $resourcePackEntries, bool $mustAccept, bool $hasAddons, bool $hasScripts, UuidInterface $worldTemplateId, string $worldTemplateVersion) : self{ $result = new self; $result->resourcePackEntries = $resourcePackEntries; $result->mustAccept = $mustAccept; $result->hasAddons = $hasAddons; $result->hasScripts = $hasScripts; + $result->worldTemplateId = $worldTemplateId; + $result->worldTemplateVersion = $worldTemplateVersion; return $result; } @@ -44,6 +49,8 @@ protected function decodePayload(PacketSerializer $in) : void{ $this->mustAccept = $in->getBool(); $this->hasAddons = $in->getBool(); $this->hasScripts = $in->getBool(); + $this->worldTemplateId = $in->getUUID(); + $this->worldTemplateVersion = $in->getString(); $resourcePackCount = $in->getLShort(); while($resourcePackCount-- > 0){ @@ -55,6 +62,8 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putBool($this->mustAccept); $out->putBool($this->hasAddons); $out->putBool($this->hasScripts); + $out->putUUID($this->worldTemplateId); + $out->putString($this->worldTemplateVersion); $out->putLShort(count($this->resourcePackEntries)); foreach($this->resourcePackEntries as $entry){ $entry->write($out); diff --git a/src/serializer/BitSet.php b/src/serializer/BitSet.php new file mode 100644 index 00000000..71e708bc --- /dev/null +++ b/src/serializer/BitSet.php @@ -0,0 +1,140 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\serializer; + +use pocketmine\utils\BinaryDataException; +use function array_pad; +use function array_values; +use function chr; +use function count; +use function intdiv; +use function ord; + +class BitSet{ + private const INT_BITS = PHP_INT_SIZE * 8; + private const SHIFT = 7; + + /** + * @param int[] $parts + */ + public function __construct( + private readonly int $length, + private array $parts = [] + ){ + $expectedPartsCount = intdiv($length + self::INT_BITS - 1, self::INT_BITS); + $partsCount = count($parts); + + if($partsCount > $expectedPartsCount){ + throw new \InvalidArgumentException("Too many parts"); + }elseif($partsCount < $expectedPartsCount){ + $parts = array_pad($parts, $expectedPartsCount, 0); + } + + $this->parts = array_values($parts); + } + + public function get(int $index) : bool{ + [$partIndex, $bitIndex] = $this->getPartIndex($index); + + return ($this->parts[$partIndex] & (1 << $bitIndex)) !== 0; + } + + public function set(int $index, bool $value) : void{ + [$partIndex, $bitIndex] = $this->getPartIndex($index); + + if($value){ + $this->parts[$partIndex] |= 1 << $bitIndex; + }else{ + $this->parts[$partIndex] &= ~(1 << $bitIndex); + } + } + + /** + * Returns the part index and the bit index within that part for a given bit index. + * + * @return array{int, int} + */ + private function getPartIndex(int $index) : array{ + if($index < 0 or $index >= $this->length){ + throw new \InvalidArgumentException("Index out of bounds"); + } + + return [ + intdiv($index, self::INT_BITS), + $index % self::INT_BITS + ]; + } + + public static function read(PacketSerializer $in, int $length) : self{ + $result = [0]; + + $currentIndex = 0; + $currentShift = 0; + + for($i = 0; $i < $length; $i += self::SHIFT){ + $b = ord($in->get(1)); + $bits = $b & 0x7f; + + $result[$currentIndex] |= $bits << $currentShift; //extra bits will be discarded + $nextShift = $currentShift + self::SHIFT; + if($nextShift >= self::INT_BITS){ + $nextShift -= self::INT_BITS; + $rightShift = self::SHIFT - $nextShift; + $result[++$currentIndex] = $bits >> $rightShift; + } + $currentShift = $nextShift; + + if(($b & 0x80) === 0){ + return new self($length, $result); + } + } + + throw new BinaryDataException("Didn't terminate after reading $length bits"); + } + + public function write(PacketSerializer $out) : void{ + $buf = ""; + + $parts = $this->parts; + $length = $this->length; + + $currentIndex = 0; + $currentShift = 0; + + for($i = 0; $i < $length; $i += self::SHIFT){ + $bits = $parts[$currentIndex] >> $currentShift; + $nextShift = $currentShift + self::SHIFT; + if($nextShift >= self::INT_BITS){ + $nextShift -= self::INT_BITS; + $bits |= $parts[++$currentIndex] << (self::SHIFT - $nextShift); + } + $currentShift = $nextShift; + + $last = $i + self::SHIFT >= $length; + $bits |= $last ? 0 : 0x80; + + $buf .= chr($bits); + if($last){ + break; + } + } + + $out->put($buf); + } + + public function getLength() : int{ + return $this->length; + } +} diff --git a/src/types/LevelSoundEvent.php b/src/types/LevelSoundEvent.php index 0ddba26e..872cd377 100644 --- a/src/types/LevelSoundEvent.php +++ b/src/types/LevelSoundEvent.php @@ -521,8 +521,20 @@ private function __construct(){ public const RECORD_PRECIPICE = 529; public const VAULT_REJECT_REWARDED_PLAYER = 530; public const IMITATE_DROWNED = 531; - + public const IMITATE_CREAKING = 532; public const BUNDLE_INSERT_FAIL = 533; + public const SPONGE_ABSORB = 534; + + public const BLOCK_CREAKING_HEART_TRAIL = 536; + public const CREAKING_HEART_SPAWN = 537; + public const ACTIVATE = 538; + public const DEACTIVATE = 539; + public const FREEZE = 540; + public const UNFREEZE = 541; + public const OPEN = 542; + public const OPEN_LONG = 543; + public const CLOSE = 544; + public const CLOSE_LONG = 545; //The following aliases are kept for backwards compatibility only public const SCULK_SENSOR_POWER_ON = self::POWER_ON_SCULK_SENSOR; diff --git a/src/types/camera/CameraAimAssistCategories.php b/src/types/camera/CameraAimAssistCategories.php new file mode 100644 index 00000000..f5a98874 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategories.php @@ -0,0 +1,58 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use function count; + +final class CameraAimAssistCategories{ + + /** + * @param CameraAimAssistCategory[] $categories + */ + public function __construct( + private string $identifier, + private array $categories + ){} + + public function getIdentifier() : string{ return $this->identifier; } + + /** + * @return CameraAimAssistCategory[] + */ + public function getCategories() : array{ return $this->categories; } + + public static function read(PacketSerializer $in) : self{ + $identifier = $in->getString(); + + $categories = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $categories[] = CameraAimAssistCategory::read($in); + } + + return new self( + $identifier, + $categories + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->identifier); + $out->putUnsignedVarInt(count($this->categories)); + foreach($this->categories as $category){ + $category->write($out); + } + } +} diff --git a/src/types/camera/CameraAimAssistCategory.php b/src/types/camera/CameraAimAssistCategory.php new file mode 100644 index 00000000..67e8af52 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategory.php @@ -0,0 +1,43 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +final class CameraAimAssistCategory{ + + public function __construct( + private string $name, + private CameraAimAssistCategoryPriorities $priorities + ){} + + public function getName() : string{ return $this->name; } + + public function getPriorities() : CameraAimAssistCategoryPriorities{ return $this->priorities; } + + public static function read(PacketSerializer $in) : self{ + $name = $in->getString(); + $priorities = CameraAimAssistCategoryPriorities::read($in); + return new self( + $name, + $priorities + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->name); + $this->priorities->write($out); + } +} diff --git a/src/types/camera/CameraAimAssistCategoryBlockPriority.php b/src/types/camera/CameraAimAssistCategoryBlockPriority.php new file mode 100644 index 00000000..9b4893e6 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategoryBlockPriority.php @@ -0,0 +1,43 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +final class CameraAimAssistCategoryBlockPriority{ + + public function __construct( + private string $identifier, + private int $priority + ){} + + public function getIdentifier() : string{ return $this->identifier; } + + public function getPriority() : int{ return $this->priority; } + + public static function read(PacketSerializer $in) : self{ + $identifier = $in->getString(); + $priority = $in->getLInt(); + return new self( + $identifier, + $priority + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->identifier); + $out->putLInt($this->priority); + } +} diff --git a/src/types/camera/CameraAimAssistCategoryEntityPriority.php b/src/types/camera/CameraAimAssistCategoryEntityPriority.php new file mode 100644 index 00000000..d6c7e1c8 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategoryEntityPriority.php @@ -0,0 +1,43 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +final class CameraAimAssistCategoryEntityPriority{ + + public function __construct( + private string $identifier, + private int $priority + ){} + + public function getIdentifier() : string{ return $this->identifier; } + + public function getPriority() : int{ return $this->priority; } + + public static function read(PacketSerializer $in) : self{ + $identifier = $in->getString(); + $priority = $in->getLInt(); + return new self( + $identifier, + $priority + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->identifier); + $out->putLInt($this->priority); + } +} diff --git a/src/types/camera/CameraAimAssistCategoryPriorities.php b/src/types/camera/CameraAimAssistCategoryPriorities.php new file mode 100644 index 00000000..838b7629 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategoryPriorities.php @@ -0,0 +1,82 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use function count; + +final class CameraAimAssistCategoryPriorities{ + + /** + * @param CameraAimAssistCategoryEntityPriority[] $entities + * @param CameraAimAssistCategoryBlockPriority[] $blocks + */ + public function __construct( + private array $entities, + private array $blocks, + private ?int $defaultEntityPriority, + private ?int $defaultBlockPriority + ){} + + /** + * @return CameraAimAssistCategoryEntityPriority[] + */ + public function getEntities() : array{ return $this->entities; } + + /** + * @return CameraAimAssistCategoryBlockPriority[] + */ + public function getBlocks() : array{ return $this->blocks; } + + public function getDefaultEntityPriority() : ?int{ return $this->defaultEntityPriority; } + + public function getDefaultBlockPriority() : ?int{ return $this->defaultBlockPriority; } + + public static function read(PacketSerializer $in) : self{ + $entities = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $entities[] = CameraAimAssistCategoryEntityPriority::read($in); + } + + $blocks = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $blocks[] = CameraAimAssistCategoryBlockPriority::read($in); + } + + $defaultEntityPriority = $in->readOptional(fn() => $in->getLInt()); + $defaultBlockPriority = $in->readOptional(fn() => $in->getLInt()); + return new self( + $entities, + $blocks, + $defaultEntityPriority, + $defaultBlockPriority + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putUnsignedVarInt(count($this->entities)); + foreach($this->entities as $entity){ + $entity->write($out); + } + + $out->putUnsignedVarInt(count($this->blocks)); + foreach($this->blocks as $block){ + $block->write($out); + } + + $out->writeOptional($this->defaultEntityPriority, fn(int $v) => $out->putLInt($v)); + $out->writeOptional($this->defaultBlockPriority, fn(int $v) => $out->putLInt($v)); + } +} diff --git a/src/types/camera/CameraAimAssistPreset.php b/src/types/camera/CameraAimAssistPreset.php new file mode 100644 index 00000000..77321537 --- /dev/null +++ b/src/types/camera/CameraAimAssistPreset.php @@ -0,0 +1,115 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use function count; + +final class CameraAimAssistPreset{ + + /** + * @param string[] $exclusionList + * @param string[] $liquidTargetingList + * @param CameraAimAssistPresetItemSettings[] $itemSettings + */ + public function __construct( + private string $identifier, + private string $categories, + private array $exclusionList, + private array $liquidTargetingList, + private array $itemSettings, + private ?string $defaultItemSettings, + private ?string $defaultHandSettings, + ){} + + public function getIdentifier() : string{ return $this->identifier; } + + public function getCategories() : string{ return $this->categories; } + + /** + * @return string[] + */ + public function getExclusionList() : array{ return $this->exclusionList; } + + /** + * @return string[] + */ + public function getLiquidTargetingList() : array{ return $this->liquidTargetingList; } + + /** + * @return CameraAimAssistPresetItemSettings[] + */ + public function getItemSettings() : array{ return $this->itemSettings; } + + public function getDefaultItemSettings() : ?string{ return $this->defaultItemSettings; } + + public function getDefaultHandSettings() : ?string{ return $this->defaultHandSettings; } + + public static function read(PacketSerializer $in) : self{ + $identifier = $in->getString(); + $categories = $in->getString(); + + $exclusionList = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $exclusionList[] = $in->getString(); + } + + $liquidTargetingList = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $liquidTargetingList[] = $in->getString(); + } + + $itemSettings = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $itemSettings[] = CameraAimAssistPresetItemSettings::read($in); + } + + $defaultItemSettings = $in->readOptional(fn() => $in->getString()); + $defaultHandSettings = $in->readOptional(fn() => $in->getString()); + + return new self( + $identifier, + $categories, + $exclusionList, + $liquidTargetingList, + $itemSettings, + $defaultItemSettings, + $defaultHandSettings + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->identifier); + $out->putString($this->categories); + + $out->putUnsignedVarInt(count($this->exclusionList)); + foreach($this->exclusionList as $exclusion){ + $out->putString($exclusion); + } + + $out->putUnsignedVarInt(count($this->liquidTargetingList)); + foreach($this->liquidTargetingList as $liquidTargeting){ + $out->putString($liquidTargeting); + } + + $out->putUnsignedVarInt(count($this->itemSettings)); + foreach($this->itemSettings as $itemSetting){ + $itemSetting->write($out); + } + + $out->writeOptional($this->defaultItemSettings, fn(string $v) => $out->putString($v)); + $out->writeOptional($this->defaultHandSettings, fn(string $v) => $out->putString($v)); + } +} diff --git a/src/types/camera/CameraAimAssistPresetItemSettings.php b/src/types/camera/CameraAimAssistPresetItemSettings.php new file mode 100644 index 00000000..61e0e639 --- /dev/null +++ b/src/types/camera/CameraAimAssistPresetItemSettings.php @@ -0,0 +1,43 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +final class CameraAimAssistPresetItemSettings{ + + public function __construct( + private string $itemIdentifier, + private string $categoryName, + ){} + + public function getItemIdentifier() : string{ return $this->itemIdentifier; } + + public function getCategoryName() : string{ return $this->categoryName; } + + public static function read(PacketSerializer $in) : self{ + $itemIdentifier = $in->getString(); + $categoryName = $in->getString(); + return new self( + $itemIdentifier, + $categoryName + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->itemIdentifier); + $out->putString($this->categoryName); + } +} diff --git a/src/types/camera/CameraPreset.php b/src/types/camera/CameraPreset.php index fe8aec91..759836a4 100644 --- a/src/types/camera/CameraPreset.php +++ b/src/types/camera/CameraPreset.php @@ -35,12 +35,14 @@ public function __construct( private ?Vector2 $horizontalRotationLimit, private ?Vector2 $verticalRotationLimit, private ?bool $continueTargeting, + private ?float $blockListeningRadius, private ?Vector2 $viewOffset, private ?Vector3 $entityOffset, private ?float $radius, private ?int $audioListenerType, private ?bool $playerEffects, - private ?bool $alignTargetAndCameraForward + private ?bool $alignTargetAndCameraForward, + private ?bool $aimAssist, ){} public function getName() : string{ return $this->name; } @@ -67,6 +69,8 @@ public function getVerticalRotationLimit() : ?Vector2{ return $this->verticalRot public function getContinueTargeting() : ?bool{ return $this->continueTargeting; } + public function getBlockListeningRadius() : ?float{ return $this->blockListeningRadius; } + public function getViewOffset() : ?Vector2{ return $this->viewOffset; } public function getEntityOffset() : ?Vector3{ return $this->entityOffset; } @@ -79,6 +83,8 @@ public function getPlayerEffects() : ?bool{ return $this->playerEffects; } public function getAlignTargetAndCameraForward() : ?bool{ return $this->alignTargetAndCameraForward; } + public function getAimAssist() : ?bool{ return $this->aimAssist; } + public static function read(PacketSerializer $in) : self{ $name = $in->getString(); $parent = $in->getString(); @@ -92,12 +98,14 @@ public static function read(PacketSerializer $in) : self{ $horizontalRotationLimit = $in->readOptional($in->getVector2(...)); $verticalRotationLimit = $in->readOptional($in->getVector2(...)); $continueTargeting = $in->readOptional($in->getBool(...)); + $blockListeningRadius = $in->readOptional($in->getLFloat(...)); $viewOffset = $in->readOptional($in->getVector2(...)); $entityOffset = $in->readOptional($in->getVector3(...)); $radius = $in->readOptional($in->getLFloat(...)); $audioListenerType = $in->readOptional($in->getByte(...)); $playerEffects = $in->readOptional($in->getBool(...)); $alignTargetAndCameraForward = $in->readOptional($in->getBool(...)); + $aimAssist = $in->readOptional($in->getBool(...)); return new self( $name, @@ -112,12 +120,14 @@ public static function read(PacketSerializer $in) : self{ $horizontalRotationLimit, $verticalRotationLimit, $continueTargeting, + $blockListeningRadius, $viewOffset, $entityOffset, $radius, $audioListenerType, $playerEffects, - $alignTargetAndCameraForward + $alignTargetAndCameraForward, + $aimAssist ); } @@ -134,11 +144,13 @@ public function write(PacketSerializer $out) : void{ $out->writeOptional($this->horizontalRotationLimit, $out->putVector2(...)); $out->writeOptional($this->verticalRotationLimit, $out->putVector2(...)); $out->writeOptional($this->continueTargeting, $out->putBool(...)); + $out->writeOptional($this->blockListeningRadius, $out->putLFloat(...)); $out->writeOptional($this->viewOffset, $out->putVector2(...)); $out->writeOptional($this->entityOffset, $out->putVector3(...)); $out->writeOptional($this->radius, $out->putLFloat(...)); $out->writeOptional($this->audioListenerType, $out->putByte(...)); $out->writeOptional($this->playerEffects, $out->putBool(...)); $out->writeOptional($this->alignTargetAndCameraForward, $out->putBool(...)); + $out->writeOptional($this->aimAssist, $out->putBool(...)); } } diff --git a/src/types/entity/EntityIds.php b/src/types/entity/EntityIds.php index fd5ea7b8..04b2a692 100644 --- a/src/types/entity/EntityIds.php +++ b/src/types/entity/EntityIds.php @@ -48,6 +48,7 @@ private function __construct(){ public const COD = "minecraft:cod"; public const COMMAND_BLOCK_MINECART = "minecraft:command_block_minecart"; public const COW = "minecraft:cow"; + public const CREAKING = "minecraft:creaking"; public const CREEPER = "minecraft:creeper"; public const DOLPHIN = "minecraft:dolphin"; public const DONKEY = "minecraft:donkey"; diff --git a/src/types/inventory/stackresponse/ItemStackResponseSlotInfo.php b/src/types/inventory/stackresponse/ItemStackResponseSlotInfo.php index 7ddc2601..fa83941d 100644 --- a/src/types/inventory/stackresponse/ItemStackResponseSlotInfo.php +++ b/src/types/inventory/stackresponse/ItemStackResponseSlotInfo.php @@ -23,6 +23,7 @@ public function __construct( private int $count, private int $itemStackId, private string $customName, + private string $filteredCustomName, private int $durabilityCorrection ){} @@ -36,6 +37,8 @@ public function getItemStackId() : int{ return $this->itemStackId; } public function getCustomName() : string{ return $this->customName; } + public function getFilteredCustomName() : string{ return $this->filteredCustomName; } + public function getDurabilityCorrection() : int{ return $this->durabilityCorrection; } public static function read(PacketSerializer $in) : self{ @@ -44,8 +47,9 @@ public static function read(PacketSerializer $in) : self{ $count = $in->getByte(); $itemStackId = $in->readServerItemStackId(); $customName = $in->getString(); + $filteredCustomName = $in->getString(); $durabilityCorrection = $in->getVarInt(); - return new self($slot, $hotbarSlot, $count, $itemStackId, $customName, $durabilityCorrection); + return new self($slot, $hotbarSlot, $count, $itemStackId, $customName, $filteredCustomName, $durabilityCorrection); } public function write(PacketSerializer $out) : void{ @@ -54,6 +58,7 @@ public function write(PacketSerializer $out) : void{ $out->putByte($this->count); $out->writeServerItemStackId($this->itemStackId); $out->putString($this->customName); + $out->putString($this->filteredCustomName); $out->putVarInt($this->durabilityCorrection); } } diff --git a/src/types/resourcepacks/ResourcePackInfoEntry.php b/src/types/resourcepacks/ResourcePackInfoEntry.php index 0bb7b4c0..5752437d 100644 --- a/src/types/resourcepacks/ResourcePackInfoEntry.php +++ b/src/types/resourcepacks/ResourcePackInfoEntry.php @@ -15,10 +15,11 @@ namespace pocketmine\network\mcpe\protocol\types\resourcepacks; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use Ramsey\Uuid\UuidInterface; class ResourcePackInfoEntry{ public function __construct( - private string $packId, + private UuidInterface $packId, private string $version, private int $sizeBytes, private string $encryptionKey = "", @@ -30,7 +31,7 @@ public function __construct( private string $cdnUrl = "" ){} - public function getPackId() : string{ + public function getPackId() : UuidInterface{ return $this->packId; } @@ -65,7 +66,7 @@ public function isRtxCapable() : bool{ return $this->isRtxCapable; } public function getCdnUrl() : string{ return $this->cdnUrl; } public function write(PacketSerializer $out) : void{ - $out->putString($this->packId); + $out->putUUID($this->packId); $out->putString($this->version); $out->putLLong($this->sizeBytes); $out->putString($this->encryptionKey); @@ -78,7 +79,7 @@ public function write(PacketSerializer $out) : void{ } public static function read(PacketSerializer $in) : self{ - $uuid = $in->getString(); + $uuid = $in->getUUID(); $version = $in->getString(); $sizeBytes = $in->getLLong(); $encryptionKey = $in->getString(); diff --git a/tests/phpunit/BitSetTest.php b/tests/phpunit/BitSetTest.php new file mode 100644 index 00000000..ca8b19ac --- /dev/null +++ b/tests/phpunit/BitSetTest.php @@ -0,0 +1,41 @@ + + * + * BedrockProtocol 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. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +use PHPUnit\Framework\TestCase; +use pocketmine\network\mcpe\protocol\serializer\BitSet; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +class BitSetTest extends TestCase{ + + public function testBitSet() : void{ + $test = new BitSet(65); + + $test->set(0, true); + $test->set(64, true); + + $packetSerializer = PacketSerializer::encoder(); + $test->write($packetSerializer); + + $packetSerializer = PacketSerializer::decoder($packetSerializer->getBuffer(), 0); + $test = BitSet::read($packetSerializer, 65); + + self::assertTrue($test->get(0)); + for($i = 1; $i < 64; ++$i){ + self::assertFalse($test->get($i)); + } + self::assertTrue($test->get(64)); + } +} From 3c5f22eb907fe1da7eeccd4ad60781008c2868c4 Mon Sep 17 00:00:00 2001 From: Dries C Date: Mon, 25 Nov 2024 22:59:12 +0100 Subject: [PATCH 07/10] Register CameraAimAssistPresetsPacket --- src/PacketPool.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PacketPool.php b/src/PacketPool.php index e9a4be85..8813e6e1 100644 --- a/src/PacketPool.php +++ b/src/PacketPool.php @@ -237,6 +237,7 @@ public function __construct(){ $this->registerPacket(new ContainerRegistryCleanupPacket()); $this->registerPacket(new MovementEffectPacket()); $this->registerPacket(new SetMovementAuthorityPacket()); + $this->registerPacket(new CameraAimAssistPresetsPacket()); } public function registerPacket(Packet $packet) : void{ From 6def86c96f6d99db599413747a76aa35cc135c9e Mon Sep 17 00:00:00 2001 From: Dries C Date: Tue, 3 Dec 2024 22:10:00 +0100 Subject: [PATCH 08/10] Resolve last byte flags requiring an extra part --- src/serializer/BitSet.php | 31 +++++++++------- tests/phpunit/BitSetTest.php | 72 ++++++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 21 deletions(-) diff --git a/src/serializer/BitSet.php b/src/serializer/BitSet.php index 71e708bc..1e4a3807 100644 --- a/src/serializer/BitSet.php +++ b/src/serializer/BitSet.php @@ -14,13 +14,11 @@ namespace pocketmine\network\mcpe\protocol\serializer; -use pocketmine\utils\BinaryDataException; use function array_pad; +use function array_slice; use function array_values; -use function chr; use function count; use function intdiv; -use function ord; class BitSet{ private const INT_BITS = PHP_INT_SIZE * 8; @@ -33,7 +31,7 @@ public function __construct( private readonly int $length, private array $parts = [] ){ - $expectedPartsCount = intdiv($length + self::INT_BITS - 1, self::INT_BITS); + $expectedPartsCount = self::getExpectedPartsCount($length); $partsCount = count($parts); if($partsCount > $expectedPartsCount){ @@ -77,6 +75,17 @@ private function getPartIndex(int $index) : array{ ]; } + /** + * @internal + */ + public function getPartsCount() : int{ + return count($this->parts); + } + + private static function getExpectedPartsCount(int $length) : int{ + return intdiv($length + self::INT_BITS - 1, self::INT_BITS); + } + public static function read(PacketSerializer $in, int $length) : self{ $result = [0]; @@ -84,7 +93,7 @@ public static function read(PacketSerializer $in, int $length) : self{ $currentShift = 0; for($i = 0; $i < $length; $i += self::SHIFT){ - $b = ord($in->get(1)); + $b = $in->getByte(); $bits = $b & 0x7f; $result[$currentIndex] |= $bits << $currentShift; //extra bits will be discarded @@ -97,16 +106,14 @@ public static function read(PacketSerializer $in, int $length) : self{ $currentShift = $nextShift; if(($b & 0x80) === 0){ - return new self($length, $result); + return new self($length, array_slice($result, 0, self::getExpectedPartsCount($length))); } } - throw new BinaryDataException("Didn't terminate after reading $length bits"); + return new self($length, array_slice($result, 0, self::getExpectedPartsCount($length))); } public function write(PacketSerializer $out) : void{ - $buf = ""; - $parts = $this->parts; $length = $this->length; @@ -118,20 +125,18 @@ public function write(PacketSerializer $out) : void{ $nextShift = $currentShift + self::SHIFT; if($nextShift >= self::INT_BITS){ $nextShift -= self::INT_BITS; - $bits |= $parts[++$currentIndex] << (self::SHIFT - $nextShift); + $bits |= ($parts[++$currentIndex] ?? 0) << (self::SHIFT - $nextShift); } $currentShift = $nextShift; $last = $i + self::SHIFT >= $length; $bits |= $last ? 0 : 0x80; - $buf .= chr($bits); + $out->putByte($bits); if($last){ break; } } - - $out->put($buf); } public function getLength() : int{ diff --git a/tests/phpunit/BitSetTest.php b/tests/phpunit/BitSetTest.php index ca8b19ac..f419a26f 100644 --- a/tests/phpunit/BitSetTest.php +++ b/tests/phpunit/BitSetTest.php @@ -17,25 +17,81 @@ use PHPUnit\Framework\TestCase; use pocketmine\network\mcpe\protocol\serializer\BitSet; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use function PHPUnit\Framework\assertTrue; +use function var_dump; class BitSetTest extends TestCase{ public function testBitSet() : void{ - $test = new BitSet(65); + $writeTest = new BitSet(65); - $test->set(0, true); - $test->set(64, true); + $writeTest->set(0, true); + $writeTest->set(64, true); + + $packetSerializer = PacketSerializer::encoder(); + $writeTest->write($packetSerializer); + + $packetSerializer = PacketSerializer::decoder($packetSerializer->getBuffer(), 0); + $readTest = BitSet::read($packetSerializer, 65); + + assertTrue($this->setsEqual($writeTest, $readTest)); + } + + public function testBitSetConstructor() : void{ + $test = new BitSet(65, [-9223372036854775808, 1]); + $test2 = new BitSet(65, [-9223372036854775808]); + + $test2->set(64, true); $packetSerializer = PacketSerializer::encoder(); $test->write($packetSerializer); + $packetSerializer2 = PacketSerializer::encoder(); + $test2->write($packetSerializer2); + + self::assertEquals($packetSerializer->getBuffer(), $packetSerializer2->getBuffer()); + } + + public function testBitSetParts() : void{ + $writeTest = new BitSet(128); + $writeTest->set(127, true); + + $packetSerializer = PacketSerializer::encoder(); + $writeTest->write($packetSerializer); + + $packetSerializer = PacketSerializer::decoder($packetSerializer->getBuffer(), 0); + $readTest = BitSet::read($packetSerializer, 128); + + assertTrue($this->setsEqual($writeTest, $readTest)); + } + + public function testVarUnsignedLongCompatibility() : void{ + $packetSerializer = PacketSerializer::encoder(); + $packetSerializer->putUnsignedVarLong(0 | 1 << 63); + $packetSerializer = PacketSerializer::decoder($packetSerializer->getBuffer(), 0); - $test = BitSet::read($packetSerializer, 65); + $readTest = BitSet::read($packetSerializer, 64); - self::assertTrue($test->get(0)); - for($i = 1; $i < 64; ++$i){ - self::assertFalse($test->get($i)); + $expectedResult = new BitSet(64); + $expectedResult->set(63, true); + + assertTrue($this->setsEqual($expectedResult, $readTest)); + } + + private function setsEqual(BitSet $a, BitSet $b) : bool{ + $length = $a->getLength(); + if($length !== $b->getLength()){ + var_dump($length, $b->getLength()); + return false; } - self::assertTrue($test->get(64)); + + for($i = 0; $i < $length; ++$i){ + if($a->get($i) !== $b->get($i)){ + var_dump($i, $a->get($i), $b->get($i)); + return false; + } + } + + return $a->getPartsCount() === $b->getPartsCount(); } } From 0c3ce03816b8b64f2fdf32b7dde4002782136252 Mon Sep 17 00:00:00 2001 From: Dries C Date: Tue, 3 Dec 2024 22:10:08 +0100 Subject: [PATCH 09/10] Add missing input flags --- src/types/PlayerAuthInputFlags.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/types/PlayerAuthInputFlags.php b/src/types/PlayerAuthInputFlags.php index 60809451..b77e80d2 100644 --- a/src/types/PlayerAuthInputFlags.php +++ b/src/types/PlayerAuthInputFlags.php @@ -111,5 +111,11 @@ final class PlayerAuthInputFlags{ public const IS_ROT_CONTROLLED_BY_MOVE_DIRECTION = 55; public const START_SPIN_ATTACK = 56; public const STOP_SPIN_ATTACK = 57; + public const JUMP_RELEASED_RAW = 58; + public const JUMP_PRESSED_RAW = 59; + public const JUMP_CURRENT_RAW = 60; + public const SNEAK_RELEASED_RAW = 61; + public const SNEAK_PRESSED_RAW = 62; + public const SNEAK_CURRENT_RAW = 63; } From d55b7fcb0860c940fb62191c69d525b86059878f Mon Sep 17 00:00:00 2001 From: Dries C Date: Tue, 3 Dec 2024 22:16:41 +0100 Subject: [PATCH 10/10] Those are definitely ints --- tests/phpstan/configs/phpstan-bugs.neon | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index 969e9629..5d525eb1 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -9,3 +9,12 @@ parameters: message: "#^Parameter \\#3 \\$packets of static method pocketmine\\\\network\\\\mcpe\\\\protocol\\\\serializer\\\\PacketBatch\\:\\:encodePackets\\(\\) expects array\\, array\\ given\\.$#" count: 1 path: ../../../src/serializer/PacketBatch.php + - + message: "#^Parameter \\#2 \\$parts of class pocketmine\\\\network\\\\mcpe\\\\protocol\\\\serializer\\\\BitSet constructor expects array\\, array\\ given\\.$#" + count: 1 + path: tests/phpunit/BitSetTest.php + + - + message: "#^Parameter \\#2 \\$parts of class pocketmine\\\\network\\\\mcpe\\\\protocol\\\\serializer\\\\BitSet constructor expects array\\, array\\ given\\.$#" + count: 1 + path: tests/phpunit/BitSetTest.php