diff --git a/src/pocketmine/world/WorldManager.php b/src/pocketmine/world/WorldManager.php index ee48c201b..55e0d1fbf 100644 --- a/src/pocketmine/world/WorldManager.php +++ b/src/pocketmine/world/WorldManager.php @@ -30,6 +30,7 @@ use pocketmine\event\world\WorldUnloadEvent; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\utils\Utils; +use pocketmine\world\format\io\exception\CorruptedWorldException; use pocketmine\world\format\io\exception\UnsupportedWorldFormatException; use pocketmine\world\format\io\FormatConverter; use pocketmine\world\format\io\WorldProvider; @@ -195,7 +196,7 @@ class WorldManager{ */ public function loadWorld(string $name, bool $autoUpgrade = false) : bool{ if(trim($name) === ""){ - throw new WorldException("Invalid empty world name"); + throw new \InvalidArgumentException("Invalid empty world name"); } if($this->isWorldLoaded($name)){ return true; @@ -221,7 +222,15 @@ class WorldManager{ * @var WorldProvider $provider * @see WorldProvider::__construct() */ - $provider = new $providerClass($path); + try{ + $provider = new $providerClass($path); + }catch(CorruptedWorldException $e){ + $this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.level.loadError", [$name, "Corruption detected: " . $e->getMessage()])); + return false; + }catch(UnsupportedWorldFormatException $e){ + $this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.level.loadError", [$name, "Unsupported format: " . $e->getMessage()])); + return false; + } try{ GeneratorManager::getGenerator($provider->getWorldData()->getGenerator(), true); }catch(\InvalidArgumentException $e){ @@ -230,7 +239,7 @@ class WorldManager{ } if(!($provider instanceof WritableWorldProvider)){ if(!$autoUpgrade){ - throw new WorldException("World \"$name\" is in an unsupported format and needs to be upgraded"); + throw new UnsupportedWorldFormatException("World \"$name\" is in an unsupported format and needs to be upgraded"); } $this->server->getLogger()->notice("Upgrading world \"$name\" to new format. This may take a while."); @@ -240,12 +249,7 @@ class WorldManager{ $this->server->getLogger()->notice("Upgraded world \"$name\" to new format successfully. Backed up pre-conversion world at " . $converter->getBackupPath()); } - try{ - $world = new World($this->server, $name, $provider); - }catch(UnsupportedWorldFormatException $e){ - $this->server->getLogger()->error($this->server->getLanguage()->translateString("pocketmine.level.loadError", [$name, $e->getMessage()])); - return false; - } + $world = new World($this->server, $name, $provider); $this->worlds[$world->getId()] = $world; $world->setAutoSave($this->autoSave); diff --git a/src/pocketmine/world/format/io/BaseWorldProvider.php b/src/pocketmine/world/format/io/BaseWorldProvider.php index 97be13a15..890e78d81 100644 --- a/src/pocketmine/world/format/io/BaseWorldProvider.php +++ b/src/pocketmine/world/format/io/BaseWorldProvider.php @@ -25,7 +25,9 @@ namespace pocketmine\world\format\io; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\exception\CorruptedChunkException; +use pocketmine\world\format\io\exception\CorruptedWorldException; use pocketmine\world\format\io\exception\UnsupportedChunkFormatException; +use pocketmine\world\format\io\exception\UnsupportedWorldFormatException; use pocketmine\world\WorldException; use function file_exists; @@ -44,6 +46,11 @@ abstract class BaseWorldProvider implements WorldProvider{ $this->worldData = $this->loadLevelData(); } + /** + * @return WorldData + * @throws CorruptedWorldException + * @throws UnsupportedWorldFormatException + */ abstract protected function loadLevelData() : WorldData; public function getPath() : string{ diff --git a/src/pocketmine/world/format/io/WorldProvider.php b/src/pocketmine/world/format/io/WorldProvider.php index 62f98cb68..71cebcaae 100644 --- a/src/pocketmine/world/format/io/WorldProvider.php +++ b/src/pocketmine/world/format/io/WorldProvider.php @@ -25,12 +25,16 @@ namespace pocketmine\world\format\io; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\exception\CorruptedChunkException; +use pocketmine\world\format\io\exception\CorruptedWorldException; use pocketmine\world\format\io\exception\UnsupportedChunkFormatException; +use pocketmine\world\format\io\exception\UnsupportedWorldFormatException; interface WorldProvider{ /** * @param string $path + * @throws CorruptedWorldException + * @throws UnsupportedWorldFormatException */ public function __construct(string $path); diff --git a/src/pocketmine/world/format/io/data/BaseNbtWorldData.php b/src/pocketmine/world/format/io/data/BaseNbtWorldData.php index 3d2568f1f..4fd3fc1fc 100644 --- a/src/pocketmine/world/format/io/data/BaseNbtWorldData.php +++ b/src/pocketmine/world/format/io/data/BaseNbtWorldData.php @@ -25,8 +25,9 @@ namespace pocketmine\world\format\io\data; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; +use pocketmine\world\format\io\exception\CorruptedWorldException; +use pocketmine\world\format\io\exception\UnsupportedWorldFormatException; use pocketmine\world\format\io\WorldData; -use pocketmine\world\WorldException; use function file_exists; abstract class BaseNbtWorldData implements WorldData{ @@ -37,26 +38,38 @@ abstract class BaseNbtWorldData implements WorldData{ /** @var CompoundTag */ protected $compoundTag; + /** + * @param string $dataPath + * + * @throws CorruptedWorldException + * @throws UnsupportedWorldFormatException + */ public function __construct(string $dataPath){ $this->dataPath = $dataPath; if(!file_exists($this->dataPath)){ - throw new WorldException("World data not found at $dataPath"); + throw new CorruptedWorldException("World data not found at $dataPath"); } - $this->compoundTag = $this->load(); - if($this->compoundTag === null){ - throw new WorldException("Invalid world data"); + try{ + $this->compoundTag = $this->load(); + }catch(CorruptedWorldException $e){ + throw new CorruptedWorldException("Corrupted world data: " . $e->getMessage(), 0, $e); } $this->fix(); } /** * @return CompoundTag + * @throws CorruptedWorldException + * @throws UnsupportedWorldFormatException */ - abstract protected function load() : ?CompoundTag; - + abstract protected function load() : CompoundTag; + /** + * @throws CorruptedWorldException + * @throws UnsupportedWorldFormatException + */ abstract protected function fix() : void; /** diff --git a/src/pocketmine/world/format/io/data/BedrockWorldData.php b/src/pocketmine/world/format/io/data/BedrockWorldData.php index 042cbfee2..b38c5fa83 100644 --- a/src/pocketmine/world/format/io/data/BedrockWorldData.php +++ b/src/pocketmine/world/format/io/data/BedrockWorldData.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\world\format\io\data; use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\NbtDataException; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\StringTag; @@ -31,6 +32,7 @@ use pocketmine\nbt\TreeRoot; use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\utils\Binary; use pocketmine\utils\Utils; +use pocketmine\world\format\io\exception\CorruptedWorldException; use pocketmine\world\format\io\exception\UnsupportedWorldFormatException; use pocketmine\world\generator\Flat; use pocketmine\world\generator\Generator; @@ -102,13 +104,17 @@ class BedrockWorldData extends BaseNbtWorldData{ file_put_contents($path . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); } - protected function load() : ?CompoundTag{ + protected function load() : CompoundTag{ $nbt = new LittleEndianNbtSerializer(); - $worldData = $nbt->read(substr(file_get_contents($this->dataPath), 8))->getTag(); + try{ + $worldData = $nbt->read(substr(file_get_contents($this->dataPath), 8))->getTag(); + }catch(NbtDataException $e){ + throw new CorruptedWorldException($e->getMessage(), 0, $e); + } $version = $worldData->getInt("StorageVersion", INT32_MAX, true); if($version > self::CURRENT_STORAGE_VERSION){ - throw new UnsupportedWorldFormatException("Specified LevelDB world format version ($version) is not supported by " . \pocketmine\NAME); + throw new UnsupportedWorldFormatException("LevelDB world format version $version is currently unsupported"); } return $worldData; @@ -130,7 +136,7 @@ class BedrockWorldData extends BaseNbtWorldData{ case self::GENERATOR_LIMITED: throw new UnsupportedWorldFormatException("Limited worlds are not currently supported"); default: - throw new UnsupportedWorldFormatException("Unknown LevelDB world format type, this world cannot be loaded"); + throw new UnsupportedWorldFormatException("Unknown LevelDB generator type"); } }else{ $this->compoundTag->setString("generatorName", "default"); diff --git a/src/pocketmine/world/format/io/data/JavaWorldData.php b/src/pocketmine/world/format/io/data/JavaWorldData.php index 875b800b0..2b142a648 100644 --- a/src/pocketmine/world/format/io/data/JavaWorldData.php +++ b/src/pocketmine/world/format/io/data/JavaWorldData.php @@ -24,11 +24,13 @@ declare(strict_types=1); namespace pocketmine\world\format\io\data; use pocketmine\nbt\BigEndianNbtSerializer; +use pocketmine\nbt\NbtDataException; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\FloatTag; use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\TreeRoot; use pocketmine\utils\Utils; +use pocketmine\world\format\io\exception\CorruptedWorldException; use pocketmine\world\generator\Generator; use pocketmine\world\generator\GeneratorManager; use pocketmine\world\World; @@ -67,13 +69,18 @@ class JavaWorldData extends BaseNbtWorldData{ file_put_contents($path . "level.dat", $buffer); } - protected function load() : ?CompoundTag{ + protected function load() : CompoundTag{ $nbt = new BigEndianNbtSerializer(); - $worldData = $nbt->readCompressed(file_get_contents($this->dataPath))->getTag(); - if($worldData->hasTag("Data", CompoundTag::class)){ - return $worldData->getCompoundTag("Data"); + try{ + $worldData = $nbt->readCompressed(file_get_contents($this->dataPath))->getTag(); + }catch(NbtDataException $e){ + throw new CorruptedWorldException($e->getMessage(), 0, $e); } - return null; + + if(!$worldData->hasTag("Data", CompoundTag::class)){ + throw new CorruptedWorldException("Missing 'Data' key or wrong type"); + } + return $worldData->getCompoundTag("Data"); } protected function fix() : void{ diff --git a/src/pocketmine/world/format/io/exception/CorruptedWorldException.php b/src/pocketmine/world/format/io/exception/CorruptedWorldException.php new file mode 100644 index 000000000..982bc5e5f --- /dev/null +++ b/src/pocketmine/world/format/io/exception/CorruptedWorldException.php @@ -0,0 +1,30 @@ + LEVELDB_ZLIB_RAW_COMPRESSION @@ -115,7 +123,12 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ self::checkForLevelDBExtension(); parent::__construct($path); - $this->db = self::createDB($path); + try{ + $this->db = self::createDB($path); + }catch(\LevelDBException $e){ + //we can't tell the difference between errors caused by bad permissions and actual corruption :( + throw new CorruptedWorldException(trim($e->getMessage()), 0, $e); + } } protected function loadLevelData() : WorldData{