diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..d13fb4498 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Help & support on Discord + url: https://discord.gg/bmSAZBG + about: We don't accept support requests on the issue tracker. Please try asking on Discord instead. + - name: Help & support on forums + url: https://forums.pmmp.io + about: We don't accept support requests on the issue tracker. Please try asking on forums instead. + - name: Documentation + url: https://pmmp.rtfd.io + about: PocketMine-MP documentation diff --git a/.github/ISSUE_TEMPLATE/help---support.md b/.github/ISSUE_TEMPLATE/help---support.md deleted file mode 100644 index cc34e496b..000000000 --- a/.github/ISSUE_TEMPLATE/help---support.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Help & support -about: We don't accept support requests here. Try the links on the README. -title: '' -labels: Support request -assignees: '' - ---- - -We don't accept support requests on the issue tracker. Please try the following links instead: - -Documentation: http://pmmp.rtfd.io -Forums: https://forums.pmmp.io -Discord: https://discord.gg/bmSAZBG diff --git a/.github/ISSUE_TEMPLATE/security-dos-vulnerability.md b/.github/ISSUE_TEMPLATE/security-dos-vulnerability.md deleted file mode 100644 index c39833685..000000000 --- a/.github/ISSUE_TEMPLATE/security-dos-vulnerability.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: Security/DoS vulnerability -about: 'Bug or exploit that can be used to attack servers (hint: don''t report it - on a public issue tracker)' -title: '' -labels: 'Auto: Spam' -assignees: '' - ---- - -Please DO NOT report security vulnerabilities here. -Instead, send an email to team@pmmp.io or contact a developer directly, IN PRIVATE. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..4fcd2ac5d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,33 @@ +# Security Policy + +## Supported Versions +The following release lines are currently receiving active security updates and bug fixes: + +| Version | Supported | +| -------- | ------------------ | +| 3.15.x | :white_check_mark: | +| < 3.15.0 | :x: | + +## Reporting a Vulnerability + +**DO NOT report vulnerabilities on the GitHub issue tracker.** +GitHub is public and anyone can see the issues you post on the issue tracker, including people who would exploit vulnerabilities for their own gain. + +**WARNING: You may put live servers at risk by reporting a vulnerability on the GitHub issue tracker.** + +**Contact us** by sending an email to [**team@pmmp.io**](mailto:team@pmmp.io?subject=Security%20Vulnerability%20in%20PocketMine-MP). Include the following information: + +- Version of PocketMine-MP +- Detailed description of the vulnerability (e.g. how to exploit it, what the effects are) + +Please note that we can't guarantee a reply to every email. + +## FAQ +### Do you offer a bug bounty? +No. + +### How soon can I expect a fix for a vulnerability I've reported? +This depends on the nature of the problem. We can't provide any general ETA (nor would it be wise to provide one). +In general, it depends on when developers have time to look into the problem, how complex the problem is to fix, and how many users it impacts. + +When a fix for a severe vulnerability is pushed, a patch release for the target version will usually be released within 24 hours so that users can update. diff --git a/changelogs/3.15.md b/changelogs/3.15.md index e6439fd03..afba42b49 100644 --- a/changelogs/3.15.md +++ b/changelogs/3.15.md @@ -15,3 +15,15 @@ Plugin developers should **only** update their required API to this version if y - Pumpkin and melon stems may not connect to their corresponding pumpkin/melon - New blocks, items & mobs aren't implemented - Nether doesn't exist + +# 3.15.1 +- Fixed various PHP 7.4 compatibility issues in Composer dependencies (primarily callback-validator). +- Fixed LevelDB worlds with corrupted `level.dat` crashing the server instead of failing gracefully. +- Fixed error spam when using strings for layers in flatworld presets (`e.g. bedrock,3xdirt,grass`). +- Fixed blocks not getting updated properly on explosions. +- Fixed `BlockGrowEvent` not being called when sugarcane grows. +- Potato crops now drop poisonous potatoes when harvested. +- Fixed the wrong number of potatoes being dropped when harvesting potato crops. +- Players no longer get pullbacks when sprinting on slabs, stairs and various other blocks when `player.anti-cheat.allow-movement-cheats` is set to `false`. (This bug has been around for over 5 years, so many of you will be used to its existence.) +- Fixed entity collision box calculation not taking clip distance into account. +- Entities now step up the correct height of the target block, instead of jumping into the air 0.6 blocks and falling back down. diff --git a/composer.json b/composer.json index 6ccdacf4e..c226a88f1 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "respect/validation": "^2.0" }, "require-dev": { - "phpstan/phpstan": "0.12.37", + "phpstan/phpstan": "0.12.42", "phpstan/phpstan-phpunit": "^0.12.6", "phpstan/phpstan-strict-rules": "^0.12.2", "phpunit/phpunit": "^9.2" diff --git a/composer.lock b/composer.lock index edd142f56..a23295a64 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "3a8f754965da97b4e3237fd32168a6aa", + "content-hash": "adc74d30203a9378694bc478baf7a142", "packages": [ { "name": "adhocore/json-comment", @@ -1443,16 +1443,16 @@ }, { "name": "phpstan/phpstan", - "version": "0.12.37", + "version": "0.12.42", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "5e16d83e6eb2dd784fbdaeaece5e2bca72e4f68a" + "reference": "7c43b7c2d5ca6554f6231e82e342a710163ac5f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/5e16d83e6eb2dd784fbdaeaece5e2bca72e4f68a", - "reference": "5e16d83e6eb2dd784fbdaeaece5e2bca72e4f68a", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7c43b7c2d5ca6554f6231e82e342a710163ac5f4", + "reference": "7c43b7c2d5ca6554f6231e82e342a710163ac5f4", "shasum": "" }, "require": { @@ -1495,7 +1495,7 @@ "type": "tidelift" } ], - "time": "2020-08-09T14:32:41+00:00" + "time": "2020-09-02T13:14:53+00:00" }, { "name": "phpstan/phpstan-phpunit", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 3f7c79404..5ab264768 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -24,6 +24,8 @@ parameters: checkExplicitMixed: true bootstrapFiles: - tests/phpstan/bootstrap.php + scanDirectories: + - tests/plugins/TesterPlugin scanFiles: - src/PocketMine.php - build/make-release.php @@ -34,6 +36,7 @@ parameters: - build/make-release.php - build/server-phar.php - tests/phpunit + - tests/plugins/TesterPlugin dynamicConstantNames: - pocketmine\VersionInfo::IS_DEVELOPMENT_BUILD - pocketmine\DEBUG diff --git a/src/block/Potato.php b/src/block/Potato.php index 06ac93f81..1bfda0d5c 100644 --- a/src/block/Potato.php +++ b/src/block/Potato.php @@ -30,9 +30,13 @@ use function mt_rand; class Potato extends Crops{ public function getDropsForCompatibleTool(Item $item) : array{ - return [ - VanillaItems::POTATO()->setCount($this->age >= 7 ? mt_rand(1, 4) : 1) + $result = [ + VanillaItems::POTATO()->setCount($this->age >= 7 ? mt_rand(1, 5) : 1) ]; + if($this->age >= 7 && mt_rand(0, 49) === 0){ + $result[] = VanillaItems::POISONOUS_POTATO(); + } + return $result; } public function getPickedItem(bool $addUserData = false) : Item{ diff --git a/src/block/Sugarcane.php b/src/block/Sugarcane.php index d3444052f..d66c84bfb 100644 --- a/src/block/Sugarcane.php +++ b/src/block/Sugarcane.php @@ -98,7 +98,12 @@ class Sugarcane extends Flowable{ for($y = 1; $y < 3; ++$y){ $b = $this->pos->getWorld()->getBlockAt($this->pos->x, $this->pos->y + $y, $this->pos->z); if($b->getId() === BlockLegacyIds::AIR){ - $this->pos->getWorld()->setBlock($b->pos, VanillaBlocks::SUGARCANE()); + $ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE()); + $ev->call(); + if($ev->isCancelled()){ + break; + } + $this->pos->getWorld()->setBlock($b->pos, $ev->getNewState()); break; } } diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 3fdcc1fae..c3ac15109 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -75,6 +75,7 @@ use const M_PI_2; abstract class Entity{ public const MOTION_THRESHOLD = 0.00001; + protected const STEP_CLIP_MULTIPLIER = 0.4; /** @var int */ private static $entityCount = 1; @@ -137,6 +138,8 @@ abstract class Entity{ /** @var int */ private $maxHealth = 20; + /** @var float */ + protected $ySize = 0.0; /** @var float */ protected $stepHeight = 0.0; /** @var bool */ @@ -329,10 +332,10 @@ abstract class Entity{ $this->boundingBox = new AxisAlignedBB( $this->location->x - $halfWidth, - $this->location->y, + $this->location->y + $this->ySize, $this->location->z - $halfWidth, $this->location->x + $halfWidth, - $this->location->y + $this->height, + $this->location->y + $this->height + $this->ySize, $this->location->z + $halfWidth ); } @@ -1081,6 +1084,7 @@ abstract class Entity{ if($this->keepMovement){ $this->boundingBox->offset($dx, $dy, $dz); }else{ + $this->ySize *= self::STEP_CLIP_MULTIPLIER; /* if($this->isColliding){ //With cobweb? @@ -1175,7 +1179,12 @@ abstract class Entity{ $stepBB->offset(0, 0, $dz); - //TODO: here we need to shift back down on the Y-axis to the top of the target block (we don't want to jump into the air when walking onto carpet) + $reverseDY = -$dy; + foreach($list as $bb){ + $reverseDY = $bb->calculateYOffset($stepBB, $reverseDY); + } + $dy += $reverseDY; + $stepBB->offset(0, $reverseDY, 0); if(($cx ** 2 + $cz ** 2) >= ($dx ** 2 + $dz ** 2)){ $dx = $cx; @@ -1183,6 +1192,7 @@ abstract class Entity{ $dz = $cz; }else{ $moveBB = $stepBB; + $this->ySize += $dy; } } @@ -1190,7 +1200,7 @@ abstract class Entity{ } $this->location->x = ($this->boundingBox->minX + $this->boundingBox->maxX) / 2; - $this->location->y = $this->boundingBox->minY; + $this->location->y = $this->boundingBox->minY - $this->ySize; $this->location->z = ($this->boundingBox->minZ + $this->boundingBox->maxZ) / 2; $this->checkChunks(); @@ -1420,6 +1430,7 @@ abstract class Entity{ if($ev->isCancelled()){ return false; } + $this->ySize = 0; $pos = $ev->getTo(); $this->setMotion(new Vector3(0, 0, 0)); diff --git a/src/entity/utils/ExperienceUtils.php b/src/entity/utils/ExperienceUtils.php index 9b387b397..f23fd2d52 100644 --- a/src/entity/utils/ExperienceUtils.php +++ b/src/entity/utils/ExperienceUtils.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\entity\utils; use pocketmine\math\Math; +use pocketmine\utils\AssumptionFailedError; use function max; abstract class ExperienceUtils{ @@ -59,6 +60,9 @@ abstract class ExperienceUtils{ * This returns a floating-point number, the decimal part being the progress through the resulting level. */ public static function getLevelFromXp(int $xp) : float{ + if($xp < 0){ + throw new \InvalidArgumentException("XP must be at least 0"); + } if($xp <= self::getXpToReachLevel(16)){ $a = 1; $b = 6; @@ -74,6 +78,9 @@ abstract class ExperienceUtils{ } $x = Math::solveQuadratic($a, $b, $c - $xp); + if(count($x) === 0){ + throw new AssumptionFailedError("Expected at least 1 solution"); + } return max($x); //we're only interested in the positive solution } diff --git a/src/player/Player.php b/src/player/Player.php index 6a2c6c4f4..1a00dc2c2 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -2207,6 +2207,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->networkSession->syncMovement($pos, $yaw, $pitch, $mode); $this->forceMoveSync = $pos->asVector3(); + $this->ySize = 0; } /** diff --git a/tests/phpstan/configs/l8-baseline.neon b/tests/phpstan/configs/l8-baseline.neon index 7721ba804..506fef3bf 100644 --- a/tests/phpstan/configs/l8-baseline.neon +++ b/tests/phpstan/configs/l8-baseline.neon @@ -115,11 +115,6 @@ parameters: count: 1 path: ../../../src/command/defaults/SetWorldSpawnCommand.php - - - message: "#^Parameter \\#1 \\$name of method pocketmine\\\\Server\\:\\:getPlayer\\(\\) expects string, string\\|null given\\.$#" - count: 1 - path: ../../../src/command/defaults/TellCommand.php - - message: "#^Cannot call method getTime\\(\\) on pocketmine\\\\world\\\\World\\|null\\.$#" count: 1 @@ -417,11 +412,6 @@ parameters: - message: "#^Parameter \\#1 \\$str of function trim expects string, string\\|null given\\.$#" - count: 4 - path: ../../../src/permission/BanEntry.php - - - - message: "#^Parameter \\#1 \\$date of static method pocketmine\\\\permission\\\\BanEntry\\:\\:parseDate\\(\\) expects string, string\\|null given\\.$#" count: 1 path: ../../../src/permission/BanEntry.php @@ -961,7 +951,7 @@ parameters: path: ../../phpunit/event/HandlerListManagerTest.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\handler\\\\StupidJsonDecodeTest\\:\\:\\$stupidJsonDecodeFunc \\(Closure\\) does not accept Closure\\|null\\.$#" + message: "#^Cannot call method cancel\\(\\) on pocketmine\\\\scheduler\\\\TaskHandler\\|null\\.$#" count: 1 - path: ../../phpunit/network/mcpe/handler/StupidJsonDecodeTest.php + path: ../../plugins/TesterPlugin/src/pmmp/TesterPlugin/CheckTestCompletionTask.php diff --git a/tests/phpstan/configs/php-bugs.neon b/tests/phpstan/configs/php-bugs.neon index 5232fdf60..b0d65acb6 100644 --- a/tests/phpstan/configs/php-bugs.neon +++ b/tests/phpstan/configs/php-bugs.neon @@ -10,3 +10,8 @@ parameters: count: 1 path: ../../phpunit/network/mcpe/handler/StupidJsonDecodeTest.php + - + message: "#^Property pocketmine\\\\network\\\\mcpe\\\\handler\\\\StupidJsonDecodeTest\\:\\:\\$stupidJsonDecodeFunc \\(Closure\\(string, bool\\)\\: mixed\\) does not accept Closure\\|null\\.$#" + count: 1 + path: ../../phpunit/network/mcpe/handler/StupidJsonDecodeTest.php + diff --git a/tests/phpunit/network/mcpe/handler/StupidJsonDecodeTest.php b/tests/phpunit/network/mcpe/handler/StupidJsonDecodeTest.php index 9e79edf86..f24af1476 100644 --- a/tests/phpunit/network/mcpe/handler/StupidJsonDecodeTest.php +++ b/tests/phpunit/network/mcpe/handler/StupidJsonDecodeTest.php @@ -26,7 +26,10 @@ namespace pocketmine\network\mcpe\handler; use PHPUnit\Framework\TestCase; class StupidJsonDecodeTest extends TestCase{ - /** @var \Closure */ + /** + * @var \Closure + * @phpstan-var \Closure(string $json, bool $assoc=) : mixed + */ private $stupidJsonDecodeFunc; public function setUp() : void{ diff --git a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php index b28d8c7e6..85af44b72 100644 --- a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php +++ b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Main.php @@ -38,14 +38,14 @@ class Main extends PluginBase implements Listener{ /** @var int */ protected $currentTestNumber = 0; - public function onEnable(){ + public function onEnable() : void{ $this->getServer()->getPluginManager()->registerEvents($this, $this); $this->getScheduler()->scheduleRepeatingTask(new CheckTestCompletionTask($this), 10); $this->waitingTests = []; } - public function onServerCommand(CommandEvent $event){ + public function onServerCommand(CommandEvent $event) : void{ //The CI will send this command as a failsafe to prevent the build from hanging if the tester plugin failed to //run. However, if the plugin loaded successfully we don't want to allow this to stop the server as there may //be asynchronous tests running. Instead we cancel this and stop the server of our own accord once all tests @@ -55,10 +55,7 @@ class Main extends PluginBase implements Listener{ } } - /** - * @return Test|null - */ - public function getCurrentTest(){ + public function getCurrentTest() : ?Test{ return $this->currentTest; } @@ -73,7 +70,7 @@ class Main extends PluginBase implements Listener{ return false; } - public function onTestCompleted(Test $test){ + public function onTestCompleted(Test $test) : void{ $message = "Finished test #" . $this->currentTestNumber . " (" . $test->getName() . "): "; switch($test->getResult()){ case Test::RESULT_OK: @@ -99,7 +96,7 @@ class Main extends PluginBase implements Listener{ $this->currentTest = null; } - public function onAllTestsCompleted(){ + public function onAllTestsCompleted() : void{ $this->getLogger()->notice("All tests finished, stopping the server"); $this->getServer()->shutdown(); } diff --git a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php index 5ddb3d8a6..f20402336 100644 --- a/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php +++ b/tests/plugins/TesterPlugin/src/pmmp/TesterPlugin/Test.php @@ -29,9 +29,13 @@ abstract class Test{ const RESULT_FAILED = 1; const RESULT_ERROR = 2; + /** @var Main */ private $plugin; + /** @var int */ private $result = Test::RESULT_WAITING; + /** @var int */ private $startTime; + /** @var int */ private $timeout = 60; //seconds public function __construct(Main $plugin){ @@ -42,7 +46,7 @@ abstract class Test{ return $this->plugin; } - final public function start(){ + final public function start() : void{ $this->startTime = time(); try{ $this->run(); @@ -55,11 +59,11 @@ abstract class Test{ } } - public function tick(){ + public function tick() : void{ } - abstract public function run(); + abstract public function run() : void; public function isFinished() : bool{ return $this->result !== Test::RESULT_WAITING; @@ -69,7 +73,7 @@ abstract class Test{ return !$this->isFinished() and time() - $this->timeout > $this->startTime; } - protected function setTimeout(int $timeout){ + protected function setTimeout(int $timeout) : void{ $this->timeout = $timeout; } @@ -77,7 +81,7 @@ abstract class Test{ return $this->result; } - public function setResult(int $result){ + public function setResult(int $result) : void{ $this->result = $result; }