PocketMine-MP 5.15.1 git-5ef247620a7c6301a849b54e5ef1009217729fc8
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
60use Ramsey\Uuid\Uuid;
61use Ramsey\Uuid\UuidInterface;
62use function count;
63use function strlen;
64use function strrev;
65use function substr;
66
68 protected function __construct(string $buffer = "", int $offset = 0){
69 //overridden to change visibility
70 parent::__construct($buffer, $offset);
71 }
72
73 public static function encoder() : self{
74 return new self();
75 }
76
77 public static function decoder(string $buffer, int $offset) : self{
78 return new self($buffer, $offset);
79 }
80
84 public function getString() : string{
85 return $this->get($this->getUnsignedVarInt());
86 }
87
88 public function putString(string $v) : void{
89 $this->putUnsignedVarInt(strlen($v));
90 $this->put($v);
91 }
92
96 public function getUUID() : UuidInterface{
97 //This is two little-endian longs: bytes 7-0 followed by bytes 15-8
98 $p1 = strrev($this->get(8));
99 $p2 = strrev($this->get(8));
100 return Uuid::fromBytes($p1 . $p2);
101 }
102
103 public function putUUID(UuidInterface $uuid) : void{
104 $bytes = $uuid->getBytes();
105 $this->put(strrev(substr($bytes, 0, 8)));
106 $this->put(strrev(substr($bytes, 8, 8)));
107 }
108
109 public function getSkin() : SkinData{
110 $skinId = $this->getString();
111 $skinPlayFabId = $this->getString();
112 $skinResourcePatch = $this->getString();
113 $skinData = $this->getSkinImage();
114 $animationCount = $this->getLInt();
115 $animations = [];
116 for($i = 0; $i < $animationCount; ++$i){
117 $skinImage = $this->getSkinImage();
118 $animationType = $this->getLInt();
119 $animationFrames = $this->getLFloat();
120 $expressionType = $this->getLInt();
121 $animations[] = new SkinAnimation($skinImage, $animationType, $animationFrames, $expressionType);
122 }
123 $capeData = $this->getSkinImage();
124 $geometryData = $this->getString();
125 $geometryDataVersion = $this->getString();
126 $animationData = $this->getString();
127 $capeId = $this->getString();
128 $fullSkinId = $this->getString();
129 $armSize = $this->getString();
130 $skinColor = $this->getString();
131 $personaPieceCount = $this->getLInt();
132 $personaPieces = [];
133 for($i = 0; $i < $personaPieceCount; ++$i){
134 $pieceId = $this->getString();
135 $pieceType = $this->getString();
136 $packId = $this->getString();
137 $isDefaultPiece = $this->getBool();
138 $productId = $this->getString();
139 $personaPieces[] = new PersonaSkinPiece($pieceId, $pieceType, $packId, $isDefaultPiece, $productId);
140 }
141 $pieceTintColorCount = $this->getLInt();
142 $pieceTintColors = [];
143 for($i = 0; $i < $pieceTintColorCount; ++$i){
144 $pieceType = $this->getString();
145 $colorCount = $this->getLInt();
146 $colors = [];
147 for($j = 0; $j < $colorCount; ++$j){
148 $colors[] = $this->getString();
149 }
150 $pieceTintColors[] = new PersonaPieceTintColor(
151 $pieceType,
152 $colors
153 );
154 }
155
156 $premium = $this->getBool();
157 $persona = $this->getBool();
158 $capeOnClassic = $this->getBool();
159 $isPrimaryUser = $this->getBool();
160 $override = $this->getBool();
161
162 return new SkinData(
163 $skinId,
164 $skinPlayFabId,
165 $skinResourcePatch,
166 $skinData,
167 $animations,
168 $capeData,
169 $geometryData,
170 $geometryDataVersion,
171 $animationData,
172 $capeId,
173 $fullSkinId,
174 $armSize,
175 $skinColor,
176 $personaPieces,
177 $pieceTintColors,
178 true,
179 $premium,
180 $persona,
181 $capeOnClassic,
182 $isPrimaryUser,
183 $override,
184 );
185 }
186
187 public function putSkin(SkinData $skin) : void{
188 $this->putString($skin->getSkinId());
189 $this->putString($skin->getPlayFabId());
190 $this->putString($skin->getResourcePatch());
191 $this->putSkinImage($skin->getSkinImage());
192 $this->putLInt(count($skin->getAnimations()));
193 foreach($skin->getAnimations() as $animation){
194 $this->putSkinImage($animation->getImage());
195 $this->putLInt($animation->getType());
196 $this->putLFloat($animation->getFrames());
197 $this->putLInt($animation->getExpressionType());
198 }
199 $this->putSkinImage($skin->getCapeImage());
200 $this->putString($skin->getGeometryData());
201 $this->putString($skin->getGeometryDataEngineVersion());
202 $this->putString($skin->getAnimationData());
203 $this->putString($skin->getCapeId());
204 $this->putString($skin->getFullSkinId());
205 $this->putString($skin->getArmSize());
206 $this->putString($skin->getSkinColor());
207 $this->putLInt(count($skin->getPersonaPieces()));
208 foreach($skin->getPersonaPieces() as $piece){
209 $this->putString($piece->getPieceId());
210 $this->putString($piece->getPieceType());
211 $this->putString($piece->getPackId());
212 $this->putBool($piece->isDefaultPiece());
213 $this->putString($piece->getProductId());
214 }
215 $this->putLInt(count($skin->getPieceTintColors()));
216 foreach($skin->getPieceTintColors() as $tint){
217 $this->putString($tint->getPieceType());
218 $this->putLInt(count($tint->getColors()));
219 foreach($tint->getColors() as $color){
220 $this->putString($color);
221 }
222 }
223 $this->putBool($skin->isPremium());
224 $this->putBool($skin->isPersona());
225 $this->putBool($skin->isPersonaCapeOnClassic());
226 $this->putBool($skin->isPrimaryUser());
227 $this->putBool($skin->isOverride());
228 }
229
230 private function getSkinImage() : SkinImage{
231 $width = $this->getLInt();
232 $height = $this->getLInt();
233 $data = $this->getString();
234 try{
235 return new SkinImage($height, $width, $data);
236 }catch(\InvalidArgumentException $e){
237 throw new PacketDecodeException($e->getMessage(), 0, $e);
238 }
239 }
240
241 private function putSkinImage(SkinImage $image) : void{
242 $this->putLInt($image->getWidth());
243 $this->putLInt($image->getHeight());
244 $this->putString($image->getData());
245 }
246
252 private function getItemStackHeader() : array{
253 $id = $this->getVarInt();
254 if($id === 0){
255 return [0, 0, 0];
256 }
257
258 $count = $this->getLShort();
259 $meta = $this->getUnsignedVarInt();
260
261 return [$id, $count, $meta];
262 }
263
264 private function putItemStackHeader(ItemStack $itemStack) : bool{
265 if($itemStack->getId() === 0){
266 $this->putVarInt(0);
267 return false;
268 }
269
270 $this->putVarInt($itemStack->getId());
271 $this->putLShort($itemStack->getCount());
272 $this->putUnsignedVarInt($itemStack->getMeta());
273
274 return true;
275 }
276
277 private function getItemStackFooter(int $id, int $meta, int $count) : ItemStack{
278 $blockRuntimeId = $this->getVarInt();
279 $rawExtraData = $this->getString();
280
281 return new ItemStack($id, $meta, $count, $blockRuntimeId, $rawExtraData);
282 }
283
284 private function putItemStackFooter(ItemStack $itemStack) : void{
285 $this->putVarInt($itemStack->getBlockRuntimeId());
286 $this->putString($itemStack->getRawExtraData());
287 }
288
294 [$id, $count, $meta] = $this->getItemStackHeader();
295
296 return $id !== 0 ? $this->getItemStackFooter($id, $meta, $count) : ItemStack::null();
297
298 }
299
300 public function putItemStackWithoutStackId(ItemStack $itemStack) : void{
301 if($this->putItemStackHeader($itemStack)){
302 $this->putItemStackFooter($itemStack);
303 }
304 }
305
306 public function getItemStackWrapper() : ItemStackWrapper{
307 [$id, $count, $meta] = $this->getItemStackHeader();
308 if($id === 0){
309 return new ItemStackWrapper(0, ItemStack::null());
310 }
311
312 $hasNetId = $this->getBool();
313 $stackId = $hasNetId ? $this->readServerItemStackId() : 0;
314
315 $itemStack = $this->getItemStackFooter($id, $meta, $count);
316
317 return new ItemStackWrapper($stackId, $itemStack);
318 }
319
320 public function putItemStackWrapper(ItemStackWrapper $itemStackWrapper) : void{
321 $itemStack = $itemStackWrapper->getItemStack();
322 if($this->putItemStackHeader($itemStack)){
323 $hasNetId = $itemStackWrapper->getStackId() !== 0;
324 $this->putBool($hasNetId);
325 if($hasNetId){
326 $this->writeServerItemStackId($itemStackWrapper->getStackId());
327 }
328
329 $this->putItemStackFooter($itemStack);
330 }
331 }
332
333 public function getRecipeIngredient() : RecipeIngredient{
334 $descriptorType = $this->getByte();
335 $descriptor = match($descriptorType){
336 ItemDescriptorType::INT_ID_META => IntIdMetaItemDescriptor::read($this),
337 ItemDescriptorType::STRING_ID_META => StringIdMetaItemDescriptor::read($this),
338 ItemDescriptorType::TAG => TagItemDescriptor::read($this),
339 ItemDescriptorType::MOLANG => MolangItemDescriptor::read($this),
340 ItemDescriptorType::COMPLEX_ALIAS => ComplexAliasItemDescriptor::read($this),
341 default => null
342 };
343 $count = $this->getVarInt();
344
345 return new RecipeIngredient($descriptor, $count);
346 }
347
348 public function putRecipeIngredient(RecipeIngredient $ingredient) : void{
349 $type = $ingredient->getDescriptor();
350
351 $this->putByte($type?->getTypeId() ?? 0);
352 $type?->write($this);
353
354 $this->putVarInt($ingredient->getCount());
355 }
356
366 public function getEntityMetadata() : array{
367 $count = $this->getUnsignedVarInt();
368 $data = [];
369 for($i = 0; $i < $count; ++$i){
370 $key = $this->getUnsignedVarInt();
371 $type = $this->getUnsignedVarInt();
372
373 $data[$key] = $this->readMetadataProperty($type);
374 }
375
376 return $data;
377 }
378
379 private function readMetadataProperty(int $type) : MetadataProperty{
380 return match($type){
381 ByteMetadataProperty::ID => ByteMetadataProperty::read($this),
382 ShortMetadataProperty::ID => ShortMetadataProperty::read($this),
383 IntMetadataProperty::ID => IntMetadataProperty::read($this),
384 FloatMetadataProperty::ID => FloatMetadataProperty::read($this),
385 StringMetadataProperty::ID => StringMetadataProperty::read($this),
386 CompoundTagMetadataProperty::ID => CompoundTagMetadataProperty::read($this),
387 BlockPosMetadataProperty::ID => BlockPosMetadataProperty::read($this),
388 LongMetadataProperty::ID => LongMetadataProperty::read($this),
389 Vec3MetadataProperty::ID => Vec3MetadataProperty::read($this),
390 default => throw new PacketDecodeException("Unknown entity metadata type " . $type),
391 };
392 }
393
401 public function putEntityMetadata(array $metadata) : void{
402 $this->putUnsignedVarInt(count($metadata));
403 foreach($metadata as $key => $d){
404 $this->putUnsignedVarInt($key);
405 $this->putUnsignedVarInt($d->getTypeId());
406 $d->write($this);
407 }
408 }
409
416 public function getAttributeList() : array{
417 $list = [];
418 $count = $this->getUnsignedVarInt();
419
420 for($i = 0; $i < $count; ++$i){
421 $min = $this->getLFloat();
422 $max = $this->getLFloat();
423 $current = $this->getLFloat();
424 $default = $this->getLFloat();
425 $id = $this->getString();
426
427 $modifiers = [];
428 for($j = 0, $modifierCount = $this->getUnsignedVarInt(); $j < $modifierCount; $j++){
429 $modifiers[] = AttributeModifier::read($this);
430 }
431
432 $list[] = new Attribute($id, $min, $max, $current, $default, $modifiers);
433 }
434
435 return $list;
436 }
437
441 public function putAttributeList(Attribute ...$attributes) : void{
442 $this->putUnsignedVarInt(count($attributes));
443 foreach($attributes as $attribute){
444 $this->putLFloat($attribute->getMin());
445 $this->putLFloat($attribute->getMax());
446 $this->putLFloat($attribute->getCurrent());
447 $this->putLFloat($attribute->getDefault());
448 $this->putString($attribute->getId());
449
450 $this->putUnsignedVarInt(count($attribute->getModifiers()));
451 foreach($attribute->getModifiers() as $modifier){
452 $modifier->write($this);
453 }
454 }
455 }
456
460 final public function getActorUniqueId() : int{
461 return $this->getVarLong();
462 }
463
464 public function putActorUniqueId(int $eid) : void{
465 $this->putVarLong($eid);
466 }
467
471 final public function getActorRuntimeId() : int{
472 return $this->getUnsignedVarLong();
473 }
474
475 public function putActorRuntimeId(int $eid) : void{
476 $this->putUnsignedVarLong($eid);
477 }
478
484 public function getBlockPosition() : BlockPosition{
485 $x = $this->getVarInt();
486 $y = Binary::signInt($this->getUnsignedVarInt()); //Y coordinate may be signed, but it's written unsigned :<
487 $z = $this->getVarInt();
488 return new BlockPosition($x, $y, $z);
489 }
490
494 public function putBlockPosition(BlockPosition $blockPosition) : void{
495 $this->putVarInt($blockPosition->getX());
496 $this->putUnsignedVarInt(Binary::unsignInt($blockPosition->getY())); //Y coordinate may be signed, but it's written unsigned :<
497 $this->putVarInt($blockPosition->getZ());
498 }
499
506 $x = $this->getVarInt();
507 $y = $this->getVarInt();
508 $z = $this->getVarInt();
509 return new BlockPosition($x, $y, $z);
510 }
511
515 public function putSignedBlockPosition(BlockPosition $blockPosition) : void{
516 $this->putVarInt($blockPosition->getX());
517 $this->putVarInt($blockPosition->getY());
518 $this->putVarInt($blockPosition->getZ());
519 }
520
526 public function getVector3() : Vector3{
527 $x = $this->getLFloat();
528 $y = $this->getLFloat();
529 $z = $this->getLFloat();
530 return new Vector3($x, $y, $z);
531 }
532
541 public function putVector3Nullable(?Vector3 $vector) : void{
542 if($vector !== null){
543 $this->putVector3($vector);
544 }else{
545 $this->putLFloat(0.0);
546 $this->putLFloat(0.0);
547 $this->putLFloat(0.0);
548 }
549 }
550
554 public function putVector3(Vector3 $vector) : void{
555 $this->putLFloat($vector->x);
556 $this->putLFloat($vector->y);
557 $this->putLFloat($vector->z);
558 }
559
563 public function getRotationByte() : float{
564 return ($this->getByte() * (360 / 256));
565 }
566
567 public function putRotationByte(float $rotation) : void{
568 $this->putByte((int) ($rotation / (360 / 256)));
569 }
570
571 private function readGameRule(int $type, bool $isPlayerModifiable) : GameRule{
572 return match($type){
573 BoolGameRule::ID => BoolGameRule::decode($this, $isPlayerModifiable),
574 IntGameRule::ID => IntGameRule::decode($this, $isPlayerModifiable),
575 FloatGameRule::ID => FloatGameRule::decode($this, $isPlayerModifiable),
576 default => throw new PacketDecodeException("Unknown gamerule type $type"),
577 };
578 }
579
589 public function getGameRules() : array{
590 $count = $this->getUnsignedVarInt();
591 $rules = [];
592 for($i = 0; $i < $count; ++$i){
593 $name = $this->getString();
594 $isPlayerModifiable = $this->getBool();
595 $type = $this->getUnsignedVarInt();
596 $rules[$name] = $this->readGameRule($type, $isPlayerModifiable);
597 }
598
599 return $rules;
600 }
601
608 public function putGameRules(array $rules) : void{
609 $this->putUnsignedVarInt(count($rules));
610 foreach($rules as $name => $rule){
611 $this->putString($name);
612 $this->putBool($rule->isPlayerModifiable());
613 $this->putUnsignedVarInt($rule->getTypeId());
614 $rule->encode($this);
615 }
616 }
617
621 public function getEntityLink() : EntityLink{
622 $fromActorUniqueId = $this->getActorUniqueId();
623 $toActorUniqueId = $this->getActorUniqueId();
624 $type = $this->getByte();
625 $immediate = $this->getBool();
626 $causedByRider = $this->getBool();
627 return new EntityLink($fromActorUniqueId, $toActorUniqueId, $type, $immediate, $causedByRider);
628 }
629
630 public function putEntityLink(EntityLink $link) : void{
631 $this->putActorUniqueId($link->fromActorUniqueId);
632 $this->putActorUniqueId($link->toActorUniqueId);
633 $this->putByte($link->type);
634 $this->putBool($link->immediate);
635 $this->putBool($link->causedByRider);
636 }
637
642 $result = new CommandOriginData();
643
644 $result->type = $this->getUnsignedVarInt();
645 $result->uuid = $this->getUUID();
646 $result->requestId = $this->getString();
647
648 if($result->type === CommandOriginData::ORIGIN_DEV_CONSOLE or $result->type === CommandOriginData::ORIGIN_TEST){
649 $result->playerActorUniqueId = $this->getVarLong();
650 }
651
652 return $result;
653 }
654
655 public function putCommandOriginData(CommandOriginData $data) : void{
656 $this->putUnsignedVarInt($data->type);
657 $this->putUUID($data->uuid);
658 $this->putString($data->requestId);
659
660 if($data->type === CommandOriginData::ORIGIN_DEV_CONSOLE or $data->type === CommandOriginData::ORIGIN_TEST){
661 $this->putVarLong($data->playerActorUniqueId);
662 }
663 }
664
665 public function getStructureSettings() : StructureSettings{
666 $result = new StructureSettings();
667
668 $result->paletteName = $this->getString();
669
670 $result->ignoreEntities = $this->getBool();
671 $result->ignoreBlocks = $this->getBool();
672 $result->allowNonTickingChunks = $this->getBool();
673
674 $result->dimensions = $this->getBlockPosition();
675 $result->offset = $this->getBlockPosition();
676
677 $result->lastTouchedByPlayerID = $this->getActorUniqueId();
678 $result->rotation = $this->getByte();
679 $result->mirror = $this->getByte();
680 $result->animationMode = $this->getByte();
681 $result->animationSeconds = $this->getLFloat();
682 $result->integrityValue = $this->getLFloat();
683 $result->integritySeed = $this->getLInt();
684 $result->pivot = $this->getVector3();
685
686 return $result;
687 }
688
689 public function putStructureSettings(StructureSettings $structureSettings) : void{
690 $this->putString($structureSettings->paletteName);
691
692 $this->putBool($structureSettings->ignoreEntities);
693 $this->putBool($structureSettings->ignoreBlocks);
694 $this->putBool($structureSettings->allowNonTickingChunks);
695
696 $this->putBlockPosition($structureSettings->dimensions);
697 $this->putBlockPosition($structureSettings->offset);
698
699 $this->putActorUniqueId($structureSettings->lastTouchedByPlayerID);
700 $this->putByte($structureSettings->rotation);
701 $this->putByte($structureSettings->mirror);
702 $this->putByte($structureSettings->animationMode);
703 $this->putLFloat($structureSettings->animationSeconds);
704 $this->putLFloat($structureSettings->integrityValue);
705 $this->putLInt($structureSettings->integritySeed);
706 $this->putVector3($structureSettings->pivot);
707 }
708
709 public function getStructureEditorData() : StructureEditorData{
710 $result = new StructureEditorData();
711
712 $result->structureName = $this->getString();
713 $result->structureDataField = $this->getString();
714
715 $result->includePlayers = $this->getBool();
716 $result->showBoundingBox = $this->getBool();
717
718 $result->structureBlockType = $this->getVarInt();
719 $result->structureSettings = $this->getStructureSettings();
720 $result->structureRedstoneSaveMode = $this->getVarInt();
721
722 return $result;
723 }
724
725 public function putStructureEditorData(StructureEditorData $structureEditorData) : void{
726 $this->putString($structureEditorData->structureName);
727 $this->putString($structureEditorData->structureDataField);
728
729 $this->putBool($structureEditorData->includePlayers);
730 $this->putBool($structureEditorData->showBoundingBox);
731
732 $this->putVarInt($structureEditorData->structureBlockType);
733 $this->putStructureSettings($structureEditorData->structureSettings);
734 $this->putVarInt($structureEditorData->structureRedstoneSaveMode);
735 }
736
737 public function getNbtRoot() : TreeRoot{
738 $offset = $this->getOffset();
739 try{
740 return (new NetworkNbtSerializer())->read($this->getBuffer(), $offset, 512);
741 }catch(NbtDataException $e){
742 throw PacketDecodeException::wrap($e, "Failed decoding NBT root");
743 }finally{
744 $this->setOffset($offset);
745 }
746 }
747
748 public function getNbtCompoundRoot() : CompoundTag{
749 try{
750 return $this->getNbtRoot()->mustGetCompoundTag();
751 }catch(NbtDataException $e){
752 throw PacketDecodeException::wrap($e, "Expected TAG_Compound NBT root");
753 }
754 }
755
756 public function readRecipeNetId() : int{
757 return $this->getUnsignedVarInt();
758 }
759
760 public function writeRecipeNetId(int $id) : void{
761 $this->putUnsignedVarInt($id);
762 }
763
764 public function readCreativeItemNetId() : int{
765 return $this->getUnsignedVarInt();
766 }
767
768 public function writeCreativeItemNetId(int $id) : void{
769 $this->putUnsignedVarInt($id);
770 }
771
782 public function readItemStackNetIdVariant() : int{
783 return $this->getVarInt();
784 }
785
791 public function writeItemStackNetIdVariant(int $id) : void{
792 $this->putVarInt($id);
793 }
794
795 public function readItemStackRequestId() : int{
796 return $this->getVarInt();
797 }
798
799 public function writeItemStackRequestId(int $id) : void{
800 $this->putVarInt($id);
801 }
802
803 public function readLegacyItemStackRequestId() : int{
804 return $this->getVarInt();
805 }
806
807 public function writeLegacyItemStackRequestId(int $id) : void{
808 $this->putVarInt($id);
809 }
810
811 public function readServerItemStackId() : int{
812 return $this->getVarInt();
813 }
814
815 public function writeServerItemStackId(int $id) : void{
816 $this->putVarInt($id);
817 }
818
824 public function readOptional(\Closure $reader) : mixed{
825 if($this->getBool()){
826 return $reader();
827 }
828 return null;
829 }
830
836 public function writeOptional(mixed $value, \Closure $writer) : void{
837 if($value !== null){
838 $this->putBool(true);
839 $writer($value);
840 }else{
841 $this->putBool(false);
842 }
843 }
844}