Compare commits

..

52 Commits

Author SHA1 Message Date
827731cc70 Fixup exception handling 2025-09-12 01:44:05 +01:00
0850b5b6e5 Update deps 2025-09-12 01:17:34 +01:00
8b8ce08c88 Merge branch 'minor-next' into ext-encoding 2025-09-12 01:16:28 +01:00
aee1ab0ae0 Merge branch 'stable' into minor-next 2025-09-12 01:14:16 +01:00
636b96a9a5 Updated composer dependencies 2025-09-12 01:13:55 +01:00
b459583aed cs! 2025-09-11 22:11:33 +01:00
1ea3996fb3 Stop using array-of-type functions (removed in 1.0.0) 2025-09-11 22:01:03 +01:00
527eda30c9 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/17629911301
2025-09-11 00:03:05 +00:00
937186be3a Add runtime check for ext-encoding 2025-09-11 00:13:07 +01:00
c854f2c765 ItemSerializer: avoid slow NBT check
hasNamedTag() rebuilds the NBT from item properties to decide if there's any NBT.
This is a waste of resources when we then fetch the tag to encode anyway.
2025-09-10 20:06:35 +01:00
4b4fc52cd7 Updated Bitcoin donation address 2025-09-10 19:10:56 +01:00
11862279d8 Spawning works again 2025-09-10 18:21:29 +01:00
212cc1df9a Update RakLib, spawn now almost working 2025-09-10 17:57:31 +01:00
dd022569e9 Update 2025-09-10 16:56:49 +01:00
831bb086d9 Integrate updated libraries 2025-09-10 16:36:38 +01:00
fd0555c53a Merge branch 'minor-next' into ext-encoding 2025-09-10 16:22:51 +01:00
54e8ad2a9c Update BedrockProtocol 2025-09-10 16:21:29 +01:00
9fcb16b6c1 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/17567495443
2025-09-09 00:02:53 +00:00
1541cfa90e First pass integrating ext-encoding where possible 2025-09-08 21:44:32 +01:00
ce90835c7b Bump build/php from b839e52 to 1d9cda6 (#6796) 2025-09-08 10:21:54 +00:00
2f2c53067b Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/17507094833
2025-09-06 00:02:53 +00:00
dc04992ba9 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2025-09-05 19:25:31 +01:00
bddab47ee8 Bump build/php from ce1b095 to b839e52 (#6795) 2025-09-05 10:32:17 +00:00
1bc7cf340d Merge branch 'stable' into minor-next 2025-09-04 23:30:09 +01:00
3411103e11 Remove dead PHPStan ignores 2025-09-04 23:29:55 +01:00
26cd5c471c Merge branch 'stable' into minor-next 2025-09-04 22:03:22 +01:00
1868536916 Add PHP 8.4 to test matrix 2025-09-04 21:58:12 +01:00
3999a1f9f4 Fix sneaking hitbox height (#6771) 2025-09-03 08:53:32 +02:00
925b34e5c6 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/17418990148
2025-09-03 00:02:53 +00:00
fa5cc3301c Strip unnecessary NBT from network items (#6790)
TypeConverter: Strip unnecessary NBT from clientbound items
Item containers like shulker boxes, or chests with block entity data obtained with ctrl+middle click, will often have extremely large NBT payloads.
This problem gets exponentially worse in cases where it's possible to nest inventories, as in #4665.

We can't easily address this at the core level, because tiles are not able to exist within items (due to position requirement)
so we don't have a good way to avoid this useless NBT in the first place. However, we can strip it before the item is sent to
the client, which dramatically reduces the network costs of such items, as well as removing any reason the client could have
to send such enormous items to the server.

A fully loaded shulker box with written books now takes only 5-6 KB on the wire instead of ~1 MB, which is a substantial improvement.

Dealing with this in a less hacky way is currently blocked on #6147.
2025-09-02 18:36:00 +01:00
9a0a8a55b1 Bump shivammathur/setup-php in the github-actions group (#6787) 2025-09-02 13:17:41 +00:00
b2d0be5b75 Support editing the back side of signs (#6774)
* Deprecate BaseSign get/set/updateText(), add get/set/updateFaceText() which accepts true/false for front/back
* add isFrontFace() to SignChangeEvent
* add optional frontFace to Player::openSignEditor()
* add BaseSign::getFacingDegrees() and getHitboxCenter() which need to be implemented by subclasses
2025-09-01 17:15:29 +01:00
d3c36e6287 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/17364003235
2025-09-01 00:03:50 +00:00
e569cc3275 stfu 2025-08-31 21:47:49 +01:00
c931437a30 InventoryTransaction: Remove action shuffling
we used to have this to prevent dependence on client ordering, and make ordering consistently not work.
However, since the introduction of the ItemStackRequest protocol, we don't expect to see client actions
in the wrong order anymore, so this shouldn't be needed anymore.

closes #6701
2025-08-31 21:45:55 +01:00
09cc76ae2b 5.33.2 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/17351431906
2025-08-31 02:28:17 +00:00
a540de1e3c Prepare 5.33.1 patch release (#6784) 2025-08-31 03:27:21 +01:00
9eee1a9a6e Banner: don't bail on missing type tags
we didn't set these prior to 5.33.0, so these won't be present on older worlds.
2025-08-31 03:22:58 +01:00
dca9d3a010 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/17346805858
2025-08-30 17:49:04 +00:00
f673159471 5.33.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/17346780638
2025-08-30 17:46:07 +00:00
831c5a0464 Merge pull request #6783 from pmmp/r5.33.0
Release 5.33.0
2025-08-30 18:45:11 +01:00
5c363965f0 Fix build date
we really need a better way to deal with this
2025-08-30 18:43:18 +01:00
95679b5a29 Update BedrockData and some transient deps 2025-08-30 18:36:42 +01:00
9310c46ea1 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/17336647491
2025-08-30 00:02:47 +00:00
f1b1e1977e Harden validation for server auth block breaking 2025-08-29 20:37:29 +01:00
23d612f1af Suggested additions 2025-08-29 18:49:08 +01:00
8f7e16a9ad Prepare 5.33.0 release 2025-08-29 14:11:50 +01:00
beaedc3627 Tidy up in block properties aisle 2025-08-29 13:07:09 +01:00
48ba334218 CS again :< 2025-08-29 12:33:50 +01:00
0be15a7403 Rename MultiFacing -> MultiAnyFacing
to match the trait name
2025-08-29 12:33:04 +01:00
2404d63b1f Ageable: added getMaxAge()
we'll probably need this...
2025-08-29 12:24:24 +01:00
dd9030f1f5 tools/generate-bedrock-data-from-packets: generate less noise for items
if we have only a name (the majority case), we can just return the name directly instead of an object.
this massively reduces the amount of noise in the files as seen in pmmp/BedrockData@f814036229
2025-08-28 21:15:09 +01:00
76 changed files with 1049 additions and 529 deletions

View File

@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Setup PHP and tools - name: Setup PHP and tools
uses: shivammathur/setup-php@2.35.3 uses: shivammathur/setup-php@2.35.4
with: with:
php-version: 8.2 php-version: 8.2

View File

@ -49,7 +49,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@2.35.3 uses: shivammathur/setup-php@2.35.4
with: with:
php-version: 8.2 php-version: 8.2

View File

@ -87,7 +87,7 @@ jobs:
submodules: true submodules: true
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@2.35.3 uses: shivammathur/setup-php@2.35.4
with: with:
php-version: ${{ env.PHP_VERSION }} php-version: ${{ env.PHP_VERSION }}

View File

@ -11,7 +11,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
php: ["8.1", "8.2", "8.3"] php: ["8.1", "8.2", "8.3", "8.4"]
uses: ./.github/workflows/main-php-matrix.yml uses: ./.github/workflows/main-php-matrix.yml
with: with:
@ -28,7 +28,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Setup PHP and tools - name: Setup PHP and tools
uses: shivammathur/setup-php@2.35.3 uses: shivammathur/setup-php@2.35.4
with: with:
php-version: 8.3 php-version: 8.3
tools: php-cs-fixer:3.75 tools: php-cs-fixer:3.75

View File

@ -73,7 +73,7 @@ PocketMine-MP is free, but it requires a lot of time and effort from unpaid volu
You can support development using the following methods: You can support development using the following methods:
- [Patreon](https://www.patreon.com/pocketminemp) - [Patreon](https://www.patreon.com/pocketminemp)
- Bitcoin (BTC): `171u8K9e4FtU6j3e5sqNoxKUgEw9qWQdRV` - Bitcoin (BTC): `bc1q2v5ngyf8ugyd55kqa9ep35g2rv342ueqm6ks33`
- Stellar Lumens (XLM): `GAAC5WZ33HCTE3BFJFZJXONMEIBNHFLBXM2HJVAZHXXPYA3HP5XPPS7T` - Stellar Lumens (XLM): `GAAC5WZ33HCTE3BFJFZJXONMEIBNHFLBXM2HJVAZHXXPYA3HP5XPPS7T`
Thanks for your support! Thanks for your support!

135
changelogs/5.33.md Normal file
View File

@ -0,0 +1,135 @@
# 5.33.0
Released 30th August 2025.
This is a minor feature release containing internals improvements, API improvements and new gameplay features.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## Performance
- Worlds now remember when a chunk isn't generated. This reduces world I/O during world generation.
- `BlockObjectToStateSerializer` now creates fewer objects in certain cases.
## Gameplay
- The following blocks have been added and/or are now properly supported:
- Hanging signs
- Illager banners
## Tools
- `generate-bedrock-data-from-packets.php` now represents items as strings directly when only an ID is present. This significantly improves readability in `BedrockData` and reduces file sizes.
## API
### `pocketmine\block`
- Added (and implemented) interfaces for many common block properties, to allow `instanceof` to be used:
- `Ageable`: for blocks with age, such as crops
- `AnyFacing`: for blocks which can face up, down, and horizontal directions (not the same as `HorizontalFacing`!)
- `Colored`: for blocks with 16 `DyeColor` variants
- `CoralMaterial`: for coral blocks, provides access to coral type and dead/alive
- `HorizontalFacing`: for blocks which can **only** face horizontal directions (not the same as `AnyFacing`!)
- `Lightable`: for light-source blocks which can be turned on and off, e.g. redstone lamp
- `MultiAnyFacing`: for blocks which can appear in multiple faces of the same block (including up, down, and horizontal faces), e.g. glow lichen
- `PillarRotation`: for blocks which can be oriented on an axis, e.g. logs
- `PoweredByRedstone`: for blocks which receive power from a redstone component, e.g. redstone lamp
- `SignLikeRotation`: for blocks which can be rotated 16 ways, e.g. signs, banners
- `WoodMaterial`: for blocks made from wood
- These interfaces have been implemented on many blocks. For the sake of brevity, they are not listed here, but you can expect to see them wherever the corresponding traits were used.
- The following classes have been added:
- `BaseOminousBanner`
- `CeilingCenterHangingSign` - both chains connected to the same point on the block above, can face 16 directions
- `CeilingEdgesHangingSign` - each chain connected to separate edges of the block above, can face 4 directions
- `OminousFloorBanner` - floor version of illager banner, can face 16 directions
- `OminousWallBanner` - wall version of illager banner, can face 4 directions
- `WallHangingSign` - hangs from a horizontal beam, can face 4 directions
- The following API methods have been added:
- `public ChiseledBookshelf->setSlots(list<ChiseledBookshelfSlot> $slots) : $this`
- `public static VanillaBlocks` methods:
- `ACACIA_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `ACACIA_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `ACACIA_WALL_HANGING_SIGN() : WallHangingSign`
- `BIRCH_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `BIRCH_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `BIRCH_WALL_HANGING_SIGN() : WallHangingSign`
- `CHERRY_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `CHERRY_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `CHERRY_WALL_HANGING_SIGN() : WallHangingSign`
- `CRIMSON_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `CRIMSON_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `CRIMSON_WALL_HANGING_SIGN() : WallHangingSign`
- `DARK_OAK_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `DARK_OAK_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `DARK_OAK_WALL_HANGING_SIGN() : WallHangingSign`
- `JUNGLE_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `JUNGLE_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `JUNGLE_WALL_HANGING_SIGN() : WallHangingSign`
- `MANGROVE_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `MANGROVE_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `MANGROVE_WALL_HANGING_SIGN() : WallHangingSign`
- `OAK_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `OAK_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `OAK_WALL_HANGING_SIGN() : WallHangingSign`
- `OMINOUS_FLOOR_BANNER() : OminousFloorBanner`
- `OMINOUS_WALL_BANNER() : OminousWallBanner`
- `PALE_OAK_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `PALE_OAK_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `PALE_OAK_WALL_HANGING_SIGN() : WallHangingSign`
- `SPRUCE_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `SPRUCE_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `SPRUCE_WALL_HANGING_SIGN() : WallHangingSign`
- `WARPED_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
- `WARPED_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
- `WARPED_WALL_HANGING_SIGN() : WallHangingSign`
- `public AgeableTrait->getMaxAge() : int` (included by all growable plant-like blocks, e.g. crops)
### `pocketmine\data\bedrock\block\convert`
- A new system for symmetric block serializers and deserializers has been introduced.
- This allows registering both a serializer and a deserializer with the same code, meaning way less code
- It also eliminates information duplication and potential inconsistencies, improving maintainability.
- A proper way to deal with flattened IDs (e.g. color blocks) has been introduced which _doesn't_ require hardcoding a giant mess of IDs
- This symmetric system covers 99% of blocks which have a 1:1 association between PM and vanilla blocks, or 1:N where IDs are flattened
- However, there are still some special cases which require registering separate serializers and deserializers (usually in cases where the PM implementation deviates from Mojang where Mojang's implementation sucks, such as hanging signs or big dripleaf).
- No backwards compatibility breaks are expected as a result of this change. However, it's recommended to migrate old code to this new system for maintainability.
- The following new classes have been added:
- `BlockSerializerDeserializerRegistrar` - handles unified registration of block serializers and deserializers, based on a provided block model
- `FlattenedIdModel` - represents a block with some properties baked into its Minecraft ID, e.g. coral or color blocks
- `Model` - represents a regular block with all properties in its `states` NBT
- `property\BoolFromStringProperty<TBlock>` - property mapping a bool value from a string NBT state
- `property\BoolProperty<TBlock>`
- `property\CommonProperties` - singleton containing commonly-used block property definitions and groups, e.g. facing, stair properties
- `property\EnumFromRawStateMap<TEnum, TRaw>` - maps a raw NBT value to a PHP `enum` and vice versa
- `property\IntFromRawStateMap<TRaw>` - maps a raw NBT value to PM integer constants and vice versa
- `property\IntProperty<TBlock>` - an integer range property with a min, max, and optional offset
- `property\Property<TBlock>` - interface implemented by all property definitions accepted by a `Model` or `FlattenedIdModel`
- `property\StateMap<TValue, TRaw>` - interface implemented by classes accepted by mapping properties, e.g. `BoolFromStringProperty`
- `property\StringProperty<TBlock>` - interface implemented by properties whose raw outputs are strings - these can be used as ID components in `FlattenedIdModel`
- `property\ValueFromIntProperty<TBlock, TValue>` - property mapping a generic PM value from an int NBT state
- `property\ValueFromStringProperty<TBlock, TValue>` - same as above, but for a string NBT state
- `property\ValueSetFromIntProperty<TBlock, TOption>` - a property mapping an `int[]` or `enum[]` from a set of flags in NBT states
- `property\ValueMappings` - singleton containing commonly-needed `StateMap`s
- The following classes have been deprecated:
- `BlockStateDeserializerHelper`
- `BlockStateSerializerHelper`
- The following methods have been deprecated:
- All methods for decoding mapped property types in `BlockStateReader`, e.g. `readFacingDirection()`
- All methods for encoding mapped property types in `BlockStateWriter`, e.g. `writeFacingDirection()`
- All specific blocktype mapping functions in `BlockStateToObjectDeserializer`, e.g. `mapStairs()`
- All specific blocktype mapping functions in `BlockObjectToStateSerializer`, e.g. `mapStairs()`
### `pocketmine\item`
- The following hooks have been added:
- `public Item->getPlacementTransaction(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction` - allows more complex logic for itemblocks to place blocks, without duplicating their placement conditions (used for hanging signs)
### `pocketmine\world`
- `World->setChunk()` now verifies that blockstate IDs in the provided chunk are all registered in `RuntimeBlockStateRegistry`. This should provide earlier detection for custom block registration errors by plugins.
## Internals
- `BlockStateUpgrader` is now almost entirely independent from `BlockStateData`. It's anticipated that the upgrader library will be separable from the core in the future.
- `Block->readStateFromWorld()` is now triggered on chunk load for any position containing a tile. This should allow more effective updating of blocks with properties in their tiles.
# 5.33.1
Released 31st August 2025.
## Fixes
- Fixed banners placed in prior versions getting their tiles deleted (due to missing `Type` tags).

View File

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

231
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "402ad5667b1e636a8ec6acf2f1b5f055", "content-hash": "9e8e5feec61d5dfb760c54d56a0bcc42",
"packages": [ "packages": [
{ {
"name": "adhocore/json-comment", "name": "adhocore/json-comment",
@ -204,16 +204,16 @@
}, },
{ {
"name": "pocketmine/bedrock-data", "name": "pocketmine/bedrock-data",
"version": "5.3.0+bedrock-1.21.100", "version": "6.0.0+bedrock-1.21.100",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockData.git", "url": "https://github.com/pmmp/BedrockData.git",
"reference": "5279e76261df158d5af187cfaafc1618c1da9e3f" "reference": "edc0d829175e5e1e57c87001acfd03526c63fd34"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/5279e76261df158d5af187cfaafc1618c1da9e3f", "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/edc0d829175e5e1e57c87001acfd03526c63fd34",
"reference": "5279e76261df158d5af187cfaafc1618c1da9e3f", "reference": "edc0d829175e5e1e57c87001acfd03526c63fd34",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@ -224,9 +224,9 @@
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
"support": { "support": {
"issues": "https://github.com/pmmp/BedrockData/issues", "issues": "https://github.com/pmmp/BedrockData/issues",
"source": "https://github.com/pmmp/BedrockData/tree/5.3.0+bedrock-1.21.100" "source": "https://github.com/pmmp/BedrockData/tree/6.0.0+bedrock-1.21.100"
}, },
"time": "2025-07-30T22:07:56+00:00" "time": "2025-08-30T17:25:42+00:00"
}, },
{ {
"name": "pocketmine/bedrock-item-upgrade-schema", "name": "pocketmine/bedrock-item-upgrade-schema",
@ -256,25 +256,26 @@
}, },
{ {
"name": "pocketmine/bedrock-protocol", "name": "pocketmine/bedrock-protocol",
"version": "40.0.0+bedrock-1.21.100", "version": "dev-ext-encoding",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git", "url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca" "reference": "a1698a102715a9e5bfa27cf5ef192afef30c42fe"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca", "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a1698a102715a9e5bfa27cf5ef192afef30c42fe",
"reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca", "reference": "a1698a102715a9e5bfa27cf5ef192afef30c42fe",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-encoding": "~1.0.0",
"ext-json": "*", "ext-json": "*",
"php": "^8.1", "php": "^8.1",
"pocketmine/binaryutils": "^0.2.0", "pocketmine/binaryutils": "^0.2.0",
"pocketmine/color": "^0.2.0 || ^0.3.0", "pocketmine/color": "^0.2.0 || ^0.3.0",
"pocketmine/math": "^0.3.0 || ^0.4.0 || ^1.0.0", "pocketmine/math": "^0.3.0 || ^0.4.0 || ^1.0.0",
"pocketmine/nbt": "^1.0.0", "pocketmine/nbt": "dev-ext-encoding",
"ramsey/uuid": "^4.1" "ramsey/uuid": "^4.1"
}, },
"require-dev": { "require-dev": {
@ -296,9 +297,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": { "support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues", "issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/40.0.0+bedrock-1.21.100" "source": "https://github.com/pmmp/BedrockProtocol/tree/ext-encoding"
}, },
"time": "2025-08-06T15:13:45+00:00" "time": "2025-09-11T23:11:09+00:00"
}, },
{ {
"name": "pocketmine/binaryutils", "name": "pocketmine/binaryutils",
@ -576,22 +577,22 @@
}, },
{ {
"name": "pocketmine/nbt", "name": "pocketmine/nbt",
"version": "1.1.1", "version": "dev-ext-encoding",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/NBT.git", "url": "https://github.com/pmmp/NBT.git",
"reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1" "reference": "5d6a088ca8ad8ab9f7c5415e7eacfe41e5b52721"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/c3c7b0a7295daeaf7873d90fed5c5d10381d12e1", "url": "https://api.github.com/repos/pmmp/NBT/zipball/5d6a088ca8ad8ab9f7c5415e7eacfe41e5b52721",
"reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1", "reference": "5d6a088ca8ad8ab9f7c5415e7eacfe41e5b52721",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.4 || ^8.0", "ext-encoding": "~1.0.0",
"php-64bit": "*", "php": "^8.1",
"pocketmine/binaryutils": "^0.2.0" "php-64bit": "*"
}, },
"require-dev": { "require-dev": {
"phpstan/extension-installer": "^1.0", "phpstan/extension-installer": "^1.0",
@ -612,30 +613,30 @@
"description": "PHP library for working with Named Binary Tags", "description": "PHP library for working with Named Binary Tags",
"support": { "support": {
"issues": "https://github.com/pmmp/NBT/issues", "issues": "https://github.com/pmmp/NBT/issues",
"source": "https://github.com/pmmp/NBT/tree/1.1.1" "source": "https://github.com/pmmp/NBT/tree/ext-encoding"
}, },
"time": "2025-03-09T01:46:03+00:00" "time": "2025-09-11T22:56:21+00:00"
}, },
{ {
"name": "pocketmine/raklib", "name": "pocketmine/raklib",
"version": "1.2.0", "version": "dev-ext-encoding",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/RakLib.git", "url": "https://github.com/pmmp/RakLib.git",
"reference": "a28d05216d34dbd00e8aed827a58df6b4c11510b" "reference": "f1c22f6cb5fe34c06ee2a2624525e8edb46a00d4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/a28d05216d34dbd00e8aed827a58df6b4c11510b", "url": "https://api.github.com/repos/pmmp/RakLib/zipball/f1c22f6cb5fe34c06ee2a2624525e8edb46a00d4",
"reference": "a28d05216d34dbd00e8aed827a58df6b4c11510b", "reference": "f1c22f6cb5fe34c06ee2a2624525e8edb46a00d4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-encoding": "~1.0.0",
"ext-sockets": "*", "ext-sockets": "*",
"php": "^8.1", "php": "^8.1",
"php-64bit": "*", "php-64bit": "*",
"php-ipv6": "*", "php-ipv6": "*",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/log": "^0.3.0 || ^0.4.0" "pocketmine/log": "^0.3.0 || ^0.4.0"
}, },
"require-dev": { "require-dev": {
@ -655,33 +656,33 @@
"description": "A RakNet server implementation written in PHP", "description": "A RakNet server implementation written in PHP",
"support": { "support": {
"issues": "https://github.com/pmmp/RakLib/issues", "issues": "https://github.com/pmmp/RakLib/issues",
"source": "https://github.com/pmmp/RakLib/tree/1.2.0" "source": "https://github.com/pmmp/RakLib/tree/ext-encoding"
}, },
"time": "2025-06-08T17:36:06+00:00" "time": "2025-09-11T22:56:53+00:00"
}, },
{ {
"name": "pocketmine/raklib-ipc", "name": "pocketmine/raklib-ipc",
"version": "1.0.1", "version": "dev-ext-encoding",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/RakLibIpc.git", "url": "https://github.com/pmmp/RakLibIpc.git",
"reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc" "reference": "98de061d74e005e397200977e224709bf0342382"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/ce632ef2c6743e71eddb5dc329c49af6555f90bc", "url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/98de061d74e005e397200977e224709bf0342382",
"reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc", "reference": "98de061d74e005e397200977e224709bf0342382",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-encoding": "~1.0.0",
"php": "^8.0", "php": "^8.0",
"php-64bit": "*", "php-64bit": "*",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/raklib": "^0.15.0 || ^1.0.0" "pocketmine/raklib": "^0.15.0 || ^1.0.0"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "1.10.1", "phpstan/phpstan": "2.1.0",
"phpstan/phpstan-strict-rules": "^1.0.0" "phpstan/phpstan-strict-rules": "^2.0.0"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -696,9 +697,9 @@
"description": "Channel-based protocols for inter-thread/inter-process communication with RakLib", "description": "Channel-based protocols for inter-thread/inter-process communication with RakLib",
"support": { "support": {
"issues": "https://github.com/pmmp/RakLibIpc/issues", "issues": "https://github.com/pmmp/RakLibIpc/issues",
"source": "https://github.com/pmmp/RakLibIpc/tree/1.0.1" "source": "https://github.com/pmmp/RakLibIpc/tree/ext-encoding"
}, },
"time": "2024-03-01T15:55:05+00:00" "time": "2025-09-11T22:19:12+00:00"
}, },
{ {
"name": "pocketmine/snooze", "name": "pocketmine/snooze",
@ -818,20 +819,20 @@
}, },
{ {
"name": "ramsey/uuid", "name": "ramsey/uuid",
"version": "4.9.0", "version": "4.9.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/ramsey/uuid.git", "url": "https://github.com/ramsey/uuid.git",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", "url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", "reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
"php": "^8.0", "php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0" "ramsey/collection": "^1.2 || ^2.0"
}, },
@ -890,22 +891,22 @@
], ],
"support": { "support": {
"issues": "https://github.com/ramsey/uuid/issues", "issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.9.0" "source": "https://github.com/ramsey/uuid/tree/4.9.1"
}, },
"time": "2025-06-25T14:20:11+00:00" "time": "2025-09-04T20:59:21+00:00"
}, },
{ {
"name": "symfony/filesystem", "name": "symfony/filesystem",
"version": "v6.4.13", "version": "v6.4.24",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/filesystem.git", "url": "https://github.com/symfony/filesystem.git",
"reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", "url": "https://api.github.com/repos/symfony/filesystem/zipball/75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
"reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", "reference": "75ae2edb7cdcc0c53766c30b0a2512b8df574bd8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -942,7 +943,7 @@
"description": "Provides basic utilities for the filesystem", "description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/filesystem/tree/v6.4.13" "source": "https://github.com/symfony/filesystem/tree/v6.4.24"
}, },
"funding": [ "funding": [
{ {
@ -953,27 +954,31 @@
"url": "https://github.com/fabpot", "url": "https://github.com/fabpot",
"type": "github" "type": "github"
}, },
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{ {
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-10-25T15:07:50+00:00" "time": "2025-07-10T08:14:14+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
"version": "1.13.1", "version": "1.13.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/myclabs/DeepCopy.git", "url": "https://github.com/myclabs/DeepCopy.git",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1012,7 +1017,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/myclabs/DeepCopy/issues", "issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4"
}, },
"funding": [ "funding": [
{ {
@ -1020,20 +1025,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-04-29T12:36:36+00:00" "time": "2025-08-01T08:46:24+00:00"
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v5.5.0", "version": "v5.6.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9" "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
"reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1052,7 +1057,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "5.0-dev" "dev-master": "5.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -1076,9 +1081,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1"
}, },
"time": "2025-05-31T08:24:38+00:00" "time": "2025-08-13T20:13:15+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
@ -1680,16 +1685,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "10.5.47", "version": "10.5.54",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3" "reference": "b1dbbaaf96106b76d500b9d3db51f9b01f6a3589"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b1dbbaaf96106b76d500b9d3db51f9b01f6a3589",
"reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3", "reference": "b1dbbaaf96106b76d500b9d3db51f9b01f6a3589",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1699,7 +1704,7 @@
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-xml": "*", "ext-xml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.13.1", "myclabs/deep-copy": "^1.13.4",
"phar-io/manifest": "^2.0.4", "phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1", "phar-io/version": "^3.2.1",
"php": ">=8.1", "php": ">=8.1",
@ -1716,7 +1721,7 @@
"sebastian/exporter": "^5.1.2", "sebastian/exporter": "^5.1.2",
"sebastian/global-state": "^6.0.2", "sebastian/global-state": "^6.0.2",
"sebastian/object-enumerator": "^5.0.0", "sebastian/object-enumerator": "^5.0.0",
"sebastian/recursion-context": "^5.0.0", "sebastian/recursion-context": "^5.0.1",
"sebastian/type": "^4.0.0", "sebastian/type": "^4.0.0",
"sebastian/version": "^4.0.1" "sebastian/version": "^4.0.1"
}, },
@ -1761,7 +1766,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.47" "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.54"
}, },
"funding": [ "funding": [
{ {
@ -1785,7 +1790,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-06-20T11:29:11+00:00" "time": "2025-09-11T06:19:38+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
@ -1957,16 +1962,16 @@
}, },
{ {
"name": "sebastian/comparator", "name": "sebastian/comparator",
"version": "5.0.3", "version": "5.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git", "url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2022,15 +2027,27 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues", "issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy", "security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
}, },
"funding": [ "funding": [
{ {
"url": "https://github.com/sebastianbergmann", "url": "https://github.com/sebastianbergmann",
"type": "github" "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": "2024-10-18T14:56:07+00:00" "time": "2025-09-07T05:25:07+00:00"
}, },
{ {
"name": "sebastian/complexity", "name": "sebastian/complexity",
@ -2533,23 +2550,23 @@
}, },
{ {
"name": "sebastian/recursion-context", "name": "sebastian/recursion-context",
"version": "5.0.0", "version": "5.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git", "url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "05909fb5bc7df4c52992396d0116aed689f93712" "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a",
"reference": "05909fb5bc7df4c52992396d0116aed689f93712", "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=8.1" "php": ">=8.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^10.0" "phpunit/phpunit": "^10.5"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -2584,15 +2601,28 @@
"homepage": "https://github.com/sebastianbergmann/recursion-context", "homepage": "https://github.com/sebastianbergmann/recursion-context",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues", "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1"
}, },
"funding": [ "funding": [
{ {
"url": "https://github.com/sebastianbergmann", "url": "https://github.com/sebastianbergmann",
"type": "github" "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/recursion-context",
"type": "tidelift"
} }
], ],
"time": "2023-02-03T07:05:40+00:00" "time": "2025-08-10T07:50:56+00:00"
}, },
{ {
"name": "sebastian/type", "name": "sebastian/type",
@ -2754,9 +2784,33 @@
"time": "2024-03-03T12:36:25+00:00" "time": "2024-03-03T12:36:25+00:00"
} }
], ],
"aliases": [], "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"
}
],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": {}, "stability-flags": {
"pocketmine/bedrock-protocol": 20,
"pocketmine/nbt": 20,
"pocketmine/raklib": 20,
"pocketmine/raklib-ipc": 20
},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
@ -2767,6 +2821,7 @@
"ext-ctype": "*", "ext-ctype": "*",
"ext-curl": "*", "ext-curl": "*",
"ext-date": "*", "ext-date": "*",
"ext-encoding": "~1.0.0",
"ext-gmp": "*", "ext-gmp": "*",
"ext-hash": "*", "ext-hash": "*",
"ext-igbinary": "^3.0.1", "ext-igbinary": "^3.0.1",

View File

@ -4,6 +4,8 @@ includes:
- tests/phpstan/configs/impossible-generics.neon - tests/phpstan/configs/impossible-generics.neon
- tests/phpstan/configs/php-bugs.neon - tests/phpstan/configs/php-bugs.neon
- tests/phpstan/configs/phpstan-bugs.neon - tests/phpstan/configs/phpstan-bugs.neon
- tests/phpstan/configs/property-hook-sadness.neon
- tests/phpstan/configs/reflection-class-sadness.neon
- tests/phpstan/configs/spl-fixed-array-sucks.neon - tests/phpstan/configs/spl-fixed-array-sucks.neon
- vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon - vendor/phpstan/phpstan-phpunit/rules.neon

View File

@ -98,6 +98,7 @@ namespace pocketmine {
"crypto" => "php-crypto", "crypto" => "php-crypto",
"ctype" => "ctype", "ctype" => "ctype",
"date" => "Date", "date" => "Date",
"encoding" => "pmmp/ext-encoding",
"gmp" => "GMP", "gmp" => "GMP",
"hash" => "Hash", "hash" => "Hash",
"igbinary" => "igbinary", "igbinary" => "igbinary",
@ -155,6 +156,12 @@ 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")){ if(extension_loaded("pocketmine")){
$messages[] = "The native PocketMine extension is no longer supported."; $messages[] = "The native PocketMine extension is no longer supported.";
} }

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{ final class VersionInfo{
public const NAME = "PocketMine-MP"; public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.32.2"; public const BASE_VERSION = "5.33.2";
public const IS_DEVELOPMENT_BUILD = true; public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable"; public const BUILD_CHANNEL = "stable";

View File

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@ use pocketmine\item\Item;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction; use pocketmine\world\BlockTransaction;
final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing{ final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing{
@ -65,4 +66,14 @@ final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing
$supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL || $supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL ||
(($supportBlock instanceof WallHangingSign || $supportBlock instanceof CeilingEdgesHangingSign) && Facing::axis($supportBlock->getFacing()) === Facing::axis($this->facing)); (($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,4 +48,8 @@ final class FloorSign extends BaseSign implements SignLikeRotation{
} }
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
} }
protected function getFacingDegrees() : float{
return $this->rotation * 22.5;
}
} }

View File

@ -24,8 +24,8 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper; use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\MultiAnyFacing;
use pocketmine\block\utils\MultiAnySupportTrait; use pocketmine\block\utils\MultiAnySupportTrait;
use pocketmine\block\utils\MultiFacing;
use pocketmine\block\utils\SupportType; use pocketmine\block\utils\SupportType;
use pocketmine\item\Fertilizer; use pocketmine\item\Fertilizer;
use pocketmine\item\Item; use pocketmine\item\Item;
@ -36,7 +36,7 @@ use pocketmine\world\World;
use function count; use function count;
use function shuffle; use function shuffle;
class GlowLichen extends Transparent implements MultiFacing{ class GlowLichen extends Transparent implements MultiAnyFacing{
use MultiAnySupportTrait; use MultiAnySupportTrait;
public function getLightLevel() : int{ public function getLightLevel() : int{

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\block; namespace pocketmine\block;
use pocketmine\block\utils\MultiAnyFacing;
use pocketmine\block\utils\MultiAnySupportTrait; use pocketmine\block\utils\MultiAnySupportTrait;
use pocketmine\block\utils\MultiFacing;
use pocketmine\block\utils\SupportType; use pocketmine\block\utils\SupportType;
final class ResinClump extends Transparent implements MultiFacing{ final class ResinClump extends Transparent implements MultiAnyFacing{
use MultiAnySupportTrait; use MultiAnySupportTrait;
public function isSolid() : bool{ public function isSolid() : bool{

View File

@ -91,7 +91,6 @@ class RuntimeBlockStateRegistry{
foreach(VanillaBlocks::getAll() as $block){ foreach(VanillaBlocks::getAll() as $block){
$this->register($block); $this->register($block);
} }
gc_ignore($this);
} }
/** /**

View File

@ -32,6 +32,7 @@ use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction; use pocketmine\world\BlockTransaction;
final class WallHangingSign extends BaseSign implements HorizontalFacing{ final class WallHangingSign extends BaseSign implements HorizontalFacing{
@ -78,4 +79,14 @@ final class WallHangingSign extends BaseSign implements HorizontalFacing{
($block instanceof WallHangingSign && Facing::axis(Facing::rotateY($block->getFacing(), clockwise: true)) === Facing::axis($face)) || ($block instanceof WallHangingSign && Facing::axis(Facing::rotateY($block->getFacing(), clockwise: true)) === Facing::axis($face)) ||
$block->getSupportType(Facing::opposite($face)) === SupportType::FULL; $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,6 +30,7 @@ use pocketmine\math\Axis;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\BlockTransaction; use pocketmine\world\BlockTransaction;
final class WallSign extends BaseSign implements HorizontalFacing{ final class WallSign extends BaseSign implements HorizontalFacing{
@ -46,4 +47,25 @@ final class WallSign extends BaseSign implements HorizontalFacing{
$this->facing = $face; $this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); 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

@ -82,7 +82,7 @@ class Banner extends Spawnable{
} }
} }
$this->type = $nbt->getInt(self::TAG_TYPE); $this->type = $nbt->getInt(self::TAG_TYPE, self::TYPE_NORMAL);
} }
protected function writeSaveData(CompoundTag $nbt) : void{ protected function writeSaveData(CompoundTag $nbt) : void{

View File

@ -70,16 +70,18 @@ class Sign extends Spawnable{
} }
protected SignText $text; protected SignText $text;
protected SignText $backText;
private bool $waxed = false; private bool $waxed = false;
protected ?int $editorEntityRuntimeId = null; protected ?int $editorEntityRuntimeId = null;
public function __construct(World $world, Vector3 $pos){ public function __construct(World $world, Vector3 $pos){
$this->text = new SignText(); $this->text = new SignText();
$this->backText = new SignText();
parent::__construct($world, $pos); parent::__construct($world, $pos);
} }
private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : void{ private function readTextTag(CompoundTag $nbt, bool $lightingBugResolved) : SignText{
$baseColor = new Color(0, 0, 0); $baseColor = new Color(0, 0, 0);
$glowingText = false; $glowingText = false;
if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){ if(($baseColorTag = $nbt->getTag(self::TAG_TEXT_COLOR)) instanceof IntTag){
@ -90,19 +92,27 @@ class Sign extends Spawnable{
//see https://bugs.mojang.com/browse/MCPE-117835 //see https://bugs.mojang.com/browse/MCPE-117835
$glowingText = $glowingTextTag->getValue() !== 0; $glowingText = $glowingTextTag->getValue() !== 0;
} }
$this->text = SignText::fromBlob(mb_scrub($nbt->getString(self::TAG_TEXT_BLOB), 'UTF-8'), $baseColor, $glowingText); 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);
} }
public function readSaveData(CompoundTag $nbt) : void{ public function readSaveData(CompoundTag $nbt) : void{
$frontTextTag = $nbt->getTag(self::TAG_FRONT_TEXT); $frontTextTag = $nbt->getTag(self::TAG_FRONT_TEXT);
if($frontTextTag instanceof CompoundTag){ if($frontTextTag instanceof CompoundTag){
$this->readTextTag($frontTextTag, true); $this->text = $this->readTextTag($frontTextTag, true);
}elseif($nbt->getTag(self::TAG_TEXT_BLOB) instanceof StringTag){ //MCPE 1.2 save format }elseif($nbt->getTag(self::TAG_TEXT_BLOB) instanceof StringTag){ //MCPE 1.2 save format
$lightingBugResolved = false; $lightingBugResolved = false;
if(($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag){ if(($lightingBugResolvedTag = $nbt->getTag(self::TAG_LEGACY_BUG_RESOLVE)) instanceof ByteTag){
$lightingBugResolved = $lightingBugResolvedTag->getValue() !== 0; $lightingBugResolved = $lightingBugResolvedTag->getValue() !== 0;
} }
$this->readTextTag($nbt, $lightingBugResolved); $this->text = $this->readTextTag($nbt, $lightingBugResolved);
}else{ }else{
$text = []; $text = [];
for($i = 0; $i < SignText::LINE_COUNT; ++$i){ for($i = 0; $i < SignText::LINE_COUNT; ++$i){
@ -113,22 +123,14 @@ class Sign extends Spawnable{
} }
$this->text = new SignText($text); $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; $this->waxed = $nbt->getByte(self::TAG_WAXED, 0) !== 0;
} }
protected function writeSaveData(CompoundTag $nbt) : void{ protected function writeSaveData(CompoundTag $nbt) : void{
$nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create() $nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text));
->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n")) $nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText));
->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); $nbt->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
} }
@ -141,6 +143,10 @@ class Sign extends Spawnable{
$this->text = $text; $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 isWaxed() : bool{ return $this->waxed; }
public function setWaxed(bool $waxed) : void{ $this->waxed = $waxed; } public function setWaxed(bool $waxed) : void{ $this->waxed = $waxed; }
@ -162,19 +168,8 @@ class Sign extends Spawnable{
} }
protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ protected function addAdditionalSpawnData(CompoundTag $nbt) : void{
$nbt->setTag(self::TAG_FRONT_TEXT, CompoundTag::create() $nbt->setTag(self::TAG_FRONT_TEXT, $this->writeTextTag($this->text));
->setString(self::TAG_TEXT_BLOB, rtrim(implode("\n", $this->text->getLines()), "\n")) $nbt->setTag(self::TAG_BACK_TEXT, $this->writeTextTag($this->backText));
->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->setByte(self::TAG_WAXED, $this->waxed ? 1 : 0);
$nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1); $nbt->setLong(self::TAG_LOCKED_FOR_EDITING_BY, $this->editorEntityRuntimeId ?? -1);
} }

View File

@ -27,7 +27,10 @@ interface Ageable{
public function getAge() : int; public function getAge() : int;
public function getMaxAge() : int;
/** /**
* Must be in range 0 - getMaxAge()
* @return $this * @return $this
*/ */
public function setAge(int $age) : self; public function setAge(int $age) : self;

View File

@ -38,6 +38,8 @@ trait AgeableTrait{
public function getAge() : int{ return $this->age; } public function getAge() : int{ return $this->age; }
public function getMaxAge() : int{ return self::MAX_AGE; }
/** /**
* @return $this * @return $this
*/ */

View File

@ -25,7 +25,7 @@ namespace pocketmine\block\utils;
use pocketmine\math\Facing; use pocketmine\math\Facing;
interface MultiFacing{ interface MultiAnyFacing{
/** /**
* @return int[] * @return int[]

View File

@ -91,7 +91,6 @@ class SimpleCommandMap implements CommandMap{
public function __construct(private Server $server){ public function __construct(private Server $server){
$this->setDefaultCommands(); $this->setDefaultCommands();
gc_ignore($this);
} }
private function setDefaultCommands() : void{ private function setDefaultCommands() : void{

View File

@ -23,10 +23,11 @@ declare(strict_types=1);
namespace pocketmine\crafting; namespace pocketmine\crafting;
use pmmp\encoding\ByteBufferWriter;
use pmmp\encoding\VarInt;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\nbt\LittleEndianNbtSerializer; use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\TreeRoot; use pocketmine\nbt\TreeRoot;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\DestructorCallbackTrait; use pocketmine\utils\DestructorCallbackTrait;
use pocketmine\utils\ObjectSet; use pocketmine\utils\ObjectSet;
use function array_shift; use function array_shift;
@ -97,7 +98,6 @@ class CraftingManager{
} }
}); });
} }
gc_ignore($this);
} }
/** @phpstan-return ObjectSet<\Closure() : void> */ /** @phpstan-return ObjectSet<\Closure() : void> */
@ -115,11 +115,13 @@ class CraftingManager{
} }
private static function hashOutput(Item $output) : string{ private static function hashOutput(Item $output) : string{
$write = new BinaryStream(); $write = new ByteBufferWriter();
$write->putVarInt($output->getStateId()); VarInt::writeSignedInt($write, $output->getStateId());
$write->put((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag()))); //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())));
return $write->getBuffer(); return $write->getData();
} }
/** /**

View File

@ -162,7 +162,7 @@ final class CraftingManagerFromDataHelper{
} }
$mapper = new \JsonMapper(); $mapper = new \JsonMapper();
$mapper->bStrictObjectTypeChecking = true; $mapper->bStrictObjectTypeChecking = false; //to allow hydrating ItemStackData - since this is only used for offline data it's safe
$mapper->bExceptionOnUndefinedProperty = true; $mapper->bExceptionOnUndefinedProperty = true;
$mapper->bExceptionOnMissingData = true; $mapper->bExceptionOnMissingData = true;

View File

@ -40,7 +40,6 @@ final class FurnaceRecipeManager{
private ObjectSet $recipeRegisteredCallbacks; private ObjectSet $recipeRegisteredCallbacks;
public function __construct(){ public function __construct(){
gc_ignore($this);
$this->recipeRegisteredCallbacks = new ObjectSet(); $this->recipeRegisteredCallbacks = new ObjectSet();
} }

View File

@ -23,7 +23,9 @@ declare(strict_types=1);
namespace pocketmine\crafting\json; namespace pocketmine\crafting\json;
final class ItemStackData{ use function count;
final class ItemStackData implements \JsonSerializable{
/** @required */ /** @required */
public string $name; public string $name;
@ -40,4 +42,15 @@ final class ItemStackData{
public function __construct(string $name){ public function __construct(string $name){
$this->name = $name; $this->name = $name;
} }
/**
* @return mixed[]|string
*/
public function jsonSerialize() : array|string{
$result = (array) $this;
if(count($result) === 1 && isset($result["name"])){
return $this->name;
}
return $result;
}
} }

View File

@ -118,10 +118,10 @@ use pocketmine\data\bedrock\block\convert\property\EnumFromRawStateMap;
use pocketmine\data\bedrock\block\convert\property\FlattenedCaveVinesVariant; use pocketmine\data\bedrock\block\convert\property\FlattenedCaveVinesVariant;
use pocketmine\data\bedrock\block\convert\property\IntFromRawStateMap; use pocketmine\data\bedrock\block\convert\property\IntFromRawStateMap;
use pocketmine\data\bedrock\block\convert\property\IntProperty; use pocketmine\data\bedrock\block\convert\property\IntProperty;
use pocketmine\data\bedrock\block\convert\property\OptionSetFromIntProperty;
use pocketmine\data\bedrock\block\convert\property\ValueFromIntProperty; use pocketmine\data\bedrock\block\convert\property\ValueFromIntProperty;
use pocketmine\data\bedrock\block\convert\property\ValueFromStringProperty; use pocketmine\data\bedrock\block\convert\property\ValueFromStringProperty;
use pocketmine\data\bedrock\block\convert\property\ValueMappings; use pocketmine\data\bedrock\block\convert\property\ValueMappings;
use pocketmine\data\bedrock\block\convert\property\ValueSetFromIntProperty;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use function array_map; use function array_map;
use function min; use function min;
@ -581,7 +581,7 @@ final class VanillaBlockMappings{
$reg->mapModel(Model::create(Blocks::RESIN_CLUMP(), Ids::RESIN_CLUMP)->properties([$commonProperties->multiFacingFlags])); $reg->mapModel(Model::create(Blocks::RESIN_CLUMP(), Ids::RESIN_CLUMP)->properties([$commonProperties->multiFacingFlags]));
$reg->mapModel(Model::create(Blocks::VINES(), Ids::VINE)->properties([ $reg->mapModel(Model::create(Blocks::VINES(), Ids::VINE)->properties([
new OptionSetFromIntProperty( new ValueSetFromIntProperty(
StateNames::VINE_DIRECTION_BITS, StateNames::VINE_DIRECTION_BITS,
IntFromRawStateMap::int([ IntFromRawStateMap::int([
Facing::NORTH => BlockLegacyMetadata::VINE_FLAG_NORTH, Facing::NORTH => BlockLegacyMetadata::VINE_FLAG_NORTH,
@ -1267,7 +1267,7 @@ final class VanillaBlockMappings{
$reg->mapModel(Model::create(Blocks::CHAIN(), Ids::CHAIN)->properties([$commonProperties->pillarAxis])); $reg->mapModel(Model::create(Blocks::CHAIN(), Ids::CHAIN)->properties([$commonProperties->pillarAxis]));
$reg->mapModel(Model::create(Blocks::CHISELED_BOOKSHELF(), Ids::CHISELED_BOOKSHELF)->properties([ $reg->mapModel(Model::create(Blocks::CHISELED_BOOKSHELF(), Ids::CHISELED_BOOKSHELF)->properties([
$commonProperties->horizontalFacingSWNE, $commonProperties->horizontalFacingSWNE,
new OptionSetFromIntProperty( new ValueSetFromIntProperty(
StateNames::BOOKS_STORED, StateNames::BOOKS_STORED,
EnumFromRawStateMap::int(ChiseledBookshelfSlot::class, fn(ChiseledBookshelfSlot $case) => match($case){ EnumFromRawStateMap::int(ChiseledBookshelfSlot::class, fn(ChiseledBookshelfSlot $case) => match($case){
//these are (currently) the same as the internal values, but it's best not to rely on those in case Mojang mess with the flags //these are (currently) the same as the internal values, but it's best not to rely on those in case Mojang mess with the flags

View File

@ -46,7 +46,7 @@ use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\HorizontalFacing; use pocketmine\block\utils\HorizontalFacing;
use pocketmine\block\utils\Lightable; use pocketmine\block\utils\Lightable;
use pocketmine\block\utils\MultiFacing; use pocketmine\block\utils\MultiAnyFacing;
use pocketmine\block\utils\PillarRotation; use pocketmine\block\utils\PillarRotation;
use pocketmine\block\utils\SignLikeRotation; use pocketmine\block\utils\SignLikeRotation;
use pocketmine\block\utils\SlabType; use pocketmine\block\utils\SlabType;
@ -80,8 +80,8 @@ final class CommonProperties{
/** @phpstan-var ValueFromIntProperty<AnyFacing, int> */ /** @phpstan-var ValueFromIntProperty<AnyFacing, int> */
public readonly ValueFromIntProperty $anyFacingClassic; public readonly ValueFromIntProperty $anyFacingClassic;
/** @phpstan-var OptionSetFromIntProperty<MultiFacing, int> */ /** @phpstan-var ValueSetFromIntProperty<MultiAnyFacing, int> */
public readonly OptionSetFromIntProperty $multiFacingFlags; public readonly ValueSetFromIntProperty $multiFacingFlags;
/** @phpstan-var IntProperty<SignLikeRotation> */ /** @phpstan-var IntProperty<SignLikeRotation> */
public readonly IntProperty $floorSignLikeRotation; public readonly IntProperty $floorSignLikeRotation;
@ -242,7 +242,7 @@ final class CommonProperties{
fn(AnyFacing $b, int $v) => $b->setFacing($v) fn(AnyFacing $b, int $v) => $b->setFacing($v)
); );
$this->multiFacingFlags = new OptionSetFromIntProperty( $this->multiFacingFlags = new ValueSetFromIntProperty(
StateNames::MULTI_FACE_DIRECTION_BITS, StateNames::MULTI_FACE_DIRECTION_BITS,
IntFromRawStateMap::int([ IntFromRawStateMap::int([
Facing::DOWN => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_DOWN, Facing::DOWN => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_DOWN,
@ -252,8 +252,8 @@ final class CommonProperties{
Facing::WEST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_WEST, Facing::WEST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_WEST,
Facing::EAST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_EAST Facing::EAST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_EAST
]), ]),
fn(MultiFacing $b) => $b->getFaces(), fn(MultiAnyFacing $b) => $b->getFaces(),
fn(MultiFacing $b, array $v) => $b->setFaces($v) fn(MultiAnyFacing $b, array $v) => $b->setFaces($v)
); );
$this->floorSignLikeRotation = new IntProperty(StateNames::GROUND_SIGN_DIRECTION, 0, 15, fn(SignLikeRotation $b) => $b->getRotation(), fn(SignLikeRotation $b, int $v) => $b->setRotation($v)); $this->floorSignLikeRotation = new IntProperty(StateNames::GROUND_SIGN_DIRECTION, 0, 15, fn(SignLikeRotation $b) => $b->getRotation(), fn(SignLikeRotation $b, int $v) => $b->setRotation($v));

View File

@ -32,7 +32,7 @@ use pocketmine\utils\AssumptionFailedError;
* @phpstan-template TOption of int|\UnitEnum * @phpstan-template TOption of int|\UnitEnum
* @phpstan-implements Property<TBlock> * @phpstan-implements Property<TBlock>
*/ */
class OptionSetFromIntProperty implements Property{ class ValueSetFromIntProperty implements Property{
private int $maxValue = 0; private int $maxValue = 0;

View File

@ -30,6 +30,7 @@ use pocketmine\data\bedrock\block\BlockStateStringValues;
* Internally we use null for no connections, but accepting this in the mapping code would require a fair amount of * Internally we use null for no connections, but accepting this in the mapping code would require a fair amount of
* extra complexity for this one case. This shim allows us to use the regular systems for handling walls. * extra complexity for this one case. This shim allows us to use the regular systems for handling walls.
* TODO: get rid of this in PM6 and make the internal enum have a NONE case * TODO: get rid of this in PM6 and make the internal enum have a NONE case
* @internal
*/ */
enum WallConnectionTypeShim{ enum WallConnectionTypeShim{
case NONE; case NONE;

View File

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

View File

@ -81,7 +81,6 @@ final class BlockStateUpgradeSchema{
private int $schemaId private int $schemaId
){ ){
$this->versionId = ($this->maxVersionMajor << 24) | ($this->maxVersionMinor << 16) | ($this->maxVersionPatch << 8) | $this->maxVersionRevision; $this->versionId = ($this->maxVersionMajor << 24) | ($this->maxVersionMinor << 16) | ($this->maxVersionPatch << 8) | $this->maxVersionRevision;
gc_ignore($this);
} }
/** /**

View File

@ -45,7 +45,6 @@ final class ItemDeserializer{
private BlockStateDeserializer $blockStateDeserializer private BlockStateDeserializer $blockStateDeserializer
){ ){
new ItemSerializerDeserializerRegistrar($this, null); new ItemSerializerDeserializerRegistrar($this, null);
gc_ignore($this);
} }
/** /**

View File

@ -55,7 +55,6 @@ final class ItemSerializer{
){ ){
$this->registerSpecialBlockSerializers(); $this->registerSpecialBlockSerializers();
new ItemSerializerDeserializerRegistrar(null, $this); new ItemSerializerDeserializerRegistrar(null, $this);
gc_ignore($this);
} }
/** /**
@ -118,8 +117,8 @@ final class ItemSerializer{
$data = $serializer($item); $data = $serializer($item);
} }
if($item->hasNamedTag()){ $resultTag = $item->getNamedTag();
$resultTag = $item->getNamedTag(); if($resultTag->count() > 0){
$extraTag = $data->getTag(); $extraTag = $data->getTag();
if($extraTag !== null){ if($extraTag !== null){
$resultTag = $resultTag->merge($extraTag); $resultTag = $resultTag->merge($extraTag);

View File

@ -247,6 +247,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
return $this->enderInventory; 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. * For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
*/ */

View File

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

View File

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

View File

@ -30,13 +30,11 @@ use pocketmine\inventory\transaction\action\SlotChangeAction;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use function array_keys;
use function array_values; use function array_values;
use function assert; use function assert;
use function count; use function count;
use function get_class; use function get_class;
use function min; use function min;
use function shuffle;
use function spl_object_hash; use function spl_object_hash;
use function spl_object_id; use function spl_object_id;
@ -95,10 +93,13 @@ class InventoryTransaction{
} }
/** /**
* Returns an **unordered** set of actions involved in this transaction. * Returns a set of actions involved in this transaction.
* *
* WARNING: This system is **explicitly designed NOT to care about ordering**. Any order seen in this set has NO * Note: This system is designed to care only about item balances. While you can usually assume that the actions
* significance and should not be relied on. * 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.
* *
* @return InventoryAction[] * @return InventoryAction[]
* @phpstan-return array<int, InventoryAction> * @phpstan-return array<int, InventoryAction>
@ -119,19 +120,6 @@ 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[] $needItems
* @param Item[] $haveItems * @param Item[] $haveItems
@ -308,8 +296,6 @@ class InventoryTransaction{
throw new TransactionValidationException("Transaction has already been executed"); throw new TransactionValidationException("Transaction has already been executed");
} }
$this->shuffleActions();
$this->validate(); $this->validate();
if(!$this->callExecuteEvent()){ if(!$this->callExecuteEvent()){

View File

@ -112,7 +112,6 @@ class Language{
$this->lang = self::loadLang($path, $this->langName); $this->lang = self::loadLang($path, $this->langName);
$this->fallbackLang = self::loadLang($path, $fallback); $this->fallbackLang = self::loadLang($path, $fallback);
gc_ignore($this);
} }
public function getName() : string{ public function getName() : string{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -76,7 +76,6 @@ final class BlockStateDictionary{
$this->stateDataToStateIdLookup[$name] = $stateIds; $this->stateDataToStateIdLookup[$name] = $stateIds;
} }
} }
gc_ignore($this);
} }
/** /**

View File

@ -46,9 +46,7 @@ final class ItemTranslator{
private ItemSerializer $itemSerializer, private ItemSerializer $itemSerializer,
private ItemDeserializer $itemDeserializer, private ItemDeserializer $itemDeserializer,
private BlockItemIdMap $blockItemIdMap private BlockItemIdMap $blockItemIdMap
){ ){}
gc_ignore($this->itemTypeDictionary);
}
/** /**
* @return int[]|null * @return int[]|null

View File

@ -23,6 +23,9 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\convert; namespace pocketmine\network\mcpe\convert;
use pmmp\encoding\ByteBufferReader;
use pmmp\encoding\ByteBufferWriter;
use pocketmine\block\tile\Container;
use pocketmine\block\VanillaBlocks; use pocketmine\block\VanillaBlocks;
use pocketmine\crafting\ExactRecipeIngredient; use pocketmine\crafting\ExactRecipeIngredient;
use pocketmine\crafting\MetaWildcardRecipeIngredient; use pocketmine\crafting\MetaWildcardRecipeIngredient;
@ -31,12 +34,17 @@ use pocketmine\crafting\TagWildcardRecipeIngredient;
use pocketmine\data\bedrock\BedrockDataFiles; use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\data\bedrock\item\BlockItemIdMap; use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\data\bedrock\item\ItemTypeNames; use pocketmine\data\bedrock\item\ItemTypeNames;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\item\VanillaItems; use pocketmine\item\VanillaItems;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\NBT;
use pocketmine\nbt\NbtException; use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\CompoundTag; 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\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode; use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData;
@ -52,11 +60,13 @@ use pocketmine\utils\SingletonTrait;
use pocketmine\world\format\io\GlobalBlockStateHandlers; use pocketmine\world\format\io\GlobalBlockStateHandlers;
use pocketmine\world\format\io\GlobalItemDataHandlers; use pocketmine\world\format\io\GlobalItemDataHandlers;
use function get_class; use function get_class;
use function hash;
class TypeConverter{ class TypeConverter{
use SingletonTrait; use SingletonTrait;
private const PM_ID_TAG = "___Id___"; private const PM_ID_TAG = "___Id___";
private const PM_FULL_NBT_HASH_TAG = "___FullNbtHash___";
private const RECIPE_INPUT_WILDCARD_META = 0x7fff; private const RECIPE_INPUT_WILDCARD_META = 0x7fff;
@ -197,6 +207,85 @@ class TypeConverter{
return new ExactRecipeIngredient($result); 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{ public function coreItemStackToNet(Item $itemStack) : ItemStack{
if($itemStack->isNull()){ if($itemStack->isNull()){
return ItemStack::null(); return ItemStack::null();
@ -205,7 +294,7 @@ class TypeConverter{
if($nbt->count() === 0){ if($nbt->count() === 0){
$nbt = null; $nbt = null;
}else{ }else{
$nbt = clone $nbt; $nbt = $this->cleanupUnnecessaryItemNBT($nbt);
} }
$idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack); $idMeta = $this->itemTranslator->toNetworkIdQuiet($itemStack);
@ -224,7 +313,7 @@ class TypeConverter{
$extraData = $id === $this->shieldRuntimeId ? $extraData = $id === $this->shieldRuntimeId ?
new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) : new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) :
new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []); new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []);
$extraDataSerializer = PacketSerializer::encoder(); $extraDataSerializer = new ByteBufferWriter();
$extraData->write($extraDataSerializer); $extraData->write($extraDataSerializer);
return new ItemStack( return new ItemStack(
@ -232,7 +321,7 @@ class TypeConverter{
$meta, $meta,
$itemStack->getCount(), $itemStack->getCount(),
$blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID, $blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
$extraDataSerializer->getBuffer(), $extraDataSerializer->getData(),
); );
} }
@ -271,7 +360,7 @@ class TypeConverter{
} }
public function deserializeItemStackExtraData(string $extraData, int $id) : ItemStackExtraData{ public function deserializeItemStackExtraData(string $extraData, int $id) : ItemStackExtraData{
$extraDataDeserializer = PacketSerializer::decoder($extraData, 0); $extraDataDeserializer = new ByteBufferReader($extraData);
return $id === $this->shieldRuntimeId ? return $id === $this->shieldRuntimeId ?
ItemStackExtraDataShield::read($extraDataDeserializer) : ItemStackExtraDataShield::read($extraDataDeserializer) :
ItemStackExtraData::read($extraDataDeserializer); ItemStackExtraData::read($extraDataDeserializer);

View File

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

View File

@ -719,6 +719,7 @@ class InGamePacketHandler extends PacketHandler{
case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK: case PlayerAction::CREATIVE_PLAYER_DESTROY_BLOCK:
//TODO: do we need to handle this? //TODO: do we need to handle this?
case PlayerAction::PREDICT_DESTROY_BLOCK: case PlayerAction::PREDICT_DESTROY_BLOCK:
self::validateFacing($face);
if(!$this->player->breakBlock($pos)){ if(!$this->player->breakBlock($pos)){
$this->syncBlocksNearby($pos, $face); $this->syncBlocksNearby($pos, $face);
} }
@ -755,6 +756,43 @@ class InGamePacketHandler extends PacketHandler{
return true; //this packet is useless 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{ public function handleBlockActorData(BlockActorDataPacket $packet) : bool{
$pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ()); $pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ());
if($pos->distanceSquared($this->player->getLocation()) > 10000){ if($pos->distanceSquared($this->player->getLocation()) > 10000){
@ -766,29 +804,9 @@ class InGamePacketHandler extends PacketHandler{
if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
if($block instanceof BaseSign){ if($block instanceof BaseSign){
$frontTextTag = $nbt->getTag(Sign::TAG_FRONT_TEXT); if(!$this->updateSignText($nbt, Sign::TAG_FRONT_TEXT, true, $block, $pos)){
if(!$frontTextTag instanceof CompoundTag){ //only one side can be updated at a time
throw new PacketHandlingException("Invalid tag type " . get_debug_type($frontTextTag) . " for tag \"" . Sign::TAG_FRONT_TEXT . "\" in sign update data"); $this->updateSignText($nbt, Sign::TAG_BACK_TEXT, false, $block, $pos);
}
$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; return true;

View File

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

View File

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

View File

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

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\network\query; namespace pocketmine\network\query;
use pmmp\encoding\LE;
use pocketmine\player\GameMode; use pocketmine\player\GameMode;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\plugin\Plugin; use pocketmine\plugin\Plugin;
use pocketmine\Server; use pocketmine\Server;
use pocketmine\utils\Binary;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use pocketmine\YmlServerProperties; use pocketmine\YmlServerProperties;
use function array_map; use function array_map;
@ -236,6 +236,6 @@ final class QueryInfo{
} }
public function getShortQuery() : string{ public function getShortQuery() : string{
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"); 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");
} }
} }

View File

@ -2838,13 +2838,12 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/** /**
* Opens the player's sign editor GUI for the sign at the given position. * 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) : void{ public function openSignEditor(Vector3 $position, bool $frontFace = true) : void{
$block = $this->getWorld()->getBlock($position); $block = $this->getWorld()->getBlock($position);
if($block instanceof BaseSign){ if($block instanceof BaseSign){
$this->getWorld()->setBlock($position, $block->setEditorEntityRuntimeId($this->getId())); $this->getWorld()->setBlock($position, $block->setEditorEntityRuntimeId($this->getId()));
$this->getNetworkSession()->onOpenSignEditor($position, true); $this->getNetworkSession()->onOpenSignEditor($position, $frontFace);
}else{ }else{
throw new \InvalidArgumentException("Block at this position is not a sign"); throw new \InvalidArgumentException("Block at this position is not a sign");
} }

View File

@ -34,7 +34,6 @@ trait SingletonTrait{
public static function getInstance() : self{ public static function getInstance() : self{
if(self::$instance === null){ if(self::$instance === null){
self::$instance = self::make(); self::$instance = self::make();
gc_ignore(self::$instance);
} }
return self::$instance; return self::$instance;
} }

View File

@ -485,7 +485,6 @@ class World implements ChunkManager{
private WritableWorldProvider $provider, private WritableWorldProvider $provider,
private AsyncPool $workerPool private AsyncPool $workerPool
){ ){
gc_ignore($this);
$this->folderName = $name; $this->folderName = $name;
$this->worldId = self::$worldIdCounter++; $this->worldId = self::$worldIdCounter++;

View File

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

View File

@ -56,8 +56,6 @@ final class GlobalBlockStateHandlers{
if(self::$registrar === null){ if(self::$registrar === null){
$deserializer = new BlockStateToObjectDeserializer(); $deserializer = new BlockStateToObjectDeserializer();
$serializer = new BlockObjectToStateSerializer(); $serializer = new BlockObjectToStateSerializer();
gc_ignore($deserializer);
gc_ignore($serializer);
self::$registrar = new BlockSerializerDeserializerRegistrar($deserializer, $serializer); self::$registrar = new BlockSerializerDeserializerRegistrar($deserializer, $serializer);
VanillaBlockMappings::init(self::$registrar); VanillaBlockMappings::init(self::$registrar);
} }
@ -78,17 +76,15 @@ final class GlobalBlockStateHandlers{
Path::join(BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH, 'nbt_upgrade_schema'), Path::join(BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH, 'nbt_upgrade_schema'),
PHP_INT_MAX PHP_INT_MAX
)); ));
$blockIdMetaUpgrader = BlockIdMetaUpgrader::loadFromString(
Filesystem::fileGetContents(Path::join(
BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH,
'id_meta_to_nbt/1.12.0.bin'
)),
LegacyBlockIdToStringIdMap::getInstance(),
$blockStateUpgrader
);
gc_ignore($blockIdMetaUpgrader);
self::$blockDataUpgrader = new BlockDataUpgrader( self::$blockDataUpgrader = new BlockDataUpgrader(
$blockIdMetaUpgrader, BlockIdMetaUpgrader::loadFromString(
Filesystem::fileGetContents(Path::join(
BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH,
'id_meta_to_nbt/1.12.0.bin'
)),
LegacyBlockIdToStringIdMap::getInstance(),
$blockStateUpgrader
),
$blockStateUpgrader $blockStateUpgrader
); );
} }

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@ final class AsyncGeneratorExecutor implements GeneratorExecutor{
\Logger $logger, \Logger $logger,
private readonly AsyncPool $workerPool, private readonly AsyncPool $workerPool,
private readonly GeneratorExecutorSetupParameters $setupParameters, private readonly GeneratorExecutorSetupParameters $setupParameters,
int $asyncContextId = null ?int $asyncContextId = null
){ ){
$this->logger = new \PrefixedLogger($logger, "AsyncGeneratorExecutor"); $this->logger = new \PrefixedLogger($logger, "AsyncGeneratorExecutor");

View File

@ -1158,12 +1158,6 @@ parameters:
count: 2 count: 2
path: ../../../src/world/World.php path: ../../../src/world/World.php
-
message: '#^Parameter \#2 \$x of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#'
identifier: argument.type
count: 2
path: ../../../src/world/World.php
- -
message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' message: '#^Parameter \#2 \$y of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#'
identifier: argument.type identifier: argument.type
@ -1188,12 +1182,6 @@ parameters:
count: 2 count: 2
path: ../../../src/world/World.php path: ../../../src/world/World.php
-
message: '#^Parameter \#3 \$y of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#'
identifier: argument.type
count: 2
path: ../../../src/world/World.php
- -
message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' message: '#^Parameter \#3 \$z of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#'
identifier: argument.type identifier: argument.type
@ -1218,12 +1206,6 @@ parameters:
count: 2 count: 2
path: ../../../src/world/World.php path: ../../../src/world/World.php
-
message: '#^Parameter \#4 \$z of method pocketmine\\block\\Block\:\:position\(\) expects int, float\|int given\.$#'
identifier: argument.type
count: 2
path: ../../../src/world/World.php
- -
message: '#^Method pocketmine\\world\\biome\\BiomeRegistry\:\:getBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#' message: '#^Method pocketmine\\world\\biome\\BiomeRegistry\:\:getBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#'
identifier: return.type identifier: return.type

View File

@ -168,6 +168,12 @@ parameters:
count: 1 count: 1
path: ../../../src/crafting/ShapedRecipe.php path: ../../../src/crafting/ShapedRecipe.php
-
message: '#^Offset ''name'' on \*NEVER\* in isset\(\) always exists and is not nullable\.$#'
identifier: isset.offset
count: 1
path: ../../../src/crafting/json/ItemStackData.php
- -
message: '#^Property pocketmine\\crash\\CrashDumpData\:\:\$parameters \(list\<string\>\) does not accept array\.$#' message: '#^Property pocketmine\\crash\\CrashDumpData\:\:\$parameters \(list\<string\>\) does not accept array\.$#'
identifier: assign.propertyType identifier: assign.propertyType

View File

@ -0,0 +1,61 @@
parameters:
ignoreErrors:
-
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$enderInventory because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/entity/Human.php
-
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$hungerManager because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/entity/Human.php
-
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$inventory because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/entity/Human.php
-
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$offHandInventory because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/entity/Human.php
-
message: '#^Cannot unset property pocketmine\\entity\\Human\:\:\$xpManager because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/entity/Human.php
-
message: '#^Cannot unset property pocketmine\\entity\\Living\:\:\$armorInventory because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/entity/Living.php
-
message: '#^Cannot unset property pocketmine\\entity\\Living\:\:\$effectManager because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/entity/Living.php
-
message: '#^Cannot unset property pocketmine\\player\\Player\:\:\$craftingGrid because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/player/Player.php
-
message: '#^Cannot unset property pocketmine\\player\\Player\:\:\$cursorInventory because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/player/Player.php
-
message: '#^Cannot unset property pocketmine\\world\\format\\io\\leveldb\\LevelDB\:\:\$db because it might have hooks in a subclass\.$#'
identifier: unset.possiblyHookedProperty
count: 1
path: ../../../src/world/format/io/leveldb/LevelDB.php

View File

@ -0,0 +1,31 @@
parameters:
ignoreErrors:
-
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @param for parameter \$class is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
identifier: generics.callSiteVarianceRedundant
count: 2
path: ../../phpunit/event/HandlerListManagerTest.php
-
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @param for parameter \$expect is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
identifier: generics.callSiteVarianceRedundant
count: 1
path: ../../phpunit/event/HandlerListManagerTest.php
-
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @return is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
identifier: generics.callSiteVarianceRedundant
count: 3
path: ../../phpunit/event/HandlerListManagerTest.php
-
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @var for property pocketmine\\event\\HandlerListManagerTest\:\:\$isValidFunc is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
identifier: generics.callSiteVarianceRedundant
count: 1
path: ../../phpunit/event/HandlerListManagerTest.php
-
message: '#^Call\-site variance of covariant pocketmine\\event\\Event in generic type ReflectionClass\<covariant pocketmine\\event\\Event\> in PHPDoc tag @var for property pocketmine\\event\\HandlerListManagerTest\:\:\$resolveParentFunc is redundant, template type T of object of class ReflectionClass has the same variance\.$#'
identifier: generics.callSiteVarianceRedundant
count: 2
path: ../../phpunit/event/HandlerListManagerTest.php

View File

@ -35,12 +35,12 @@ class HandlerListManagerTest extends TestCase{
/** /**
* @var \Closure * @var \Closure
* @phpstan-var \Closure(\ReflectionClass<Event>) : bool * @phpstan-var \Closure(\ReflectionClass<covariant Event>) : bool
*/ */
private $isValidFunc; private $isValidFunc;
/** /**
* @var \Closure * @var \Closure
* @phpstan-var \Closure(\ReflectionClass<Event>) : ?\ReflectionClass<Event> * @phpstan-var \Closure(\ReflectionClass<covariant Event>) : ?\ReflectionClass<covariant Event>
*/ */
private $resolveParentFunc; private $resolveParentFunc;
@ -53,7 +53,7 @@ class HandlerListManagerTest extends TestCase{
/** /**
* @return \Generator|mixed[][] * @return \Generator|mixed[][]
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, bool, string}, void, void> * @phpstan-return \Generator<int, array{\ReflectionClass<covariant Event>, bool, string}, void, void>
*/ */
public static function isValidClassProvider() : \Generator{ public static function isValidClassProvider() : \Generator{
yield [new \ReflectionClass(Event::class), false, "event base should not be handleable"]; yield [new \ReflectionClass(Event::class), false, "event base should not be handleable"];
@ -65,7 +65,7 @@ class HandlerListManagerTest extends TestCase{
/** /**
* @dataProvider isValidClassProvider * @dataProvider isValidClassProvider
* *
* @phpstan-param \ReflectionClass<Event> $class * @phpstan-param \ReflectionClass<covariant Event> $class
*/ */
public function testIsValidClass(\ReflectionClass $class, bool $isValid, string $reason) : void{ public function testIsValidClass(\ReflectionClass $class, bool $isValid, string $reason) : void{
self::assertSame($isValid, ($this->isValidFunc)($class), $reason); self::assertSame($isValid, ($this->isValidFunc)($class), $reason);
@ -73,7 +73,7 @@ class HandlerListManagerTest extends TestCase{
/** /**
* @return \Generator|\ReflectionClass[][] * @return \Generator|\ReflectionClass[][]
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, \ReflectionClass<Event>|null}, void, void> * @phpstan-return \Generator<int, array{\ReflectionClass<covariant Event>, \ReflectionClass<covariant Event>|null}, void, void>
*/ */
public static function resolveParentClassProvider() : \Generator{ public static function resolveParentClassProvider() : \Generator{
yield [new \ReflectionClass(TestConcreteExtendsAllowHandleEvent::class), new \ReflectionClass(TestAbstractAllowHandleEvent::class)]; yield [new \ReflectionClass(TestConcreteExtendsAllowHandleEvent::class), new \ReflectionClass(TestAbstractAllowHandleEvent::class)];
@ -85,8 +85,8 @@ class HandlerListManagerTest extends TestCase{
/** /**
* @dataProvider resolveParentClassProvider * @dataProvider resolveParentClassProvider
* *
* @phpstan-param \ReflectionClass<Event> $class * @phpstan-param \ReflectionClass<covariant Event> $class
* @phpstan-param \ReflectionClass<Event>|null $expect * @phpstan-param \ReflectionClass<covariant Event>|null $expect
*/ */
public function testResolveParentClass(\ReflectionClass $class, ?\ReflectionClass $expect) : void{ public function testResolveParentClass(\ReflectionClass $class, ?\ReflectionClass $expect) : void{
if($expect === null){ if($expect === null){

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\tools\generate_bedrock_data_from_packets; namespace pocketmine\tools\generate_bedrock_data_from_packets;
use pmmp\encoding\ByteBufferReader;
use pocketmine\crafting\json\FurnaceRecipeData; use pocketmine\crafting\json\FurnaceRecipeData;
use pocketmine\crafting\json\ItemStackData; use pocketmine\crafting\json\ItemStackData;
use pocketmine\crafting\json\PotionContainerChangeRecipeData; use pocketmine\crafting\json\PotionContainerChangeRecipeData;
@ -52,7 +53,6 @@ use pocketmine\network\mcpe\protocol\CreativeContentPacket;
use pocketmine\network\mcpe\protocol\ItemRegistryPacket; use pocketmine\network\mcpe\protocol\ItemRegistryPacket;
use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; 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\StartGamePacket;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry; use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
@ -191,7 +191,7 @@ class ParserPacketHandler extends PacketHandler{
$rawExtraData = $itemStack->getRawExtraData(); $rawExtraData = $itemStack->getRawExtraData();
if($rawExtraData !== ""){ if($rawExtraData !== ""){
$decoder = PacketSerializer::decoder($rawExtraData, 0); $decoder = new ByteBufferReader($rawExtraData);
$extraData = $itemStringId === ItemTypeNames::SHIELD ? ItemStackExtraDataShield::read($decoder) : ItemStackExtraData::read($decoder); $extraData = $itemStringId === ItemTypeNames::SHIELD ? ItemStackExtraDataShield::read($decoder) : ItemStackExtraData::read($decoder);
$nbt = $extraData->getNbt(); $nbt = $extraData->getNbt();
if($nbt !== null && count($nbt) > 0){ if($nbt !== null && count($nbt) > 0){
@ -209,11 +209,18 @@ class ParserPacketHandler extends PacketHandler{
return $data; return $data;
} }
/** private static function objectToOrderedArray(object $object) : mixed{
* @return mixed[] if($object instanceof \JsonSerializable){
*/ $result = $object->jsonSerialize();
private static function objectToOrderedArray(object $object) : array{ if(is_object($result)){
$result = (array) ($object instanceof \JsonSerializable ? $object->jsonSerialize() : $object); $result = (array) $result;
}elseif(!is_array($result)){
return $result;
}
}else{
$result = (array) $object;
}
ksort($result, SORT_STRING); ksort($result, SORT_STRING);
foreach(Utils::promoteKeys($result) as $property => $value){ foreach(Utils::promoteKeys($result) as $property => $value){
@ -280,7 +287,7 @@ class ParserPacketHandler extends PacketHandler{
file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n"); file_put_contents($this->bedrockDataPath . '/required_item_list.json', json_encode($table, JSON_PRETTY_PRINT) . "\n");
echo "updating item registry\n"; echo "updating item registry\n";
$items = array_map(function(ItemTypeEntry $entry) : array{ $items = array_map(function(ItemTypeEntry $entry) : mixed{
return self::objectToOrderedArray($entry); return self::objectToOrderedArray($entry);
}, $packet->getEntries()); }, $packet->getEntries());
file_put_contents($this->bedrockDataPath . '/item_registry.json', json_encode($items, JSON_PRETTY_PRINT) . "\n"); file_put_contents($this->bedrockDataPath . '/item_registry.json', json_encode($items, JSON_PRETTY_PRINT) . "\n");
@ -642,12 +649,13 @@ function main(array $argv) : int{
fwrite(STDERR, "Unknown packet on line " . ($lineNum + 1) . ": " . $parts[1]); fwrite(STDERR, "Unknown packet on line " . ($lineNum + 1) . ": " . $parts[1]);
continue; continue;
} }
$serializer = PacketSerializer::decoder($raw, 0); $serializer = new ByteBufferReader($raw);
$pk->decode($serializer); $pk->decode($serializer);
$pk->handle($handler); $pk->handle($handler);
if(!$serializer->feof()){ $remaining = strlen($serializer->getData()) - $serializer->getOffset();
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"; 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";
} }
} }
return 0; return 0;

View File

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