Merge remote-tracking branch 'origin/minor-next' into major-next

This commit is contained in:
Dylan K. Taylor
2023-03-22 22:49:22 +00:00
10 changed files with 168 additions and 59 deletions

View File

@@ -102,6 +102,7 @@ use pocketmine\player\XboxLivePlayerInfo;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\BinaryDataException;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\TextFormat;
@@ -393,7 +394,7 @@ class NetworkSession{
throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName());
}
}
}catch(PacketDecodeException $e){
}catch(PacketDecodeException|BinaryDataException $e){
$this->logger->logException($e);
throw PacketHandlingException::wrap($e, "Packet batch decode error");
}

View File

@@ -383,35 +383,51 @@ class InGamePacketHandler extends PacketHandler{
throw new PacketHandlingException("Expected exactly 2 actions for dropping an item");
}
$sourceSlot = null;
$clientItemStack = null;
$droppedCount = null;
foreach($data->getActions() as $networkInventoryAction){
if($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_WORLD){
//drop item - we don't need to validate this, we only care about the count
//if the resulting actions don't match the client for some reason, it will trigger an automatic
//prediction rollback anyway.
//it's technically possible to see this more than once, but a normal client should never do that.
$inventory = $this->player->getInventory();
$heldItemStack = TypeConverter::getInstance()->coreItemStackToNet($inventory->getItemInHand());
$droppedItemStack = $networkInventoryAction->newItem->getItemStack();
//because the client doesn't tell us the expected itemstack ID, we have to deep-compare our known
//itemstack info with the one the client sent. This is costly, but we don't have any other option :(
if($heldItemStack->getCount() < $droppedItemStack->getCount() || !$heldItemStack->equalsWithoutCount($droppedItemStack)){
return false;
if($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_WORLD && $networkInventoryAction->inventorySlot == NetworkInventoryAction::ACTION_MAGIC_SLOT_DROP_ITEM){
$droppedCount = $networkInventoryAction->newItem->getItemStack()->getCount();
if($droppedCount <= 0){
throw new PacketHandlingException("Expected positive count for dropped item");
}
$newHeldItem = $inventory->getItemInHand();
$droppedItem = $newHeldItem->pop($droppedItemStack->getCount());
$builder = new TransactionBuilder();
$builder->getInventory($inventory)->setItem($inventory->getHeldItemIndex(), $newHeldItem);
$builder->addAction(new DropItemAction($droppedItem));
$transaction = new InventoryTransaction($this->player, $builder->generateActions());
return $this->executeInventoryTransaction($transaction, $itemStackRequestId);
}elseif($networkInventoryAction->sourceType === NetworkInventoryAction::SOURCE_CONTAINER && $networkInventoryAction->windowId === ContainerIds::INVENTORY){
//mobile players can drop an item from a non-selected hotbar slot
$sourceSlot = $networkInventoryAction->inventorySlot;
$clientItemStack = $networkInventoryAction->oldItem->getItemStack();
}else{
throw new PacketHandlingException("Unexpected action type in drop item transaction");
}
}
if($sourceSlot === null || $clientItemStack === null || $droppedCount === null){
throw new PacketHandlingException("Missing information in drop item transaction, need source slot, client item stack and dropped count");
}
throw new PacketHandlingException("Legacy 'normal' transactions should only be used for dropping items");
$inventory = $this->player->getInventory();
if(!$inventory->slotExists($sourceSlot)){
return false; //TODO: size desync??
}
$sourceSlotItem = $inventory->getItem($sourceSlot);
$serverItemStack = TypeConverter::getInstance()->coreItemStackToNet($sourceSlotItem);
//because the client doesn't tell us the expected itemstack ID, we have to deep-compare our known
//itemstack info with the one the client sent. This is costly, but we don't have any other option :(
if(!$serverItemStack->equals($clientItemStack)){
return false;
}
//this modifies $sourceSlotItem
$droppedItem = $sourceSlotItem->pop($droppedCount);
$builder = new TransactionBuilder();
$builder->getInventory($inventory)->setItem($sourceSlot, $sourceSlotItem);
$builder->addAction(new DropItemAction($droppedItem));
$transaction = new InventoryTransaction($this->player, $builder->generateActions());
return $this->executeInventoryTransaction($transaction, $itemStackRequestId);
}
private function handleUseItemTransaction(UseItemTransactionData $data) : bool{

View File

@@ -58,7 +58,7 @@ use function array_key_first;
use function count;
use function spl_object_id;
final class ItemStackRequestExecutor{
class ItemStackRequestExecutor{
private TransactionBuilder $builder;
/** @var ItemStackRequestSlotInfo[] */
@@ -81,7 +81,7 @@ final class ItemStackRequestExecutor{
$this->builder = new TransactionBuilder();
}
private function prettyInventoryAndSlot(Inventory $inventory, int $slot) : string{
protected function prettyInventoryAndSlot(Inventory $inventory, int $slot) : string{
if($inventory instanceof TransactionBuilderInventory){
$inventory = $inventory->getActualInventory();
}
@@ -111,7 +111,7 @@ final class ItemStackRequestExecutor{
*
* @throws ItemStackRequestProcessException
*/
private function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : array{
protected function getBuilderInventoryAndSlot(ItemStackRequestSlotInfo $info) : array{
$windowId = ItemStackContainerIdTranslator::translate($info->getContainerId(), $this->inventoryManager->getCurrentWindowId());
$windowAndSlot = $this->inventoryManager->locateWindowAndSlot($windowId, $info->getSlotId());
if($windowAndSlot === null){
@@ -129,7 +129,10 @@ final class ItemStackRequestExecutor{
return [$this->builder->getInventory($inventory), $slot];
}
private function transferItems(ItemStackRequestSlotInfo $source, ItemStackRequestSlotInfo $destination, int $count) : void{
/**
* @throws ItemStackRequestProcessException
*/
protected function transferItems(ItemStackRequestSlotInfo $source, ItemStackRequestSlotInfo $destination, int $count) : void{
$removed = $this->removeItemFromSlot($source, $count);
$this->addItemToSlot($destination, $removed, $count);
}
@@ -138,9 +141,13 @@ final class ItemStackRequestExecutor{
* Deducts items from an inventory slot, returning a stack containing the removed items.
* @throws ItemStackRequestProcessException
*/
private function removeItemFromSlot(ItemStackRequestSlotInfo $slotInfo, int $count) : Item{
protected function removeItemFromSlot(ItemStackRequestSlotInfo $slotInfo, int $count) : Item{
$this->requestSlotInfos[] = $slotInfo;
[$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo);
if($count < 1){
//this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits
throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack");
}
$existingItem = $inventory->getItem($slot);
if($existingItem->getCount() < $count){
@@ -155,10 +162,15 @@ final class ItemStackRequestExecutor{
/**
* Adds items to the target slot, if they are stackable.
* @throws ItemStackRequestProcessException
*/
private function addItemToSlot(ItemStackRequestSlotInfo $slotInfo, Item $item, int $count) : void{
protected function addItemToSlot(ItemStackRequestSlotInfo $slotInfo, Item $item, int $count) : void{
$this->requestSlotInfos[] = $slotInfo;
[$inventory, $slot] = $this->getBuilderInventoryAndSlot($slotInfo);
if($count < 1){
//this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits
throw new ItemStackRequestProcessException($this->prettyInventoryAndSlot($inventory, $slot) . ": Cannot take less than 1 items from a stack");
}
$existingItem = $inventory->getItem($slot);
if(!$existingItem->isNull() && !$existingItem->canStackWith($item)){
@@ -174,7 +186,7 @@ final class ItemStackRequestExecutor{
/**
* @throws ItemStackRequestProcessException
*/
private function setNextCreatedItem(?Item $item, bool $creative = false) : void{
protected function setNextCreatedItem(?Item $item, bool $creative = false) : void{
if($item !== null && $item->isNull()){
$item = null;
}
@@ -196,13 +208,19 @@ final class ItemStackRequestExecutor{
/**
* @throws ItemStackRequestProcessException
*/
private function beginCrafting(int $recipeId, int $repetitions) : void{
protected function beginCrafting(int $recipeId, int $repetitions) : void{
if($this->specialTransaction !== null){
throw new ItemStackRequestProcessException("Another special transaction is already in progress");
}
if($repetitions < 1){ //TODO: upper bound?
if($repetitions < 1){
throw new ItemStackRequestProcessException("Cannot craft a recipe less than 1 time");
}
if($repetitions > 256){
//TODO: we can probably lower this limit to 64, but I'm unsure if there are cases where the client may
//request more than 64 repetitions of a recipe.
//It's already hard-limited to 256 repetitions in the protocol, so this is just a sanity check.
throw new ItemStackRequestProcessException("Cannot craft a recipe more than 256 times");
}
$craftingManager = $this->player->getServer()->getCraftingManager();
$recipe = $craftingManager->getCraftingRecipeFromIndex($recipeId);
if($recipe === null){
@@ -228,7 +246,14 @@ final class ItemStackRequestExecutor{
}
}
private function takeCreatedItem(ItemStackRequestSlotInfo $destination, int $count) : void{
/**
* @throws ItemStackRequestProcessException
*/
protected function takeCreatedItem(ItemStackRequestSlotInfo $destination, int $count) : void{
if($count < 1){
//this should be impossible at the protocol level, but in case of buggy core code this will prevent exploits
throw new ItemStackRequestProcessException("Cannot take less than 1 created item");
}
$createdItem = $this->nextCreatedItem;
if($createdItem === null){
throw new ItemStackRequestProcessException("No created item is waiting to be taken");
@@ -264,7 +289,7 @@ final class ItemStackRequestExecutor{
/**
* @throws ItemStackRequestProcessException
*/
private function processItemStackRequestAction(ItemStackRequestAction $action) : void{
protected function processItemStackRequestAction(ItemStackRequestAction $action) : void{
if(
$action instanceof TakeStackRequestAction ||
$action instanceof PlaceStackRequestAction