PocketMine-MP 5.19.1 git-5cc1068cd43264d3363295eb8d6901e02f467897
bedrock-protocol/src/serializer/PacketSerializer.php
1<?php
2
3/*
4 * This file is part of BedrockProtocol.
5 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
6 *
7 * BedrockProtocol is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 */
12
13declare(strict_types=1);
14
15namespace pocketmine\network\mcpe\protocol\serializer;
16
59use Ramsey\Uuid\Uuid;
60use Ramsey\Uuid\UuidInterface;
61use function count;
62use function strlen;
63use function strrev;
64use function substr;
65
67 protected function __construct(string $buffer = "", int $offset = 0){
68 //overridden to change visibility
69 parent::__construct($buffer, $offset);
70 }
71
72 public static function encoder() : self{
73 return new self();
74 }
75
76 public static function decoder(string $buffer, int $offset) : self{
77 return new self($buffer, $offset);
78 }
79
83 public function getString() : string{
84 return $this->get($this->getUnsignedVarInt());
85 }
86
87 public function putString(string $v) : void{
88 $this->putUnsignedVarInt(strlen($v));
89 $this->put($v);
90 }
91
95 public function getUUID() : UuidInterface{
96 //This is two little-endian longs: bytes 7-0 followed by bytes 15-8
97 $p1 = strrev($this->get(8));
98 $p2 = strrev($this->get(8));
99 return Uuid::fromBytes($p1 . $p2);
100 }
101
102 public function putUUID(UuidInterface $uuid) : void{
103 $bytes = $uuid->getBytes();
104 $this->put(strrev(substr($bytes, 0, 8)));
105 $this->put(strrev(substr($bytes, 8, 8)));
106 }
107
108 public function getSkin() : SkinData{
109 $skinId = $this->getString();
110 $skinPlayFabId = $this->getString();
111 $skinResourcePatch = $this->getString();
112 $skinData = $this->getSkinImage();
113 $animationCount = $this->getLInt();
114 $animations = [];
115 for($i = 0; $i < $animationCount; ++$i){
116 $skinImage = $this->getSkinImage();
117 $animationType = $this->getLInt();
118 $animationFrames = $this->getLFloat();
119 $expressionType = $this->getLInt();
120 $animations[] = new SkinAnimation($skinImage, $animationType, $animationFrames, $expressionType);
121 }
122 $capeData = $this->getSkinImage();
123 $geometryData = $this->getString();
124 $geometryDataVersion = $this->getString();
125 $animationData = $this->getString();
126 $capeId = $this->getString();
127 $fullSkinId = $this->getString();
128 $armSize = $this->getString();
129 $skinColor = $this->getString();
130 $personaPieceCount = $this->getLInt();
131 $personaPieces = [];
132 for($i = 0; $i < $personaPieceCount; ++$i){
133 $pieceId = $this->getString();
134 $pieceType = $this->getString();
135 $packId = $this->getString();
136 $isDefaultPiece = $this->getBool();
137 $productId = $this->getString();
138 $personaPieces[] = new PersonaSkinPiece($pieceId, $pieceType, $packId, $isDefaultPiece, $productId);
139 }
140 $pieceTintColorCount = $this->getLInt();
141 $pieceTintColors = [];
142 for($i = 0; $i < $pieceTintColorCount; ++$i){
143 $pieceType = $this->getString();
144 $colorCount = $this->getLInt();
145 $colors = [];
146 for($j = 0; $j < $colorCount; ++$j){
147 $colors[] = $this->getString();
148 }
149 $pieceTintColors[] = new PersonaPieceTintColor(
150 $pieceType,
151 $colors
152 );
153 }
154
155 $premium = $this->getBool();
156 $persona = $this->getBool();
157 $capeOnClassic = $this->getBool();
158 $isPrimaryUser = $this->getBool();
159 $override = $this->getBool();
160
161 return new SkinData(
162 $skinId,
163 $skinPlayFabId,
164 $skinResourcePatch,
165 $skinData,
166 $animations,
167 $capeData,
168 $geometryData,
169 $geometryDataVersion,
170 $animationData,
171 $capeId,
172 $fullSkinId,
173 $armSize,
174 $skinColor,
175 $personaPieces,
176 $pieceTintColors,
177 true,
178 $premium,
179 $persona,
180 $capeOnClassic,
181 $isPrimaryUser,
182 $override,
183 );
184 }
185
186 public function putSkin(SkinData $skin) : void{
187 $this->putString($skin->getSkinId());
188 $this->putString($skin->getPlayFabId());
189 $this->putString($skin->getResourcePatch());
190 $this->putSkinImage($skin->getSkinImage());
191 $this->putLInt(count($skin->getAnimations()));
192 foreach($skin->getAnimations() as $animation){
193 $this->putSkinImage($animation->getImage());
194 $this->putLInt($animation->getType());
195 $this->putLFloat($animation->getFrames());
196 $this->putLInt($animation->getExpressionType());
197 }
198 $this->putSkinImage($skin->getCapeImage());
199 $this->putString($skin->getGeometryData());
200 $this->putString($skin->getGeometryDataEngineVersion());
201 $this->putString($skin->getAnimationData());
202 $this->putString($skin->getCapeId());
203 $this->putString($skin->getFullSkinId());
204 $this->putString($skin->getArmSize());
205 $this->putString($skin->getSkinColor());
206 $this->putLInt(count($skin->getPersonaPieces()));
207 foreach($skin->getPersonaPieces() as $piece){
208 $this->putString($piece->getPieceId());
209 $this->putString($piece->getPieceType());
210 $this->putString($piece->getPackId());
211 $this->putBool($piece->isDefaultPiece());
212 $this->putString($piece->getProductId());
213 }
214 $this->putLInt(count($skin->getPieceTintColors()));
215 foreach($skin->getPieceTintColors() as $tint){
216 $this->putString($tint->getPieceType());
217 $this->putLInt(count($tint->getColors()));
218 foreach($tint->getColors() as $color){
219 $this->putString($color);
220 }
221 }
222 $this->putBool($skin->isPremium());
223 $this->putBool($skin->isPersona());
224 $this->putBool($skin->isPersonaCapeOnClassic());
225 $this->putBool($skin->isPrimaryUser());
226 $this->putBool($skin->isOverride());
227 }
228
229 private function getSkinImage() : SkinImage{
230 $width = $this->getLInt();
231 $height = $this->getLInt();
232 $data = $this->getString();
233 try{
234 return new SkinImage($height, $width, $data);
235 }catch(\InvalidArgumentException $e){
236 throw new PacketDecodeException($e->getMessage(), 0, $e);
237 }
238 }
239
240 private function putSkinImage(SkinImage $image) : void{
241 $this->putLInt($image->getWidth());
242 $this->putLInt($image->getHeight());
243 $this->putString($image->getData());
244 }
245
251 private function getItemStackHeader() : array{
252 $id = $this->getVarInt();
253 if($id === 0){
254 return [0, 0, 0];
255 }
256
257 $count = $this->getLShort();
258 $meta = $this->getUnsignedVarInt();
259
260 return [$id, $count, $meta];
261 }
262
263 private function putItemStackHeader(ItemStack $itemStack) : bool{
264 if($itemStack->getId() === 0){
265 $this->putVarInt(0);
266 return false;
267 }
268
269 $this->putVarInt($itemStack->getId());
270 $this->putLShort($itemStack->getCount());
271 $this->putUnsignedVarInt($itemStack->getMeta());
272
273 return true;
274 }
275
276 private function getItemStackFooter(int $id, int $meta, int $count) : ItemStack{
277 $blockRuntimeId = $this->getVarInt();
278 $rawExtraData = $this->getString();
279
280 return new ItemStack($id, $meta, $count, $blockRuntimeId, $rawExtraData);
281 }
282
283 private function putItemStackFooter(ItemStack $itemStack) : void{
284 $this->putVarInt($itemStack->getBlockRuntimeId());
285 $this->putString($itemStack->getRawExtraData());
286 }
287
293 [$id, $count, $meta] = $this->getItemStackHeader();
294
295 return $id !== 0 ? $this->getItemStackFooter($id, $meta, $count) : ItemStack::null();
296
297 }
298
299 public function putItemStackWithoutStackId(ItemStack $itemStack) : void{
300 if($this->putItemStackHeader($itemStack)){
301 $this->putItemStackFooter($itemStack);
302 }
303 }
304
305 public function getItemStackWrapper() : ItemStackWrapper{
306 [$id, $count, $meta] = $this->getItemStackHeader();
307 if($id === 0){
308 return new ItemStackWrapper(0, ItemStack::null());
309 }
310
311 $hasNetId = $this->getBool();
312 $stackId = $hasNetId ? $this->readServerItemStackId() : 0;
313
314 $itemStack = $this->getItemStackFooter($id, $meta, $count);
315
316 return new ItemStackWrapper($stackId, $itemStack);
317 }
318
319 public function putItemStackWrapper(ItemStackWrapper $itemStackWrapper) : void{
320 $itemStack = $itemStackWrapper->getItemStack();
321 if($this->putItemStackHeader($itemStack)){
322 $hasNetId = $itemStackWrapper->getStackId() !== 0;
323 $this->putBool($hasNetId);
324 if($hasNetId){
325 $this->writeServerItemStackId($itemStackWrapper->getStackId());
326 }
327
328 $this->putItemStackFooter($itemStack);
329 }
330 }
331
332 public function getRecipeIngredient() : RecipeIngredient{
333 $descriptorType = $this->getByte();
334 $descriptor = match($descriptorType){
335 ItemDescriptorType::INT_ID_META => IntIdMetaItemDescriptor::read($this),
336 ItemDescriptorType::STRING_ID_META => StringIdMetaItemDescriptor::read($this),
337 ItemDescriptorType::TAG => TagItemDescriptor::read($this),
338 ItemDescriptorType::MOLANG => MolangItemDescriptor::read($this),
339 ItemDescriptorType::COMPLEX_ALIAS => ComplexAliasItemDescriptor::read($this),
340 default => null
341 };
342 $count = $this->getVarInt();
343
344 return new RecipeIngredient($descriptor, $count);
345 }
346
347 public function putRecipeIngredient(RecipeIngredient $ingredient) : void{
348 $type = $ingredient->getDescriptor();
349
350 $this->putByte($type?->getTypeId() ?? 0);
351 $type?->write($this);
352
353 $this->putVarInt($ingredient->getCount());
354 }
355
365 public function getEntityMetadata() : array{
366 $count = $this->getUnsignedVarInt();
367 $data = [];
368 for($i = 0; $i < $count; ++$i){
369 $key = $this->getUnsignedVarInt();
370 $type = $this->getUnsignedVarInt();
371
372 $data[$key] = $this->readMetadataProperty($type);
373 }
374
375 return $data;
376 }
377
378 private function readMetadataProperty(int $type) : MetadataProperty{
379 return match($type){
380 ByteMetadataProperty::ID => ByteMetadataProperty::read($this),
381 ShortMetadataProperty::ID => ShortMetadataProperty::read($this),
382 IntMetadataProperty::ID => IntMetadataProperty::read($this),
383 FloatMetadataProperty::ID => FloatMetadataProperty::read($this),
384 StringMetadataProperty::ID => StringMetadataProperty::read($this),
385 CompoundTagMetadataProperty::ID => CompoundTagMetadataProperty::read($this),
386 BlockPosMetadataProperty::ID => BlockPosMetadataProperty::read($this),
387 LongMetadataProperty::ID => LongMetadataProperty::read($this),
388 Vec3MetadataProperty::ID => Vec3MetadataProperty::read($this),
389 default => throw new PacketDecodeException("Unknown entity metadata type " . $type),
390 };
391 }
392
400 public function putEntityMetadata(array $metadata) : void{
401 $this->putUnsignedVarInt(count($metadata));
402 foreach($metadata as $key => $d){
403 $this->putUnsignedVarInt($key);
404 $this->putUnsignedVarInt($d->getTypeId());
405 $d->write($this);
406 }
407 }
408
412 final public function getActorUniqueId() : int{
413 return $this->getVarLong();
414 }
415
416 public function putActorUniqueId(int $eid) : void{
417 $this->putVarLong($eid);
418 }
419
423 final public function getActorRuntimeId() : int{
424 return $this->getUnsignedVarLong();
425 }
426
427 public function putActorRuntimeId(int $eid) : void{
428 $this->putUnsignedVarLong($eid);
429 }
430
436 public function getBlockPosition() : BlockPosition{
437 $x = $this->getVarInt();
438 $y = Binary::signInt($this->getUnsignedVarInt()); //Y coordinate may be signed, but it's written unsigned :<
439 $z = $this->getVarInt();
440 return new BlockPosition($x, $y, $z);
441 }
442
446 public function putBlockPosition(BlockPosition $blockPosition) : void{
447 $this->putVarInt($blockPosition->getX());
448 $this->putUnsignedVarInt(Binary::unsignInt($blockPosition->getY())); //Y coordinate may be signed, but it's written unsigned :<
449 $this->putVarInt($blockPosition->getZ());
450 }
451
458 $x = $this->getVarInt();
459 $y = $this->getVarInt();
460 $z = $this->getVarInt();
461 return new BlockPosition($x, $y, $z);
462 }
463
467 public function putSignedBlockPosition(BlockPosition $blockPosition) : void{
468 $this->putVarInt($blockPosition->getX());
469 $this->putVarInt($blockPosition->getY());
470 $this->putVarInt($blockPosition->getZ());
471 }
472
478 public function getVector3() : Vector3{
479 $x = $this->getLFloat();
480 $y = $this->getLFloat();
481 $z = $this->getLFloat();
482 return new Vector3($x, $y, $z);
483 }
484
490 public function getVector2() : Vector2{
491 $x = $this->getLFloat();
492 $y = $this->getLFloat();
493 return new Vector2($x, $y);
494 }
495
504 public function putVector3Nullable(?Vector3 $vector) : void{
505 if($vector !== null){
506 $this->putVector3($vector);
507 }else{
508 $this->putLFloat(0.0);
509 $this->putLFloat(0.0);
510 $this->putLFloat(0.0);
511 }
512 }
513
517 public function putVector3(Vector3 $vector) : void{
518 $this->putLFloat($vector->x);
519 $this->putLFloat($vector->y);
520 $this->putLFloat($vector->z);
521 }
522
526 public function putVector2(Vector2 $vector2) : void{
527 $this->putLFloat($vector2->x);
528 $this->putLFloat($vector2->y);
529 }
530
534 public function getRotationByte() : float{
535 return ($this->getByte() * (360 / 256));
536 }
537
538 public function putRotationByte(float $rotation) : void{
539 $this->putByte((int) ($rotation / (360 / 256)));
540 }
541
542 private function readGameRule(int $type, bool $isPlayerModifiable) : GameRule{
543 return match($type){
544 BoolGameRule::ID => BoolGameRule::decode($this, $isPlayerModifiable),
545 IntGameRule::ID => IntGameRule::decode($this, $isPlayerModifiable),
546 FloatGameRule::ID => FloatGameRule::decode($this, $isPlayerModifiable),
547 default => throw new PacketDecodeException("Unknown gamerule type $type"),
548 };
549 }
550
560 public function getGameRules() : array{
561 $count = $this->getUnsignedVarInt();
562 $rules = [];
563 for($i = 0; $i < $count; ++$i){
564 $name = $this->getString();
565 $isPlayerModifiable = $this->getBool();
566 $type = $this->getUnsignedVarInt();
567 $rules[$name] = $this->readGameRule($type, $isPlayerModifiable);
568 }
569
570 return $rules;
571 }
572
579 public function putGameRules(array $rules) : void{
580 $this->putUnsignedVarInt(count($rules));
581 foreach($rules as $name => $rule){
582 $this->putString($name);
583 $this->putBool($rule->isPlayerModifiable());
584 $this->putUnsignedVarInt($rule->getTypeId());
585 $rule->encode($this);
586 }
587 }
588
592 public function getEntityLink() : EntityLink{
593 $fromActorUniqueId = $this->getActorUniqueId();
594 $toActorUniqueId = $this->getActorUniqueId();
595 $type = $this->getByte();
596 $immediate = $this->getBool();
597 $causedByRider = $this->getBool();
598 $vehicleAngularVelocity = $this->getLFloat();
599 return new EntityLink($fromActorUniqueId, $toActorUniqueId, $type, $immediate, $causedByRider, $vehicleAngularVelocity);
600 }
601
602 public function putEntityLink(EntityLink $link) : void{
603 $this->putActorUniqueId($link->fromActorUniqueId);
604 $this->putActorUniqueId($link->toActorUniqueId);
605 $this->putByte($link->type);
606 $this->putBool($link->immediate);
607 $this->putBool($link->causedByRider);
608 $this->putLFloat($link->vehicleAngularVelocity);
609 }
610
615 $result = new CommandOriginData();
616
617 $result->type = $this->getUnsignedVarInt();
618 $result->uuid = $this->getUUID();
619 $result->requestId = $this->getString();
620
621 if($result->type === CommandOriginData::ORIGIN_DEV_CONSOLE or $result->type === CommandOriginData::ORIGIN_TEST){
622 $result->playerActorUniqueId = $this->getVarLong();
623 }
624
625 return $result;
626 }
627
628 public function putCommandOriginData(CommandOriginData $data) : void{
629 $this->putUnsignedVarInt($data->type);
630 $this->putUUID($data->uuid);
631 $this->putString($data->requestId);
632
633 if($data->type === CommandOriginData::ORIGIN_DEV_CONSOLE or $data->type === CommandOriginData::ORIGIN_TEST){
634 $this->putVarLong($data->playerActorUniqueId);
635 }
636 }
637
638 public function getStructureSettings() : StructureSettings{
639 $result = new StructureSettings();
640
641 $result->paletteName = $this->getString();
642
643 $result->ignoreEntities = $this->getBool();
644 $result->ignoreBlocks = $this->getBool();
645 $result->allowNonTickingChunks = $this->getBool();
646
647 $result->dimensions = $this->getBlockPosition();
648 $result->offset = $this->getBlockPosition();
649
650 $result->lastTouchedByPlayerID = $this->getActorUniqueId();
651 $result->rotation = $this->getByte();
652 $result->mirror = $this->getByte();
653 $result->animationMode = $this->getByte();
654 $result->animationSeconds = $this->getLFloat();
655 $result->integrityValue = $this->getLFloat();
656 $result->integritySeed = $this->getLInt();
657 $result->pivot = $this->getVector3();
658
659 return $result;
660 }
661
662 public function putStructureSettings(StructureSettings $structureSettings) : void{
663 $this->putString($structureSettings->paletteName);
664
665 $this->putBool($structureSettings->ignoreEntities);
666 $this->putBool($structureSettings->ignoreBlocks);
667 $this->putBool($structureSettings->allowNonTickingChunks);
668
669 $this->putBlockPosition($structureSettings->dimensions);
670 $this->putBlockPosition($structureSettings->offset);
671
672 $this->putActorUniqueId($structureSettings->lastTouchedByPlayerID);
673 $this->putByte($structureSettings->rotation);
674 $this->putByte($structureSettings->mirror);
675 $this->putByte($structureSettings->animationMode);
676 $this->putLFloat($structureSettings->animationSeconds);
677 $this->putLFloat($structureSettings->integrityValue);
678 $this->putLInt($structureSettings->integritySeed);
679 $this->putVector3($structureSettings->pivot);
680 }
681
682 public function getStructureEditorData() : StructureEditorData{
683 $result = new StructureEditorData();
684
685 $result->structureName = $this->getString();
686 $result->structureDataField = $this->getString();
687
688 $result->includePlayers = $this->getBool();
689 $result->showBoundingBox = $this->getBool();
690
691 $result->structureBlockType = $this->getVarInt();
692 $result->structureSettings = $this->getStructureSettings();
693 $result->structureRedstoneSaveMode = $this->getVarInt();
694
695 return $result;
696 }
697
698 public function putStructureEditorData(StructureEditorData $structureEditorData) : void{
699 $this->putString($structureEditorData->structureName);
700 $this->putString($structureEditorData->structureDataField);
701
702 $this->putBool($structureEditorData->includePlayers);
703 $this->putBool($structureEditorData->showBoundingBox);
704
705 $this->putVarInt($structureEditorData->structureBlockType);
706 $this->putStructureSettings($structureEditorData->structureSettings);
707 $this->putVarInt($structureEditorData->structureRedstoneSaveMode);
708 }
709
710 public function getNbtRoot() : TreeRoot{
711 $offset = $this->getOffset();
712 try{
713 return (new NetworkNbtSerializer())->read($this->getBuffer(), $offset, 512);
714 }catch(NbtDataException $e){
715 throw PacketDecodeException::wrap($e, "Failed decoding NBT root");
716 }finally{
717 $this->setOffset($offset);
718 }
719 }
720
721 public function getNbtCompoundRoot() : CompoundTag{
722 try{
723 return $this->getNbtRoot()->mustGetCompoundTag();
724 }catch(NbtDataException $e){
725 throw PacketDecodeException::wrap($e, "Expected TAG_Compound NBT root");
726 }
727 }
728
729 public function readRecipeNetId() : int{
730 return $this->getUnsignedVarInt();
731 }
732
733 public function writeRecipeNetId(int $id) : void{
734 $this->putUnsignedVarInt($id);
735 }
736
737 public function readCreativeItemNetId() : int{
738 return $this->getUnsignedVarInt();
739 }
740
741 public function writeCreativeItemNetId(int $id) : void{
742 $this->putUnsignedVarInt($id);
743 }
744
755 public function readItemStackNetIdVariant() : int{
756 return $this->getVarInt();
757 }
758
764 public function writeItemStackNetIdVariant(int $id) : void{
765 $this->putVarInt($id);
766 }
767
768 public function readItemStackRequestId() : int{
769 return $this->getVarInt();
770 }
771
772 public function writeItemStackRequestId(int $id) : void{
773 $this->putVarInt($id);
774 }
775
776 public function readLegacyItemStackRequestId() : int{
777 return $this->getVarInt();
778 }
779
780 public function writeLegacyItemStackRequestId(int $id) : void{
781 $this->putVarInt($id);
782 }
783
784 public function readServerItemStackId() : int{
785 return $this->getVarInt();
786 }
787
788 public function writeServerItemStackId(int $id) : void{
789 $this->putVarInt($id);
790 }
791
797 public function readOptional(\Closure $reader) : mixed{
798 if($this->getBool()){
799 return $reader();
800 }
801 return null;
802 }
803
809 public function writeOptional(mixed $value, \Closure $writer) : void{
810 if($value !== null){
811 $this->putBool(true);
812 $writer($value);
813 }else{
814 $this->putBool(false);
815 }
816 }
817}