Cleaned up the InventoryTransactionPacket decoding clusterfuck

@shoghicp, y u do dis... I almost created a sub-packet architecture to deal with this shit :(

This mess really ought to be split into multiple packets. Perhaps the PacketPool can be extended to do that in the future.
This commit is contained in:
Dylan K. Taylor
2019-01-03 16:54:42 +00:00
parent 134956ac58
commit 4d15eb3327
9 changed files with 699 additions and 205 deletions

View File

@ -63,6 +63,11 @@ use pocketmine\network\mcpe\protocol\ShowCreditsPacket;
use pocketmine\network\mcpe\protocol\SpawnExperienceOrbPacket;
use pocketmine\network\mcpe\protocol\SubClientLoginPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\types\MismatchTransactionData;
use pocketmine\network\mcpe\protocol\types\NormalTransactionData;
use pocketmine\network\mcpe\protocol\types\ReleaseItemTransactionData;
use pocketmine\network\mcpe\protocol\types\UseItemOnEntityTransactionData;
use pocketmine\network\mcpe\protocol\types\UseItemTransactionData;
use pocketmine\Player;
/**
@ -112,22 +117,52 @@ class SimpleSessionHandler extends SessionHandler{
return true;
}
$result = true;
if($packet->trData instanceof NormalTransactionData){
$result = $this->handleNormalTransaction($packet->trData);
}elseif($packet->trData instanceof MismatchTransactionData){
$this->player->sendAllInventories();
$result = true;
}elseif($packet->trData instanceof UseItemTransactionData){
$result = $this->handleUseItemTransaction($packet->trData);
}elseif($packet->trData instanceof UseItemOnEntityTransactionData){
$result = $this->handleUseItemOnEntityTransaction($packet->trData);
}elseif($packet->trData instanceof ReleaseItemTransactionData){
$result = $this->handleReleaseItemTransaction($packet->trData);
}
if(!$result){
$this->player->sendAllInventories();
}
return $result;
}
private function handleNormalTransaction(NormalTransactionData $data) : bool{
/** @var InventoryAction[] $actions */
$actions = [];
foreach($packet->actions as $networkInventoryAction){
$isCrafting = false;
$isFinalCraftingPart = false;
foreach($data->getActions() as $networkInventoryAction){
$isCrafting = $isCrafting || $networkInventoryAction->isCraftingPart();
$isFinalCraftingPart = $isFinalCraftingPart || $networkInventoryAction->isFinalCraftingPart();
try{
$action = $networkInventoryAction->createInventoryAction($this->player);
if($action !== null){
$actions[] = $action;
}
}catch(\Exception $e){
}catch(\UnexpectedValueException $e){
$this->player->getServer()->getLogger()->debug("Unhandled inventory action from " . $this->player->getName() . ": " . $e->getMessage());
$this->player->sendAllInventories();
return false;
}
}
if($packet->isCraftingPart){
if($isCrafting){
//we get the actions for this in several packets, so we need to wait until we have all the pieces before
//trying to execute it
if($this->craftingTransaction === null){
$this->craftingTransaction = new CraftingTransaction($this->player, $actions);
}else{
@ -136,113 +171,98 @@ class SimpleSessionHandler extends SessionHandler{
}
}
if($packet->isFinalCraftingPart){
//we get the actions for this in several packets, so we need to wait until we have all the pieces before
//trying to execute it
$ret = true;
if($isFinalCraftingPart){
try{
$this->craftingTransaction->execute();
}catch(TransactionValidationException $e){
$this->player->getServer()->getLogger()->debug("Failed to execute crafting transaction for " . $this->player->getName() . ": " . $e->getMessage());
$ret = false;
return false;
}finally{
$this->craftingTransaction = null;
}
}
}else{
//normal transaction fallthru
if($this->craftingTransaction !== null){
$this->player->getServer()->getLogger()->debug("Got unexpected normal inventory action with incomplete crafting transaction from " . $this->player->getName() . ", refusing to execute crafting");
$this->craftingTransaction = null;
return $ret;
return false;
}
return true;
}
if($this->craftingTransaction !== null){
$this->player->getServer()->getLogger()->debug("Got unexpected normal inventory action with incomplete crafting transaction from " . $this->player->getName() . ", refusing to execute crafting");
$this->craftingTransaction = null;
$transaction = new InventoryTransaction($this->player, $actions);
try{
$transaction->execute();
}catch(TransactionValidationException $e){
$logger = $this->player->getServer()->getLogger();
$logger->debug("Failed to execute inventory transaction from " . $this->player->getName() . ": " . $e->getMessage());
$logger->debug("Actions: " . json_encode($data->getActions()));
return false;
}
//TODO: fix achievement for getting iron from furnace
}
switch($packet->transactionType){
case InventoryTransactionPacket::TYPE_NORMAL:
$transaction = new InventoryTransaction($this->player, $actions);
return true;
}
try{
$transaction->execute();
}catch(TransactionValidationException $e){
$this->player->getServer()->getLogger()->debug("Failed to execute inventory transaction from " . $this->player->getName() . ": " . $e->getMessage());
$this->player->getServer()->getLogger()->debug("Actions: " . json_encode($packet->actions));
return false;
private function handleUseItemTransaction(UseItemTransactionData $data) : bool{
switch($data->getActionType()){
case UseItemTransactionData::ACTION_CLICK_BLOCK:
//TODO: start hack for client spam bug
$clickPos = $data->getClickPos();
$spamBug = ($this->lastRightClickPos !== null and
microtime(true) - $this->lastRightClickTime < 0.1 and //100ms
$this->lastRightClickPos->distanceSquared($clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
);
//get rid of continued spam if the player clicks and holds right-click
$this->lastRightClickPos = clone $clickPos;
$this->lastRightClickTime = microtime(true);
if($spamBug){
return true;
}
//TODO: fix achievement for getting iron from furnace
//TODO: end hack for client spam bug
$this->player->interactBlock($data->getBlockPos(), $data->getFace(), $clickPos);
return true;
case InventoryTransactionPacket::TYPE_MISMATCH:
if(count($packet->actions) > 0){
$this->player->getServer()->getLogger()->debug("Expected 0 actions for mismatch, got " . count($packet->actions) . ", " . json_encode($packet->actions));
}
$this->player->sendAllInventories();
case UseItemTransactionData::ACTION_BREAK_BLOCK:
$this->player->breakBlock($data->getBlockPos());
return true;
case UseItemTransactionData::ACTION_CLICK_AIR:
$this->player->useHeldItem();
return true;
}
return false;
}
private function handleUseItemOnEntityTransaction(UseItemOnEntityTransactionData $data) : bool{
$target = $this->player->getLevel()->getEntity($data->getEntityRuntimeId());
if($target === null){
return false;
}
switch($data->getActionType()){
case UseItemOnEntityTransactionData::ACTION_INTERACT:
$this->player->interactEntity($target, $data->getClickPos());
return true;
case UseItemOnEntityTransactionData::ACTION_ATTACK:
$this->player->attackEntity($target);
return true;
}
return false;
}
private function handleReleaseItemTransaction(ReleaseItemTransactionData $data) : bool{
switch($data->getActionType()){
case ReleaseItemTransactionData::ACTION_RELEASE:
$this->player->releaseHeldItem();
return true;
case ReleaseItemTransactionData::ACTION_CONSUME:
$this->player->consumeHeldItem();
return true;
case InventoryTransactionPacket::TYPE_USE_ITEM:
$blockVector = new Vector3($packet->trData->x, $packet->trData->y, $packet->trData->z);
$face = $packet->trData->face;
$type = $packet->trData->actionType;
switch($type){
case InventoryTransactionPacket::USE_ITEM_ACTION_CLICK_BLOCK:
//TODO: start hack for client spam bug
$spamBug = ($this->lastRightClickPos !== null and
microtime(true) - $this->lastRightClickTime < 0.1 and //100ms
$this->lastRightClickPos->distanceSquared($packet->trData->clickPos) < 0.00001 //signature spam bug has 0 distance, but allow some error
);
//get rid of continued spam if the player clicks and holds right-click
$this->lastRightClickPos = clone $packet->trData->clickPos;
$this->lastRightClickTime = microtime(true);
if($spamBug){
return true;
}
//TODO: end hack for client spam bug
$this->player->interactBlock($blockVector, $face, $packet->trData->clickPos);
return true;
case InventoryTransactionPacket::USE_ITEM_ACTION_BREAK_BLOCK:
$this->player->breakBlock($blockVector);
return true;
case InventoryTransactionPacket::USE_ITEM_ACTION_CLICK_AIR:
$this->player->useHeldItem();
return true;
}
break;
case InventoryTransactionPacket::TYPE_USE_ITEM_ON_ENTITY:
$target = $this->player->getLevel()->getEntity($packet->trData->entityRuntimeId);
if($target === null){
return false;
}
switch($packet->trData->actionType){
case InventoryTransactionPacket::USE_ITEM_ON_ENTITY_ACTION_INTERACT:
$this->player->interactEntity($target, $packet->trData->clickPos);
return true;
case InventoryTransactionPacket::USE_ITEM_ON_ENTITY_ACTION_ATTACK:
$this->player->attackEntity($target);
return true;
}
break;
case InventoryTransactionPacket::TYPE_RELEASE_ITEM:
switch($packet->trData->actionType){
case InventoryTransactionPacket::RELEASE_ITEM_ACTION_RELEASE:
$this->player->releaseHeldItem();
return true;
case InventoryTransactionPacket::RELEASE_ITEM_ACTION_CONSUME:
$this->player->consumeHeldItem();
return true;
}
break;
default:
break;
}
$this->player->sendAllInventories();
return false;
}