Compare commits

..

85 Commits

Author SHA1 Message Date
0eb751c1c9 Release 4.12.9 2023-01-16 18:25:17 +00:00
7e16f9be8f InGamePacketHandler: handle block actions before use item transactions
the START_BREAK and transaction to break the block may arrive in the same packet, causing events to be fired in the wrong order.

fixes #5490
2023-01-16 17:50:11 +00:00
768650cee0 CS 2023-01-16 17:46:50 +00:00
c2c529e2da Include Snooze interrupts in timings results
fixes #5511

This requires any Timings instances to be updated to
pmmp/timings@5410f62436, otherwise the TPS
reported will be incorrect.
2023-01-16 17:40:39 +00:00
ba18a81e88 NetworkSession: fixed rate limit getting exhausted after 2.5 seconds during PvP 2023-01-13 15:28:03 +00:00
39218017ca Fixed walls and thin blocks not connecting to each other
closes #5498
2023-01-12 22:16:41 +00:00
f4a1d69075 Bell: fixed support requirements
this somehow got overlooked in the support types refactor.
2023-01-12 21:45:25 +00:00
cbeae906e1 Torch: remove unused variable 2023-01-12 21:34:44 +00:00
b25e8e26f0 BaseBanner: fixed incorrect support requirements 2023-01-12 21:31:50 +00:00
a79be994de World: fixed block placement when clicking on replaceable blocks
in vanilla, it appears to behave as if the player always clicked on the up face if a block was replaced.

In PM, we were still using the original face, which caused bugs when, for example, placing a button next to a wall by clicking on the side of tallgrass. The button would replace the tallgrass, but stick to the wall, instead of placing itself on the ground like vanilla expects.

This may appear unusual to anyone who also happens to implement canBePlacedAt(), since the facing behaviour will be different. However, this behaviour appears to match vanilla, and even slabs (which I feared might break because of this change) work perfectly.

