Improved error handling for loading broken entity / tile data

This commit is contained in:
Dylan K. Taylor
2021-11-23 17:39:20 +00:00
parent eb0cf52d81
commit 5c7125f190
7 changed files with 92 additions and 44 deletions

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\entity;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\math\Vector3;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
@ -38,34 +39,40 @@ final class EntityDataHelper{
//NOOP
}
/**
* @throws SavedDataLoadingException
*/
public static function parseLocation(CompoundTag $nbt, World $world) : Location{
$pos = self::parseVec3($nbt, "Pos", false);
$yawPitch = $nbt->getTag("Rotation");
if(!($yawPitch instanceof ListTag) or $yawPitch->getTagType() !== NBT::TAG_Float){
throw new \UnexpectedValueException("'Rotation' should be a List<Float>");
throw new SavedDataLoadingException("'Rotation' should be a List<Float>");
}
/** @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<Double> or List<Float>");
throw new SavedDataLoadingException("'$tagName' should be a List<Double> or List<Float>");
}
/** @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());
}

View File

@ -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{

View File

@ -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"),