Compare commits

..

1 Commits

Author SHA1 Message Date
fbe6a89777 EndCrystal: defer explosion until next tick update 2025-09-10 20:55:20 +01:00
39 changed files with 420 additions and 689 deletions

View File

@ -12,7 +12,6 @@
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-encoding": "~1.0.0",
"ext-gmp": "*",
"ext-hash": "*",
"ext-igbinary": "^3.0.1",
@ -37,7 +36,7 @@
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
"pocketmine/bedrock-data": "~6.0.0+bedrock-1.21.100",
"pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100",
"pocketmine/bedrock-protocol": "dev-ext-encoding",
"pocketmine/bedrock-protocol": "~40.0.0+bedrock-1.21.100",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0",
@ -45,9 +44,9 @@
"pocketmine/locale-data": "~2.25.0",
"pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0",
"pocketmine/nbt": "dev-ext-encoding as 1.1.1",
"pocketmine/raklib": "dev-ext-encoding as 1.2.0",
"pocketmine/raklib-ipc": "dev-ext-encoding as 1.0.0",
"pocketmine/nbt": "~1.1.0",
"pocketmine/raklib": "~1.2.0",
"pocketmine/raklib-ipc": "~1.0.0",
"pocketmine/snooze": "^0.5.0",
"ramsey/uuid": "~4.9.0",
"symfony/filesystem": "~6.4.0"