In the future, it may be desirable to pass some other value here, such as null, to indicate that the clicked block is being replaced. However, that's a BC break and therefore outside of the scope of a stable bug fix.
2023-01-12 21:11:48 +00:00
e26c8b9e9f block: eliminate suspicious usages of $blockClicked in place() 2023-01-12 20:35:26 +00:00
4e9c3e101d Bell: fixed blocks not being able to be placed when not ringing the bell 2023-01-12 19:42:33 +00:00
d295e1be54 PressurePlate: destroy self when no support is present 2023-01-12 19:36:47 +00:00
2f3fcef97c Fixed blocks incorrectly using blockClicked for support checks
this caused some interesting bugs, such as being able to place floating pressure plates by clicking on the side of a solid block halfway up a wall.
2023-01-12 19:36:23 +00:00
c5056e0a43 phpstan 1.9.11 2023-01-12 19:01:57 +00:00
a47aa50477 Update composer dependencies 2023-01-12 18:59:18 +00:00
f7930a3a0b 4.12.9 is next 2023-01-09 00:04:40 +00:00
bb7df60a4d Release 4.12.8 2023-01-09 00:04:40 +00:00
992cb06da6 NetworkSession: fixed rate limit not being increased correctly on Windows
due to the 15ms scheduler interval, the server will often sleep 45ms instead of 50ms, which causes the budget not to get updated.
2023-01-09 00:01:56 +00:00
bb3f87f862 NetworkSession: allow 2 batches per tick
apparently InventoryTransactionPacket may arrive outside of the normal update cycle, since it's prioritized to reduce latency.
2023-01-09 00:00:39 +00:00
fc77b14760 4.12.8 is next 2023-01-08 20:23:18 +00:00
52b6f1a492 Release 4.12.7 2023-01-08 20:23:15 +00:00
0233e74f4f NetworkSession: micro optimisation - do not check if a debugger is active unless the packet limit is exceeded 2023-01-08 19:45:14 +00:00
dd355c58d8 NetworkSession: fix CS 2023-01-08 19:35:43 +00:00
267032cff9 NetworkSession: do not rate limit packets if a debugging session is active 2023-01-08 19:31:39 +00:00
6cecd690b2 CS 2023-01-07 21:33:53 +00:00
91e38d1f97 NetworkSession: compensate for server lag in batch budgeting 2023-01-07 21:33:34 +00:00
653178c1fd 4.12.7 is next 2023-01-07 16:22:01 +00:00
eb06535ed1 Release 4.12.6 2023-01-07 16:22:01 +00:00
f9bcc8e862 NetworkSession: added a rate-limit for incoming batches 2023-01-07 16:19:02 +00:00
f43ca405d4 4.12.6 is next 2023-01-06 00:44:58 +00:00
d146175d27 Release 4.12.5 2023-01-06 00:44:58 +00:00
3baa5ab712 InGamePacketHandler: removed obsolete workaround 2023-01-06 00:41:57 +00:00
c02bead12b Bump phpstan/phpstan from 1.9.6 to 1.9.7 (#5495)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.9.6 to 1.9.7.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.9.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.9.6...1.9.7)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-05 16:46:45 +00:00
e9ca25c1cb typo 2023-01-05 16:45:56 +00:00
a513cca582 Stop Dependabot from creating PRs for locale-data minor and major versions 2023-01-05 16:45:22 +00:00
9a47c1d401 Bump shivammathur/setup-php from 2.22.0 to 2.23.0 (#5474)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.22.0 to 2.23.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.22.0...2.23.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-04 20:15:11 +00:00
433f5451d7 Improve World::notifyNeighbourBlockUpdate() doc (#5491) 2023-01-04 20:11:55 +00:00
64e505defb Bump phpstan/phpstan from 1.9.4 to 1.9.6 (#5492)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.9.4 to 1.9.6.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.9.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.9.4...1.9.6)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-04 20:05:34 +00:00
33d1755eae Player: fixed not receiving slot updates for small crafting grid
I'm not really sure why this tracking logic is here and not in InventoryManager, but for now it is what it is.
2023-01-04 01:28:25 +00:00
fc63c54116 CraftingManager: more detailed type information for shapelessRecipes and shapedRecipes 2023-01-04 00:32:26 +00:00
db07976aab TypeConverter: do not send useless meta to the client 2023-01-03 23:50:31 +00:00
0e4b79ea77 InGamePacketHandler: fixed client-side predictions not getting rolled back for block placement 2023-01-03 19:43:21 +00:00
588c9b114b 4.12.5 is next 2023-01-03 19:30:02 +00:00
b095f606c1 Release 4.12.4 2023-01-03 19:29:47 +00:00
b312e93176 Limit list max size in transactions
this duct tape is to limit the impact of a security vulnerability being actively exploited.
2023-01-02 22:59:48 +00:00
61933624d2 NetworkSession: Lift batch limit to 1300 to allow shift-click crafting to work correctly
the theoretical limit for transactions in this case is 64x9 (inputs) + 64x9 (output on crafting grid) + 64 (outputs to main slot) + 64 CraftingEventPackets = 1280.
This is an extreme case which assumes that a recipe could generate up to 64x10 (640) output items per iteration, filling every slot of the output grid, which should never occur in any reasonable circumstances.
2022-12-30 22:11:35 +00:00
90beaeaeb4 4.12.4 is next 2022-12-28 21:01:50 +00:00
466f7e98ed Release 4.12.3 2022-12-28 21:01:47 +00:00
59be901efe Fixed unauthenticated sessions taking up player slots 2022-12-28 20:42:33 +00:00
5d2b0acfc8 GamemodeCommand: report failure if the target's game mode is already the desired game mode
this has irritated me for years.
2022-12-22 18:38:10 +00:00
7d1d62042c attempted fix for GitHub rate limiting php-cs-fixer installation
from https://github.com/shivammathur/setup-php/issues/678#issuecomment-1363128626
2022-12-22 18:13:03 +00:00
17dde140a5 Bump phpstan/phpstan-phpunit from 1.3.2 to 1.3.3 (#5465)
Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 1.3.2 to 1.3.3.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/1.3.2...1.3.3)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-22 15:23:10 +00:00
1e5597f0d5 World: account for null chunk edge case in tickChunk()
the target chunk may no longer be loaded if it was unloaded during a previous chunk's tick (e.g. during BlockGrowEvent).
Since the parent function iterates over a pre-selected array of chunks, the chunk will still be present in the list even if it's no longer loaded by the time it's reached.
2022-12-19 20:20:52 +00:00
97ef209c5f HandlerList: added missing class-string type for constructor 2022-12-19 16:26:36 +00:00
1305fd5fb2 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2022-12-19 15:52:56 +00:00
7a137f932f Updated build/php submodule to pmmp/php-build-scripts@6b605ed7c4 2022-12-19 15:52:44 +00:00
63e8b1cf3a Bump phpstan/phpstan from 1.9.3 to 1.9.4 (#5458)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.9.3 to 1.9.4.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.9.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.9.3...1.9.4)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-19 15:39:59 +00:00
65bce762ff Updated transient Composer dependencies 2022-12-19 15:34:17 +00:00
529700bb8b PotionTypeIdMap: fixed uninitialized fields
I have no idea why this didn't ever cause a problem before... probably an edge-case in the typed properties implementation
2022-12-19 15:33:15 +00:00
437fa615b8 4.12.3 is next 2022-12-15 21:08:28 +00:00
0ee6cdb058 Release 4.12.2 2022-12-15 21:08:23 +00:00
97d6a79b25 Updated BedrockProtocol 2022-12-15 20:37:36 +00:00
8b5e4c1c16 Updated PHPStan 2022-12-15 20:16:56 +00:00
214a5ddc15 Bump netresearch/jsonmapper from 4.0.0 to 4.1.0 (#5446)
Bumps [netresearch/jsonmapper](https://github.com/cweiske/jsonmapper) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/cweiske/jsonmapper/releases)
- [Changelog](https://github.com/cweiske/jsonmapper/blob/master/ChangeLog)
- [Commits](https://github.com/cweiske/jsonmapper/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: netresearch/jsonmapper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-15 19:28:11 +00:00
9f7dfe3355 Bump phpunit/phpunit from 9.5.26 to 9.5.27 (#5447)
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.5.26 to 9.5.27.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/main/ChangeLog-9.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.5.26...9.5.27)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-15 19:21:39 +00:00
9b55d18393 Bump ncipollo/release-action from 1.11.2 to 1.12.0 (#5451)
Bumps [ncipollo/release-action](https://github.com/ncipollo/release-action) from 1.11.2 to 1.12.0.
- [Release notes](https://github.com/ncipollo/release-action/releases)
- [Commits](https://github.com/ncipollo/release-action/compare/v1.11.2...v1.12.0)

---
updated-dependencies:
- dependency-name: ncipollo/release-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-15 19:20:28 +00:00
31465525e3 Fixed PHP-CS-Fixer not import global constants. (#5449) 2022-12-12 17:12:33 +00:00
74613b9b09 Bump phpstan/phpstan-phpunit from 1.2.2 to 1.3.0 (#5445)
Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/1.2.2...1.3.0)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-phpunit
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-10 21:17:03 +00:00
1cefe24414 InGamePacketHandler: fixed transaction predictions not getting rolled back for failed use/release/interact transactions
this was caused by b5cfab497d.
2022-12-10 21:16:08 +00:00
8bf85d4a18 bootstrap: avoid using is_writable() to check for data directory access permissions
closes #5440
2022-12-06 14:13:37 +00:00
b5e6dec0c6 Filesystem: capture error information from attempted lock file creation 2022-12-06 14:06:08 +00:00
a3306914cc bootstrap: do not require the plugins dir to be writable
on a modern installation, the plugins folder does not need to be writable by the server. Perhaps DevTools should be checking this for /genplugin, but it's not required otherwise.
2022-12-06 13:58:50 +00:00
3b32ea1b0b Update PhpStorm code style settings 2022-12-06 13:22:54 +00:00
7ec32f981e Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2022-12-06 13:22:15 +00:00
b0c87e9d06 Added phpdoc_align rule for php-cs-fixer 2022-12-06 13:21:57 +00:00
99996b62d6 Align PhpDoc @param tags according to PHP-CS-Fixer 2022-12-06 13:21:20 +00:00
1cb6d9f5af Bump dessant/support-requests from 2 to 3 (#5442)
Bumps [dessant/support-requests](https://github.com/dessant/support-requests) from 2 to 3.
- [Release notes](https://github.com/dessant/support-requests/releases)
- [Changelog](https://github.com/dessant/support-requests/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dessant/support-requests/compare/v2...v3)

---
updated-dependencies:
- dependency-name: dessant/support-requests
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-05 21:10:09 +00:00
0a9b52618d 4.12.2 is next 2022-12-04 23:11:48 +00:00
7ae6425d05 Release 4.12.1 2022-12-04 23:11:48 +00:00
b5cfab497d Clean up inventory content syncing, fixes #5441
these remnants should have been cleaned up in 4.11, but I somehow managed to skip over them.
2022-12-04 23:05:30 +00:00
774e23137e changelog: added detail about InventoryManager internals changes
this isn't considered part of the API, but certain specialized popular plugins (e.g. InvMenu) require it.
2022-12-01 20:48:38 +00:00
43bc3c7b25 Added tool to dump JSON contents of encoded crashdump data 2022-12-01 19:36:31 +00:00
eb62dc3294 Added documentation for RegistryTrait, EnumTrait and CloningRegistryTrait 2022-11-30 21:04:17 +00:00
279056fe2f 4.12.1 is next 2022-11-30 16:35:58 +00:00
96 changed files with 710 additions and 423 deletions

View File

@ -6,6 +6,12 @@ updates:
interval: daily
time: "10:00"
open-pull-requests-limit: 10
ignore:
#only allow patch updates for locale-data - this has to be updated manually due to codegen
- dependency-name: pocketmine/locale-data
update-types:
- "version-update:semver-major"
- "version-update:semver-minor"
- package-ecosystem: gitsubmodule
directory: "/"

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@2.23.0
with:
php-version: 8.0

View File

@ -18,7 +18,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@2.23.0
with:
php-version: 8.0
@ -69,7 +69,7 @@ jobs:
${{ github.workspace }}/build_info.json
- name: Create draft release
uses: ncipollo/release-action@v1.11.2
uses: ncipollo/release-action@v1.12.0
with:
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json
commit: ${{ github.sha }}

View File

@ -195,10 +195,12 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.22.0
uses: shivammathur/setup-php@2.23.0
with:
php-version: 8.0
tools: php-cs-fixer:3.11
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run PHP-CS-Fixer
run: php-cs-fixer fix --dry-run --diff --ansi

View File

@ -8,7 +8,7 @@ jobs:
support:
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v2
- uses: dessant/support-requests@v3
with:
github-token: ${{ github.token }}
support-label: "Support request"

View File

@ -62,6 +62,11 @@
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Shell Script">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="neon">
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />

View File

@ -66,6 +66,9 @@ BODY,
],
'indentation_type' => true,
'logical_operators' => true,
'native_constant_invocation' => [
'scope' => 'namespaced'
],
'native_function_invocation' => [
'scope' => 'namespaced',
'include' => ['@all'],
@ -92,6 +95,12 @@ BODY,
],
'sort_algorithm' => 'alpha'
],
'phpdoc_align' => [
'align' => 'vertical',
'tags' => [
'param',
]
],
'phpdoc_line_span' => [
'property' => 'single',
'method' => null,

View File

@ -39,6 +39,7 @@ use function sprintf;
use function str_replace;
use function substr;
use const SORT_STRING;
use const STDERR;
if(count($argv) !== 2){
fwrite(STDERR, "Provide a path to process\n");

View File

@ -40,12 +40,13 @@ use function rtrim;
use function sprintf;
use function str_replace;
use function unlink;
use const DIRECTORY_SEPARATOR;
use const PHP_EOL;
require dirname(__DIR__) . '/vendor/autoload.php';
/**
* @param string[] $strings
* @param string[] $strings
*
* @return string[]
*/

View File

@ -95,6 +95,10 @@ Released 25th November 2022.
- Improved performance of `ContainerTrait` dropping items on block destroy. ([link](https://github.com/pmmp/PocketMine-MP/commits/24e72ec109c1442b09558df89b6833cf2f2e0ec7))
- Avoid repeated calls to `Position->getWorld()` (use local variables). ([link](https://github.com/pmmp/PocketMine-MP/commit/2940547026db40ce76deb46e992870de3ead79ad))
- Revamped the way `InventoryManager` handles fake inventory slot mappings for stuff like crafting tables. ([link](https://github.com/pmmp/PocketMine-MP/commit/e90abecf38d9c57635fa0497514bba7e546a2469))
- Inventories are now mapped on a per-slot basis. This means that more than one inventory can be mapped to the same window ID, which is necessary for correctly handling "UI" inventories like crafting tables.
- `InventoryManager->getWindow(int $windowId) : ?Inventory` is replaced by `locateWindowAndSlot` (see below).
- Added `InventoryManager->locateWindowAndSlot(int $windowId, int $netSlotId) : array{Inventory, int}` - accepts a window ID and absolute slot ID, and returns the associated inventory and the slot relative to the inventory's own start (for use with `getItem()` etc.).
- Slot offset mapping for "UI" inventories is now handled in `InventoryManager->createComplexSlotMapping()` instead of in `TypeConverter`.
- Console polling is now done on the main thread (no longer a performance concern). ([link](https://github.com/pmmp/PocketMine-MP/commit/b3f03d7ae645de67a54b7300c09b94eeca16298e))
- Console reader subprocess should now automatically die if the server main process is killed, instead of persisting as a zombie. ([link](https://github.com/pmmp/PocketMine-MP/commit/2585160ca2c4df5758b8b980331307402ff9f0fb))
- `ConsoleCommandSender` is no longer responsible for relaying broadcast messages to `MainLogger`. A new `BroadcastLoggerForwarder` has been added, which is subscribed to the appropriate server broadcast channels in order to relay messages. This ensures that chat messages and command audit messages are logged. ([link](https://github.com/pmmp/PocketMine-MP/commit/83e5b0adb6fa0dddec377182bb1c7945ac8f7820))

View File

@ -12,3 +12,84 @@ Released 30th November 2022.
## General
- Added support for Minecraft: Bedrock Edition 1.19.50.
- Removed support for older versions.
# 4.12.1
Released 4th December 2022.
## Fixes
- Fixed items glitching when dragging a stack of items across the crafting grid (desync issues).
# 4.12.2
Released 15th December 2022.
## Fixes
- Folder used for plugins (optionally specified by `--plugins`) is no longer required to be writable.
- Fixed broken writable check for server data folder (`is_writable()` broken on NFS and similar filesystems).
- `Filesystem::createLockFile()` exceptions now include more information about why the lock file could not be created.
- Fixed client-side item predictions not being rolled back when cancelling events such as `PlayerItemUseEvent`.
## Dependencies
- Updated BedrockProtocol to [17.1.0](https://github.com/pmmp/BedrockProtocol/releases/tag/17.1.0+bedrock-1.19.50). This adds some missing `LevelSoundEvent` constants and fixes the values for `ContainerUIIds`.
# 4.12.3
Released 28th December 2022.
## Fixes
- Fixed unauthenticated connections taking up player count slots, preventing players from joining.
- Fixed a possible crash in `World->tickChunk()` when plugins unload chunks during some events.
- `/gamemode` will now report a failure to change game mode if the player is already in the requested game mode.
# 4.12.4
Released 3rd January 2023.
## Fixes
- Added workarounds for an active exploit being used to deny service to servers.
# 4.12.5
Released 6th January 2023.
## Fixes
- Removed a workaround for an old client bug in custom form responses. The code contained a denial-of-service vulnerability.
# 4.12.6
Released 7th January 2023.
## Changes
- Added a new security measure to `NetworkSession` to detect and ban players who flood the server with packets.
# 4.12.7
Released 8th January 2023.
## Fixes
- Fixed players getting kicked when the server lags for too long.
- Fixed players getting kicked when a debugging session is active and a breakpoint is hit.
# 4.12.8
Released 9th January 2023.
## Fixes
- Fixed players getting kicked during PvP.
- Fixed players randomly getting kicked on Windows (improper rate limit handling wrt. 15ms timer resolution).
# 4.12.9
Released 16th January 2023.
## Improvements
### Timings
- Added new timers:
- `Server Mid-Tick Processing` - time spent processing Snooze interrupts between ticks (e.g. incoming network packets)
- `Server Tick Update Cycle` - time spent processing regular per-tick updates (e.g. entity movement, world updates, etc.) (`Server->tick()`)
- `Full Server Tick` timer now counts the total of `Server Mid-Tick Processing` and `Server Tick Update Cycle`, which generates more accurate performance metrics.
- Previously, this timer only counted the time spent during regular per-tick updates, and the time recorded by `Server Mid-Tick Processing` was not included in the report at all.
## Fixes
- Fixed blocks such as pressure plates being able to be placed without the correct supporting blocks if the clicked block was solid.
- Pressure plates now self-destruct when the block below them is removed.
- Fixed being unable to place blocks by clicking on the side of a bell (when the click doesn't result in ringing the bell).
- Fixed various rotation-aware blocks (e.g. stairs) behaving incorrectly when placed by clicking on the side of a replaceable block (e.g. tall grass).
- Fixed banners being able to be placed on top of blocks such as skulls.
- Fixed server-side collision boxes of walls and glass (which should connect, but didn't). Note that wall connections still don't show client side - this just fixes the collision boxes.
- Fixed `PlayerInteractEvent` with `LEFT_CLICK` sometimes firing before `BlockBreakEvent` when breaking blocks.
## Other changes
- Increased packet batch budget for player sessions.

View File

@ -35,7 +35,7 @@
"fgrosse/phpasn1": "^2.3",
"netresearch/jsonmapper": "^4.0",
"pocketmine/bedrock-data": "~1.13.0+bedrock-1.19.50",
"pocketmine/bedrock-protocol": "~17.0.0+bedrock-1.19.50",
"pocketmine/bedrock-protocol": "~17.1.0+bedrock-1.19.50",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "^0.2.0",
@ -54,7 +54,7 @@
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "1.9.2",
"phpstan/phpstan": "1.9.11",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "^9.2"

196
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "6818c4f3d55e45758df83a56d60653cf",
"content-hash": "d5ea4a4f41bf80636b678e6d3d06eac0",
"packages": [
{
"name": "adhocore/json-comment",
@ -123,24 +123,24 @@
},
{
"name": "fgrosse/phpasn1",
"version": "v2.4.0",
"version": "v2.5.0",
"source": {
"type": "git",
"url": "https://github.com/fgrosse/PHPASN1.git",
"reference": "eef488991d53e58e60c9554b09b1201ca5ba9296"
"reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/eef488991d53e58e60c9554b09b1201ca5ba9296",
"reference": "eef488991d53e58e60c9554b09b1201ca5ba9296",
"url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b",
"reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b",
"shasum": ""
},
"require": {
"php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0"
"php": "^7.1 || ^8.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "~2.0",
"phpunit/phpunit": "^6.3 || ^7.0 || ^8.0"
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
},
"suggest": {
"ext-bcmath": "BCmath is the fallback extension for big integer calculations",
@ -192,22 +192,23 @@
],
"support": {
"issues": "https://github.com/fgrosse/PHPASN1/issues",
"source": "https://github.com/fgrosse/PHPASN1/tree/v2.4.0"
"source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0"
},
"time": "2021-12-11T12:41:06+00:00"
"abandoned": true,
"time": "2022-12-19T11:08:26+00:00"
},
{
"name": "netresearch/jsonmapper",
"version": "v4.0.0",
"version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/cweiske/jsonmapper.git",
"reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d"
"reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d",
"reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d",
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/cfa81ea1d35294d64adb9c68aa4cb9e92400e53f",
"reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f",
"shasum": ""
},
"require": {
@ -243,9 +244,9 @@
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
"source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0"
"source": "https://github.com/cweiske/jsonmapper/tree/v4.1.0"
},
"time": "2020-12-01T19:48:11+00:00"
"time": "2022-12-08T20:46:14+00:00"
},
{
"name": "pocketmine/bedrock-data",
@ -275,16 +276,16 @@
},
{
"name": "pocketmine/bedrock-protocol",
"version": "17.0.0+bedrock-1.19.50",
"version": "17.1.0+bedrock-1.19.50",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "272e10197bb1603c0a81075bf5b9dae0d081a6e2"
"reference": "c572706cf5e3202718dd35a35dd30fe08cd671de"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/272e10197bb1603c0a81075bf5b9dae0d081a6e2",
"reference": "272e10197bb1603c0a81075bf5b9dae0d081a6e2",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c572706cf5e3202718dd35a35dd30fe08cd671de",
"reference": "c572706cf5e3202718dd35a35dd30fe08cd671de",
"shasum": ""
},
"require": {
@ -298,7 +299,7 @@
"ramsey/uuid": "^4.1"
},
"require-dev": {
"phpstan/phpstan": "1.9.0",
"phpstan/phpstan": "1.9.3",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5"
@ -316,9 +317,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/17.0.0+bedrock-1.19.50"
"source": "https://github.com/pmmp/BedrockProtocol/tree/17.1.0+bedrock-1.19.50"
},
"time": "2022-11-30T16:16:27+00:00"
"time": "2022-12-15T20:34:49+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -851,42 +852,53 @@
},
{
"name": "ramsey/collection",
"version": "1.2.2",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
"reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a"
"reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a",
"reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a",
"url": "https://api.github.com/repos/ramsey/collection/zipball/ad7475d1c9e70b190ecffc58f2d989416af339b4",
"reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4",
"shasum": ""
},
"require": {
"php": "^7.3 || ^8",
"php": "^7.4 || ^8.0",
"symfony/polyfill-php81": "^1.23"
},
"require-dev": {
"captainhook/captainhook": "^5.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"ergebnis/composer-normalize": "^2.6",
"fakerphp/faker": "^1.5",
"hamcrest/hamcrest-php": "^2",
"jangregor/phpstan-prophecy": "^0.8",
"mockery/mockery": "^1.3",
"captainhook/plugin-composer": "^5.3",
"ergebnis/composer-normalize": "^2.28.3",
"fakerphp/faker": "^1.21",
"hamcrest/hamcrest-php": "^2.0",
"jangregor/phpstan-prophecy": "^1.0",
"mockery/mockery": "^1.5",
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1",
"phpstan/phpstan": "^0.12.32",
"phpstan/phpstan-mockery": "^0.12.5",
"phpstan/phpstan-phpunit": "^0.12.11",
"phpunit/phpunit": "^8.5 || ^9",
"psy/psysh": "^0.10.4",
"slevomat/coding-standard": "^6.3",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.4"
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5",
"psalm/plugin-mockery": "^1.1",
"psalm/plugin-phpunit": "^0.18.4",
"ramsey/coding-standard": "^2.0.3",
"ramsey/conventional-commits": "^1.3",
"vimeo/psalm": "^5.4"
},
"type": "library",
"extra": {
"captainhook": {
"force-install": true
},
"ramsey/conventional-commits": {
"configFile": "conventional-commits.json"
}
},
"autoload": {
"psr-4": {
"Ramsey\\Collection\\": "src/"
@ -914,7 +926,7 @@
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/1.2.2"
"source": "https://github.com/ramsey/collection/tree/1.3.0"
},
"funding": [
{
@ -926,27 +938,27 @@
"type": "tidelift"
}
],
"time": "2021-10-10T03:01:02+00:00"
"time": "2022-12-27T19:12:24+00:00"
},
{
"name": "ramsey/uuid",
"version": "4.6.0",
"version": "4.7.3",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f"
"reference": "433b2014e3979047db08a17a205f410ba3869cf2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/ad63bc700e7d021039e30ce464eba384c4a1d40f",
"reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/433b2014e3979047db08a17a205f410ba3869cf2",
"reference": "433b2014e3979047db08a17a205f410ba3869cf2",
"shasum": ""
},
"require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10",
"ext-json": "*",
"php": "^8.0",
"ramsey/collection": "^1.0"
"ramsey/collection": "^1.2 || ^2.0"
},
"replace": {
"rhumsaa/uuid": "self.version"
@ -1006,7 +1018,7 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.6.0"
"source": "https://github.com/ramsey/uuid/tree/4.7.3"
},
"funding": [
{
@ -1018,7 +1030,7 @@
"type": "tidelift"
}
],
"time": "2022-11-05T23:03:38+00:00"
"time": "2023-01-12T18:13:24+00:00"
},
{
"name": "symfony/filesystem",
@ -1524,30 +1536,30 @@
"packages-dev": [
{
"name": "doctrine/instantiator",
"version": "1.4.1",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc"
"reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc",
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
"reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^9",
"doctrine/coding-standard": "^9 || ^11",
"ext-pdo": "*",
"ext-phar": "*",
"phpbench/phpbench": "^0.16 || ^1",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-phpunit": "^1",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"vimeo/psalm": "^4.22"
"vimeo/psalm": "^4.30 || ^5.4"
},
"type": "library",
"autoload": {
@ -1574,7 +1586,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/1.4.1"
"source": "https://github.com/doctrine/instantiator/tree/1.5.0"
},
"funding": [
{
@ -1590,7 +1602,7 @@
"type": "tidelift"
}
],
"time": "2022-03-03T08:28:38+00:00"
"time": "2022-12-30T00:15:36+00:00"
},
{
"name": "myclabs/deep-copy",
@ -1820,16 +1832,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.9.2",
"version": "1.9.11",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa"
"reference": "60f3d68481eef216199eae7a2603cd5fe124d464"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d6fdf01c53978b6429f1393ba4afeca39cc68afa",
"reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/60f3d68481eef216199eae7a2603cd5fe124d464",
"reference": "60f3d68481eef216199eae7a2603cd5fe124d464",
"shasum": ""
},
"require": {
@ -1859,7 +1871,7 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.9.2"
"source": "https://github.com/phpstan/phpstan/tree/1.9.11"
},
"funding": [
{
@ -1875,25 +1887,25 @@
"type": "tidelift"
}
],
"time": "2022-11-10T09:56:11+00:00"
"time": "2023-01-12T14:04:13+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
"version": "1.2.2",
"version": "1.3.3",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "dea1f87344c6964c607d9076dee42d891f3923f0"
"reference": "54a24bd23e9e80ee918cdc24f909d376c2e273f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/dea1f87344c6964c607d9076dee42d891f3923f0",
"reference": "dea1f87344c6964c607d9076dee42d891f3923f0",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/54a24bd23e9e80ee918cdc24f909d376c2e273f7",
"reference": "54a24bd23e9e80ee918cdc24f909d376c2e273f7",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.8.11"
"phpstan/phpstan": "^1.9.3"
},
"conflict": {
"phpunit/phpunit": "<7.0"
@ -1925,27 +1937,27 @@
"description": "PHPUnit extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.2.2"
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.3"
},
"time": "2022-10-28T10:23:07+00:00"
"time": "2022-12-21T15:25:00+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "1.4.4",
"version": "1.4.5",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6"
"reference": "361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6",
"reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d",
"reference": "361f75b06066f3fdaba87c1f57bdb1ffc28d6f1d",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.8.6"
"phpstan/phpstan": "^1.9.7"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
@ -1973,22 +1985,22 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.4"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.5"
},
"time": "2022-09-21T11:38:17+00:00"
"time": "2023-01-11T14:16:29+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.19",
"version": "9.2.23",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559"
"reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559",
"reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c",
"reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c",
"shasum": ""
},
"require": {
@ -2044,7 +2056,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23"
},
"funding": [
{
@ -2052,7 +2064,7 @@
"type": "github"
}
],
"time": "2022-11-18T07:47:47+00:00"
"time": "2022-12-28T12:41:10+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -2297,16 +2309,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.5.26",
"version": "9.5.27",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2"
"reference": "a2bc7ffdca99f92d959b3f2270529334030bba38"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2",
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38",
"reference": "a2bc7ffdca99f92d959b3f2270529334030bba38",
"shasum": ""
},
"require": {
@ -2379,7 +2391,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.27"
},
"funding": [
{
@ -2395,7 +2407,7 @@
"type": "tidelift"
}
],
"time": "2022-10-28T06:00:21+00:00"
"time": "2022-12-09T07:31:23+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@ -286,7 +286,7 @@ class MemoryManager{
/**
* Static memory dumper accessible from any thread.
*
* @param mixed $startingObject
* @param mixed $startingObject
*/
public static function dumpMemory($startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
$hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist");
@ -480,7 +480,7 @@ class MemoryManager{
/**
* @param mixed $from
* @param object[] $objects reference parameter
* @param object[] $objects reference parameter
* @param int[] $refCounts reference parameter
*
* @phpstan-param array<string, object> $objects

View File

@ -39,11 +39,14 @@ namespace pocketmine {
use function extension_loaded;
use function function_exists;
use function getcwd;
use function is_dir;
use function mkdir;
use function phpversion;
use function preg_match;
use function preg_quote;
use function realpath;
use function version_compare;
use const DIRECTORY_SEPARATOR;
require_once __DIR__ . '/VersionInfo.php';
@ -273,25 +276,33 @@ JIT_WARNING
$pluginPath = getopt_string("plugins") ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
if(!@mkdir($dataPath, 0777, true) && (!is_dir($dataPath) || !is_writable($dataPath))){
if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){
critical_error("Unable to create/access data directory at $dataPath. Check that the target location is accessible by the current user.");
exit(1);
}
//this has to be done after we're sure the data path exists
$dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
if(!@mkdir($pluginPath, 0777, true) && (!is_dir($pluginPath) || !is_writable($pluginPath))){
critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
exit(1);
}
$pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
$lockFilePath = Path::join($dataPath, 'server.lock');
if(($pid = Filesystem::createLockFile($lockFilePath)) !== null){
try{
$pid = Filesystem::createLockFile($lockFilePath);
}catch(\InvalidArgumentException $e){
critical_error($e->getMessage());
critical_error("Please ensure that there is enough space on the disk and that the current user has read/write permissions to the selected data directory $dataPath.");
exit(1);
}
if($pid !== null){
critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
critical_error("Please stop the other server first before running a new one.");
exit(1);
}
if(!@mkdir($pluginPath, 0777, true) && !is_dir($pluginPath)){
critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
exit(1);
}
$pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
//Logger has a dependency on timezone
Timezone::init();

View File

@ -90,6 +90,7 @@ use pocketmine\scheduler\AsyncPool;
use pocketmine\snooze\SleeperHandler;
use pocketmine\stats\SendUsageTask;
use pocketmine\timings\Timings;
use pocketmine\timings\TimingsAwareSleeperHandler;
use pocketmine\timings\TimingsHandler;
use pocketmine\updater\UpdateChecker;
use pocketmine\utils\AssumptionFailedError;
@ -781,7 +782,8 @@ class Server{
self::$instance = $this;
$this->startTime = microtime(true);
$this->tickSleeper = new SleeperHandler();
Timings::init();
$this->tickSleeper = new TimingsAwareSleeperHandler(Timings::$serverInterrupts);
$this->signalHandler = new SignalHandler(function() : void{
$this->logger->info("Received signal interrupt, stopping the server");
@ -961,7 +963,6 @@ class Server{
)));
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
Timings::init();
TimingsHandler::setEnabled($this->configGroup->getPropertyBool("settings.enable-profiling", false));
$this->profilingTickRate = $this->configGroup->getPropertyInt("settings.profile-report-trigger", 20);
@ -1268,7 +1269,7 @@ class Server{
}
/**
* @param CommandSender[]|null $recipients
* @param CommandSender[]|null $recipients
*/
public function broadcastMessage(Translatable|string $message, ?array $recipients = null) : int{
$recipients = $recipients ?? $this->getBroadcastChannelSubscribers(self::BROADCAST_CHANNEL_USERS);
@ -1321,9 +1322,9 @@ class Server{
}
/**
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
* @param int $stay Duration in ticks to stay on screen for
* @param int $fadeOut Duration in ticks for fade-out.
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
* @param int $stay Duration in ticks to stay on screen for
* @param int $fadeOut Duration in ticks for fade-out.
* @param Player[]|null $recipients
*/
public function broadcastTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1, ?array $recipients = null) : int{

View File

@ -44,7 +44,7 @@ final class ServerConfigGroup{
){}
/**
* @param mixed $defaultValue
* @param mixed $defaultValue
*
* @return mixed
*/

View File

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

View File

@ -87,7 +87,7 @@ abstract class BaseBanner extends Transparent{
}
/**
* @param BannerPatternLayer[] $patterns
* @param BannerPatternLayer[] $patterns
*
* @phpstan-param list<BannerPatternLayer> $patterns
* @return $this
@ -112,7 +112,14 @@ abstract class BaseBanner extends Transparent{
return SupportType::NONE();
}
private function canBeSupportedBy(Block $block) : bool{
return $block->isSolid();
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedBy($blockReplace->getSide($this->getSupportingFace()))){
return false;
}
if($item instanceof ItemBanner){
$this->color = $item->getColor();
$this->setPatterns($item->getPatterns());
@ -124,7 +131,7 @@ abstract class BaseBanner extends Transparent{
abstract protected function getSupportingFace() : int;
public function onNearbyBlockChange() : void{
if($this->getSide($this->getSupportingFace())->getId() === BlockLegacyIds::AIR){
if(!$this->canBeSupportedBy($this->getSide($this->getSupportingFace()))){
$this->position->getWorld()->useBreakOn($this->position);
}
}

View File

@ -114,14 +114,13 @@ final class Bell extends Transparent{
return $this;
}
private function canBeSupportedBy(Block $block) : bool{
//TODO: this isn't the actual logic, but it's the closest approximation we can support for now
return $block->isSolid();
private function canBeSupportedBy(Block $block, int $face) : bool{
return !$block->getSupportType($face)->equals(SupportType::NONE());
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($face === Facing::UP){
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->down()))){
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->down()), Facing::UP)){
return false;
}
if($player !== null){
@ -129,18 +128,18 @@ final class Bell extends Transparent{
}
$this->setAttachmentType(BellAttachmentType::FLOOR());
}elseif($face === Facing::DOWN){
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->up()))){
if(!$this->canBeSupportedBy($tx->fetchBlock($this->position->up()), Facing::DOWN)){
return false;
}
$this->setAttachmentType(BellAttachmentType::CEILING());
}else{
$this->setFacing($face);
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide(Facing::opposite($face))))){
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide(Facing::opposite($face))), $face)){
$this->setAttachmentType(BellAttachmentType::ONE_WALL());
}else{
return false;
}
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide($face)))){
if($this->canBeSupportedBy($tx->fetchBlock($this->position->getSide($face)), Facing::opposite($face))){
$this->setAttachmentType(BellAttachmentType::TWO_WALLS());
}
}
@ -149,10 +148,10 @@ final class Bell extends Transparent{
public function onNearbyBlockChange() : void{
if(
($this->attachmentType->equals(BellAttachmentType::CEILING()) && !$this->canBeSupportedBy($this->getSide(Facing::UP))) ||
($this->attachmentType->equals(BellAttachmentType::FLOOR()) && !$this->canBeSupportedBy($this->getSide(Facing::DOWN))) ||
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) && !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)))) ||
($this->attachmentType->equals(BellAttachmentType::TWO_WALLS()) && (!$this->canBeSupportedBy($this->getSide($this->facing)) || !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)))))
($this->attachmentType->equals(BellAttachmentType::CEILING()) && !$this->canBeSupportedBy($this->getSide(Facing::UP), Facing::DOWN)) ||
($this->attachmentType->equals(BellAttachmentType::FLOOR()) && !$this->canBeSupportedBy($this->getSide(Facing::DOWN), Facing::UP)) ||
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) && !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)) ||
($this->attachmentType->equals(BellAttachmentType::TWO_WALLS()) && (!$this->canBeSupportedBy($this->getSide($this->facing), Facing::opposite($this->facing)) || !$this->canBeSupportedBy($this->getSide(Facing::opposite($this->facing)), $this->facing)))
){
$this->position->getWorld()->useBreakOn($this->position);
}
@ -161,21 +160,20 @@ final class Bell extends Transparent{
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$faceHit = Facing::opposite($player->getHorizontalFacing());
if($this->attachmentType->equals(BellAttachmentType::CEILING())){
$this->ring($faceHit);
}
if($this->attachmentType->equals(BellAttachmentType::FLOOR()) && Facing::axis($faceHit) === Facing::axis($this->facing)){
$this->ring($faceHit);
}
if(
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) || $this->attachmentType->equals(BellAttachmentType::TWO_WALLS())) &&
($faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true))
$this->attachmentType->equals(BellAttachmentType::CEILING()) ||
($this->attachmentType->equals(BellAttachmentType::FLOOR()) && Facing::axis($faceHit) === Facing::axis($this->facing)) ||
(
($this->attachmentType->equals(BellAttachmentType::ONE_WALL()) || $this->attachmentType->equals(BellAttachmentType::TWO_WALLS())) &&
($faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true))
)
){
$this->ring($faceHit);
return true;
}
}
return true;
return false;
}
public function ring(int $faceHit) : void{

View File

@ -62,7 +62,7 @@ class Block{
protected ?array $collisionBoxes = null;
/**
* @param string $name English name of the block type (TODO: implement translations)
* @param string $name English name of the block type (TODO: implement translations)
*/
public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){
if(($idInfo->getVariant() & $this->getStateBitmask()) !== 0){

View File

@ -968,7 +968,7 @@ class BlockFactory{
* NOTE: If you are registering a new block type, you will need to add it to the creative inventory yourself - it
* will not automatically appear there.
*
* @param bool $override Whether to override existing registrations
* @param bool $override Whether to override existing registrations
*
* @throws \RuntimeException if something attempted to override an already-registered block without specifying the
* $override parameter.

View File

@ -61,7 +61,7 @@ abstract class Button extends Flowable{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->canBeSupportedBy($blockClicked, $face)){
if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}

View File

@ -175,7 +175,7 @@ class ItemFrame extends Flowable{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($face === Facing::DOWN || $face === Facing::UP || !$blockClicked->isSolid()){
if($face === Facing::DOWN || $face === Facing::UP || !$blockReplace->getSide(Facing::opposite($face))->isSolid()){
return false;
}

View File

@ -70,7 +70,7 @@ class Ladder extends Transparent{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->canBeSupportedBy($blockClicked, $face) && Facing::axis($face) !== Axis::Y){
if($this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face) && Facing::axis($face) !== Axis::Y){
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}

View File

@ -95,7 +95,7 @@ class Lever extends Flowable{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedBy($blockClicked, $face)){
if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
return false;
}

View File

@ -45,7 +45,7 @@ abstract class PressurePlate extends Transparent{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($this->canBeSupportedBy($blockClicked)){
if($this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
return false;
@ -55,5 +55,11 @@ abstract class PressurePlate extends Transparent{
return !$block->getSupportType(Facing::UP)->equals(SupportType::NONE());
}
public function onNearbyBlockChange() : void{
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
$this->position->getWorld()->useBreakOn($this->position);
}
}
//TODO
}

View File

@ -41,7 +41,7 @@ class Thin extends Transparent{
foreach(Facing::HORIZONTAL as $facing){
$side = $this->getSide($facing);
if($side instanceof Thin || $side->isFullCube()){
if($side instanceof Thin || $side instanceof Wall || $side->isFullCube()){
$this->connections[$facing] = true;
}else{
unset($this->connections[$facing]);

View File

@ -65,7 +65,6 @@ class Torch extends Flowable{
}
public function onNearbyBlockChange() : void{
$below = $this->getSide(Facing::DOWN);
$face = Facing::opposite($this->facing);
if(!$this->canBeSupportedBy($this->getSide($face), $this->facing)){
@ -74,10 +73,7 @@ class Torch extends Flowable{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($blockClicked->canBeReplaced() && $this->canBeSupportedBy($blockClicked->getSide(Facing::DOWN), Facing::UP)){
$this->facing = Facing::UP;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}elseif($face !== Facing::DOWN && $this->canBeSupportedBy($blockClicked, $face)){
if($face !== Facing::DOWN && $this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
$this->facing = $face;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}else{

View File

@ -116,7 +116,7 @@ class Vine extends Flowable{
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$blockClicked->isFullCube() || Facing::axis($face) === Axis::Y){
if(!$blockReplace->getSide(Facing::opposite($face))->isFullCube() || Facing::axis($face) === Axis::Y){
return false;
}

View File

@ -39,7 +39,7 @@ class Wall extends Transparent{
foreach(Facing::HORIZONTAL as $facing){
$block = $this->getSide($facing);
if($block instanceof static || $block instanceof FenceGate || ($block->isSolid() && !$block->isTransparent())){
if($block instanceof static || $block instanceof FenceGate || $block instanceof Thin || ($block->isSolid() && !$block->isTransparent())){
$this->connections[$facing] = $facing;
}else{
unset($this->connections[$facing]);

View File

@ -113,7 +113,7 @@ final class WallCoralFan extends BaseCoral{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$axis = Facing::axis($face);
if(($axis !== Axis::X && $axis !== Axis::Z) || !$this->canBeSupportedBy($blockClicked, $face)){
if(($axis !== Axis::X && $axis !== Axis::Z) || !$this->canBeSupportedBy($blockReplace->getSide(Facing::opposite($face)), $face)){
return false;
}
$this->facing = $face;

View File

@ -39,19 +39,23 @@ class WaterLily extends Flowable{
return [AxisAlignedBB::one()->contract(1 / 16, 0, 1 / 16)->trim(Facing::UP, 63 / 64)];
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($blockClicked instanceof Water){
$up = $blockClicked->getSide(Facing::UP);
if($up->canBeReplaced()){
return parent::place($tx, $item, $up, $blockClicked, $face, $clickVector, $player);
}
}
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
return !$blockReplace instanceof Water && parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
return false;
private function canBeSupportedBy(Block $block) : bool{
return $block instanceof Water;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if(!$this->canBeSupportedBy($blockReplace->getSide(Facing::DOWN))){
return false;
}
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
if(!($this->getSide(Facing::DOWN) instanceof Water)){
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){
$this->position->getWorld()->useBreakOn($this->position);
}
}

View File

@ -135,7 +135,7 @@ class Banner extends Spawnable{
}
/**
* @param BannerPatternLayer[] $patterns
* @param BannerPatternLayer[] $patterns
*
* @phpstan-param list<BannerPatternLayer> $patterns
*/

View File

@ -26,7 +26,7 @@ namespace pocketmine\command;
interface CommandExecutor{
/**
* @param string[] $args
* @param string[] $args
*/
public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool;

View File

@ -72,6 +72,11 @@ class GamemodeCommand extends VanillaCommand{
throw new InvalidCommandSyntaxException();
}
if($target->getGamemode()->equals($gameMode)){
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName()));
return true;
}
$target->setGamemode($gameMode);
if(!$gameMode->equals($target->getGamemode())){
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_gamemode_failure($target->getName()));

View File

@ -34,9 +34,15 @@ use function usort;
class CraftingManager{
use DestructorCallbackTrait;
/** @var ShapedRecipe[][] */
/**
* @var ShapedRecipe[][]
* @phpstan-var array<string, list<ShapedRecipe>>
*/
protected $shapedRecipes = [];
/** @var ShapelessRecipe[][] */
/**
* @var ShapelessRecipe[][]
* @phpstan-var array<string, list<ShapelessRecipe>>
*/
protected $shapelessRecipes = [];
/**
@ -133,6 +139,7 @@ class CraftingManager{
/**
* @return ShapelessRecipe[][]
* @phpstan-return array<string, list<ShapelessRecipe>>
*/
public function getShapelessRecipes() : array{
return $this->shapelessRecipes;
@ -140,6 +147,7 @@ class CraftingManager{
/**
* @return ShapedRecipe[][]
* @phpstan-return array<string, list<ShapedRecipe>>
*/
public function getShapedRecipes() : array{
return $this->shapedRecipes;
@ -201,7 +209,7 @@ class CraftingManager{
}
/**
* @param Item[] $outputs
* @param Item[] $outputs
*/
public function matchRecipe(CraftingGrid $grid, array $outputs) : ?CraftingRecipe{
//TODO: try to match special recipes before anything else (first they need to be implemented!)

View File

@ -46,15 +46,15 @@ class ShapedRecipe implements CraftingRecipe{
/**
* Constructs a ShapedRecipe instance.
*
* @param string[] $shape <br>
* Array of 1, 2, or 3 strings representing the rows of the recipe.
* This accepts an array of 1, 2 or 3 strings. Each string should be of the same length and must be at most 3
* characters long. Each character represents a unique type of ingredient. Spaces are interpreted as air.
* @param string[] $shape <br>
* Array of 1, 2, or 3 strings representing the rows of the recipe.
* This accepts an array of 1, 2 or 3 strings. Each string should be of the same length and must be at most 3
* characters long. Each character represents a unique type of ingredient. Spaces are interpreted as air.
* @param Item[] $ingredients <br>
* Char => Item map of items to be set into the shape.
* This accepts an array of Items, indexed by character. Every unique character (except space) in the shape
* array MUST have a corresponding item in this list. Space character is automatically treated as air.
* @param Item[] $results List of items that this recipe produces when crafted.
* Char => Item map of items to be set into the shape.
* This accepts an array of Items, indexed by character. Every unique character (except space) in the shape
* array MUST have a corresponding item in this list. Space character is automatically treated as air.
* @param Item[] $results List of items that this recipe produces when crafted.
*
* Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns.
*/

View File

@ -36,7 +36,8 @@ class ShapelessRecipe implements CraftingRecipe{
/**
* @param Item[] $ingredients No more than 9 total. This applies to sum of item stack counts, not count of array.
* @param Item[] $results List of result items created by this recipe.
* @param Item[] $results List of result items created by this recipe.
*
* TODO: we'll want to make the type parameter mandatory in PM5
*/
public function __construct(array $ingredients, array $results, ?ShapelessRecipeType $type = null){

View File

@ -33,13 +33,13 @@ final class PotionTypeIdMap{
* @var PotionType[]
* @phpstan-var array<int, PotionType>
*/
private array $idToEnum;
private array $idToEnum = [];
/**
* @var int[]
* @phpstan-var array<int, int>
*/
private array $enumToId;
private array $enumToId = [];
private function __construct(){
$this->register(PotionTypeIds::WATER, PotionType::WATER());

View File

@ -1598,7 +1598,7 @@ abstract class Entity{
/**
* @param Player[]|null $targets
* @param MetadataProperty[] $data Properly formatted entity data, defaults to everything
* @param MetadataProperty[] $data Properly formatted entity data, defaults to everything
*
* @phpstan-param array<int, MetadataProperty> $data
*/

View File

@ -35,9 +35,9 @@ class Effect{
use NotSerializable;
/**
* @param Translatable|string $name Translation key used for effect name
* @param Color $color Color of bubbles given by this effect
* @param bool $bad Whether the effect is harmful
* @param Translatable|string $name Translation key used for effect name
* @param Color $color Color of bubbles given by this effect
* @param bool $bad Whether the effect is harmful
* @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles)
*/
public function __construct(

View File

@ -36,7 +36,7 @@ class EffectInstance{
private Color $color;
/**
* @param int|null $duration Passing null will use the effect type's default duration
* @param int|null $duration Passing null will use the effect type's default duration
*/
public function __construct(Effect $effectType, ?int $duration = null, int $amplifier = 0, bool $visible = true, bool $ambient = false, ?Color $overrideColor = null){
$this->effectType = $effectType;

View File

@ -31,6 +31,10 @@ class HandlerList{
/** @var RegisteredListener[][] */
private array $handlerSlots = [];
/**
* @phpstan-template TEvent of Event
* @phpstan-param class-string<TEvent> $class
*/
public function __construct(
private string $class,
private ?HandlerList $parentList

View File

@ -52,7 +52,7 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
/**
* @param Block[] $blocks
* @param float $yield 0-100
* @param float $yield 0-100
*/
public function __construct(Entity $entity, Position $position, array $blocks, float $yield){
$this->entity = $entity;

View File

@ -35,8 +35,8 @@ class CraftItemEvent extends Event implements Cancellable{
use CancellableTrait;
/**
* @param Item[] $inputs
* @param Item[] $outputs
* @param Item[] $inputs
* @param Item[] $outputs
*/
public function __construct(
private CraftingTransaction $transaction,

View File

@ -34,7 +34,7 @@ interface Form extends \JsonSerializable{
/**
* Handles a form response from a player.
*
* @param mixed $data
* @param mixed $data
*
* @throws FormValidationException if the data could not be processed
*/

View File

@ -75,7 +75,7 @@ class Banner extends ItemBlockWallOrFloor{
}
/**
* @param BannerPatternLayer[] $patterns
* @param BannerPatternLayer[] $patterns
*
* @phpstan-param list<BannerPatternLayer> $patterns
*

View File

@ -564,7 +564,7 @@ class Item implements \JsonSerializable{
/**
* Compares an Item to this Item and check if they match.
*
* @param bool $checkDamage Whether to verify that the damage values match.
* @param bool $checkDamage Whether to verify that the damage values match.
* @param bool $checkCompound Whether to verify that the items' NBT match.
*/
final public function equals(Item $item, bool $checkDamage = true, bool $checkCompound = true) : bool{

View File

@ -35,7 +35,7 @@ interface AdvancedNetworkInterface extends NetworkInterface{
/**
* Prevents packets received from the IP address getting processed for the given timeout.
*
* @param int $timeout Seconds
* @param int $timeout Seconds
*/
public function blockAddress(string $address, int $timeout = 300) : void;

View File

@ -80,6 +80,10 @@ class Network{
return $this->sessionManager->getSessionCount();
}
public function getValidConnectionCount() : int{
return $this->sessionManager->getValidSessionCount();
}
public function tick() : void{
foreach($this->interfaces as $interface){
$interface->tick();

View File

@ -32,12 +32,25 @@ class NetworkSessionManager{
/** @var NetworkSession[] */
private array $sessions = [];
/** @var NetworkSession[] */
private array $pendingLoginSessions = [];
/**
* Adds a network session to the manager. This should only be called on session creation.
*/
public function add(NetworkSession $session) : void{
$idx = spl_object_id($session);
$this->sessions[$idx] = $session;
$this->pendingLoginSessions[$idx] = $session;
}
/**
* Marks the session as having sent a login request. After this point, they are counted towards the total player
* count.
*/
public function markLoginReceived(NetworkSession $session) : void{
$idx = spl_object_id($session);
unset($this->pendingLoginSessions[$idx]);
}
/**
@ -47,15 +60,24 @@ class NetworkSessionManager{
public function remove(NetworkSession $session) : void{
$idx = spl_object_id($session);
unset($this->sessions[$idx]);
unset($this->pendingLoginSessions[$idx]);
}
/**
* Returns the number of known connected sessions.
* Returns the number of known connected sessions, including sessions which have not yet sent a login request.
*/
public function getSessionCount() : int{
return count($this->sessions);
}
/**
* Returns the number of connected sessions which have either sent a login request, or have already completed the
* login sequence.
*/
public function getValidSessionCount() : int{
return count($this->sessions) - count($this->pendingLoginSessions);
}
/** @return NetworkSession[] */
public function getSessions() : array{ return $this->sessions; }

View File

@ -191,8 +191,10 @@ class InventoryManager{
*/
public function addPredictedSlotChanges(array $networkInventoryActions) : void{
foreach($networkInventoryActions as $action){
if($action->sourceType === NetworkInventoryAction::SOURCE_CONTAINER && isset($this->windowMap[$action->windowId])){
//this won't cover stuff like crafting grid due to too much magic
if($action->sourceType === NetworkInventoryAction::SOURCE_CONTAINER && (
isset($this->windowMap[$action->windowId]) ||
($action->windowId === ContainerIds::UI && isset($this->complexSlotToWindowMap[$action->inventorySlot]))
)){
try{
$item = TypeConverter::getInstance()->netItemStackToCore($action->newItem->getItemStack());
}catch(TypeConversionException $e){

View File

@ -128,20 +128,39 @@ use function array_values;
use function base64_encode;
use function bin2hex;
use function count;
use function function_exists;
use function get_class;
use function hrtime;
use function in_array;
use function intdiv;
use function json_encode;
use function ksort;
use function min;
use function strcasecmp;
use function strlen;
use function strtolower;
use function substr;
use function time;
use function ucfirst;
use function xdebug_is_debugger_active;
use const JSON_THROW_ON_ERROR;
use const SORT_NUMERIC;
class NetworkSession{
private const INCOMING_PACKET_BATCH_PER_TICK = 2; //usually max 1 per tick, but transactions may arrive separately
private const INCOMING_PACKET_BATCH_MAX_BUDGET = 100 * self::INCOMING_PACKET_BATCH_PER_TICK; //enough to account for a 5-second lag spike
/**
* At most this many more packets can be received. If this reaches zero, any additional packets received will cause
* the player to be kicked from the server.
* This number is increased every tick up to a maximum limit.
*
* @see self::INCOMING_PACKET_BATCH_PER_TICK
* @see self::INCOMING_PACKET_BATCH_MAX_BUDGET
*/
private int $incomingPacketBatchBudget = self::INCOMING_PACKET_BATCH_MAX_BUDGET;
private int $lastPacketBudgetUpdateTimeNs;
private \PrefixedLogger $logger;
private ?Player $player = null;
private ?PlayerInfo $info = null;
@ -199,6 +218,7 @@ class NetworkSession{
$this->disposeHooks = new ObjectSet();
$this->connectTime = time();
$this->lastPacketBudgetUpdateTimeNs = hrtime(true);
$this->setHandler(new SessionStartPacketHandler(
$this->server,
@ -229,6 +249,7 @@ class NetworkSession{
$this->info = $info;
$this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET);
$this->logger->setPrefix($this->getLogPrefix());
$this->manager->markLoginReceived($this);
},
function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{
$this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey);
@ -338,6 +359,17 @@ class NetworkSession{
return;
}
if($this->incomingPacketBatchBudget <= 0){
if(!function_exists('xdebug_is_debugger_active') || !xdebug_is_debugger_active()){
throw new PacketHandlingException("Receiving packets too fast");
}else{
//when a debugging session is active, the server may halt at any point for an indefinite length of time,
//in which time the client will continue to send packets
$this->incomingPacketBatchBudget = self::INCOMING_PACKET_BATCH_MAX_BUDGET;
}
}
$this->incomingPacketBatchBudget--;
if($this->cipher !== null){
Timings::$playerNetworkReceiveDecrypt->startTiming();
try{
@ -365,7 +397,7 @@ class NetworkSession{
}
try{
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){
foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 1300) as [$packet, $buffer]){
if($packet === null){
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
throw new PacketHandlingException("Unknown packet received");
@ -1128,5 +1160,16 @@ class NetworkSession{
}
$this->flushSendBuffer();
$nowNs = hrtime(true);
$timeSinceLastUpdateNs = $nowNs - $this->lastPacketBudgetUpdateTimeNs;
if($timeSinceLastUpdateNs > 50_000_000){
$ticksSinceLastUpdate = intdiv($timeSinceLastUpdateNs, 50_000_000);
$this->incomingPacketBatchBudget = min(
$this->incomingPacketBatchBudget + (self::INCOMING_PACKET_BATCH_PER_TICK * 2 * $ticksSinceLastUpdate),
self::INCOMING_PACKET_BATCH_MAX_BUDGET
);
$this->lastPacketBudgetUpdateTimeNs = $nowNs;
}
}
}

View File

@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\ClientboundPacket;
interface PacketBroadcaster{
/**
* @param NetworkSession[] $recipients
* @param NetworkSession[] $recipients
* @param ClientboundPacket[] $packets
*/
public function broadcastPackets(array $recipients, array $packets) : void;

View File

@ -118,7 +118,7 @@ final class ItemTranslator{
}
/**
* @param int[] $simpleMappings
* @param int[] $simpleMappings
* @param int[][] $complexMappings
* @phpstan-param array<string, int> $simpleMappings
* @phpstan-param array<string, array<int, int>> $complexMappings

View File

@ -175,6 +175,7 @@ class TypeConverter{
$nbt = new CompoundTag();
}
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
$meta = 0;
}elseif($isBlockItem && $itemStack->getMeta() !== 0){
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
@ -183,6 +184,7 @@ class TypeConverter{
$nbt = new CompoundTag();
}
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
$meta = 0;
}
}

View File

@ -34,6 +34,7 @@ use function openssl_digest;
use function openssl_error_string;
use function openssl_pkey_derive;
use function str_pad;
use const STR_PAD_LEFT;
final class EncryptionUtils{

View File

@ -112,7 +112,6 @@ use function array_push;
use function base64_encode;
use function count;
use function fmod;
use function implode;
use function in_array;
use function is_bool;
use function is_infinite;
@ -122,12 +121,9 @@ use function json_encode;
use function max;
use function mb_strlen;
use function microtime;
use function preg_match;
use function sprintf;
use function strlen;
use function strpos;
use function substr;
use function trim;
use const JSON_THROW_ON_ERROR;
/**
@ -252,18 +248,11 @@ class InGamePacketHandler extends PacketHandler{
$packetHandled = true;
$useItemTransaction = $packet->getItemInteractionData();
if($useItemTransaction !== null){
if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){
$packetHandled = false;
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
}else{
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
}
}
$blockActions = $packet->getBlockActions();
if($blockActions !== null){
if(count($blockActions) > 100){
throw new PacketHandlingException("Too many block actions in PlayerAuthInputPacket");
}
foreach($blockActions as $k => $blockAction){
$actionHandled = false;
if($blockAction instanceof PlayerBlockActionStopBreak){
@ -279,6 +268,20 @@ class InGamePacketHandler extends PacketHandler{
}
}
$useItemTransaction = $packet->getItemInteractionData();
if($useItemTransaction !== null){
if(count($useItemTransaction->getTransactionData()->getActions()) > 100){
throw new PacketHandlingException("Too many actions in item use transaction");
}
$this->inventoryManager->addPredictedSlotChanges($useItemTransaction->getTransactionData()->getActions());
if(!$this->handleUseItemTransaction($useItemTransaction->getTransactionData())){
$packetHandled = false;
$this->session->getLogger()->debug("Unhandled transaction in PlayerAuthInputPacket (type " . $useItemTransaction->getTransactionData()->getActionType() . ")");
}else{
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
}
}
return $packetHandled;
}
@ -310,6 +313,12 @@ class InGamePacketHandler extends PacketHandler{
public function handleInventoryTransaction(InventoryTransactionPacket $packet) : bool{
$result = true;
if(count($packet->trData->getActions()) > 100){
throw new PacketHandlingException("Too many actions in inventory transaction");
}
$this->inventoryManager->addPredictedSlotChanges($packet->trData->getActions());
if($packet->trData instanceof NormalTransactionData){
$result = $this->handleNormalTransaction($packet->trData);
}elseif($packet->trData instanceof MismatchTransactionData){
@ -324,9 +333,7 @@ class InGamePacketHandler extends PacketHandler{
$result = $this->handleReleaseItemTransaction($packet->trData);
}
if(!$result){
$this->inventoryManager->syncAll();
}else{
if($this->craftingTransaction === null){ //don't sync if we're waiting to complete a crafting transaction
$this->inventoryManager->syncMismatchedPredictedSlotChanges();
}
return $result;
@ -381,15 +388,9 @@ class InGamePacketHandler extends PacketHandler{
}
$this->player->setUsingItem(false);
try{
$this->inventoryManager->onTransactionStart($this->craftingTransaction);
$this->craftingTransaction->execute();
}catch(TransactionException $e){
$this->session->getLogger()->debug("Failed to execute crafting transaction: " . $e->getMessage());
//TODO: only sync slots that the client tried to change
foreach($this->craftingTransaction->getInventories() as $inventory){
$this->inventoryManager->syncContents($inventory);
}
return false;
}finally{
$this->craftingTransaction = null;
@ -409,18 +410,12 @@ class InGamePacketHandler extends PacketHandler{
$this->player->setUsingItem(false);
$transaction = new InventoryTransaction($this->player, $actions);
$this->inventoryManager->onTransactionStart($transaction);
try{
$transaction->execute();
}catch(TransactionException $e){
$logger = $this->session->getLogger();
$logger->debug("Failed to execute inventory transaction: " . $e->getMessage());
$logger->debug("Actions: " . json_encode($data->getActions()));
foreach($transaction->getInventories() as $inventory){
$this->inventoryManager->syncContents($inventory);
}
return false;
}
}
@ -430,7 +425,6 @@ class InGamePacketHandler extends PacketHandler{
private function handleUseItemTransaction(UseItemTransactionData $data) : bool{
$this->player->selectHotbarSlot($data->getHotbarSlot());
$this->inventoryManager->addPredictedSlotChanges($data->getActions());
switch($data->getActionType()){
case UseItemTransactionData::ACTION_CLICK_BLOCK:
@ -516,9 +510,7 @@ class InGamePacketHandler extends PacketHandler{
}
$this->player->selectHotbarSlot($data->getHotbarSlot());
$this->inventoryManager->addPredictedSlotChanges($data->getActions());
//TODO: use transactiondata for rollbacks here
switch($data->getActionType()){
case UseItemOnEntityTransactionData::ACTION_INTERACT:
$this->player->interactEntity($target, $data->getClickPosition());
@ -533,15 +525,10 @@ class InGamePacketHandler extends PacketHandler{
private function handleReleaseItemTransaction(ReleaseItemTransactionData $data) : bool{
$this->player->selectHotbarSlot($data->getHotbarSlot());
$this->inventoryManager->addPredictedSlotChanges($data->getActions());
//TODO: use transactiondata for rollbacks here (resending entire inventory is very wasteful)
switch($data->getActionType()){
case ReleaseItemTransactionData::ACTION_RELEASE:
if(!$this->player->releaseHeldItem()){
$this->inventoryManager->syncContents($this->player->getInventory());
}
return true;
if($data->getActionType() == ReleaseItemTransactionData::ACTION_RELEASE){
$this->player->releaseHeldItem();
return true;
}
return false;
@ -884,60 +871,17 @@ class InGamePacketHandler extends PacketHandler{
//TODO: make APIs for this to allow plugins to use this information
return $this->player->onFormSubmit($packet->formId, null);
}elseif($packet->formData !== null){
return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true));
try{
$responseData = json_decode($packet->formData, true, self::MAX_FORM_RESPONSE_DEPTH, JSON_THROW_ON_ERROR);
}catch(\JsonException $e){
throw PacketHandlingException::wrap($e, "Failed to decode form response data");
}
return $this->player->onFormSubmit($packet->formId, $responseData);
}else{
throw new PacketHandlingException("Expected either formData or cancelReason to be set in ModalFormResponsePacket");
}
}
/**
* Hack to work around a stupid bug in Minecraft W10 which causes empty strings to be sent unquoted in form responses.
*
* @return mixed
* @throws PacketHandlingException
*/
private static function stupid_json_decode(string $json, bool $assoc = false){
if(preg_match('/^\[(.+)\]$/s', $json, $matches) > 0){
$raw = $matches[1];
$lastComma = -1;
$newParts = [];
$inQuotes = false;
for($i = 0, $len = strlen($raw); $i <= $len; ++$i){
if($i === $len || ($raw[$i] === "," && !$inQuotes)){
$part = substr($raw, $lastComma + 1, $i - ($lastComma + 1));
if(trim($part) === ""){ //regular parts will have quotes or something else that makes them non-empty
$part = '""';
}
$newParts[] = $part;
$lastComma = $i;
}elseif($raw[$i] === '"'){
if(!$inQuotes){
$inQuotes = true;
}else{
$backslashes = 0;
for(; $backslashes < $i && $raw[$i - $backslashes - 1] === "\\"; ++$backslashes){}
if(($backslashes % 2) === 0){ //unescaped quote
$inQuotes = false;
}
}
}
}
$fixed = "[" . implode(",", $newParts) . "]";
try{
return json_decode($fixed, $assoc, self::MAX_FORM_RESPONSE_DEPTH, JSON_THROW_ON_ERROR);
}catch(\JsonException $e){
throw PacketHandlingException::wrap($e, "Failed to fix JSON (original: $json, modified: $fixed)");
}
}
try{
return json_decode($json, $assoc, self::MAX_FORM_RESPONSE_DEPTH, JSON_THROW_ON_ERROR);
}catch(\JsonException $e){
throw PacketHandlingException::wrap($e);
}
}
public function handleServerSettingsRequest(ServerSettingsRequestPacket $packet) : bool{
return false; //TODO: GUI stuff
}

View File

@ -123,7 +123,7 @@ class LoginPacketHandler extends PacketHandler{
$this->session->getPort(),
$this->server->requiresAuthentication()
);
if($this->server->getNetwork()->getConnectionCount() > $this->server->getMaxPlayers()){
if($this->server->getNetwork()->getValidConnectionCount() > $this->server->getMaxPlayers()){
$ev->setKickReason(PlayerPreLoginEvent::KICK_REASON_SERVER_FULL, KnownTranslationKeys::DISCONNECTIONSCREEN_SERVERFULL);
}
if(!$this->server->isWhitelisted($playerInfo->getUsername())){

View File

@ -52,6 +52,7 @@ use function mt_rand;
use function random_bytes;
use function rtrim;
use function substr;
use const PHP_INT_MAX;
class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
/**

View File

@ -40,6 +40,7 @@ use function strlen;
use function time;
use function trim;
use const AF_INET;
use const AF_INET6;
use const IPPROTO_IPV6;
use const IPV6_V6ONLY;
use const PHP_INT_MAX;

View File

@ -73,6 +73,12 @@ use function sprintf;
use function strlen;
use function trim;
use const AF_INET;
use const PREG_BACKTRACK_LIMIT_ERROR;
use const PREG_BAD_UTF8_ERROR;
use const PREG_BAD_UTF8_OFFSET_ERROR;
use const PREG_INTERNAL_ERROR;
use const PREG_JIT_STACKLIMIT_ERROR;
use const PREG_RECURSION_LIMIT_ERROR;
use const SO_RCVTIMEO;
use const SOCK_DGRAM;
use const SOCKET_ETIMEDOUT;

View File

@ -1390,7 +1390,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/**
* Returns whether the player can interact with the specified position. This checks distance and direction.
*
* @param float $maxDiff defaults to half of the 3D diagonal width of a block
* @param float $maxDiff defaults to half of the 3D diagonal width of a block
*/
public function canInteract(Vector3 $pos, float $maxDistance, float $maxDiff = M_SQRT3 / 2) : bool{
$eyePos = $this->getEyePos();
@ -1916,9 +1916,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/**
* Adds a title text to the user's screen, with an optional subtitle.
*
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
* @param int $stay Duration in ticks to stay on screen for
* @param int $fadeOut Duration in ticks for fade-out.
* @param int $fadeIn Duration in ticks for fade-in. If -1 is given, client-sided defaults will be used.
* @param int $stay Duration in ticks to stay on screen for
* @param int $fadeOut Duration in ticks for fade-out.
*/
public function sendTitle(string $title, string $subtitle = "", int $fadeIn = -1, int $stay = -1, int $fadeOut = -1) : void{
$this->setTitleDuration($fadeIn, $stay, $fadeOut);
@ -1959,8 +1959,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
/**
* Sets the title duration.
*
* @param int $fadeIn Title fade-in time in ticks.
* @param int $stay Title stay time in ticks.
* @param int $fadeIn Title fade-in time in ticks.
* @param int $stay Title stay time in ticks.
* @param int $fadeOut Title fade-out time in ticks.
*/
public function setTitleDuration(int $fadeIn, int $stay, int $fadeOut) : void{
@ -2061,7 +2061,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* Transfers a player to another server.
*
* @param string $address The IP address or hostname of the destination server
* @param int $port The destination port, defaults to 19132
* @param int $port The destination port, defaults to 19132
* @param string $message Message to show in the console when closing the player
*
* @return bool if transfer was successful.
@ -2105,7 +2105,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
*
* Note for internals developers: Do not call this from network sessions. It will cause a feedback loop.
*
* @param string $reason Shown to the player, usually this will appear on their disconnect screen.
* @param string $reason Shown to the player, usually this will appear on their disconnect screen.
* @param Translatable|string|null $quitMessage Message to broadcast to online players (null will use default)
*/
public function disconnect(string $reason, Translatable|string|null $quitMessage = null) : void{
@ -2121,7 +2121,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* @internal
* This method executes post-disconnect actions and cleanups.
*
* @param string $reason Shown to the player, usually this will appear on their disconnect screen.
* @param string $reason Shown to the player, usually this will appear on their disconnect screen.
* @param Translatable|string|null $quitMessage Message to broadcast to online players (null will use default)
*/
public function onPostDisconnect(string $reason, Translatable|string|null $quitMessage) : void{
@ -2448,7 +2448,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->cursorInventory = new PlayerCursorInventory($this);
$this->craftingGrid = new PlayerCraftingInventory($this);
$this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory);
$this->addPermanentInventories($this->inventory, $this->armorInventory, $this->cursorInventory, $this->offHandInventory, $this->craftingGrid);
//TODO: more windows
}

View File

@ -45,6 +45,7 @@ use function stream_copy_to_stream;
use function strpos;
use function strtolower;
use function trim;
use const DIRECTORY_SEPARATOR;
abstract class PluginBase implements Plugin, CommandExecutor{
private bool $isEnabled = false;
@ -201,7 +202,7 @@ abstract class PluginBase implements Plugin, CommandExecutor{
}
/**
* @param string[] $args
* @param string[] $args
*/
public function onCommand(CommandSender $sender, Command $command, string $label, array $args) : bool{
return false;

View File

@ -57,7 +57,7 @@ interface ResourcePack{
* Note that resource packs must **always** be in zip archive format for sending.
* A folder resource loader may need to perform on-the-fly compression for this purpose.
*
* @param int $start Offset to start reading the chunk from
* @param int $start Offset to start reading the chunk from
* @param int $length Maximum length of data to return.
*
* @return string byte-array

View File

@ -58,7 +58,7 @@ class ResourcePackManager{
private array $encryptionKeys = [];
/**
* @param string $path Path to resource-packs directory.
* @param string $path Path to resource-packs directory.
*/
public function __construct(string $path, \Logger $logger){
$this->path = $path;

View File

@ -187,7 +187,7 @@ abstract class AsyncTask extends \Threaded{
* {@link AsyncTask::onCompletion} is called.
*
* @param mixed $progress The parameter passed to {@link AsyncTask#publishProgress}. It is serialize()'ed
* and then unserialize()'ed, as if it has been cloned.
* and then unserialize()'ed, as if it has been cloned.
*/
public function onProgressUpdate($progress) : void{
@ -215,7 +215,7 @@ abstract class AsyncTask extends \Threaded{
* Objects stored in this storage can be retrieved using fetchLocal() on the same thread that this method was called
* from.
*
* @param mixed $complexData the data to store
* @param mixed $complexData the data to store
*/
protected function storeLocal(string $key, $complexData) : void{
if(self::$threadLocalStorage === null){

View File

@ -77,7 +77,7 @@ class AsyncWorker extends Worker{
* Saves mixed data into the worker's thread-local object store. This can be used to store objects which you
* want to use on this worker thread from multiple AsyncTasks.
*
* @param mixed $value
* @param mixed $value
*/
public function saveToThreadStore(string $identifier, $value) : void{
if(\Thread::getCurrentThread() !== $this){

View File

@ -26,7 +26,7 @@ namespace pocketmine\scheduler;
final class BulkCurlTaskOperation{
/**
* @param string[] $extraHeaders
* @param mixed[] $extraOpts
* @param mixed[] $extraOpts
* @phpstan-param list<string> $extraHeaders
* @phpstan-param array<int, mixed> $extraOpts
*/

View File

@ -41,6 +41,8 @@ abstract class Timings{
/** @var TimingsHandler */
public static $serverTick;
/** @var TimingsHandler */
public static $serverInterrupts;
/** @var TimingsHandler */
public static $memoryManager;
/** @var TimingsHandler */
public static $garbageCollector;
@ -140,7 +142,8 @@ abstract class Timings{
self::$initialized = true;
self::$fullTick = new TimingsHandler("Full Server Tick");
self::$serverTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Full Server Tick", self::$fullTick);
self::$serverTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Server Tick Update Cycle", self::$fullTick);
self::$serverInterrupts = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Server Mid-Tick Processing", self::$fullTick);
self::$memoryManager = new TimingsHandler("Memory Manager");
self::$garbageCollector = new TimingsHandler("Garbage Collector", self::$memoryManager);
self::$titleTick = new TimingsHandler("Console Title Tick");

View File

@ -0,0 +1,47 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\timings;
use pocketmine\snooze\SleeperHandler;
/**
* Custom Snooze sleeper handler which captures notification processing time.
*/
final class TimingsAwareSleeperHandler extends SleeperHandler{
public function __construct(
private TimingsHandler $timings
){
parent::__construct();
}
public function processNotifications() : void{
$this->timings->startTiming();
try{
parent::processNotifications();
}finally{
$this->timings->stopTiming();
}
}
}

View File

@ -29,6 +29,7 @@ use pocketmine\lang\Translatable;
use pocketmine\permission\PermissibleBase;
use pocketmine\permission\PermissibleDelegateTrait;
use pocketmine\Server;
use const PHP_INT_MAX;
/**
* Forwards any messages it receives via sendMessage() to the given logger. Used for forwarding chat messages and

View File

@ -23,6 +23,10 @@ declare(strict_types=1);
namespace pocketmine\utils;
/**
* This trait offers the same functionality as RegistryTrait, but also clones any returned objects to prevent outside
* modification.
*/
trait CloningRegistryTrait{
use RegistryTrait;

View File

@ -55,6 +55,7 @@ use const CASE_LOWER;
use const JSON_BIGINT_AS_STRING;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
use const YAML_UTF8_ENCODING;
/**
* Config Class for simple config manipulation of multiple formats.
@ -108,8 +109,8 @@ class Config{
];
/**
* @param string $file Path of the file to be loaded
* @param int $type Config type to load, -1 by default (detect)
* @param string $file Path of the file to be loaded
* @param int $type Config type to load, -1 by default (detect)
* @param mixed[] $default Array with the default values that will be written to the file if it did not exist
* @phpstan-param array<string, mixed> $default
*/
@ -493,7 +494,7 @@ class Config{
/**
* @param mixed[] $default
* @param mixed[] $data reference parameter
* @param mixed[] $data reference parameter
* @phpstan-param array<string, mixed> $default
* @phpstan-param array<string, mixed> $data
* @phpstan-param-out array<string, mixed> $data

View File

@ -23,6 +23,13 @@ declare(strict_types=1);
namespace pocketmine\utils;
/**
* This trait allows a class to simulate a Java-style enum. Members are exposed as static methods and handled via
* __callStatic().
*
* Classes using this trait need to include \@method tags in their class docblock for every enum member.
* Alternatively, just put \@generate-registry-docblock in the docblock and run tools/generate-registry-annotations.php
*/
trait EnumTrait{
use RegistryTrait;
use NotCloneable;

View File

@ -187,9 +187,10 @@ final class Filesystem{
* @throws \InvalidArgumentException if the lock file path is invalid (e.g. parent directory doesn't exist, permission denied)
*/
public static function createLockFile(string $lockFilePath) : ?int{
$resource = fopen($lockFilePath, "a+b");
if($resource === false){
throw new \InvalidArgumentException("Invalid lock file path or read/write permissions denied");
try{
$resource = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($lockFilePath, "a+b"));
}catch(\ErrorException $e){
throw new \InvalidArgumentException("Failed to open lock file: " . $e->getMessage(), 0, $e);
}
if(!flock($resource, LOCK_EX | LOCK_NB)){
//wait for a shared lock to avoid race conditions if two servers started at the same time - this makes sure the

View File

@ -36,7 +36,7 @@ final class Git{
/**
* Returns the git hash of the currently checked out head of the given repository, or null on failure.
*
* @param bool $dirty reference parameter, set to whether the repo has local changes
* @param bool $dirty reference parameter, set to whether the repo has local changes
*/
public static function getRepositoryState(string $dir, bool &$dirty) : ?string{
if(Process::execute("git -C \"$dir\" rev-parse HEAD", $out) === 0 && $out !== false && strlen($out = trim($out)) === 40){

View File

@ -141,9 +141,9 @@ class Internet{
*
* @phpstan-template TErrorVar of mixed
*
* @param int $timeout default 10
* @param int $timeout default 10
* @param string[] $extraHeaders
* @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
* @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
* @phpstan-param list<string> $extraHeaders
* @phpstan-param TErrorVar $err
* @phpstan-param-out TErrorVar|string $err
@ -165,7 +165,7 @@ class Internet{
*
* @param string[]|string $args
* @param string[] $extraHeaders
* @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation.
* @param string|null $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation.
* @phpstan-param string|array<string, string> $args
* @phpstan-param list<string> $extraHeaders
* @phpstan-param TErrorVar $err
@ -187,7 +187,7 @@ class Internet{
* General cURL shorthand function.
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
*
* @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms.
* @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms.
* @param string[] $extraHeaders extra headers to send as a plain string array
* @param array $extraOpts extra CURLOPT_* to set as an [opt => value] map
* @param \Closure|null $onSuccess function to be called if there is no error. Accepts a resource argument as the cURL handle.

View File

@ -150,8 +150,8 @@ final class Process{
/**
* @param string $command Command to execute
* @param string|null $stdout Reference parameter to write stdout to
* @param string|null $stderr Reference parameter to write stderr to
* @param string|null $stdout Reference parameter to write stdout to
* @param string|null $stderr Reference parameter to write stderr to
* @phpstan-param-out string $stdout
* @phpstan-param-out string $stderr
*

View File

@ -115,7 +115,7 @@ class Random{
* Returns a random integer between $start and $end
*
* @param int $start default 0
* @param int $end default 0x7fffffff
* @param int $end default 0x7fffffff
*/
public function nextRange(int $start = 0, int $end = 0x7fffffff) : int{
return $start + ($this->nextInt() % ($end + 1 - $start));

View File

@ -28,6 +28,13 @@ use function count;
use function mb_strtoupper;
use function preg_match;
/**
* This trait allows a class to simulate object class constants, since PHP doesn't currently support this.
* These faux constants are exposed in static class methods, which are handled using __callStatic().
*
* Classes using this trait need to include \@method tags in their class docblock for every faux constant.
* Alternatively, just put \@generate-registry-docblock in the docblock and run tools/generate-registry-annotations.php
*/
trait RegistryTrait{
/**
* @var object[]

View File

@ -539,7 +539,7 @@ final class Utils{
* incompatible.
*
* @param callable|CallbackType $signature Dummy callable with the required parameters and return type
* @param callable $subject Callable to check the signature of
* @param callable $subject Callable to check the signature of
* @phpstan-param anyCallable|CallbackType $signature
* @phpstan-param anyCallable $subject
*

View File

@ -58,6 +58,7 @@ use pocketmine\item\StringToItemParser;
use pocketmine\item\VanillaItems;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
@ -643,7 +644,7 @@ class World implements ChunkManager{
* Returns a list of players who are in the given filter and also using the chunk containing the target position.
* Used for broadcasting sounds and particles with specific targets.
*
* @param Player[] $allowed
* @param Player[] $allowed
* @phpstan-param list<Player> $allowed
*
* @return array<int, Player>
@ -1222,7 +1223,8 @@ class World implements ChunkManager{
private function tickChunk(int $chunkX, int $chunkZ) : void{
$chunk = $this->getChunk($chunkX, $chunkZ);
if($chunk === null){
throw new \InvalidArgumentException("Chunk is not loaded");
//the chunk may have been unloaded during a previous chunk's update (e.g. during BlockGrowEvent)
return;
}
foreach($this->getChunkEntities($chunkX, $chunkZ) as $entity){
$entity->onRandomUpdate();
@ -1320,7 +1322,9 @@ class World implements ChunkManager{
/**
* Notify the blocks at and around the position that the block at the position may have changed.
* This will cause onNeighbourBlockUpdate() to be called for these blocks.
* This will cause onNearbyBlockChange() to be called for these blocks.
*
* @see Block::onNearbyBlockChange()
*/
public function notifyNeighbourBlockUpdate(Vector3 $pos) : void{
$this->tryAddToNeighbourUpdateQueue($pos);
@ -1634,8 +1638,8 @@ class World implements ChunkManager{
* Note: If you're using this for performance-sensitive code, and you're guaranteed to be supplying ints in the
* specified vector, consider using {@link getBlockAt} instead for better performance.
*
* @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate)
* @param bool $addToCache Whether to cache the block object created by this method call.
* @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate)
* @param bool $addToCache Whether to cache the block object created by this method call.
*/
public function getBlock(Vector3 $pos, bool $cached = true, bool $addToCache = true) : Block{
return $this->getBlockAt((int) floor($pos->x), (int) floor($pos->y), (int) floor($pos->z), $cached, $addToCache);
@ -1647,7 +1651,7 @@ class World implements ChunkManager{
* Note for plugin developers: If you are using this method a lot (thousands of times for many positions for
* example), you may want to set addToCache to false to avoid using excessive amounts of memory.
*
* @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate)
* @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate)
* @param bool $addToCache Whether to cache the block object created by this method call.
*/
public function getBlockAt(int $x, int $y, int $z, bool $cached = true, bool $addToCache = true) : Block{
@ -1794,7 +1798,7 @@ class World implements ChunkManager{
* Tries to break a block using a item, including Player time checks if available
* It'll try to lower the durability if Item is a tool, and set it to Air if broken.
*
* @param Item $item reference parameter (if null, can break anything)
* @param Item $item reference parameter (if null, can break anything)
* @phpstan-param-out Item $item
*/
public function useBreakOn(Vector3 $vector, Item &$item = null, ?Player $player = null, bool $createParticles = false) : bool{
@ -1896,8 +1900,8 @@ class World implements ChunkManager{
/**
* Uses a item on a position and face, placing it or activating the block
*
* @param Player|null $player default null
* @param bool $playSound Whether to play a block-place sound if the block was placed successfully.
* @param Player|null $player default null
* @param bool $playSound Whether to play a block-place sound if the block was placed successfully.
*/
public function useItemOn(Vector3 $vector, Item &$item, int $face, ?Vector3 $clickVector = null, ?Player $player = null, bool $playSound = false) : bool{
$blockClicked = $this->getBlock($vector);
@ -1952,6 +1956,10 @@ class World implements ChunkManager{
if($hand->canBePlacedAt($blockClicked, $clickVector, $face, true)){
$blockReplace = $blockClicked;
//TODO: while this mimics the vanilla behaviour with replaceable blocks, we should really pass some other
//value like NULL and let place() deal with it. This will look like a bug to anyone who doesn't know about
//the vanilla behaviour.
$face = Facing::UP;
$hand->position($this, $blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z);
}elseif(!$hand->canBePlacedAt($blockReplace, $clickVector, $face, false)){
return false;
@ -2092,8 +2100,8 @@ class World implements ChunkManager{
/**
* Returns the closest Entity to the specified position, within the given radius.
*
* @param string $entityType Class of entity to use for instanceof
* @param bool $includeDead Whether to include entitites which are dead
* @param string $entityType Class of entity to use for instanceof
* @param bool $includeDead Whether to include entitites which are dead
* @phpstan-template TEntity of Entity
* @phpstan-param class-string<TEntity> $entityType
*

View File

@ -166,7 +166,7 @@ class WorldManager{
/**
* Loads a world from the data directory
*
* @param bool $autoUpgrade Converts worlds to the default format if the world's format is not writable / deprecated
* @param bool $autoUpgrade Converts worlds to the default format if the world's format is not writable / deprecated
*
* @throws WorldException
*/

View File

@ -164,8 +164,8 @@ class Chunk{
/**
* Sets the biome ID at the specified X/Z chunk block coordinates
*
* @param int $x 0-15
* @param int $z 0-15
* @param int $x 0-15
* @param int $z 0-15
* @param int $biomeId 0-255
*/
public function setBiomeId(int $x, int $z, int $biomeId) : void{

View File

@ -504,7 +504,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
/**
* @param CompoundTag[] $targets
* @param CompoundTag[] $targets
*/
private function writeTags(array $targets, string $index, \LevelDBWriteBatch $write) : void{
if(count($targets) > 0){

View File

@ -38,7 +38,7 @@ use function preg_match_all;
final class FlatGeneratorOptions{
/**
* @param int[] $structure
* @param int[] $structure
* @param mixed[] $extraOptions
* @phpstan-param array<int, int> $structure
* @phpstan-param array<string, array<string, string>|true> $extraOptions

View File

@ -58,10 +58,10 @@ final class GeneratorManager{
}
/**
* @param string $class Fully qualified name of class that extends \pocketmine\world\generator\Generator
* @param string $name Alias for this generator type that can be written in configs
* @param \Closure $presetValidator Callback to validate generator options for new worlds
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
* @param string $class Fully qualified name of class that extends \pocketmine\world\generator\Generator
* @param string $name Alias for this generator type that can be written in configs
* @param \Closure $presetValidator Callback to validate generator options for new worlds
* @param bool $overwrite Whether to force overwriting any existing registered generator with the same name
*
* @phpstan-param \Closure(string) : ?InvalidGeneratorOptionsException $presetValidator
*

View File

@ -208,8 +208,8 @@ class SkyLightUpdate extends LightUpdate{
/**
* Recalculates the heightmap for the block column at the specified X/Z chunk coordinates
*
* @param int $x 0-15
* @param int $z 0-15
* @param int $x 0-15
* @param int $z 0-15
* @param \SplFixedArray|bool[] $directSkyLightBlockers
* @phpstan-param \SplFixedArray<bool> $directSkyLightBlockers
*

View File

@ -15,11 +15,6 @@ parameters:
count: 1
path: ../../../src/entity/projectile/Projectile.php
-
message: "#^Empty array passed to foreach\\.$#"
count: 1
path: ../../../src/network/mcpe/cache/ChunkCache.php
-
message: "#^Match arm comparison between 4 and 4 is always true\\.$#"
count: 1
@ -51,7 +46,7 @@ parameters:
path: ../../../src/plugin/PluginManager.php
-
message: "#^Offset \\(int\\|string\\) on non\\-empty\\-array\\<pocketmine\\\\plugin\\\\Plugin\\> in isset\\(\\) always exists and is not nullable\\.$#"
message: "#^Offset \\(int\\|string\\) on array\\<pocketmine\\\\plugin\\\\Plugin\\> in isset\\(\\) always exists and is not nullable\\.$#"
count: 1
path: ../../../src/plugin/PluginManager.php
@ -70,11 +65,6 @@ parameters:
count: 1
path: ../../../src/thread/Worker.php
-
message: "#^Offset \\(int\\|string\\) on non\\-empty\\-array\\<pocketmine\\\\world\\\\World\\> in isset\\(\\) always exists and is not nullable\\.$#"
count: 1
path: ../../../src/world/WorldManager.php
-
message: "#^Call to function is_resource\\(\\) with resource will always evaluate to true\\.$#"
count: 2

View File

@ -1,68 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use PHPUnit\Framework\TestCase;
class StupidJsonDecodeTest extends TestCase{
/**
* @var \Closure
* @phpstan-var \Closure(string $json, bool $assoc=) : mixed
*/
private $stupidJsonDecodeFunc;
public function setUp() : void{
$this->stupidJsonDecodeFunc = (new \ReflectionMethod(InGamePacketHandler::class, 'stupid_json_decode'))->getClosure();
}
/**
* @return mixed[][]
* @phpstan-return list<array{string,mixed}>
*/
public function stupidJsonDecodeProvider() : array{
return [
["[\n \"a\",\"b,c,d,e\\\" \",,0,1,2, false, 0.001]", ['a', 'b,c,d,e" ', '', 0, 1, 2, false, 0.001]],
["0", 0],
["false", false],
["null", null],
['["\",,\"word","a\",,\"word2",]', ['",,"word', 'a",,"word2', '']],
['["\",,\"word","a\",,\"word2",""]', ['",,"word', 'a",,"word2', '']],
['["Hello,, PocketMine"]', ['Hello,, PocketMine']],
['[,]', ['', '']],
['[]', []]
];
}
/**
* @dataProvider stupidJsonDecodeProvider
*
* @param mixed $expect
*
* @throws \ReflectionException
*/
public function testStupidJsonDecode(string $brokenJson, $expect) : void{
$decoded = ($this->stupidJsonDecodeFunc)($brokenJson, true);
self::assertEquals($expect, $decoded);
}
}

View File

@ -43,6 +43,10 @@ use function rename;
use function round;
use function scandir;
use function unlink;
use const PATHINFO_EXTENSION;
use const PHP_BINARY;
use const SCANDIR_SORT_NONE;
use const SORT_NUMERIC;
require dirname(__DIR__) . '/vendor/autoload.php';

View File

@ -0,0 +1,89 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\tools\decode_crashdump;
use function array_pop;
use function array_slice;
use function base64_decode;
use function count;
use function file;
use function file_put_contents;
use function fwrite;
use function implode;
use function json_decode;
use function json_encode;
use function realpath;
use function trim;
use function zlib_decode;
use const FILE_IGNORE_NEW_LINES;
use const JSON_PRETTY_PRINT;
use const JSON_UNESCAPED_SLASHES;
use const PHP_EOL;
use const STDERR;
if(count($argv) === 2){
$input = $argv[1];
$output = "decoded.json";
}elseif(count($argv) === 3){
[, $input, $output] = $argv;
}else{
fwrite(STDERR, "Required arguments: input file, output file" . PHP_EOL);
exit(1);
}
$lines = file($input, FILE_IGNORE_NEW_LINES);
if($lines === false){
fwrite(STDERR, "Unable to read file $input" . PHP_EOL);
exit(1);
}
$start = -1;
foreach($lines as $num => $line){
if(trim($line) === "===BEGIN CRASH DUMP==="){
$start = $num + 1;
break;
}
}
if($start === -1){
fwrite(STDERR, "Crashdump encoded data not found in target file" . PHP_EOL);
exit(1);
}
$data = array_slice($lines, $start);
array_pop($data);
$zlibData = base64_decode(implode("", $data), true);
if($zlibData === false){
fwrite(STDERR, "Invalid encoded data in crashdump" . PHP_EOL);
exit(1);
}
$decoded = zlib_decode($zlibData);
if($decoded === false){
fwrite(STDERR, "Invalid compressed data in crashdump" . PHP_EOL);
exit(1);
}
file_put_contents($output, json_encode(json_decode($decoded), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
echo "Wrote decoded crashdump to " . realpath($output) . PHP_EOL;

View File

@ -49,9 +49,13 @@ use function socket_strerror;
use function strlen;
use function time;
use function trim;
use const AF_INET;
use const MSG_DONTROUTE;
use const PHP_BINARY;
use const PHP_INT_MAX;
use const SOCK_DGRAM;
use const SOL_UDP;
use const STDIN;
require_once 'vendor/autoload.php';