diff --git a/.github/workflows/team-pr-auto-approve.yml b/.github/workflows/team-pr-auto-approve.yml new file mode 100644 index 00000000..8f40b16e --- /dev/null +++ b/.github/workflows/team-pr-auto-approve.yml @@ -0,0 +1,37 @@ +#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 + +jobs: + dispatch: + name: Request approval + runs-on: ubuntu-latest + + steps: + - name: Generate access token + id: generate-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }} + private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }} + owner: ${{ github.repository_owner }} + repositories: RestrictedActions + + - name: Dispatch restricted action + uses: peter-evans/repository-dispatch@v3 + with: + 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 }}" }' 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/PacketHandlerInterface.php b/src/PacketHandlerInterface.php index 9d01c022..45b314c4 100644 --- a/src/PacketHandlerInterface.php +++ b/src/PacketHandlerInterface.php @@ -429,4 +429,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/PacketPool.php b/src/PacketPool.php index fb4633d5..d0874ec2 100644 --- a/src/PacketPool.php +++ b/src/PacketPool.php @@ -239,6 +239,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{ diff --git a/src/PlayerAuthInputPacket.php b/src/PlayerAuthInputPacket.php index 4ccd1cb2..2e736187 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; @@ -39,7 +40,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; @@ -55,6 +56,7 @@ class PlayerAuthInputPacket extends DataPacket implements ServerboundPacket{ private float $analogMoveVecX; private float $analogMoveVecZ; private Vector3 $cameraOrientation; + private Vector2 $rawMove; /** * @generate-create-func @@ -67,7 +69,7 @@ private static function internalCreate( float $headYaw, float $moveVecX, float $moveVecZ, - int $inputFlags, + BitSet $inputFlags, int $inputMode, int $playMode, int $interactionMode, @@ -82,6 +84,7 @@ private static function internalCreate( float $analogMoveVecX, float $analogMoveVecZ, Vector3 $cameraOrientation, + Vector2 $rawMove, ) : self{ $result = new self; $result->position = $position; @@ -105,15 +108,15 @@ 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 - * @param Vector3|null $vrGazeDirection only used when PlayMode::VR * @param PlayerBlockAction[]|null $blockActions Blocks that the client has interacted with */ public static function create( @@ -123,7 +126,7 @@ public static function create( float $headYaw, float $moveVecX, float $moveVecZ, - int $inputFlags, + BitSet $inputFlags, int $inputMode, int $playMode, int $interactionMode, @@ -137,23 +140,18 @@ public static function create( ?PlayerAuthInputVehicleInfo $vehicleInfo, float $analogMoveVecX, float $analogMoveVecZ, - Vector3 $cameraOrientation + Vector3 $cameraOrientation, + Vector2 $rawMove ) : 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; - } - if($itemInteractionData !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION; - } - if($blockActions !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS; - } + $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, @@ -162,7 +160,7 @@ public static function create( $headYaw, $moveVecX, $moveVecZ, - $realInputFlags, + $inputFlags, $inputMode, $playMode, $interactionMode, @@ -176,7 +174,8 @@ public static function create( $vehicleInfo, $analogMoveVecX, $analogMoveVecZ, - $cameraOrientation + $cameraOrientation, + $rawMove ); } @@ -207,7 +206,7 @@ public function getMoveVecZ() : float{ /** * @see PlayerAuthInputFlags */ - public function getInputFlags() : int{ + public function getInputFlags() : BitSet{ return $this->inputFlags; } @@ -232,10 +231,6 @@ 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{ @@ -269,9 +264,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(); @@ -280,7 +273,7 @@ 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, $in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50 ? 65 : 64); $this->inputMode = $in->getUnsignedVarInt(); $this->playMode = $in->getUnsignedVarInt(); $this->interactionMode = $in->getUnsignedVarInt(); @@ -291,13 +284,13 @@ protected function decodePayload(PacketSerializer $in) : void{ } $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){ @@ -309,13 +302,16 @@ protected function decodePayload(PacketSerializer $in) : void{ }; } } - if($this->hasFlag(PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE) && $in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_20_60){ + if($this->inputFlags->get(PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE) && $in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_20_60){ $this->vehicleInfo = PlayerAuthInputVehicleInfo::read($in); } $this->analogMoveVecX = $in->getLFloat(); $this->analogMoveVecZ = $in->getLFloat(); if($in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_40){ $this->cameraOrientation = $in->getVector3(); + if($in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $this->rawMove = $in->getVector2(); + } } } @@ -323,7 +319,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $inputFlags = $this->inputFlags; if($this->vehicleInfo !== null && $out->getProtocolId() >= ProtocolInfo::PROTOCOL_1_20_60){ - $inputFlags |= 1 << PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE; + $inputFlags->set(PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE, true); } $out->putLFloat($this->pitch); @@ -332,7 +328,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putLFloat($this->moveVecX); $out->putLFloat($this->moveVecZ); $out->putLFloat($this->headYaw); - $out->putUnsignedVarLong($inputFlags); + $this->inputFlags->write($out, $out->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50 ? 65 : 64); $out->putUnsignedVarInt($this->inputMode); $out->putUnsignedVarInt($this->playMode); $out->putUnsignedVarInt($this->interactionMode); @@ -364,6 +360,9 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putLFloat($this->analogMoveVecZ); if($out->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_40){ $out->putVector3($this->cameraOrientation); + if($out->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $out->putVector2($this->rawMove); + } } } diff --git a/src/ProtocolInfo.php b/src/ProtocolInfo.php index 64ddf03c..f3dbcd2d 100644 --- a/src/ProtocolInfo.php +++ b/src/ProtocolInfo.php @@ -32,7 +32,7 @@ private function __construct(){ */ /** Actual Minecraft: PE protocol version */ - public const CURRENT_PROTOCOL = self::PROTOCOL_1_21_40; + public const CURRENT_PROTOCOL = self::PROTOCOL_1_21_50; public const ACCEPTED_PROTOCOL = [ self::PROTOCOL_1_20_0, self::PROTOCOL_1_20_10, @@ -46,14 +46,16 @@ private function __construct(){ self::PROTOCOL_1_21_2, self::PROTOCOL_1_21_20, self::PROTOCOL_1_21_30, + self::PROTOCOL_1_21_40, self::CURRENT_PROTOCOL, ]; /** 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 PROTOCOL_1_21_50 = 766; public const PROTOCOL_1_21_40 = 748; public const PROTOCOL_1_21_30 = 729; public const PROTOCOL_1_21_20 = 712; @@ -285,4 +287,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 1f18e028..74bfa71d 100644 --- a/src/ResourcePacksInfoPacket.php +++ b/src/ResourcePacksInfoPacket.php @@ -17,6 +17,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\resourcepacks\BehaviorPackInfoEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry; +use Ramsey\Uuid\UuidInterface; use function count; class ResourcePacksInfoPacket extends DataPacket implements ClientboundPacket{ @@ -35,6 +36,8 @@ class ResourcePacksInfoPacket extends DataPacket implements ClientboundPacket{ * @phpstan-var array */ public array $cdnUrls = []; + private UuidInterface $worldTemplateId; + private string $worldTemplateVersion; /** * @generate-create-func @@ -51,6 +54,8 @@ public static function create( bool $hasScripts, bool $forceServerPacks, array $cdnUrls, + UuidInterface $worldTemplateId, + string $worldTemplateVersion, ) : self{ $result = new self; $result->resourcePackEntries = $resourcePackEntries; @@ -60,6 +65,8 @@ public static function create( $result->hasScripts = $hasScripts; $result->forceServerPacks = $forceServerPacks; $result->cdnUrls = $cdnUrls; + $result->worldTemplateId = $worldTemplateId; + $result->worldTemplateVersion = $worldTemplateVersion; return $result; } @@ -76,6 +83,10 @@ protected function decodePayload(PacketSerializer $in) : void{ $this->behaviorPackEntries[] = BehaviorPackInfoEntry::read($in); } } + if($in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $this->worldTemplateId = $in->getUUID(); + $this->worldTemplateVersion = $in->getString(); + } $resourcePackCount = $in->getLShort(); while($resourcePackCount-- > 0){ @@ -105,6 +116,10 @@ protected function encodePayload(PacketSerializer $out) : void{ $entry->write($out); } } + if($out->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $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..1e4a3807 --- /dev/null +++ b/src/serializer/BitSet.php @@ -0,0 +1,145 @@ + + * + * 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 function array_pad; +use function array_slice; +use function array_values; +use function count; +use function intdiv; + +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 = self::getExpectedPartsCount($length); + $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 + ]; + } + + /** + * @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]; + + $currentIndex = 0; + $currentShift = 0; + + for($i = 0; $i < $length; $i += self::SHIFT){ + $b = $in->getByte(); + $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, array_slice($result, 0, self::getExpectedPartsCount($length))); + } + } + + return new self($length, array_slice($result, 0, self::getExpectedPartsCount($length))); + } + + public function write(PacketSerializer $out) : void{ + $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] ?? 0) << (self::SHIFT - $nextShift); + } + $currentShift = $nextShift; + + $last = $i + self::SHIFT >= $length; + $bits |= $last ? 0 : 0x80; + + $out->putByte($bits); + if($last){ + break; + } + } + } + + 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/PlayerAction.php b/src/types/PlayerAction.php index 5b255328..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; 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; } 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 011fd29a..7b61a36b 100644 --- a/src/types/camera/CameraPreset.php +++ b/src/types/camera/CameraPreset.php @@ -37,12 +37,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; } @@ -69,6 +71,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; } @@ -81,6 +85,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(); @@ -97,6 +103,9 @@ public static function read(PacketSerializer $in) : self{ $horizontalRotationLimit = $in->readOptional($in->getVector2(...)); $verticalRotationLimit = $in->readOptional($in->getVector2(...)); $continueTargeting = $in->readOptional($in->getBool(...)); + if($in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $blockListeningRadius = $in->readOptional($in->getLFloat(...)); + } } } $viewOffset = $in->readOptional($in->getVector2(...)); @@ -109,6 +118,9 @@ public static function read(PacketSerializer $in) : self{ $playerEffects = $in->readOptional($in->getBool(...)); if($in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_40){ $alignTargetAndCameraForward = $in->readOptional($in->getBool(...)); + if($in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $aimAssist = $in->readOptional($in->getBool(...)); + } } return new self( @@ -124,12 +136,14 @@ public static function read(PacketSerializer $in) : self{ $horizontalRotationLimit ?? null, $verticalRotationLimit ?? null, $continueTargeting ?? null, + $blockListeningRadius ?? null, $viewOffset ?? null, $entityOffset ?? null, $radius ?? null, $audioListenerType, $playerEffects, - $alignTargetAndCameraForward ?? null + $alignTargetAndCameraForward ?? null, + $aimAssist ?? null ); } @@ -150,13 +164,15 @@ public static function fromNBT(CompoundTag $nbt) : self{ null, null, null, + null, $nbt->getTag("audio_listener_type") === null ? null : match($nbt->getString("audio_listener_type")){ "camera" => self::AUDIO_LISTENER_TYPE_CAMERA, "player" => self::AUDIO_LISTENER_TYPE_PLAYER, default => throw new \InvalidArgumentException("Invalid audio listener type: " . $nbt->getString("audio_listener_type")), }, $nbt->getTag("player_effects") === null ? null : $nbt->getByte("player_effects") !== 0, - null + null, + null, ); } @@ -176,6 +192,9 @@ public function write(PacketSerializer $out) : void{ $out->writeOptional($this->horizontalRotationLimit, $out->putVector2(...)); $out->writeOptional($this->verticalRotationLimit, $out->putVector2(...)); $out->writeOptional($this->continueTargeting, $out->putBool(...)); + if($out->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $out->writeOptional($this->blockListeningRadius, $out->putLFloat(...)); + } } } $out->writeOptional($this->viewOffset, $out->putVector2(...)); @@ -188,6 +207,9 @@ public function write(PacketSerializer $out) : void{ $out->writeOptional($this->playerEffects, $out->putBool(...)); if($out->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_40){ $out->writeOptional($this->alignTargetAndCameraForward, $out->putBool(...)); + if($out->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $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/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/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 9ee7e65e..7fcc6fbb 100644 --- a/src/types/resourcepacks/ResourcePackInfoEntry.php +++ b/src/types/resourcepacks/ResourcePackInfoEntry.php @@ -16,10 +16,12 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; class ResourcePackInfoEntry{ public function __construct( - private string $packId, + private UuidInterface $packId, private string $version, private int $sizeBytes, private string $encryptionKey = "", @@ -31,7 +33,7 @@ public function __construct( private string $cdnUrl = "" ){} - public function getPackId() : string{ + public function getPackId() : UuidInterface{ return $this->packId; } @@ -66,7 +68,11 @@ public function isRtxCapable() : bool{ return $this->isRtxCapable; } public function getCdnUrl() : string{ return $this->cdnUrl; } public function write(PacketSerializer $out) : void{ - $out->putString($this->packId); + if($out->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $out->putUUID($this->packId); + }else{ + $out->putString($this->packId->toString()); + } $out->putString($this->version); $out->putLLong($this->sizeBytes); $out->putString($this->encryptionKey); @@ -83,7 +89,11 @@ public function write(PacketSerializer $out) : void{ } public static function read(PacketSerializer $in) : self{ - $uuid = $in->getString(); + if($in->getProtocolId() >= ProtocolInfo::PROTOCOL_1_21_50){ + $uuid = $in->getUUID(); + }else{ + $uuid = Uuid::fromString($in->getString()); + } $version = $in->getString(); $sizeBytes = $in->getLLong(); $encryptionKey = $in->getString(); 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 diff --git a/tests/phpunit/BitSetTest.php b/tests/phpunit/BitSetTest.php new file mode 100644 index 00000000..f419a26f --- /dev/null +++ b/tests/phpunit/BitSetTest.php @@ -0,0 +1,97 @@ + + * + * 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; +use function PHPUnit\Framework\assertTrue; +use function var_dump; + +class BitSetTest extends TestCase{ + + public function testBitSet() : void{ + $writeTest = new BitSet(65); + + $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); + $readTest = BitSet::read($packetSerializer, 64); + + $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; + } + + 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(); + } +} 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; 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'));