From 5c7125f190ad00c3eddbb65b3bc4dd29d6d56ab2 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 23 Nov 2021 17:39:20 +0000 Subject: [PATCH] Improved error handling for loading broken entity / tile data --- src/block/tile/TileFactory.php | 31 +++++++++-------- src/data/SavedDataLoadingException.php | 28 ++++++++++++++++ src/entity/EntityDataHelper.php | 15 ++++++--- src/entity/EntityFactory.php | 46 ++++++++++++++------------ src/entity/Human.php | 5 +-- src/item/Item.php | 5 ++- src/world/World.php | 6 ++-- 7 files changed, 92 insertions(+), 44 deletions(-) create mode 100644 src/data/SavedDataLoadingException.php diff --git a/src/block/tile/TileFactory.php b/src/block/tile/TileFactory.php index 9c51050fe..f8a6db746 100644 --- a/src/block/tile/TileFactory.php +++ b/src/block/tile/TileFactory.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace pocketmine\block\tile; +use pocketmine\data\SavedDataLoadingException; use pocketmine\math\Vector3; -use pocketmine\nbt\NbtDataException; +use pocketmine\nbt\NbtException; use pocketmine\nbt\tag\CompoundTag; use pocketmine\utils\SingletonTrait; use pocketmine\utils\Utils; @@ -112,21 +113,25 @@ final class TileFactory{ /** * @internal - * @throws NbtDataException + * @throws SavedDataLoadingException */ public function createFromData(World $world, CompoundTag $nbt) : ?Tile{ - $type = $nbt->getString(Tile::TAG_ID, ""); - if(!isset($this->knownTiles[$type])){ - return null; + try{ + $type = $nbt->getString(Tile::TAG_ID, ""); + if(!isset($this->knownTiles[$type])){ + return null; + } + $class = $this->knownTiles[$type]; + assert(is_a($class, Tile::class, true)); + /** + * @var Tile $tile + * @see Tile::__construct() + */ + $tile = new $class($world, new Vector3($nbt->getInt(Tile::TAG_X), $nbt->getInt(Tile::TAG_Y), $nbt->getInt(Tile::TAG_Z))); + $tile->readSaveData($nbt); + }catch(NbtException $e){ + throw new SavedDataLoadingException($e->getMessage(), 0, $e); } - $class = $this->knownTiles[$type]; - assert(is_a($class, Tile::class, true)); - /** - * @var Tile $tile - * @see Tile::__construct() - */ - $tile = new $class($world, new Vector3($nbt->getInt(Tile::TAG_X), $nbt->getInt(Tile::TAG_Y), $nbt->getInt(Tile::TAG_Z))); - $tile->readSaveData($nbt); return $tile; } diff --git a/src/data/SavedDataLoadingException.php b/src/data/SavedDataLoadingException.php new file mode 100644 index 000000000..fcc72c7ab --- /dev/null +++ b/src/data/SavedDataLoadingException.php @@ -0,0 +1,28 @@ +getTag("Rotation"); if(!($yawPitch instanceof ListTag) or $yawPitch->getTagType() !== NBT::TAG_Float){ - throw new \UnexpectedValueException("'Rotation' should be a List"); + throw new SavedDataLoadingException("'Rotation' should be a List"); } /** @var FloatTag[] $values */ $values = $yawPitch->getValue(); if(count($values) !== 2){ - throw new \UnexpectedValueException("Expected exactly 2 entries for 'Rotation'"); + throw new SavedDataLoadingException("Expected exactly 2 entries for 'Rotation'"); } return Location::fromObject($pos, $world, $values[0]->getValue(), $values[1]->getValue()); } + /** + * @throws SavedDataLoadingException + */ public static function parseVec3(CompoundTag $nbt, string $tagName, bool $optional) : Vector3{ $pos = $nbt->getTag($tagName); if($pos === null and $optional){ return new Vector3(0, 0, 0); } if(!($pos instanceof ListTag) or ($pos->getTagType() !== NBT::TAG_Double && $pos->getTagType() !== NBT::TAG_Float)){ - throw new \UnexpectedValueException("'$tagName' should be a List or List"); + throw new SavedDataLoadingException("'$tagName' should be a List or List"); } /** @var DoubleTag[]|FloatTag[] $values */ $values = $pos->getValue(); if(count($values) !== 3){ - throw new \UnexpectedValueException("Expected exactly 3 entries in '$tagName' tag"); + throw new SavedDataLoadingException("Expected exactly 3 entries in '$tagName' tag"); } return new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue()); } diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index 809d32341..0010f0ace 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -30,6 +30,7 @@ use pocketmine\block\BlockFactory; use pocketmine\data\bedrock\EntityLegacyIds; use pocketmine\data\bedrock\PotionTypeIdMap; use pocketmine\data\bedrock\PotionTypeIds; +use pocketmine\data\SavedDataLoadingException; use pocketmine\entity\object\ExperienceOrb; use pocketmine\entity\object\FallingBlock; use pocketmine\entity\object\ItemEntity; @@ -45,7 +46,7 @@ use pocketmine\entity\projectile\SplashPotion; use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; -use pocketmine\nbt\NbtDataException; +use pocketmine\nbt\NbtException; use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; @@ -113,12 +114,12 @@ final class EntityFactory{ $this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{ $itemTag = $nbt->getCompoundTag("Item"); if($itemTag === null){ - throw new \UnexpectedValueException("Expected \"Item\" NBT tag not found"); + throw new SavedDataLoadingException("Expected \"Item\" NBT tag not found"); } $item = Item::nbtDeserialize($itemTag); if($item->isNull()){ - throw new \UnexpectedValueException("Item is invalid"); + throw new SavedDataLoadingException("Item is invalid"); } return new ItemEntity(EntityDataHelper::parseLocation($nbt, $world), $item, $nbt); }, ['Item', 'minecraft:item'], EntityLegacyIds::ITEM); @@ -126,7 +127,7 @@ final class EntityFactory{ $this->register(Painting::class, function(World $world, CompoundTag $nbt) : Painting{ $motive = PaintingMotive::getMotiveByName($nbt->getString("Motive")); if($motive === null){ - throw new \UnexpectedValueException("Unknown painting motive"); + throw new SavedDataLoadingException("Unknown painting motive"); } $blockIn = new Vector3($nbt->getInt("TileX"), $nbt->getInt("TileY"), $nbt->getInt("TileZ")); if(($directionTag = $nbt->getTag("Direction")) instanceof ByteTag){ @@ -134,7 +135,7 @@ final class EntityFactory{ }elseif(($facingTag = $nbt->getTag("Facing")) instanceof ByteTag){ $facing = Painting::DATA_TO_FACING[$facingTag->getValue()] ?? Facing::NORTH; }else{ - throw new \UnexpectedValueException("Missing facing info"); + throw new SavedDataLoadingException("Missing facing info"); } return new Painting(EntityDataHelper::parseLocation($nbt, $world), $blockIn, $facing, $motive, $nbt); @@ -151,7 +152,7 @@ final class EntityFactory{ $this->register(SplashPotion::class, function(World $world, CompoundTag $nbt) : SplashPotion{ $potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort("PotionId", PotionTypeIds::WATER)); if($potionType === null){ - throw new \UnexpectedValueException("No such potion type"); + throw new SavedDataLoadingException("No such potion type"); } return new SplashPotion(EntityDataHelper::parseLocation($nbt, $world), null, $potionType, $nbt); }, ['ThrownPotion', 'minecraft:potion', 'thrownpotion'], EntityLegacyIds::SPLASH_POTION); @@ -222,25 +223,28 @@ final class EntityFactory{ /** * Creates an entity from data stored on a chunk. * - * @throws \RuntimeException - * @throws NbtDataException + * @throws SavedDataLoadingException * @internal */ public function createFromData(World $world, CompoundTag $nbt) : ?Entity{ - $saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier"); - $func = null; - if($saveId instanceof StringTag){ - $func = $this->creationFuncs[$saveId->getValue()] ?? null; - }elseif($saveId instanceof IntTag){ //legacy MCPE format - $func = $this->creationFuncs[$saveId->getValue() & 0xff] ?? null; - } - if($func === null){ - return null; - } - /** @var Entity $entity */ - $entity = $func($world, $nbt); + try{ + $saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier"); + $func = null; + if($saveId instanceof StringTag){ + $func = $this->creationFuncs[$saveId->getValue()] ?? null; + }elseif($saveId instanceof IntTag){ //legacy MCPE format + $func = $this->creationFuncs[$saveId->getValue() & 0xff] ?? null; + } + if($func === null){ + return null; + } + /** @var Entity $entity */ + $entity = $func($world, $nbt); - return $entity; + return $entity; + }catch(NbtException $e){ + throw new SavedDataLoadingException($e->getMessage(), 0, $e); + } } public function injectSaveId(string $class, CompoundTag $saveData) : void{ diff --git a/src/entity/Human.php b/src/entity/Human.php index c77de5005..c82cee7f8 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\entity; +use pocketmine\data\SavedDataLoadingException; use pocketmine\entity\animation\TotemUseAnimation; use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\VanillaEffects; @@ -104,12 +105,12 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ /** * @throws InvalidSkinException - * @throws \UnexpectedValueException + * @throws SavedDataLoadingException */ public static function parseSkinNBT(CompoundTag $nbt) : Skin{ $skinTag = $nbt->getCompoundTag("Skin"); if($skinTag === null){ - throw new \UnexpectedValueException("Missing skin data"); + throw new SavedDataLoadingException("Missing skin data"); } return new Skin( //this throws if the skin is invalid $skinTag->getString("Name"), diff --git a/src/item/Item.php b/src/item/Item.php index f37015ba9..bceb92142 100644 --- a/src/item/Item.php +++ b/src/item/Item.php @@ -31,6 +31,7 @@ use pocketmine\block\BlockBreakInfo; use pocketmine\block\BlockToolType; use pocketmine\block\VanillaBlocks; use pocketmine\data\bedrock\EnchantmentIdMap; +use pocketmine\data\SavedDataLoadingException; use pocketmine\entity\Entity; use pocketmine\item\enchantment\EnchantmentInstance; use pocketmine\math\Vector3; @@ -671,6 +672,8 @@ class Item implements \JsonSerializable{ /** * Deserializes an Item from an NBT CompoundTag + * @throws NbtException + * @throws SavedDataLoadingException */ public static function nbtDeserialize(CompoundTag $tag) : Item{ if($tag->getTag("id") === null or $tag->getTag("Count") === null){ @@ -692,7 +695,7 @@ class Item implements \JsonSerializable{ } $item->setCount($count); }else{ - throw new \InvalidArgumentException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($idTag) . " given"); + throw new SavedDataLoadingException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($idTag) . " given"); } $itemNBT = $tag->getCompoundTag("tag"); diff --git a/src/world/World.php b/src/world/World.php index dcd27805c..9549980eb 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -36,6 +36,7 @@ use pocketmine\block\tile\TileFactory; use pocketmine\block\UnknownBlock; use pocketmine\block\VanillaBlocks; use pocketmine\data\bedrock\BiomeIds; +use pocketmine\data\SavedDataLoadingException; use pocketmine\entity\Entity; use pocketmine\entity\EntityFactory; use pocketmine\entity\Location; @@ -57,7 +58,6 @@ use pocketmine\item\LegacyStringToItemParser; use pocketmine\lang\KnownTranslationFactory; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; -use pocketmine\nbt\NbtDataException; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\StringTag; use pocketmine\network\mcpe\convert\RuntimeBlockMapping; @@ -2472,7 +2472,7 @@ class World implements ChunkManager{ foreach($chunkData->getEntityNBT() as $k => $nbt){ try{ $entity = $entityFactory->createFromData($this, $nbt); - }catch(NbtDataException $e){ + }catch(SavedDataLoadingException $e){ $logger->error("Bad entity data at list position $k: " . $e->getMessage()); $logger->logException($e); continue; @@ -2500,7 +2500,7 @@ class World implements ChunkManager{ foreach($chunkData->getTileNBT() as $k => $nbt){ try{ $tile = $tileFactory->createFromData($this, $nbt); - }catch(NbtDataException $e){ + }catch(SavedDataLoadingException $e){ $logger->error("Bad tile entity data at list position $k: " . $e->getMessage()); $logger->logException($e); continue;