Merge 'minor-next' into 'major-next'

Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/18604829932
This commit is contained in:
pmmp-admin-bot[bot]
2025-10-17 21:05:54 +00:00
5 changed files with 166 additions and 2 deletions

View File

@@ -38,6 +38,7 @@ use pocketmine\item\VanillaItems as Items;
use pocketmine\utils\SingletonTrait; use pocketmine\utils\SingletonTrait;
use pocketmine\utils\StringToTParser; use pocketmine\utils\StringToTParser;
use function array_keys; use function array_keys;
use function count;
use function strtolower; use function strtolower;
/** /**
@@ -1603,6 +1604,20 @@ final class StringToItemParser extends StringToTParser{
$this->reverseMap[$item->getStateId()][$alias] = true; $this->reverseMap[$item->getStateId()][$alias] = true;
} }
public function override(string $alias, \Closure $callback) : void{
$oldItem = $this->parse($alias);
if($oldItem !== null){
$oldStateId = $oldItem->getStateId();
unset($this->reverseMap[$oldStateId][$alias]);
if(count($this->reverseMap[$oldStateId]) === 0){
unset($this->reverseMap[$oldStateId]);
}
}
parent::override($alias, $callback);
$newItem = $callback($alias);
$this->reverseMap[$newItem->getStateId()][$alias] = true;
}
/** @phpstan-param \Closure(string $input) : Block $callback */ /** @phpstan-param \Closure(string $input) : Block $callback */
public function registerBlock(string $alias, \Closure $callback) : void{ public function registerBlock(string $alias, \Closure $callback) : void{
$this->register($alias, fn(string $input) => $callback($input)->asItem()); $this->register($alias, fn(string $input) => $callback($input)->asItem());

View File

@@ -0,0 +1,31 @@
<?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;
/**
* Thrown by packet handlers to instruct network sessions to drop repeated packets.
*/
final class FilterNoisyPacketException extends \RuntimeException{
}

View File

@@ -39,6 +39,7 @@ use pocketmine\lang\Translatable;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\StringTag;
use pocketmine\network\FilterNoisyPacketException;
use pocketmine\network\mcpe\cache\ChunkCache; use pocketmine\network\mcpe\cache\ChunkCache;
use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\Compressor; use pocketmine\network\mcpe\compression\Compressor;
@@ -145,6 +146,8 @@ class NetworkSession{
private const INCOMING_GAME_PACKETS_PER_TICK = 2; private const INCOMING_GAME_PACKETS_PER_TICK = 2;
private const INCOMING_GAME_PACKETS_BUFFER_TICKS = 100; private const INCOMING_GAME_PACKETS_BUFFER_TICKS = 100;
private const INCOMING_PACKET_BATCH_HARD_LIMIT = 300;
private PacketRateLimiter $packetBatchLimiter; private PacketRateLimiter $packetBatchLimiter;
private PacketRateLimiter $gamePacketLimiter; private PacketRateLimiter $gamePacketLimiter;
@@ -195,6 +198,9 @@ class NetworkSession{
*/ */
private ObjectSet $disposeHooks; private ObjectSet $disposeHooks;
private string $noisyPacketBuffer = "";
private int $noisyPacketsDropped = 0;
public function __construct( public function __construct(
private Server $server, private Server $server,
private NetworkSessionManager $manager, private NetworkSessionManager $manager,
@@ -351,6 +357,20 @@ class NetworkSession{
} }
} }
private function checkRepeatedPacketFilter(string $buffer) : bool{
if($buffer === $this->noisyPacketBuffer){
$this->noisyPacketsDropped++;
return true;
}
//stop filtering once we see a packet with a different buffer
//this won't be any good for interleaved spammy packets, but we haven't seen any of those so far, and this
//is the simplest and most conservative filter we can do
$this->noisyPacketBuffer = "";
$this->noisyPacketsDropped = 0;
return false;
}
/** /**
* @throws PacketHandlingException * @throws PacketHandlingException
*/ */
@@ -401,9 +421,21 @@ class NetworkSession{
$decompressed = $payload; $decompressed = $payload;
} }
$count = 0;
try{ try{
$stream = new ByteBufferReader($decompressed); $stream = new ByteBufferReader($decompressed);
foreach(PacketBatch::decodeRaw($stream) as $buffer){ foreach(PacketBatch::decodeRaw($stream) as $buffer){
if(++$count >= self::INCOMING_PACKET_BATCH_HARD_LIMIT){
//this should be well more than enough; under normal conditions the game packet rate limiter
//will kick in well before this. This is only here to make sure we can't get huge batches of
//noisy packets to bog down the server, since those aren't counted by the regular limiter.
throw new PacketHandlingException("Reached hard limit of " . self::INCOMING_PACKET_BATCH_HARD_LIMIT . " per batch packet");
}
if($this->checkRepeatedPacketFilter($buffer)){
continue;
}
$this->gamePacketLimiter->decrement(); $this->gamePacketLimiter->decrement();
$packet = $this->packetPool->getPacket($buffer); $packet = $this->packetPool->getPacket($buffer);
if($packet === null){ if($packet === null){
@@ -415,6 +447,8 @@ class NetworkSession{
}catch(PacketHandlingException $e){ }catch(PacketHandlingException $e){
$this->logger->debug($packet->getName() . ": " . base64_encode($buffer)); $this->logger->debug($packet->getName() . ": " . base64_encode($buffer));
throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName()); throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName());
}catch(FilterNoisyPacketException){
$this->noisyPacketBuffer = $buffer;
} }
if(!$this->isConnected()){ if(!$this->isConnected()){
//handling this packet may have caused a disconnection //handling this packet may have caused a disconnection
@@ -433,6 +467,7 @@ class NetworkSession{
/** /**
* @throws PacketHandlingException * @throws PacketHandlingException
* @throws FilterNoisyPacketException
*/ */
public function handleDataPacket(Packet $packet, string $buffer) : void{ public function handleDataPacket(Packet $packet, string $buffer) : void{
if(!($packet instanceof ServerboundPacket)){ if(!($packet instanceof ServerboundPacket)){

View File

@@ -44,6 +44,7 @@ use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\StringTag;
use pocketmine\network\FilterNoisyPacketException;
use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\ActorEventPacket; use pocketmine\network\mcpe\protocol\ActorEventPacket;
@@ -490,7 +491,7 @@ class InGamePacketHandler extends PacketHandler{
$this->lastRightClickData = $data; $this->lastRightClickData = $data;
$this->lastRightClickTime = microtime(true); $this->lastRightClickTime = microtime(true);
if($spamBug){ if($spamBug){
return true; throw new FilterNoisyPacketException();
} }
//TODO: end hack for client spam bug //TODO: end hack for client spam bug
@@ -751,7 +752,9 @@ class InGamePacketHandler extends PacketHandler{
} }
public function handleAnimate(AnimatePacket $packet) : bool{ public function handleAnimate(AnimatePacket $packet) : bool{
return true; //Not used //this spams harder than a firehose on left click if "Improved Input Response" is enabled, and we don't even
//use it anyway :<
throw new FilterNoisyPacketException();
} }
public function handleContainerClose(ContainerClosePacket $packet) : bool{ public function handleContainerClose(ContainerClosePacket $packet) : bool{

View File

@@ -0,0 +1,80 @@
<?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\item;
use PHPUnit\Framework\TestCase;
class StringToItemParserTest extends TestCase{
public function testOverrideRemovesOldAlias() : void{
$parser = new StringToItemParser();
$item1 = VanillaItems::DIAMOND();
$item2 = VanillaItems::EMERALD();
$parser->register("test_alias", fn() => $item1);
self::assertContains("test_alias", $parser->lookupAliases($item1));
self::assertNotContains("test_alias", $parser->lookupAliases($item2));
$parser->override("test_alias", fn() => $item2);
self::assertNotContains("test_alias", $parser->lookupAliases($item1));
self::assertContains("test_alias", $parser->lookupAliases($item2));
}
public function testOverrideWithNewAlias() : void{
$parser = new StringToItemParser();
$item = VanillaItems::DIAMOND();
$parser->override("new_alias", fn() => $item);
self::assertContains("new_alias", $parser->lookupAliases($item));
self::assertSame($item, $parser->parse("new_alias"));
}
public function testOverrideMultipleAliases() : void{
$parser = new StringToItemParser();
$item1 = VanillaItems::DIAMOND();
$item2 = VanillaItems::EMERALD();
$parser->register("alias1", fn() => $item1);
$parser->register("alias2", fn() => $item1);
$parser->register("alias3", fn() => $item1);
self::assertCount(3, $parser->lookupAliases($item1));
$parser->override("alias2", fn() => $item2);
self::assertCount(2, $parser->lookupAliases($item1));
self::assertContains("alias1", $parser->lookupAliases($item1));
self::assertContains("alias3", $parser->lookupAliases($item1));
self::assertNotContains("alias2", $parser->lookupAliases($item1));
self::assertCount(1, $parser->lookupAliases($item2));
self::assertContains("alias2", $parser->lookupAliases($item2));
}
}