146
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9e8e5feec61d5dfb760c54d56a0bcc42",
"content-hash": "27fee330bdcb6ea2373c57cdfb3bc22f",
"packages": [
{
"name": "adhocore/json-comment",
@ -256,26 +256,25 @@
},
{
"name": "pocketmine/bedrock-protocol",
"version": "dev-ext-encoding",
"version": "40.0.0+bedrock-1.21.100",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "a1698a102715a9e5bfa27cf5ef192afef30c42fe"
"reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a1698a102715a9e5bfa27cf5ef192afef30c42fe",
"reference": "a1698a102715a9e5bfa27cf5ef192afef30c42fe",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca",
"reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca",
"shasum": ""
},
"require": {
"ext-encoding": "~1.0.0",
"ext-json": "*",
"php": "^8.1",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/color": "^0.2.0 || ^0.3.0",
"pocketmine/math": "^0.3.0 || ^0.4.0 || ^1.0.0",
"pocketmine/nbt": "dev-ext-encoding",
"pocketmine/nbt": "^1.0.0",
"ramsey/uuid": "^4.1"
},
"require-dev": {
@ -297,9 +296,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/ext-encoding"
"source": "https://github.com/pmmp/BedrockProtocol/tree/40.0.0+bedrock-1.21.100"
},
"time": "2025-09-11T23:11:09+00:00"
"time": "2025-08-06T15:13:45+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -577,22 +576,22 @@
},
{
"name": "pocketmine/nbt",
"version": "dev-ext-encoding",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "5d6a088ca8ad8ab9f7c5415e7eacfe41e5b52721"
"reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/5d6a088ca8ad8ab9f7c5415e7eacfe41e5b52721",
"reference": "5d6a088ca8ad8ab9f7c5415e7eacfe41e5b52721",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/c3c7b0a7295daeaf7873d90fed5c5d10381d12e1",
"reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1",
"shasum": ""
},
"require": {
"ext-encoding": "~1.0.0",
"php": "^8.1",
"php-64bit": "*"
"php": "^7.4 || ^8.0",
"php-64bit": "*",
"pocketmine/binaryutils": "^0.2.0"
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
@ -613,30 +612,30 @@
"description": "PHP library for working with Named Binary Tags",
"support": {
"issues": "https://github.com/pmmp/NBT/issues",
"source": "https://github.com/pmmp/NBT/tree/ext-encoding"
"source": "https://github.com/pmmp/NBT/tree/1.1.1"
},
"time": "2025-09-11T22:56:21+00:00"
"time": "2025-03-09T01:46:03+00:00"
},
{
"name": "pocketmine/raklib",
"version": "dev-ext-encoding",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLib.git",
"reference": "f1c22f6cb5fe34c06ee2a2624525e8edb46a00d4"
"reference": "a28d05216d34dbd00e8aed827a58df6b4c11510b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/f1c22f6cb5fe34c06ee2a2624525e8edb46a00d4",
"reference": "f1c22f6cb5fe34c06ee2a2624525e8edb46a00d4",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/a28d05216d34dbd00e8aed827a58df6b4c11510b",
"reference": "a28d05216d34dbd00e8aed827a58df6b4c11510b",
"shasum": ""
},
"require": {
"ext-encoding": "~1.0.0",
"ext-sockets": "*",
"php": "^8.1",
"php-64bit": "*",
"php-ipv6": "*",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/log": "^0.3.0 || ^0.4.0"
},
"require-dev": {
@ -656,33 +655,33 @@
"description": "A RakNet server implementation written in PHP",
"support": {
"issues": "https://github.com/pmmp/RakLib/issues",
"source": "https://github.com/pmmp/RakLib/tree/ext-encoding"
"source": "https://github.com/pmmp/RakLib/tree/1.2.0"
},
"time": "2025-09-11T22:56:53+00:00"
"time": "2025-06-08T17:36:06+00:00"
},
{
"name": "pocketmine/raklib-ipc",
"version": "dev-ext-encoding",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLibIpc.git",
"reference": "98de061d74e005e397200977e224709bf0342382"
"reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/98de061d74e005e397200977e224709bf0342382",
"reference": "98de061d74e005e397200977e224709bf0342382",
"url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/ce632ef2c6743e71eddb5dc329c49af6555f90bc",
"reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc",
"shasum": ""
},
"require": {
"ext-encoding": "~1.0.0",
"php": "^8.0",
"php-64bit": "*",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/raklib": "^0.15.0 || ^1.0.0"
},
"require-dev": {
"phpstan/phpstan": "2.1.0",
"phpstan/phpstan-strict-rules": "^2.0.0"
"phpstan/phpstan": "1.10.1",
"phpstan/phpstan-strict-rules": "^1.0.0"
},
"type": "library",
"autoload": {
@ -697,9 +696,9 @@
"description": "Channel-based protocols for inter-thread/inter-process communication with RakLib",
"support": {
"issues": "https://github.com/pmmp/RakLibIpc/issues",
"source": "https://github.com/pmmp/RakLibIpc/tree/ext-encoding"
"source": "https://github.com/pmmp/RakLibIpc/tree/1.0.1"
},
"time": "2025-09-11T22:19:12+00:00"
"time": "2024-03-01T15:55:05+00:00"
},
{
"name": "pocketmine/snooze",
@ -819,20 +818,20 @@
},
{
"name": "ramsey/uuid",
"version": "4.9.1",
"version": "4.9.0",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440"
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0",
"shasum": ""
},
"require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13",
"php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0"
},
@ -891,9 +890,9 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.9.1"
"source": "https://github.com/ramsey/uuid/tree/4.9.0"
},
"time": "2025-09-04T20:59:21+00:00"
"time": "2025-06-25T14:20:11+00:00"
},
{
"name": "symfony/filesystem",
@ -1685,16 +1684,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.54",
"version": "10.5.53",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "b1dbbaaf96106b76d500b9d3db51f9b01f6a3589"
"reference": "32768472ebfb6969e6c7399f1c7b09009723f653"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b1dbbaaf96106b76d500b9d3db51f9b01f6a3589",
"reference": "b1dbbaaf96106b76d500b9d3db51f9b01f6a3589",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/32768472ebfb6969e6c7399f1c7b09009723f653",
"reference": "32768472ebfb6969e6c7399f1c7b09009723f653",
"shasum": ""
},
"require": {
@ -1766,7 +1765,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.54"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.53"
},
"funding": [
{
@ -1790,7 +1789,7 @@
"type": "tidelift"
}
],
"time": "2025-09-11T06:19:38+00:00"
"time": "2025-08-20T14:40:06+00:00"
},
{
"name": "sebastian/cli-parser",
@ -1962,16 +1961,16 @@
},
{
"name": "sebastian/comparator",
"version": "5.0.4",
"version": "5.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
"shasum": ""
},
"require": {
@ -2027,27 +2026,15 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
"type": "tidelift"
}
],
"time": "2025-09-07T05:25:07+00:00"
"time": "2024-10-18T14:56:07+00:00"
},
{
"name": "sebastian/complexity",
@ -2784,33 +2771,9 @@
"time": "2024-03-03T12:36:25+00:00"
}
],
"aliases": [
{
"package": "pocketmine/nbt",
"version": "dev-ext-encoding",
"alias": "1.1.1",
"alias_normalized": "1.1.1.0"
},
{
"package": "pocketmine/raklib",
"version": "dev-ext-encoding",
"alias": "1.2.0",
"alias_normalized": "1.2.0.0"
},
{
"package": "pocketmine/raklib-ipc",
"version": "dev-ext-encoding",
"alias": "1.0.0",
"alias_normalized": "1.0.0.0"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"pocketmine/bedrock-protocol": 20,
"pocketmine/nbt": 20,
"pocketmine/raklib": 20,
"pocketmine/raklib-ipc": 20
},
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@ -2821,7 +2784,6 @@
"ext-ctype": "*",
"ext-curl": "*",
"ext-date": "*",
"ext-encoding": "~1.0.0",
"ext-gmp": "*",
"ext-hash": "*",
"ext-igbinary": "^3.0.1",

View File

@ -98,7 +98,6 @@ namespace pocketmine {
"crypto" => "php-crypto",
"ctype" => "ctype",
"date" => "Date",
"encoding" => "pmmp/ext-encoding",
"gmp" => "GMP",
"hash" => "Hash",
"igbinary" => "igbinary",
@ -156,12 +155,6 @@ namespace pocketmine {
}
}
if(($encoding_version = phpversion("encoding")) !== false){
if(version_compare($encoding_version, "1.0.0") < 0 || version_compare($encoding_version, "2.0.0") >= 0){
$messages[] = "pmmp/ext-encoding ^1.0.0 is required, while you have $encoding_version.";
}
}
if(extension_loaded("pocketmine")){
$messages[] = "The native PocketMine extension is no longer supported.";
}

View File

@ -41,19 +41,14 @@ use pocketmine\utils\TextFormat;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\DyeUseSound;
use pocketmine\world\sound\InkSacUseSound;
use function abs;
use function array_map;
use function assert;
use function atan2;
use function fmod;
use function rad2deg;
use function strlen;
abstract class BaseSign extends Transparent implements WoodMaterial{
use WoodTypeTrait;
protected SignText $text; //TODO: rename this (BC break)
protected SignText $backText;
protected SignText $text;
private bool $waxed = false;
protected ?int $editorEntityRuntimeId = null;
@ -68,7 +63,6 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
$this->woodType = $woodType;
parent::__construct($idInfo, $name, $typeInfo);
$this->text = new SignText();
$this->backText = new SignText();
$this->asItemCallback = $asItemCallback;
}
@ -77,7 +71,6 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
$tile = $this->position->getWorld()->getTile($this->position);
if($tile instanceof TileSign){
$this->text = $tile->getText();
$this->backText = $tile->getBackText();
$this->waxed = $tile->isWaxed();
$this->editorEntityRuntimeId = $tile->getEditorEntityRuntimeId();
}
@ -90,7 +83,6 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
$tile = $this->position->getWorld()->getTile($this->position);
assert($tile instanceof TileSign);
$tile->setText($this->text);
$tile->setBackText($this->backText);
$tile->setWaxed($this->waxed);
$tile->setEditorEntityRuntimeId($this->editorEntityRuntimeId);
}
@ -135,11 +127,11 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
}
}
private function doSignChange(SignText $newText, Player $player, Item $item, bool $frontFace) : bool{
$ev = new SignChangeEvent($this, $player, $newText, $frontFace);
private function doSignChange(SignText $newText, Player $player, Item $item) : bool{
$ev = new SignChangeEvent($this, $player, $newText);
$ev->call();
if(!$ev->isCancelled()){
$this->setFaceText($frontFace, $ev->getNewText());
$this->text = $ev->getNewText();
$this->position->getWorld()->setBlock($this->position, $this);
$item->pop();
return true;
@ -148,9 +140,8 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
return false;
}
private function changeSignGlowingState(bool $glowing, Player $player, Item $item, bool $frontFace) : bool{
$text = $this->getFaceText($frontFace);
if($text->isGlowing() !== $glowing && $this->doSignChange(new SignText($text->getLines(), $text->getBaseColor(), $glowing), $player, $item, $frontFace)){
private function changeSignGlowingState(bool $glowing, Player $player, Item $item) : bool{
if($this->text->isGlowing() !== $glowing && $this->doSignChange(new SignText($this->text->getLines(), $this->text->getBaseColor(), $glowing), $player, $item)){
$this->position->getWorld()->addSound($this->position, new InkSacUseSound());
return true;
}
@ -177,8 +168,6 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
return true;
}
$frontFace = $this->interactsFront($this->getHitboxCenter(), $player->getPosition(), $this->getFacingDegrees());
$dyeColor = $item instanceof Dye ? $item->getColor() : match($item->getTypeId()){
ItemTypeIds::BONE_MEAL => DyeColor::WHITE,
ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE,
@ -187,82 +176,40 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
};
if($dyeColor !== null){
$color = $dyeColor === DyeColor::BLACK ? new Color(0, 0, 0) : $dyeColor->getRgbValue();
$text = $this->getFaceText($frontFace);
if(
$color->toARGB() !== $text->getBaseColor()->toARGB() &&
$this->doSignChange(new SignText($text->getLines(), $color, $text->isGlowing()), $player, $item, $frontFace)
$color->toARGB() !== $this->text->getBaseColor()->toARGB() &&
$this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item)
){
$this->position->getWorld()->addSound($this->position, new DyeUseSound());
return true;
}
}elseif(match($item->getTypeId()){
ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item, $frontFace),
ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item, $frontFace),
ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item),
ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item),
ItemTypeIds::HONEYCOMB => $this->wax($player, $item),
default => false
}){
return true;
}
$player->openSignEditor($this->position, $frontFace);
$player->openSignEditor($this->position);
return true;
}
private function interactsFront(Vector3 $hitboxCenter, Vector3 $playerPosition, float $signFacingDegrees) : bool{
$playerCenterDiffX = $playerPosition->x - $hitboxCenter->x;
$playerCenterDiffZ = $playerPosition->z - $hitboxCenter->z;
$f1 = rad2deg(atan2($playerCenterDiffZ, $playerCenterDiffX)) - 90.0;
$rotationDiff = $signFacingDegrees - $f1;
$rotation = fmod($rotationDiff + 180.0, 360.0) - 180.0; // Normalize to [-180, 180]
return abs($rotation) <= 90.0;
}
/**
* Returns the center of the sign's hitbox. Used to decide which face of the sign to open when a player interacts.
*/
protected function getHitboxCenter() : Vector3{
return $this->position->add(0.5, 0.5, 0.5);
}
/**
* TODO: make this abstract (BC break)
*/
protected function getFacingDegrees() : float{
return 0;
}
/**
* Returns an object containing information about the sign text.
* @deprecated
* @see self::getFaceText()
*/
public function getText() : SignText{
return $this->text;
}
/**
* @deprecated
* @see self::setFaceText()
* @return $this
*/
/** @return $this */
public function setText(SignText $text) : self{
$this->text = $text;
return $this;
}
public function getFaceText(bool $frontFace) : SignText{
return $frontFace ? $this->text : $this->backText;
}
/** @return $this */
public function setFaceText(bool $frontFace, SignText $text) : self{
$frontFace ? $this->text = $text : $this->backText = $text;
return $this;
}
/**
* Returns whether the sign has been waxed using a honeycomb. If true, the sign cannot be edited by a player.
*/
@ -287,21 +234,13 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
return $this;
}
/**
* @deprecated
* @see self::updateFaceText()
*/
public function updateText(Player $author, SignText $text) : bool{
return $this->updateFaceText($author, true, $text);
}
/**
* Called by the player controller (network session) to update the sign text, firing events as appropriate.
*
* @return bool if the sign update was successful.
* @throws \UnexpectedValueException if the text payload is too large
*/
public function updateFaceText(Player $author, bool $frontFace, SignText $text) : bool{
public function updateText(Player $author, SignText $text) : bool{
$size = 0;
foreach($text->getLines() as $line){
$size += strlen($line);
@ -309,16 +248,15 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
if($size > 1000){
throw new \UnexpectedValueException($author->getName() . " tried to write $size bytes of text onto a sign (bigger than max 1000)");
}
$oldText = $this->getFaceText($frontFace);
$ev = new SignChangeEvent($this, $author, new SignText(array_map(function(string $line) : string{
return TextFormat::clean($line, false);
}, $text->getLines()), $oldText->getBaseColor(), $oldText->isGlowing()), $frontFace);
}, $text->getLines()), $this->text->getBaseColor(), $this->text->isGlowing()));
if($this->waxed || $this->editorEntityRuntimeId !== $author->getId()){
$ev->cancel();
}
$ev->call();
if(!$ev->isCancelled()){
$this->setFaceText($frontFace, $ev->getNewText());
$this->setText($ev->getNewText());
$this->setEditorEntityRuntimeId(null);
$this->position->getWorld()->setBlock($this->position, $this);
return true;

View File

@ -26,8 +26,6 @@ declare(strict_types=1);
*/
namespace pocketmine\block;
use pmmp\encoding\BE;
use pmmp\encoding\LE;
use pocketmine\block\tile\Spawnable;
use pocketmine\block\tile\Tile;
use pocketmine\block\utils\SupportType;
@ -51,6 +49,7 @@ use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Binary;
use pocketmine\world\BlockTransaction;
use pocketmine\world\format\Chunk;
use pocketmine\world\Position;
@ -99,10 +98,9 @@ class Block{
* of operations required to compute the state ID (micro optimization).
*/
private static function computeStateIdXorMask(int $typeId) : int{
//TODO: the mixed byte order here is probably a mistake, but it doesn't break anything for now
return
$typeId << self::INTERNAL_STATE_DATA_BITS |
(BE::unpackSignedLong(hash('xxh3', LE::packSignedLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK);
(Binary::readLong(hash('xxh3', Binary::writeLLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK);
}
/**

View File

@ -58,8 +58,4 @@ final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotatio
$supportBlock->getSupportType(Facing::DOWN)->hasCenterSupport() ||
$supportBlock->hasTypeTag(BlockTypeTags::HANGING_SIGN);
}
protected function getFacingDegrees() : float{
return $this->rotation * 22.5;
}
}

View File

@ -30,7 +30,6 @@ use pocketmine\item\Item;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction;
final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing{
@ -66,14 +65,4 @@ final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing
$supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL ||
(($supportBlock instanceof WallHangingSign || $supportBlock instanceof CeilingEdgesHangingSign) && Facing::axis($supportBlock->getFacing()) === Facing::axis($this->facing));
}
protected function getFacingDegrees() : float{
return match($this->facing){
Facing::SOUTH => 0,
Facing::WEST => 90,
Facing::NORTH => 180,
Facing::EAST => 270,
default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing),
};
}
}

View File

@ -48,8 +48,4 @@ final class FloorSign extends BaseSign implements SignLikeRotation{
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
protected function getFacingDegrees() : float{
return $this->rotation * 22.5;
}
}

View File

@ -32,7 +32,6 @@ use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction;
final class WallHangingSign extends BaseSign implements HorizontalFacing{
@ -79,14 +78,4 @@ final class WallHangingSign extends BaseSign implements HorizontalFacing{
($block instanceof WallHangingSign && Facing::axis(Facing::rotateY($block->getFacing(), clockwise: true)) === Facing::axis($face)) ||
$block->getSupportType(Facing::opposite($face)) === SupportType::FULL;
}
protected function getFacingDegrees() : float{
return match($this->facing){
Facing::SOUTH => 0,
Facing::WEST => 90,
Facing::NORTH => 180,
Facing::EAST => 270,
default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing),
};
}
}

View File

@ -30,7 +30,6 @@ use pocketmine\math\Axis;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction;
final class WallSign extends BaseSign implements HorizontalFacing{
@ -47,25 +46,4 @@ final class WallSign extends BaseSign implements HorizontalFacing{
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
protected function getHitboxCenter() : Vector3{
[$xOffset, $zOffset] = match($this->facing){
Facing::NORTH => [0, 15 / 16],
Facing::SOUTH => [0, 1 / 16],
Facing::WEST => [15 / 16, 0],
Facing::EAST => [1 / 16, 0],
default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing),
};
return $this->position->add($xOffset, 0.5, $zOffset);
}
protected function getFacingDegrees() : float{
return match($this->facing){
Facing::SOUTH => 0,
Facing::WEST => 90,
Facing::NORTH => 180,
Facing::EAST => 270,
default => throw new AssumptionFailedError("Invalid facing direction: " . $this->facing),
};
}
}

View File

@ -70,18 +70,16 @@ class Sign extends Spawnable{
}
protected SignText $text;
protected SignText $backText;
private bool $waxed = false;
protected ?int $editorEntityRuntimeId = null;
public function __construct(World $world, Vector3 $pos){
$this->text = new SignText();
$this->backText = new SignText();
parent::__construct($world, $pos);
}
private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : SignText{
private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : void{
$baseColor = new Color(0, 0, 0);
$glowingText = false;
if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){
@ -92,27 +90,19 @@ class Sign extends Spawnable{
//see https://bugs.mojang.com/browse/MCPE-117835
$glowingText = $glowingTextTag->getValue() !== 0;
}
return SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8'), $baseColor, $glowingText);
}
private function writeTextTag(SignText $text) : CompoundTag{
return CompoundTag::create()
->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $text->getLines()), "\n"))
->setInt(self::TAG_TEXT_COLOR, Binary::signInt($text->getBaseColor()->toARGB()))
->setByte(self::TAG_GLOWING_TEXT, $text->isGlowing() ? 1 : 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1);
$this->text = SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8'), $baseColor, $glowingText);
}
public function readSaveData(CompoundTag $nbt) : void{
$frontTextTag = $nbt->getTag(self::TAG_FRONT_TEXT);
if($frontTextTag instanceof CompoundTag){
$this->text = $this->readTextTag($frontTextTag, true);
$this->readTextTag($frontTextTag, true);
}elseif($nbt->getTag(self::TAG_TEXT_BLOB) instanceof StringTag){ //MCPE 1.2 save format
$lightingBugResolved = false;
if(($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag){
$lightingBugResolved = $lightingBugResolvedTag->getValue() !== 0;
}
$this->text = $this->readTextTag($nbt, $lightingBugResolved);
$this->readTextTag($nbt, $lightingBugResolved);
}else{
$text = [];
for($i = 0; $i < SignText::LINE_COUNT; ++$i){
@ -123,14 +113,22 @@ class Sign extends Spawnable{
}
$this->text = new SignText($text);
}
$backTextTag = $nbt->getTag(self::TAG_BACK_TEXT);
$this->backText = $backTextTag instanceof CompoundTag ? $this->readTextTag($backTextTag, true) : new SignText();
$this->waxed = $nbt->getByte(self::TAG_WAXED, 0) !== 0;
}
protected function writeSaveData(CompoundTag $nbt) : void{
$nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text));
$nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText));
$nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create()
->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n"))
->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB()))
->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1)
);
$nbt->setTag(self::TAG_BACK_TEXT, CompoundTag::create()
->setString(self::TAG_TEXT_BLOB, "")
->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00))
->setByte(self::TAG_GLOWING_TEXT, 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1)
);
$nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
}
@ -143,10 +141,6 @@ class Sign extends Spawnable{
$this->text = $text;
}
public function getBackText() : SignText{ return $this->backText; }
public function setBackText(SignText $backText) : void{ $this->backText = $backText; }
public function isWaxed() : bool{ return $this->waxed; }
public function setWaxed(bool $waxed) : void{ $this->waxed = $waxed; }
@ -168,8 +162,19 @@ class Sign extends Spawnable{
}
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
$nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text));
$nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText));
$nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create()
->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n"))
->setInt(self::TAG_TEXT_COLOR, Binary::signInt($this->text->getBaseColor()->toARGB()))
->setByte(self::TAG_GLOWING_TEXT, $this->text->isGlowing() ? 1 : 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1) //TODO: not sure what this is used for
);
//TODO: this is not yet used by the server, but needed to rollback any client-side changes to the back text
$nbt->setTag(self::TAG_BACK_TEXT, CompoundTag::create()
->setString(self::TAG_TEXT_BLOB, "")
->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00))
->setByte(self::TAG_GLOWING_TEXT, 0)
->setByte(self::TAG_PERSIST_FORMATTING, 1)
);
$nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
$nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1);
}

View File

@ -23,11 +23,10 @@ declare(strict_types=1);
namespace pocketmine\crafting;
use pmmp\encoding\ByteBufferWriter;
use pmmp\encoding\VarInt;
use pocketmine\item\Item;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\TreeRoot;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\DestructorCallbackTrait;
use pocketmine\utils\ObjectSet;
use function array_shift;
@ -115,13 +114,11 @@ class CraftingManager{
}
private static function hashOutput(Item $output) : string{
$write = new ByteBufferWriter();
VarInt::writeSignedInt($write, $output->getStateId());
//TODO: the NBT serializer allocates its own ByteBufferWriter, we should change the API in the future to
//allow passing our own to avoid this extra allocation
$write->writeByteArray((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag())));
$write = new BinaryStream();
$write->putVarInt($output->getStateId());
$write->put((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag())));
return $write->getData();
return $write->getBuffer();
}
/**

View File

@ -23,13 +23,11 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock\block\upgrade;
use pmmp\encoding\ByteBufferReader;
use pmmp\encoding\DataDecodeException;
use pmmp\encoding\VarInt;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
use pocketmine\nbt\LittleEndianNbtSerializer;
use function strlen;
use pocketmine\utils\BinaryDataException;
use pocketmine\utils\BinaryStream;
/**
* Handles translating legacy 1.12 block ID/meta into modern blockstates.
@ -86,25 +84,25 @@ final class BlockIdMetaUpgrader{
public static function loadFromString(string $data, LegacyBlockIdToStringIdMap $idMap, BlockStateUpgrader $blockStateUpgrader) : self{
$mappingTable = [];
$legacyStateMapReader = new ByteBufferReader($data);
$legacyStateMapReader = new BinaryStream($data);
$nbtReader = new LittleEndianNbtSerializer();
$idCount = VarInt::readUnsignedInt($legacyStateMapReader);
$idCount = $legacyStateMapReader->getUnsignedVarInt();
for($idIndex = 0; $idIndex < $idCount; $idIndex++){
$id = $legacyStateMapReader->readByteArray(VarInt::readUnsignedInt($legacyStateMapReader));
$id = $legacyStateMapReader->get($legacyStateMapReader->getUnsignedVarInt());
$metaCount = VarInt::readUnsignedInt($legacyStateMapReader);
$metaCount = $legacyStateMapReader->getUnsignedVarInt();
for($metaIndex = 0; $metaIndex < $metaCount; $metaIndex++){
$meta = VarInt::readUnsignedInt($legacyStateMapReader);
$meta = $legacyStateMapReader->getUnsignedVarInt();
$offset = $legacyStateMapReader->getOffset();
$state = $nbtReader->read($legacyStateMapReader->getData(), $offset)->mustGetCompoundTag();
$state = $nbtReader->read($legacyStateMapReader->getBuffer(), $offset)->mustGetCompoundTag();
$legacyStateMapReader->setOffset($offset);
$mappingTable[$id][$meta] = $blockStateUpgrader->upgrade(BlockStateData::fromNbt($state));
}
}
if($legacyStateMapReader->getOffset() < strlen($legacyStateMapReader->getData())){
throw new DataDecodeException("Unexpected trailing data in legacy state map data");
if(!$legacyStateMapReader->feof()){
throw new BinaryDataException("Unexpected trailing data in legacy state map data");
}
return new self($mappingTable, $idMap);

View File

@ -117,8 +117,8 @@ final class ItemSerializer{
$data = $serializer($item);
}
$resultTag = $item->getNamedTag();
if($resultTag->count() > 0){
if($item->hasNamedTag()){
$resultTag = $item->getNamedTag();
$extraTag = $data->getTag();
if($extraTag !== null){
$resultTag = $resultTag->merge($extraTag);

View File

@ -247,10 +247,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
return $this->enderInventory;
}
public function getSneakOffset() : float{
return 0.31;
}
/**
* For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
*/

View File

@ -242,10 +242,6 @@ abstract class Living extends Entity{
$this->absorptionAttr->setValue($absorption);
}
public function getSneakOffset() : float{
return 0.0;
}
public function isSneaking() : bool{
return $this->sneaking;
}
@ -296,7 +292,7 @@ abstract class Living extends Entity{
$width = $size->getWidth();
$this->setSize((new EntitySizeInfo($width, $width, $width * 0.9))->scale($this->getScale()));
}elseif($this->isSneaking()){
$this->setSize((new EntitySizeInfo($size->getHeight() - $this->getSneakOffset(), $size->getWidth(), $size->getEyeHeight() - $this->getSneakOffset()))->scale($this->getScale()));
$this->setSize((new EntitySizeInfo(3 / 4 * $size->getHeight(), $size->getWidth(), 3 / 4 * $size->getEyeHeight()))->scale($this->getScale()));
}else{
$this->setSize($size->scale($this->getScale()));
}

View File

@ -52,6 +52,8 @@ class EndCrystal extends Entity implements Explosive{
protected bool $showBase = false;
protected ?Vector3 $beamTarget = null;
private bool $primed = false;
protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(2.0, 2.0); }
protected function getInitialDragMultiplier() : float{ return 1.0; }
@ -88,11 +90,9 @@ class EndCrystal extends Entity implements Explosive{
parent::attack($source);
if(
$source->getCause() !== EntityDamageEvent::CAUSE_VOID &&
!$this->isFlaggedForDespawn() &&
!$source->isCancelled()
){
$this->flagForDespawn();
$this->explode();
$this->primed = true;
}
}
@ -125,6 +125,13 @@ class EndCrystal extends Entity implements Explosive{
return $nbt;
}
protected function onDeathUpdate(int $tickDiff) : bool{
if($this->primed){
$this->explode();
}
return true;
}
public function explode() : void{
$ev = new EntityPreExplodeEvent($this, 6);
$ev->call();

View File

@ -35,15 +35,11 @@ use pocketmine\player\Player;
class SignChangeEvent extends BlockEvent implements Cancellable{
use CancellableTrait;
private SignText $oldText;
public function __construct(
private BaseSign $sign,
private Player $player,
private SignText $text,
private bool $frontFace = true
private SignText $text
){
$this->oldText = $this->sign->getFaceText($this->frontFace);
parent::__construct($sign);
}
@ -59,7 +55,7 @@ class SignChangeEvent extends BlockEvent implements Cancellable{
* Returns the text currently on the sign.
*/
public function getOldText() : SignText{
return $this->oldText;
return $this->sign->getText();
}
/**
@ -75,6 +71,4 @@ class SignChangeEvent extends BlockEvent implements Cancellable{
public function setNewText(SignText $text) : void{
$this->text = $text;
}
public function isFrontFace() : bool{ return $this->frontFace; }
}

View File

@ -30,11 +30,13 @@ use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
use function array_keys;
use function array_values;
use function assert;
use function count;
use function get_class;
use function min;
use function shuffle;
use function spl_object_hash;
use function spl_object_id;
@ -93,13 +95,10 @@ class InventoryTransaction{
}
/**
* Returns a set of actions involved in this transaction.
* Returns an **unordered** set of actions involved in this transaction.
*
* Note: This system is designed to care only about item balances. While you can usually assume that the actions
* are provided in the correct order, it will still successfully complete transactions whose actions are provided in
* the "wrong" order, as long as the transaction balances.
* For example, you may see that an action setting a slot to a particular item may appear before the action that
* removes that item from its original slot. While unintuitive, this is still valid.
* WARNING: This system is **explicitly designed NOT to care about ordering**. Any order seen in this set has NO
* significance and should not be relied on.
*
* @return InventoryAction[]
* @phpstan-return array<int, InventoryAction>
@ -120,6 +119,19 @@ class InventoryTransaction{
}
}
/**
* Shuffles actions in the transaction to prevent external things relying on any implicit ordering.
*/
private function shuffleActions() : void{
$keys = array_keys($this->actions);
shuffle($keys);
$actions = [];
foreach($keys as $key){
$actions[$key] = $this->actions[$key];
}
$this->actions = $actions;
}
/**
* @param Item[] $needItems
* @param Item[] $haveItems
@ -296,6 +308,8 @@ class InventoryTransaction{
throw new TransactionValidationException("Transaction has already been executed");
}
$this->shuffleActions();
$this->validate();
if(!$this->callExecuteEvent()){

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pmmp\encoding\ByteBufferWriter;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\Compressor;
use pocketmine\network\mcpe\convert\TypeConverter;
@ -34,6 +33,7 @@ use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\serializer\ChunkSerializer;
use pocketmine\scheduler\AsyncTask;
use pocketmine\thread\NonThreadSafeValue;
use pocketmine\utils\BinaryStream;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\io\FastChunkSerializer;
use function chr;
@ -73,11 +73,11 @@ class ChunkRequestTask extends AsyncTask{
$converter = TypeConverter::getInstance();
$payload = ChunkSerializer::serializeFullChunk($chunk, $dimensionId, $converter->getBlockTranslator(), $this->tiles);
$stream = new ByteBufferWriter();
$stream = new BinaryStream();
PacketBatch::encodePackets($stream, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $dimensionId, $subCount, false, null, $payload)]);
$compressor = $this->compressor->deserialize();
$this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($stream->getData()));
$this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($stream->getBuffer()));
}
public function onCompletion() : void{

View File

@ -23,9 +23,8 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pmmp\encoding\Byte;
use pmmp\encoding\ByteBufferReader;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\Utils;
use function base64_decode;
use function base64_encode;
@ -131,17 +130,17 @@ final class JwtUtils{
return self::ASN1_SEQUENCE_TAG . chr(strlen($sequence)) . $sequence;
}
private static function signaturePartFromAsn1(ByteBufferReader $stream) : string{
$prefix = $stream->readByteArray(1);
private static function signaturePartFromAsn1(BinaryStream $stream) : string{
$prefix = $stream->get(1);
if($prefix !== self::ASN1_INTEGER_TAG){
throw new \InvalidArgumentException("Expected an ASN.1 INTEGER tag, got " . bin2hex($prefix));
}
//we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed
$length = Byte::readUnsigned($stream);
$length = $stream->getByte();
if($length > self::SIGNATURE_PART_LENGTH + 1){ //each part may have an extra leading 0 byte to prevent it being interpreted as a negative number
throw new \InvalidArgumentException("Expected at most 49 bytes for signature R or S, got $length");
}
$part = $stream->readByteArray($length);
$part = $stream->get($length);
return str_pad(ltrim($part, "\x00"), self::SIGNATURE_PART_LENGTH, "\x00", STR_PAD_LEFT);
}
@ -157,11 +156,11 @@ final class JwtUtils{
throw new \InvalidArgumentException("Invalid DER signature, expected $length sequence bytes, got " . strlen($parts));
}
$stream = new ByteBufferReader($parts);
$stream = new BinaryStream($parts);
$rRaw = self::signaturePartFromAsn1($stream);
$sRaw = self::signaturePartFromAsn1($stream);
if($stream->getOffset() < strlen($stream->getData())){
if(!$stream->feof()){
throw new \InvalidArgumentException("Invalid DER signature, unexpected trailing sequence data");
}

View File

@ -23,9 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pmmp\encoding\ByteBufferReader;
use pmmp\encoding\ByteBufferWriter;
use pmmp\encoding\DataDecodeException;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\event\player\PlayerDuplicateLoginEvent;
use pocketmine\event\player\PlayerResourcePackOfferEvent;
@ -73,6 +70,7 @@ use pocketmine\network\mcpe\protocol\PlayerStartItemCooldownPacket;
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\ServerboundPacket;
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
@ -111,6 +109,8 @@ use pocketmine\promise\PromiseResolver;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\BinaryDataException;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\TextFormat;
use pocketmine\world\format\io\GlobalItemDataHandlers;
@ -401,7 +401,7 @@ class NetworkSession{
}
try{
$stream = new ByteBufferReader($decompressed);
$stream = new BinaryStream($decompressed);
foreach(PacketBatch::decodeRaw($stream) as $buffer){
$this->gamePacketLimiter->decrement();
$packet = $this->packetPool->getPacket($buffer);
@ -421,7 +421,7 @@ class NetworkSession{
break;
}
}
}catch(PacketDecodeException|DataDecodeException $e){
}catch(PacketDecodeException|BinaryDataException $e){
$this->logger->logException($e);
throw PacketHandlingException::wrap($e, "Packet batch decode error");
}
@ -453,14 +453,14 @@ class NetworkSession{
$decodeTimings = Timings::getDecodeDataPacketTimings($packet);
$decodeTimings->startTiming();
try{
$stream = new ByteBufferReader($buffer);
$stream = PacketSerializer::decoder($buffer, 0);
try{
$packet->decode($stream);
}catch(PacketDecodeException $e){
throw PacketHandlingException::wrap($e);
}
if($stream->getOffset() < strlen($stream->getData())){
$remains = substr($stream->getData(), $stream->getOffset());
if(!$stream->feof()){
$remains = substr($stream->getBuffer(), $stream->getOffset());
$this->logger->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": " . bin2hex($remains));
}
}finally{
@ -478,7 +478,7 @@ class NetworkSession{
$handlerTimings->startTiming();
try{
if($this->handler === null || !$packet->handle($this->handler)){
$this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getData()));
$this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer()));
}
}finally{
$handlerTimings->stopTiming();
@ -530,10 +530,8 @@ class NetworkSession{
if($ackReceiptResolver !== null){
$this->sendBufferAckPromises[] = $ackReceiptResolver;
}
$writer = new ByteBufferWriter();
foreach($packets as $evPacket){
$writer->clear(); //memory reuse let's gooooo
$this->addToSendBuffer(self::encodePacketTimed($writer, $evPacket));
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder(), $evPacket));
}
if($immediate){
$this->flushGamePacketQueue();
@ -566,12 +564,12 @@ class NetworkSession{
/**
* @internal
*/
public static function encodePacketTimed(ByteBufferWriter $serializer, ClientboundPacket $packet) : string{
public static function encodePacketTimed(PacketSerializer $serializer, ClientboundPacket $packet) : string{
$timings = Timings::getEncodeDataPacketTimings($packet);
$timings->startTiming();
try{
$packet->encode($serializer);
return $serializer->getData();
return $serializer->getBuffer();
}finally{
$timings->stopTiming();
}
@ -593,13 +591,13 @@ class NetworkSession{
$syncMode = false;
}
$stream = new ByteBufferWriter();
$stream = new BinaryStream();
PacketBatch::encodeRaw($stream, $this->sendBuffer);
if($this->enableCompression){
$batch = $this->server->prepareBatch($stream->getData(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer);
$batch = $this->server->prepareBatch($stream->getBuffer(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer);
}else{
$batch = $stream->getData();
$batch = $stream->getBuffer();
}
$this->sendBuffer = [];
$ackPromises = $this->sendBufferAckPromises;

View File

@ -23,11 +23,12 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pmmp\encoding\ByteBufferWriter;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\BinaryStream;
use function count;
use function log;
use function spl_object_id;
@ -63,10 +64,8 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
$totalLength = 0;
$packetBuffers = [];
$writer = new ByteBufferWriter();
foreach($packets as $packet){
$writer->clear(); //memory reuse let's gooooo
$buffer = NetworkSession::encodePacketTimed($writer, $packet);
$buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder(), $packet);
//varint length prefix + packet buffer
$totalLength += (((int) log(strlen($buffer), 128)) + 1) + strlen($buffer);
$packetBuffers[] = $buffer;
@ -78,9 +77,9 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
$threshold = $compressor->getCompressionThreshold();
if(count($compressorTargets) > 1 && $threshold !== null && $totalLength >= $threshold){
//do not prepare shared batch unless we're sure it will be compressed
$stream = new ByteBufferWriter();
$stream = new BinaryStream();
PacketBatch::encodeRaw($stream, $packetBuffers);
$batchBuffer = $stream->getData();
$batchBuffer = $stream->getBuffer();
$batch = $this->server->prepareBatch($batchBuffer, $compressor, timings: Timings::$playerNetworkSendCompressBroadcast);
foreach($compressorTargets as $target){

View File

@ -27,8 +27,8 @@ use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Translatable;
use pocketmine\network\mcpe\JwtException;
use pocketmine\network\mcpe\JwtUtils;
use pocketmine\network\mcpe\protocol\types\login\legacy\LegacyAuthJwtBody;
use pocketmine\network\mcpe\protocol\types\login\SelfSignedJwtHeader;
use pocketmine\network\mcpe\protocol\types\login\JwtChainLinkBody;
use pocketmine\network\mcpe\protocol\types\login\JwtHeader;
use pocketmine\scheduler\AsyncTask;
use pocketmine\thread\NonThreadSafeValue;
use function base64_decode;
@ -128,8 +128,8 @@ class ProcessLoginTask extends AsyncTask{
$mapper->bEnforceMapType = false;
try{
/** @var SelfSignedJwtHeader $headers */
$headers = $mapper->map($headersArray, new SelfSignedJwtHeader());
/** @var JwtHeader $headers */
$headers = $mapper->map($headersArray, new JwtHeader());
}catch(\JsonMapper_Exception $e){
throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), null, 0, $e);
}
@ -172,8 +172,8 @@ class ProcessLoginTask extends AsyncTask{
$mapper->bEnforceMapType = false;
$mapper->bRemoveUndefinedAttributes = true;
try{
/** @var LegacyAuthJwtBody $claims */
$claims = $mapper->map($claimsArray, new LegacyAuthJwtBody());
/** @var JwtChainLinkBody $claims */
$claims = $mapper->map($claimsArray, new JwtChainLinkBody());
}catch(\JsonMapper_Exception $e){
throw new VerifyLoginException("Invalid chain link body: " . $e->getMessage(), null, 0, $e);
}

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\cache;
use pmmp\encoding\BE;
use pocketmine\crafting\CraftingManager;
use pocketmine\crafting\FurnaceType;
use pocketmine\crafting\ShapedRecipe;
@ -42,6 +41,7 @@ use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShaped
use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Binary;
use pocketmine\utils\SingletonTrait;
use Ramsey\Uuid\Uuid;
use function array_map;
@ -99,7 +99,7 @@ final class CraftingDataCache{
};
$recipesWithTypeIds[] = new ProtocolShapelessRecipe(
CraftingDataPacket::ENTRY_SHAPELESS,
BE::packUnsignedInt($recipeNetId), //TODO: this should probably be changed to something human-readable
Binary::writeInt($recipeNetId),
array_map($converter->coreRecipeIngredientToNet(...), $recipe->getIngredientList()),
array_map($converter->coreItemStackToNet(...), $recipe->getResults()),
$nullUUID,
@ -118,7 +118,7 @@ final class CraftingDataCache{
}
$recipesWithTypeIds[] = $r = new ProtocolShapedRecipe(
CraftingDataPacket::ENTRY_SHAPED,
BE::packUnsignedInt($recipeNetId), //TODO: this should probably be changed to something human-readable
Binary::writeInt($recipeNetId),
$inputs,
array_map($converter->coreItemStackToNet(...), $recipe->getResults()),
$nullUUID,

View File

@ -23,9 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\convert;
use pmmp\encoding\ByteBufferReader;
use pmmp\encoding\ByteBufferWriter;
use pocketmine\block\tile\Container;
use pocketmine\block\VanillaBlocks;
use pocketmine\crafting\ExactRecipeIngredient;
use pocketmine\crafting\MetaWildcardRecipeIngredient;
@ -34,17 +31,12 @@ use pocketmine\crafting\TagWildcardRecipeIngredient;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\data\bedrock\item\ItemTypeNames;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\NBT;
use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\Tag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData;
@ -60,13 +52,11 @@ use pocketmine\utils\SingletonTrait;
use pocketmine\world\format\io\GlobalBlockStateHandlers;
use pocketmine\world\format\io\GlobalItemDataHandlers;
use function get_class;
use function hash;
class TypeConverter{
use SingletonTrait;
private const PM_ID_TAG = "___Id___";
private const PM_FULL_NBT_HASH_TAG = "___FullNbtHash___";
private const RECIPE_INPUT_WILDCARD_META = 0x7fff;
@ -207,85 +197,6 @@ class TypeConverter{
return new ExactRecipeIngredient($result);
}
/**
* Strips unnecessary block actor NBT from items that have it.
* This tag can potentially be extremely large, and is not read by the client anyway.
*/
protected function stripBlockEntityNBT(CompoundTag $tag) : bool{
if(($tag->getTag(Item::TAG_BLOCK_ENTITY_TAG)) !== null){
//client doesn't use this tag, so it's fine to delete completely
$tag->removeTag(Item::TAG_BLOCK_ENTITY_TAG);
return true;
}
return false;
}
/**
* Strips non-viewable data from shulker boxes and similar blocks
* The lore for shulker boxes only requires knowing the type & count of items and possibly custom name
* We don't need to, and should not allow, sending nested inventories across the network.
*/
protected function stripContainedItemNonVisualNBT(CompoundTag $tag) : bool{
if(
($blockEntityInventoryTag = $tag->getTag(Container::TAG_ITEMS)) !== null &&
$blockEntityInventoryTag instanceof ListTag &&
$blockEntityInventoryTag->getTagType() === NBT::TAG_Compound &&
$blockEntityInventoryTag->count() > 0
){
$stripped = new ListTag();
/** @var CompoundTag $itemTag */
foreach($blockEntityInventoryTag as $itemTag){
try{
$containedItem = Item::nbtDeserialize($itemTag);
$customName = $containedItem->getCustomName();
$containedItem->clearNamedTag();
$containedItem->setCustomName($customName);
$stripped->push($containedItem->nbtSerialize());
}catch(SavedDataLoadingException){
continue;
}
}
$tag->setTag(Container::TAG_ITEMS, $stripped);
return true;
}
return false;
}
/**
* Computes a hash of an item's server-side NBT.
* This is baked into an item's network NBT to make sure the client doesn't try to stack items with the same network
* NBT but different server-side NBT.
*/
protected function hashNBT(Tag $tag) : string{
$encoded = (new LittleEndianNbtSerializer())->write(new TreeRoot($tag));
return hash('sha256', $encoded, binary: true);
}
/**
* TODO: HACK!
* Creates a copy of an item's NBT with non-viewable data stripped.
* This is a pretty yucky hack that's mainly needed because of inventories inside blockitems containing blockentity
* data. There isn't really a good way to deal with this due to the way tiles currently require a position,
* otherwise we could just keep a copy of the tile context and ask it for persistent vs network NBT as needed.
* Unfortunately, making this nice will require significant BC breaks, so this will have to do for now.
*/
protected function cleanupUnnecessaryItemNBT(CompoundTag $original) : CompoundTag{
$tag = clone $original;
$anythingStripped = false;
foreach([
$this->stripContainedItemNonVisualNBT($tag),
$this->stripBlockEntityNBT($tag)
] as $stripped){
$anythingStripped = $anythingStripped || $stripped;
}
if($anythingStripped){
$tag->setByteArray(self::PM_FULL_NBT_HASH_TAG, $this->hashNBT($original));
}
return $tag;
}
public function coreItemStackToNet(Item $itemStack) : ItemStack{
if($itemStack->isNull()){
return ItemStack::null();
@ -294,7 +205,7 @@ class TypeConverter{
if($nbt->count() === 0){
$nbt = null;
}else{
$nbt = $this->cleanupUnnecessaryItemNBT($nbt);
$nbt = clone $nbt;
}
$idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack);
@ -313,7 +224,7 @@ class TypeConverter{
$extraData = $id === $this->shieldRuntimeId ?
new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) :
new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []);
$extraDataSerializer = new ByteBufferWriter();
$extraDataSerializer = PacketSerializer::encoder();
$extraData->write($extraDataSerializer);
return new ItemStack(
@ -321,7 +232,7 @@ class TypeConverter{
$meta,
$itemStack->getCount(),
$blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
$extraDataSerializer->getData(),
$extraDataSerializer->getBuffer(),
);
}
@ -360,7 +271,7 @@ class TypeConverter{
}
public function deserializeItemStackExtraData(string $extraData, int $id) : ItemStackExtraData{
$extraDataDeserializer = new ByteBufferReader($extraData);
$extraDataDeserializer = PacketSerializer::decoder($extraData, 0);
return $id === $this->shieldRuntimeId ?
ItemStackExtraDataShield::read($extraDataDeserializer) :
ItemStackExtraData::read($extraDataDeserializer);

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\encryption;
use Crypto\Cipher;
use pmmp\encoding\LE;
use pocketmine\utils\Binary;
use function bin2hex;
use function openssl_digest;
use function openssl_error_string;
@ -104,7 +104,7 @@ class EncryptionContext{
}
private function calculateChecksum(int $counter, string $payload) : string{
$hash = openssl_digest(LE::packUnsignedLong($counter) . $payload . $this->key, self::CHECKSUM_ALGO, true);
$hash = openssl_digest(Binary::writeLLong($counter) . $payload . $this->key, self::CHECKSUM_ALGO, true);
if($hash === false){
throw new \RuntimeException("openssl_digest() error: " . openssl_error_string());
}

View File

@ -756,43 +756,6 @@ class InGamePacketHandler extends PacketHandler{
return true; //this packet is useless
}
/**
* @throws PacketHandlingException
*/
private function updateSignText(CompoundTag $nbt, string $tagName, bool $frontFace, BaseSign $block, Vector3 $pos) : bool{
$textTag = $nbt->getTag($tagName);
if(!$textTag instanceof CompoundTag){
throw new PacketHandlingException("Invalid tag type " . get_debug_type($textTag) . " for tag \"$tagName\" in sign update data");
}
$textBlobTag = $textTag->getTag(Sign::TAG_TEXT_BLOB);
if(!$textBlobTag instanceof StringTag){
throw new PacketHandlingException("Invalid tag type " . get_debug_type($textBlobTag) . " for tag \"" . Sign::TAG_TEXT_BLOB . "\" in sign update data");
}
try{
$text = SignText::fromBlob($textBlobTag->getValue());
}catch(\InvalidArgumentException $e){
throw PacketHandlingException::wrap($e, "Invalid sign text update");
}
$oldText = $block->getFaceText($frontFace);
if($text->getLines() === $oldText->getLines()){
return false;
}
try{
if(!$block->updateFaceText($this->player, $frontFace, $text)){
foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){
$this->session->sendDataPacket($updatePacket);
}
return false;
}
return true;
}catch(\UnexpectedValueException $e){
throw PacketHandlingException::wrap($e);
}
}
public function handleBlockActorData(BlockActorDataPacket $packet) : bool{
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
if($pos->distanceSquared($this->player->getLocation()) > 10000){
@ -804,9 +767,29 @@ class InGamePacketHandler extends PacketHandler{
if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
if($block instanceof BaseSign){
if(!$this->updateSignText($nbt, Sign::TAG_FRONT_TEXT, true, $block, $pos)){
//only one side can be updated at a time
$this->updateSignText($nbt, Sign::TAG_BACK_TEXT, false, $block, $pos);
$frontTextTag = $nbt->getTag(Sign::TAG_FRONT_TEXT);
if(!$frontTextTag instanceof CompoundTag){
throw new PacketHandlingException("Invalid tag type " . get_debug_type($frontTextTag) . " for tag \"" . Sign::TAG_FRONT_TEXT . "\" in sign update data");
}
$textBlobTag = $frontTextTag->getTag(Sign::TAG_TEXT_BLOB);
if(!$textBlobTag instanceof StringTag){
throw new PacketHandlingException("Invalid tag type " . get_debug_type($textBlobTag) . " for tag \"" . Sign::TAG_TEXT_BLOB . "\" in sign update data");
}
try{
$text = SignText::fromBlob($textBlobTag->getValue());
}catch(\InvalidArgumentException $e){
throw PacketHandlingException::wrap($e, "Invalid sign text update");
}
try{
if(!$block->updateText($this->player, $text)){
foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){
$this->session->sendDataPacket($updatePacket);
}
}
}catch(\UnexpectedValueException $e){
throw PacketHandlingException::wrap($e);
}
return true;

View File

@ -32,12 +32,12 @@ use pocketmine\network\mcpe\JwtException;
use pocketmine\network\mcpe\JwtUtils;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\types\login\AuthenticationData;
use pocketmine\network\mcpe\protocol\types\login\AuthenticationInfo;
use pocketmine\network\mcpe\protocol\types\login\AuthenticationType;
use pocketmine\network\mcpe\protocol\types\login\clientdata\ClientData;
use pocketmine\network\mcpe\protocol\types\login\clientdata\ClientDataToSkinDataHelper;
use pocketmine\network\mcpe\protocol\types\login\legacy\LegacyAuthChain;
use pocketmine\network\mcpe\protocol\types\login\legacy\LegacyAuthIdentityData;
use pocketmine\network\mcpe\protocol\types\login\ClientData;
use pocketmine\network\mcpe\protocol\types\login\ClientDataToSkinDataHelper;
use pocketmine\network\mcpe\protocol\types\login\JwtChain;
use pocketmine\network\PacketHandlingException;
use pocketmine\player\Player;
use pocketmine\player\PlayerInfo;
@ -180,7 +180,7 @@ class LoginPacketHandler extends PacketHandler{
/**
* @throws PacketHandlingException
*/
protected function parseJwtChain(string $chainDataJwt) : LegacyAuthChain{
protected function parseJwtChain(string $chainDataJwt) : JwtChain{
try{
$jwtChainJson = json_decode($chainDataJwt, associative: false, flags: JSON_THROW_ON_ERROR);
}catch(\JsonException $e){
@ -195,7 +195,7 @@ class LoginPacketHandler extends PacketHandler{
$mapper->bExceptionOnUndefinedProperty = true;
$mapper->bStrictObjectTypeChecking = true;
try{
$clientData = $mapper->map($jwtChainJson, new LegacyAuthChain());
$clientData = $mapper->map($jwtChainJson, new JwtChain());
}catch(\JsonMapper_Exception $e){
throw PacketHandlingException::wrap($e);
}
@ -205,8 +205,8 @@ class LoginPacketHandler extends PacketHandler{
/**
* @throws PacketHandlingException
*/
protected function fetchAuthData(LegacyAuthChain $chain) : LegacyAuthIdentityData{
/** @var LegacyAuthIdentityData|null $extraData */
protected function fetchAuthData(JwtChain $chain) : AuthenticationData{
/** @var AuthenticationData|null $extraData */
$extraData = null;
foreach($chain->chain as $jwt){
//validate every chain element
@ -229,8 +229,8 @@ class LoginPacketHandler extends PacketHandler{
$mapper->bExceptionOnUndefinedProperty = true;
$mapper->bStrictObjectTypeChecking = true;
try{
/** @var LegacyAuthIdentityData $extraData */
$extraData = $mapper->map($claims["extraData"], new LegacyAuthIdentityData());
/** @var AuthenticationData $extraData */
$extraData = $mapper->map($claims["extraData"], new AuthenticationData());
}catch(\JsonMapper_Exception $e){
throw PacketHandlingException::wrap($e);
}

View File

@ -23,16 +23,16 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\serializer;
use pmmp\encoding\Byte;
use pmmp\encoding\ByteBufferWriter;
use pmmp\encoding\VarInt;
use pocketmine\block\tile\Spawnable;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\data\bedrock\LegacyBiomeIdToStringIdMap;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\convert\BlockTranslator;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryStream;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\PalettedBlockArray;
use pocketmine\world\format\SubChunk;
@ -84,7 +84,7 @@ final class ChunkSerializer{
* @phpstan-param DimensionIds::* $dimensionId
*/
public static function serializeFullChunk(Chunk $chunk, int $dimensionId, BlockTranslator $blockTranslator, ?string $tiles = null) : string{
$stream = new ByteBufferWriter();
$stream = PacketSerializer::encoder();
$subChunkCount = self::getSubChunkCount($chunk, $dimensionId);
$writtenCount = 0;
@ -100,34 +100,37 @@ final class ChunkSerializer{
self::serializeBiomePalette($chunk->getSubChunk($y)->getBiomeArray(), $biomeIdMap, $stream);
}
Byte::writeUnsigned($stream, 0); //border block array count
$stream->putByte(0); //border block array count
//Border block entry format: 1 byte (4 bits X, 4 bits Z). These are however useless since they crash the regular client.
if($tiles !== null){
$stream->writeByteArray($tiles);
$stream->put($tiles);
}else{
$stream->writeByteArray(self::serializeTiles($chunk));
$stream->put(self::serializeTiles($chunk));
}
return $stream->getData();
return $stream->getBuffer();
}
public static function serializeSubChunk(SubChunk $subChunk, BlockTranslator $blockTranslator, ByteBufferWriter $stream, bool $persistentBlockStates) : void{
public static function serializeSubChunk(SubChunk $subChunk, BlockTranslator $blockTranslator, PacketSerializer $stream, bool $persistentBlockStates) : void{
$layers = $subChunk->getBlockLayers();
Byte::writeUnsigned($stream, 8); //version
$stream->putByte(8); //version
Byte::writeUnsigned($stream, count($layers));
$stream->putByte(count($layers));
$blockStateDictionary = $blockTranslator->getBlockStateDictionary();
foreach($layers as $blocks){
$bitsPerBlock = $blocks->getBitsPerBlock();
$words = $blocks->getWordArray();
Byte::writeUnsigned($stream, ($bitsPerBlock << 1) | ($persistentBlockStates ? 0 : 1));
$stream->writeByteArray($words);
$stream->putByte(($bitsPerBlock << 1) | ($persistentBlockStates ? 0 : 1));
$stream->put($words);
$palette = $blocks->getPalette();
if($bitsPerBlock !== 0){
VarInt::writeSignedInt($stream, count($palette)); //yes, this is intentionally zigzag
//these LSHIFT by 1 uvarints are optimizations: the client expects zigzag varints here
//but since we know they are always unsigned, we can avoid the extra fcall overhead of
//zigzag and just shift directly.
$stream->putUnsignedVarInt(count($palette) << 1); //yes, this is intentionally zigzag
}
if($persistentBlockStates){
$nbtSerializer = new NetworkNbtSerializer();
@ -138,43 +141,46 @@ final class ChunkSerializer{
$state = $blockTranslator->getFallbackStateData();
}
$stream->writeByteArray($nbtSerializer->write(new TreeRoot($state->toNbt())));
$stream->put($nbtSerializer->write(new TreeRoot($state->toNbt())));
}
}else{
//we would use writeSignedIntArray() here, but the gains of writing in batch are negated by the cost of
//allocating a temporary array for the mapped palette IDs, especially for small palettes
foreach($palette as $p){
VarInt::writeSignedInt($stream, $blockTranslator->internalIdToNetworkId($p));
$stream->put(Binary::writeUnsignedVarInt($blockTranslator->internalIdToNetworkId($p) << 1));
}
}
}
}
private static function serializeBiomePalette(PalettedBlockArray $biomePalette, LegacyBiomeIdToStringIdMap $biomeIdMap, ByteBufferWriter $stream) : void{
private static function serializeBiomePalette(PalettedBlockArray $biomePalette, LegacyBiomeIdToStringIdMap $biomeIdMap, PacketSerializer $stream) : void{
$biomePaletteBitsPerBlock = $biomePalette->getBitsPerBlock();
Byte::writeUnsigned($stream, ($biomePaletteBitsPerBlock << 1) | 1); //the last bit is non-persistence (like for blocks), though it has no effect on biomes since they always use integer IDs
$stream->writeByteArray($biomePalette->getWordArray());
$stream->putByte(($biomePaletteBitsPerBlock << 1) | 1); //the last bit is non-persistence (like for blocks), though it has no effect on biomes since they always use integer IDs
$stream->put($biomePalette->getWordArray());
//these LSHIFT by 1 uvarints are optimizations: the client expects zigzag varints here
//but since we know they are always unsigned, we can avoid the extra fcall overhead of
//zigzag and just shift directly.
$biomePaletteArray = $biomePalette->getPalette();
if($biomePaletteBitsPerBlock !== 0){
VarInt::writeSignedInt($stream, count($biomePaletteArray));
$stream->putUnsignedVarInt(count($biomePaletteArray) << 1);
}
foreach($biomePaletteArray as $p){
//we would use writeSignedIntArray() here, but the gains of writing in batch are negated by the cost of
//allocating a temporary array for the mapped palette IDs, especially for small palettes
VarInt::writeSignedInt($stream, $biomeIdMap->legacyToString($p) !== null ? $p : BiomeIds::OCEAN);
if($biomeIdMap->legacyToString($p) === null){
//make sure we aren't sending bogus biomes - the 1.18.0 client crashes if we do this
$p = BiomeIds::OCEAN;
}
$stream->put(Binary::writeUnsignedVarInt($p << 1));
}
}
public static function serializeTiles(Chunk $chunk) : string{
$stream = new ByteBufferWriter();
$stream = new BinaryStream();
foreach($chunk->getTiles() as $tile){
if($tile instanceof Spawnable){
$stream->writeByteArray($tile->getSerializedSpawnCompound()->getEncodedNbt());
$stream->put($tile->getSerializedSpawnCompound()->getEncodedNbt());
}
}
return $stream->getData();
return $stream->getBuffer();
}
}

View File

@ -27,14 +27,13 @@ declare(strict_types=1);
*/
namespace pocketmine\network\query;
use pmmp\encoding\BE;
use pmmp\encoding\Byte;
use pmmp\encoding\ByteBufferReader;
use pmmp\encoding\ByteBufferWriter;
use pmmp\encoding\DataDecodeException;
use pocketmine\network\AdvancedNetworkInterface;
use pocketmine\network\RawPacketHandler;
use pocketmine\Server;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryDataException;
use pocketmine\utils\BinaryStream;
use function chr;
use function hash;
use function random_bytes;
use function strlen;
@ -81,53 +80,51 @@ class QueryHandler implements RawPacketHandler{
}
public static function getTokenString(string $token, string $salt) : int{
return BE::unpackSignedInt(substr(hash("sha512", $salt . ":" . $token, true), 7, 4));
return Binary::readInt(substr(hash("sha512", $salt . ":" . $token, true), 7, 4));
}
public function handle(AdvancedNetworkInterface $interface, string $address, int $port, string $packet) : bool{
try{
$stream = new ByteBufferReader($packet);
$header = $stream->readByteArray(2);
$stream = new BinaryStream($packet);
$header = $stream->get(2);
if($header !== "\xfe\xfd"){ //TODO: have this filtered by the regex filter we installed above
return false;
}
$packetType = Byte::readUnsigned($stream);
$sessionID = BE::readUnsignedInt($stream);
$packetType = $stream->getByte();
$sessionID = $stream->getInt();
switch($packetType){
case self::HANDSHAKE: //Handshake
$writer = new ByteBufferWriter();
Byte::writeUnsigned($writer, self::HANDSHAKE);
BE::writeUnsignedInt($writer, $sessionID);
$writer->writeByteArray(self::getTokenString($this->token, $address) . "\x00");
$reply = chr(self::HANDSHAKE);
$reply .= Binary::writeInt($sessionID);
$reply .= self::getTokenString($this->token, $address) . "\x00";
$interface->sendRawPacket($address, $port, $writer->getData());
$interface->sendRawPacket($address, $port, $reply);
return true;
case self::STATISTICS: //Stat
$token = BE::readUnsignedInt($stream);
$token = $stream->getInt();
if($token !== ($t1 = self::getTokenString($this->token, $address)) && $token !== ($t2 = self::getTokenString($this->lastToken, $address))){
$this->logger->debug("Bad token $token from $address $port, expected $t1 or $t2");
return true;
}
$writer = new ByteBufferWriter();
Byte::writeUnsigned($writer, self::STATISTICS);
BE::writeUnsignedInt($writer, $sessionID);
$reply = chr(self::STATISTICS);
$reply .= Binary::writeInt($sessionID);
$remaining = strlen($stream->getData()) - $stream->getOffset();
if($remaining === 4){ //TODO: check this! according to the spec, this should always be here and always be FF FF FF 01
$writer->writeByteArray($this->server->getQueryInformation()->getLongQuery());
$remaining = $stream->getRemaining();
if(strlen($remaining) === 4){ //TODO: check this! according to the spec, this should always be here and always be FF FF FF 01
$reply .= $this->server->getQueryInformation()->getLongQuery();
}else{
$writer->writeByteArray($this->server->getQueryInformation()->getShortQuery());
$reply .= $this->server->getQueryInformation()->getShortQuery();
}
$interface->sendRawPacket($address, $port, $writer->getData());
$interface->sendRawPacket($address, $port, $reply);
return true;
default:
return false;
}
}catch(DataDecodeException $e){
}catch(BinaryDataException $e){
$this->logger->debug("Bad packet from $address $port: " . $e->getMessage());
return false;
}

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\network\query;
use pmmp\encoding\LE;
use pocketmine\player\GameMode;
use pocketmine\player\Player;
use pocketmine\plugin\Plugin;
use pocketmine\Server;
use pocketmine\utils\Binary;
use pocketmine\utils\Utils;
use pocketmine\YmlServerProperties;
use function array_map;
@ -236,6 +236,6 @@ final class QueryInfo{
}
public function getShortQuery() : string{
return $this->shortQueryCache ?? ($this->shortQueryCache = $this->serverName . "\x00" . $this->gametype . "\x00" . $this->map . "\x00" . $this->numPlayers . "\x00" . $this->maxPlayers . "\x00" . LE::packUnsignedShort($this->port) . $this->ip . "\x00");
return $this->shortQueryCache ?? ($this->shortQueryCache = $this->serverName . "\x00" . $this->gametype . "\x00" . $this->map . "\x00" . $this->numPlayers . "\x00" . $this->maxPlayers . "\x00" . Binary::writeLShort($this->port) . $this->ip . "\x00");
}
}

View File

@ -2838,12 +2838,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/**
* Opens the player's sign editor GUI for the sign at the given position.
* TODO: add support for editing the rear side of the sign (not currently supported due to technical limitations)
*/
public function openSignEditor(Vector3 $position, bool $frontFace = true) : void{
public function openSignEditor(Vector3 $position) : void{
$block = $this->getWorld()->getBlock($position);
if($block instanceof BaseSign){
$this->getWorld()->setBlock($position, $block->setEditorEntityRuntimeId($this->getId()));
$this->getNetworkSession()->onOpenSignEditor($position, $frontFace);
$this->getNetworkSession()->onOpenSignEditor($position, true);
}else{
throw new \InvalidArgumentException("Block at this position is not a sign");
}

View File

@ -23,10 +23,8 @@ declare(strict_types=1);
namespace pocketmine\world\format\io;
use pmmp\encoding\BE;
use pmmp\encoding\Byte;
use pmmp\encoding\ByteBufferReader;
use pmmp\encoding\ByteBufferWriter;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryStream;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\PalettedBlockArray;
use pocketmine\world\format\SubChunk;
@ -47,15 +45,15 @@ final class FastChunkSerializer{
//NOOP
}
private static function serializePalettedArray(ByteBufferWriter $stream, PalettedBlockArray $array) : void{
private static function serializePalettedArray(BinaryStream $stream, PalettedBlockArray $array) : void{
$wordArray = $array->getWordArray();
$palette = $array->getPalette();
Byte::writeUnsigned($stream, $array->getBitsPerBlock());
$stream->writeByteArray($wordArray);
$stream->putByte($array->getBitsPerBlock());
$stream->put($wordArray);
$serialPalette = pack("L*", ...$palette);
BE::writeUnsignedInt($stream, strlen($serialPalette));
$stream->writeByteArray($serialPalette);
$stream->putInt(strlen($serialPalette));
$stream->put($serialPalette);
}
/**
@ -63,20 +61,21 @@ final class FastChunkSerializer{
* TODO: tiles and entities
*/
public static function serializeTerrain(Chunk $chunk) : string{
$stream = new ByteBufferWriter();
Byte::writeUnsigned($stream, ($chunk->isPopulated() ? self::FLAG_POPULATED : 0));
$stream = new BinaryStream();
$stream->putByte(
($chunk->isPopulated() ? self::FLAG_POPULATED : 0)
);
//subchunks
$subChunks = $chunk->getSubChunks();
$count = count($subChunks);
Byte::writeUnsigned($stream, $count);
$stream->putByte($count);
foreach($subChunks as $y => $subChunk){
Byte::writeSigned($stream, $y);
BE::writeUnsignedInt($stream, $subChunk->getEmptyBlockId());
$stream->putByte($y);
$stream->putInt($subChunk->getEmptyBlockId());
$layers = $subChunk->getBlockLayers();
Byte::writeUnsigned($stream, count($layers));
$stream->putByte(count($layers));
foreach($layers as $blocks){
self::serializePalettedArray($stream, $blocks);
}
@ -84,15 +83,14 @@ final class FastChunkSerializer{
}
return $stream->getData();
return $stream->getBuffer();
}
private static function deserializePalettedArray(ByteBufferReader $stream) : PalettedBlockArray{
$bitsPerBlock = Byte::readUnsigned($stream);
$words = $stream->readByteArray(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock));
$paletteSize = BE::readUnsignedInt($stream);
private static function deserializePalettedArray(BinaryStream $stream) : PalettedBlockArray{
$bitsPerBlock = $stream->getByte();
$words = $stream->get(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock));
/** @var int[] $unpackedPalette */
$unpackedPalette = unpack("L*", $stream->readByteArray($paletteSize)); //unpack() will never fail here
$unpackedPalette = unpack("L*", $stream->get($stream->getInt())); //unpack() will never fail here
$palette = array_values($unpackedPalette);
return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
@ -102,21 +100,20 @@ final class FastChunkSerializer{
* Deserializes a fast-serialized chunk
*/
public static function deserializeTerrain(string $data) : Chunk{
$stream = new ByteBufferReader($data);
$stream = new BinaryStream($data);
$flags = Byte::readUnsigned($stream);
$flags = $stream->getByte();
$terrainPopulated = (bool) ($flags & self::FLAG_POPULATED);
$subChunks = [];
$count = Byte::readUnsigned($stream);
$count = $stream->getByte();
for($subCount = 0; $subCount < $count; ++$subCount){
$y = Byte::readSigned($stream);
//TODO: why the heck are we using big-endian here?
$airBlockId = BE::readUnsignedInt($stream);
$y = Binary::signByte($stream->getByte());
$airBlockId = $stream->getInt();
$layers = [];
for($i = 0, $layerCount = Byte::readUnsigned($stream); $i < $layerCount; ++$i){
for($i = 0, $layerCount = $stream->getByte(); $i < $layerCount; ++$i){
$layers[] = self::deserializePalettedArray($stream);
}
$biomeArray = self::deserializePalettedArray($stream);

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\world\format\io\data;
use pmmp\encoding\LE;
use pocketmine\data\bedrock\WorldDataVersions;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\NbtDataException;
@ -32,6 +31,7 @@ use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\utils\Binary;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Limits;
use pocketmine\VersionInfo;
@ -130,7 +130,7 @@ class BedrockWorldData extends BaseNbtWorldData{
$nbt = new LittleEndianNbtSerializer();
$buffer = $nbt->write(new TreeRoot($worldData));
file_put_contents(Path::join($path, "level.dat"), LE::packUnsignedInt(self::CURRENT_STORAGE_VERSION) . LE::packUnsignedInt(strlen($buffer)) . $buffer);
file_put_contents(Path::join($path, "level.dat"), Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer);
}
protected function load() : CompoundTag{
@ -208,7 +208,7 @@ class BedrockWorldData extends BaseNbtWorldData{
$nbt = new LittleEndianNbtSerializer();
$buffer = $nbt->write(new TreeRoot($this->compoundTag));
Filesystem::safeFilePutContents($this->dataPath, LE::packUnsignedInt(self::CURRENT_STORAGE_VERSION) . LE::packUnsignedInt(strlen($buffer)) . $buffer);
Filesystem::safeFilePutContents($this->dataPath, Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer);
}
public function getDifficulty() : int{

View File

@ -23,11 +23,6 @@ declare(strict_types=1);
namespace pocketmine\world\format\io\leveldb;
use pmmp\encoding\Byte;
use pmmp\encoding\ByteBufferReader;
use pmmp\encoding\ByteBufferWriter;
use pmmp\encoding\DataDecodeException;
use pmmp\encoding\LE;
use pocketmine\block\Block;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
@ -38,6 +33,9 @@ use pocketmine\nbt\NBT;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryDataException;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\Utils;
use pocketmine\VersionInfo;
use pocketmine\world\format\Chunk;
@ -151,11 +149,11 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
/**
* @throws CorruptedChunkException
*/
protected function deserializeBlockPalette(ByteBufferReader $stream, \Logger $logger) : PalettedBlockArray{
$bitsPerBlock = Byte::readUnsigned($stream) >> 1;
protected function deserializeBlockPalette(BinaryStream $stream, \Logger $logger) : PalettedBlockArray{
$bitsPerBlock = $stream->getByte() >> 1;
try{
$words = $stream->readByteArray(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock));
$words = $stream->get(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock));
}catch(\InvalidArgumentException $e){
throw new CorruptedChunkException("Failed to deserialize paletted storage: " . $e->getMessage(), 0, $e);
}
@ -176,18 +174,18 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
* - https://github.com/pmmp/PocketMine-MP/issues/5911
*/
$offset = $stream->getOffset();
$byte1 = Byte::readUnsigned($stream);
$byte1 = $stream->getByte();
$stream->setOffset($offset); //reset offset
if($byte1 !== NBT::TAG_Compound){ //normally the first byte would be the NBT of the blockstate
$susLength = LE::readUnsignedInt($stream);
$susLength = $stream->getLInt();
if($susLength !== 1){ //make sure the data isn't complete garbage
throw new CorruptedChunkException("CustomItemAPI borked 0 bpb palette should always have a length of 1");
}
$logger->error("Unexpected palette size for 0 bpb palette");
}
}else{
$paletteSize = LE::readUnsignedInt($stream);
$paletteSize = $stream->getLInt();
}
$blockDecodeErrors = [];
@ -195,7 +193,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
for($i = 0; $i < $paletteSize; ++$i){
try{
$offset = $stream->getOffset();
$blockStateNbt = $nbt->read($stream->getData(), $offset)->mustGetCompoundTag();
$blockStateNbt = $nbt->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$stream->setOffset($offset);
}catch(NbtDataException $e){
//NBT borked, unrecoverable
@ -236,20 +234,20 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
}
private function serializeBlockPalette(ByteBufferWriter $stream, PalettedBlockArray $blocks) : void{
Byte::writeUnsigned($stream, $blocks->getBitsPerBlock() << 1);
$stream->writeByteArray($blocks->getWordArray());
private function serializeBlockPalette(BinaryStream $stream, PalettedBlockArray $blocks) : void{
$stream->putByte($blocks->getBitsPerBlock() << 1);
$stream->put($blocks->getWordArray());
$palette = $blocks->getPalette();
if($blocks->getBitsPerBlock() !== 0){
LE::writeUnsignedInt($stream, count($palette));
$stream->putLInt(count($palette));
}
$tags = [];
foreach($palette as $p){
$tags[] = new TreeRoot($this->blockStateSerializer->serialize($p)->toNbt());
}
$stream->writeByteArray((new LittleEndianNbtSerializer())->writeMultiple($tags));
$stream->put((new LittleEndianNbtSerializer())->writeMultiple($tags));
}
/**
@ -269,32 +267,33 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
/**
* @throws CorruptedChunkException
*/
private static function deserializeBiomePalette(ByteBufferReader $stream, int $bitsPerBlock) : PalettedBlockArray{
private static function deserializeBiomePalette(BinaryStream $stream, int $bitsPerBlock) : PalettedBlockArray{
try{
$words = $stream->readByteArray(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock));
$words = $stream->get(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock));
}catch(\InvalidArgumentException $e){
throw new CorruptedChunkException("Failed to deserialize paletted biomes: " . $e->getMessage(), 0, $e);
}
$paletteSize = $bitsPerBlock === 0 ? 1 : LE::readUnsignedInt($stream);
$palette = [];
for($i = 0; $i < $paletteSize; $i++){
$palette[] = LE::readUnsignedInt($stream);
$paletteSize = $bitsPerBlock === 0 ? 1 : $stream->getLInt();
for($i = 0; $i < $paletteSize; ++$i){
$palette[] = $stream->getLInt();
}
//TODO: exceptions
return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
}
private static function serializeBiomePalette(ByteBufferWriter $stream, PalettedBlockArray $biomes) : void{
Byte::writeUnsigned($stream, $biomes->getBitsPerBlock() << 1);
$stream->writeByteArray($biomes->getWordArray());
private static function serializeBiomePalette(BinaryStream $stream, PalettedBlockArray $biomes) : void{
$stream->putByte($biomes->getBitsPerBlock() << 1);
$stream->put($biomes->getWordArray());
$palette = $biomes->getPalette();
if($biomes->getBitsPerBlock() !== 0){
LE::writeUnsignedInt($stream, count($palette));
$stream->putLInt(count($palette));
}
foreach($palette as $p){
LE::writeUnsignedInt($stream, $p);
$stream->putLInt($p);
}
}
@ -303,7 +302,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
* @return PalettedBlockArray[]
* @phpstan-return array<int, PalettedBlockArray>
*/
private static function deserialize3dBiomes(ByteBufferReader $stream, int $chunkVersion, \Logger $logger) : array{
private static function deserialize3dBiomes(BinaryStream $stream, int $chunkVersion, \Logger $logger) : array{
$previous = null;
$result = [];
$nextIndex = Chunk::MIN_SUBCHUNK_INDEX;
@ -311,7 +310,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$expectedCount = self::getExpected3dBiomesCount($chunkVersion);
for($i = 0; $i < $expectedCount; ++$i){
try{
$bitsPerBlock = Byte::readUnsigned($stream) >> 1;
$bitsPerBlock = $stream->getByte() >> 1;
if($bitsPerBlock === 127){
if($previous === null){
throw new CorruptedChunkException("Serialized biome palette $i has no previous palette to copy from");
@ -323,16 +322,16 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$previous = $decoded;
if($nextIndex <= Chunk::MAX_SUBCHUNK_INDEX){ //older versions wrote additional superfluous biome palettes
$result[$nextIndex++] = $decoded;
}elseif($stream->getOffset() >= strlen($stream->getData())){
}elseif($stream->feof()){
//not enough padding biome arrays for the given version - this is non-critical since we discard the excess anyway, but this should be logged
$logger->error("Wrong number of 3D biome palettes for this chunk version: expected $expectedCount, but got " . ($i + 1) . " - this is not a problem, but may indicate a corrupted chunk");
break;
}
}catch(DataDecodeException $e){
}catch(BinaryDataException $e){
throw new CorruptedChunkException("Failed to deserialize biome palette $i: " . $e->getMessage(), 0, $e);
}
}
if($stream->getOffset() < strlen($stream->getData())){
if(!$stream->feof()){
//maybe bad output produced by a third-party conversion tool like Chunker
$logger->error("Unexpected trailing data after 3D biomes data");
}
@ -343,7 +342,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
/**
* @param SubChunk[] $subChunks
*/
private static function serialize3dBiomes(ByteBufferWriter $stream, array $subChunks) : void{
private static function serialize3dBiomes(BinaryStream $stream, array $subChunks) : void{
//TODO: the server-side min/max may not coincide with the world storage min/max - we may need additional logic to handle this
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; $y++){
//TODO: is it worth trying to use the previous palette if it's the same as the current one? vanilla supports
@ -379,12 +378,12 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
/** @var PalettedBlockArray[] $extraDataLayers */
$extraDataLayers = [];
$binaryStream = new ByteBufferReader($extraRawData);
$count = LE::readUnsignedInt($binaryStream);
$binaryStream = new BinaryStream($extraRawData);
$count = $binaryStream->getLInt();
for($i = 0; $i < $count; ++$i){
$key = LE::readUnsignedInt($binaryStream);
$value = LE::readUnsignedShort($binaryStream);
$key = $binaryStream->getLInt();
$value = $binaryStream->getLShort();
self::deserializeExtraDataKey($chunkVersion, $key, $x, $fullY, $z);
@ -440,25 +439,24 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
if($legacyTerrain === false){
throw new CorruptedChunkException("Missing expected LEGACY_TERRAIN tag for format version $chunkVersion");
}
$binaryStream = new ByteBufferReader($legacyTerrain);
$binaryStream = new BinaryStream($legacyTerrain);
try{
$fullIds = $binaryStream->readByteArray(32768);
$fullData = $binaryStream->readByteArray(16384);
$binaryStream->readByteArray(32768); //legacy light info, discard it
}catch(DataDecodeException $e){
$fullIds = $binaryStream->get(32768);
$fullData = $binaryStream->get(16384);
$binaryStream->get(32768); //legacy light info, discard it
}catch(BinaryDataException $e){
throw new CorruptedChunkException($e->getMessage(), 0, $e);
}
try{
$binaryStream->readByteArray(256); //heightmap, discard it
//TODO: big endian here doesn't seem correct, but I have no way to verify
$binaryStream->get(256); //heightmap, discard it
/** @var int[] $unpackedBiomeArray */
$unpackedBiomeArray = unpack("N*", $binaryStream->readByteArray(1024)); //unpack() will never fail here
$unpackedBiomeArray = unpack("N*", $binaryStream->get(1024)); //unpack() will never fail here
$biomes3d = ChunkUtils::extrapolate3DBiomes(ChunkUtils::convertBiomeColors(array_values($unpackedBiomeArray))); //never throws
}catch(DataDecodeException $e){
}catch(BinaryDataException $e){
throw new CorruptedChunkException($e->getMessage(), 0, $e);
}
if($binaryStream->getOffset() < strlen($binaryStream->getData())){
if(!$binaryStream->feof()){
$logger->error("Unexpected trailing data in legacy terrain data");
}
@ -484,21 +482,21 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
/**
* Deserializes a subchunk stored in the legacy non-paletted format used from 1.0 until 1.2.13.
*/
private function deserializeNonPalettedSubChunkData(ByteBufferReader $binaryStream, int $chunkVersion, ?PalettedBlockArray $convertedLegacyExtraData, PalettedBlockArray $biomePalette, \Logger $logger) : SubChunk{
private function deserializeNonPalettedSubChunkData(BinaryStream $binaryStream, int $chunkVersion, ?PalettedBlockArray $convertedLegacyExtraData, PalettedBlockArray $biomePalette, \Logger $logger) : SubChunk{
try{
$blocks = $binaryStream->readByteArray(4096);
$blockData = $binaryStream->readByteArray(2048);
}catch(DataDecodeException $e){
$blocks = $binaryStream->get(4096);
$blockData = $binaryStream->get(2048);
}catch(BinaryDataException $e){
throw new CorruptedChunkException($e->getMessage(), 0, $e);
}
if($chunkVersion < ChunkVersion::v1_1_0){
try{
$binaryStream->readByteArray(4096); //legacy light info, discard it
if($binaryStream->getOffset() < strlen($binaryStream->getData())){
$binaryStream->get(4096); //legacy light info, discard it
if(!$binaryStream->feof()){
$logger->error("Unexpected trailing data in legacy subchunk data");
}
}catch(DataDecodeException $e){
}catch(BinaryDataException $e){
$logger->error("Failed to read legacy subchunk light info: " . $e->getMessage());
}
}
@ -517,7 +515,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
* @see ChunkDataKey::SUBCHUNK
* @throws CorruptedChunkException
*/
private function deserializeSubChunkData(ByteBufferReader $binaryStream, int $chunkVersion, int $subChunkVersion, ?PalettedBlockArray $convertedLegacyExtraData, PalettedBlockArray $biomePalette, \Logger $logger) : SubChunk{
private function deserializeSubChunkData(BinaryStream $binaryStream, int $chunkVersion, int $subChunkVersion, ?PalettedBlockArray $convertedLegacyExtraData, PalettedBlockArray $biomePalette, \Logger $logger) : SubChunk{
switch($subChunkVersion){
case SubChunkVersion::CLASSIC:
case SubChunkVersion::CLASSIC_BUG_2: //these are all identical to version 0, but vanilla respects these so we should also
@ -537,10 +535,10 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
case SubChunkVersion::PALETTED_MULTI_WITH_OFFSET:
//legacy extradata layers intentionally ignored because they aren't supposed to exist in v8
$storageCount = Byte::readUnsigned($binaryStream);
$storageCount = $binaryStream->getByte();
if($subChunkVersion >= SubChunkVersion::PALETTED_MULTI_WITH_OFFSET){
//height ignored; this seems pointless since this is already in the key anyway
Byte::readSigned($binaryStream);
$binaryStream->getByte();
}
$storages = [];
@ -581,11 +579,11 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
continue;
}
if($data === ""){
$binaryStream = new BinaryStream($data);
if($binaryStream->feof()){
throw new CorruptedChunkException("Unexpected empty data for subchunk $y");
}
$binaryStream = new ByteBufferReader($data);
$subChunkVersion = Byte::readUnsigned($binaryStream);;
$subChunkVersion = $binaryStream->getByte();
if($subChunkVersion < self::CURRENT_LEVEL_SUBCHUNK_VERSION){
$hasBeenUpgraded = true;
}
@ -612,27 +610,27 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
private function deserializeBiomeData(string $index, int $chunkVersion, \Logger $logger) : array{
$biomeArrays = [];
if(($maps2d = $this->db->get($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES)) !== false){
$binaryStream = new ByteBufferReader($maps2d);
$binaryStream = new BinaryStream($maps2d);
try{
$binaryStream->readByteArray(512); //heightmap, discard it
$biomes3d = ChunkUtils::extrapolate3DBiomes($binaryStream->readByteArray(256)); //never throws
if($binaryStream->getOffset() < strlen($binaryStream->getData())){
$binaryStream->get(512); //heightmap, discard it
$biomes3d = ChunkUtils::extrapolate3DBiomes($binaryStream->get(256)); //never throws
if(!$binaryStream->feof()){
$logger->error("Unexpected trailing data after 2D biome data");
}
}catch(DataDecodeException $e){
}catch(BinaryDataException $e){
throw new CorruptedChunkException($e->getMessage(), 0, $e);
}
for($i = Chunk::MIN_SUBCHUNK_INDEX; $i <= Chunk::MAX_SUBCHUNK_INDEX; ++$i){
$biomeArrays[$i] = clone $biomes3d;
}
}elseif(($maps3d = $this->db->get($index . ChunkDataKey::HEIGHTMAP_AND_3D_BIOMES)) !== false){
$binaryStream = new ByteBufferReader($maps3d);
$binaryStream = new BinaryStream($maps3d);
try{
$binaryStream->readByteArray(512);
$binaryStream->get(512);
$biomeArrays = self::deserialize3dBiomes($binaryStream, $chunkVersion, $logger);
}catch(DataDecodeException $e){
}catch(BinaryDataException $e){
throw new CorruptedChunkException($e->getMessage(), 0, $e);
}
}else{
@ -762,7 +760,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$write = new \LevelDBWriteBatch();
$write->put($index . ChunkDataKey::NEW_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
$write->put($index . ChunkDataKey::PM_DATA_VERSION, LE::packUnsignedLong(VersionInfo::WORLD_DATA_VERSION));
$write->put($index . ChunkDataKey::PM_DATA_VERSION, Binary::writeLLong(VersionInfo::WORLD_DATA_VERSION));
$subChunks = $chunkData->getSubChunks();
@ -773,26 +771,26 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
if($subChunk->isEmptyAuthoritative()){
$write->delete($key);
}else{
$subStream = new ByteBufferWriter();
Byte::writeUnsigned($subStream, self::CURRENT_LEVEL_SUBCHUNK_VERSION);
$subStream = new BinaryStream();
$subStream->putByte(self::CURRENT_LEVEL_SUBCHUNK_VERSION);
$layers = $subChunk->getBlockLayers();
Byte::writeUnsigned($subStream, count($layers));
$subStream->putByte(count($layers));
foreach($layers as $blocks){
$this->serializeBlockPalette($subStream, $blocks);
}
$write->put($key, $subStream->getData());
$write->put($key, $subStream->getBuffer());
}
}
}
if(($dirtyFlags & Chunk::DIRTY_FLAG_BIOMES) !== 0){
$write->delete($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES);
$stream = new ByteBufferWriter();
$stream->writeByteArray(str_repeat("\x00", 512)); //fake heightmap
$stream = new BinaryStream();
$stream->put(str_repeat("\x00", 512)); //fake heightmap
self::serialize3dBiomes($stream, $subChunks);
$write->put($index . ChunkDataKey::HEIGHTMAP_AND_3D_BIOMES, $stream->getData());
$write->put($index . ChunkDataKey::HEIGHTMAP_AND_3D_BIOMES, $stream->getBuffer());
}
//TODO: use this properly
@ -824,7 +822,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
public static function chunkIndex(int $chunkX, int $chunkZ) : string{
return LE::packSignedInt($chunkX) . LE::packSignedInt($chunkZ);
return Binary::writeLInt($chunkX) . Binary::writeLInt($chunkZ);
}
public function doGarbageCollection() : void{
@ -838,8 +836,8 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
public function getAllChunks(bool $skipCorrupted = false, ?\Logger $logger = null) : \Generator{
foreach($this->db->getIterator() as $key => $_){
if(strlen($key) === 9 && ($key[8] === ChunkDataKey::NEW_VERSION || $key[8] === ChunkDataKey::OLD_VERSION)){
$chunkX = LE::unpackSignedInt(substr($key, 0, 4));
$chunkZ = LE::unpackSignedInt(substr($key, 4, 4));
$chunkX = Binary::readLInt(substr($key, 0, 4));
$chunkZ = Binary::readLInt(substr($key, 4, 4));
try{
if(($chunk = $this->loadChunk($chunkX, $chunkZ)) !== null){
yield [$chunkX, $chunkZ] => $chunk;

View File

@ -23,11 +23,10 @@ declare(strict_types=1);
namespace pocketmine\world\format\io\region;
use pmmp\encoding\BE;
use pmmp\encoding\Byte;
use pmmp\encoding\ByteBufferReader;
use pmmp\encoding\DataDecodeException;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryDataException;
use pocketmine\utils\BinaryStream;
use pocketmine\world\format\ChunkException;
use pocketmine\world\format\io\exception\CorruptedChunkException;
use function assert;
@ -157,21 +156,21 @@ class RegionLoader{
if($payload === false || strlen($payload) !== $bytesToRead){
throw new CorruptedChunkException("Corrupted chunk detected (unexpected EOF, truncated or non-padded chunk found)");
}
$stream = new ByteBufferReader($payload);
$stream = new BinaryStream($payload);
try{
$length = BE::readUnsignedInt($stream);
$length = $stream->getInt();
if($length <= 0){ //TODO: if we reached here, the locationTable probably needs updating
return null;
}
$compression = Byte::readUnsigned($stream);
$compression = $stream->getByte();
if($compression !== self::COMPRESSION_ZLIB && $compression !== self::COMPRESSION_GZIP){
throw new CorruptedChunkException("Invalid compression type (got $compression, expected " . self::COMPRESSION_ZLIB . " or " . self::COMPRESSION_GZIP . ")");
}
return $stream->readByteArray($length - 1); //length prefix includes the compression byte
}catch(DataDecodeException $e){
return $stream->get($length - 1); //length prefix includes the compression byte
}catch(BinaryDataException $e){
throw new CorruptedChunkException("Corrupted chunk detected: " . $e->getMessage(), 0, $e);
}
}
@ -231,7 +230,7 @@ class RegionLoader{
/* write the chunk data into the chosen location */
fseek($this->filePointer, $newLocation->getFirstSector() << 12);
fwrite($this->filePointer, str_pad(BE::packUnsignedInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $newSize << 12, "\x00", STR_PAD_RIGHT));
fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $newSize << 12, "\x00", STR_PAD_RIGHT));
/*
* update the file header - we do this after writing the main data, so that if a failure occurs while writing,
@ -377,9 +376,9 @@ class RegionLoader{
protected function writeLocationIndex(int $index) : void{
$entry = $this->locationTable[$index];
fseek($this->filePointer, $index << 2);
fwrite($this->filePointer, BE::packUnsignedInt($entry !== null ? ($entry->getFirstSector() << 8) | $entry->getSectorCount() : 0), 4);
fwrite($this->filePointer, Binary::writeInt($entry !== null ? ($entry->getFirstSector() << 8) | $entry->getSectorCount() : 0), 4);
fseek($this->filePointer, 4096 + ($index << 2));
fwrite($this->filePointer, BE::packUnsignedInt($entry !== null ? $entry->getTimestamp() : 0), 4);
fwrite($this->filePointer, Binary::writeInt($entry !== null ? $entry->getTimestamp() : 0), 4);
clearstatcache(false, $this->filePath);
}

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\tools\generate_bedrock_data_from_packets;
use pmmp\encoding\ByteBufferReader;
use pocketmine\crafting\json\FurnaceRecipeData;
use pocketmine\crafting\json\ItemStackData;
use pocketmine\crafting\json\PotionContainerChangeRecipeData;
@ -53,6 +52,7 @@ use pocketmine\network\mcpe\protocol\CreativeContentPacket;
use pocketmine\network\mcpe\protocol\ItemRegistryPacket;
use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
@ -191,7 +191,7 @@ class ParserPacketHandler extends PacketHandler{
$rawExtraData = $itemStack->getRawExtraData();
if($rawExtraData !== ""){
$decoder = new ByteBufferReader($rawExtraData);
$decoder = PacketSerializer::decoder($rawExtraData, 0);
$extraData = $itemStringId === ItemTypeNames::SHIELD ? ItemStackExtraDataShield::read($decoder) : ItemStackExtraData::read($decoder);
$nbt = $extraData->getNbt();
if($nbt !== null && count($nbt) > 0){
@ -649,13 +649,12 @@ function main(array $argv) : int{
fwrite(STDERR, "Unknown packet on line " . ($lineNum + 1) . ": " . $parts[1]);
continue;
}
$serializer = new ByteBufferReader($raw);
$serializer = PacketSerializer::decoder($raw, 0);
$pk->decode($serializer);
$pk->handle($handler);
$remaining = strlen($serializer->getData()) - $serializer->getOffset();
if($remaining > 0){
echo "Packet on line " . ($lineNum + 1) . ": didn't read all data from " . get_class($pk) . " (stopped at offset " . $serializer->getOffset() . " of " . strlen($serializer->getData()) . " bytes): " . bin2hex($serializer->readByteArray($remaining)) . "\n";
if(!$serializer->feof()){
echo "Packet on line " . ($lineNum + 1) . ": didn't read all data from " . get_class($pk) . " (stopped at offset " . $serializer->getOffset() . " of " . strlen($serializer->getBuffer()) . " bytes): " . bin2hex($serializer->getRemaining()) . "\n";
}
}
return 0;

View File

@ -23,10 +23,9 @@ declare(strict_types=1);
namespace pocketmine\tools\ping_server;
use pmmp\encoding\ByteBufferReader;
use pmmp\encoding\ByteBufferWriter;
use pocketmine\utils\Utils;
use raklib\protocol\MessageIdentifiers;
use raklib\protocol\PacketSerializer;
use raklib\protocol\UnconnectedPing;
use raklib\protocol\UnconnectedPong;
use function bin2hex;
@ -77,9 +76,9 @@ function ping_server(\Socket $socket, string $serverIp, int $serverPort, int $ti
$ping = new UnconnectedPing();
$ping->sendPingTime = hrtime_ms();
$ping->clientId = $rakNetClientId;
$serializer = new ByteBufferWriter();
$serializer = new PacketSerializer();
$ping->encode($serializer);
if(@socket_sendto($socket, $serializer->getData(), strlen($serializer->getData()), MSG_DONTROUTE, $serverIp, $serverPort) === false){
if(@socket_sendto($socket, $serializer->getBuffer(), strlen($serializer->getBuffer()), MSG_DONTROUTE, $serverIp, $serverPort) === false){
\GlobalLogger::get()->error("Failed to send ping: " . socket_strerror(socket_last_error($socket)));
return false;
}
@ -95,7 +94,7 @@ function ping_server(\Socket $socket, string $serverIp, int $serverPort, int $ti
}
if($recvAddr === $serverIp && $recvPort === $serverPort && $recvBuffer !== "" && ord($recvBuffer[0]) === MessageIdentifiers::ID_UNCONNECTED_PONG){
$pong = new UnconnectedPong();
$pong->decode(new ByteBufferReader($recvBuffer));
$pong->decode(new PacketSerializer($recvBuffer));
\GlobalLogger::get()->info("--- Response received ---");
\GlobalLogger::get()->info("Payload: $pong->serverName");
\GlobalLogger::get()->info("Response time: " . (hrtime_ms() - $pong->sendPingTime) . " ms");