From 9d018e8d9efd0cea59c8a96e26c5fde7c316b10b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 1 Mar 2018 12:28:11 +0000 Subject: [PATCH] Level: cleaned up chunk loading error handling, close #2056 This now removes logging from the level providers (for the most part) and replaces it with exception throws and catches. The implementation using the providers should catch these exceptions if they are thrown. --- src/pocketmine/level/Level.php | 18 +- .../level/format/io/BaseLevelProvider.php | 9 +- .../level/format/io/LevelProvider.php | 13 +- .../io/exception/CorruptedChunkException.php | 30 ++ .../level/format/io/leveldb/LevelDB.php | 284 +++++++++--------- .../level/format/io/region/Anvil.php | 72 ++--- .../level/format/io/region/McRegion.php | 141 +++++---- .../level/format/io/region/RegionLoader.php | 28 +- 8 files changed, 306 insertions(+), 289 deletions(-) create mode 100644 src/pocketmine/level/format/io/exception/CorruptedChunkException.php diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index a8903d7c5..50926b0d3 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -2706,16 +2706,24 @@ class Level implements ChunkManager, Metadatable{ $this->timings->syncChunkLoadDataTimer->startTiming(); - $chunk = $this->provider->loadChunk($x, $z, $create); + $chunk = null; + + try{ + $chunk = $this->provider->loadChunk($x, $z); + }catch(\Exception $e){ + $logger = $this->server->getLogger(); + $logger->critical("An error occurred while loading chunk x=$x z=$z: " . $e->getMessage()); + $logger->logException($e); + } + + if($chunk === null and $create){ + $chunk = new Chunk($x, $z); + } $this->timings->syncChunkLoadDataTimer->stopTiming(); if($chunk === null){ $this->timings->syncChunkLoadTimer->stopTiming(); - - if($create){ - throw new \InvalidStateException("Could not create new Chunk"); - } return false; } diff --git a/src/pocketmine/level/format/io/BaseLevelProvider.php b/src/pocketmine/level/format/io/BaseLevelProvider.php index 3ddb211fc..bcd754728 100644 --- a/src/pocketmine/level/format/io/BaseLevelProvider.php +++ b/src/pocketmine/level/format/io/BaseLevelProvider.php @@ -113,13 +113,8 @@ abstract class BaseLevelProvider implements LevelProvider{ file_put_contents($this->getPath() . "level.dat", $buffer); } - public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : ?Chunk{ - $chunk = $this->readChunk($chunkX, $chunkZ); - if($chunk === null and $create){ - $chunk = new Chunk($chunkX, $chunkZ); - } - - return $chunk; + public function loadChunk(int $chunkX, int $chunkZ) : ?Chunk{ + return $this->readChunk($chunkX, $chunkZ); } public function saveChunk(Chunk $chunk) : void{ diff --git a/src/pocketmine/level/format/io/LevelProvider.php b/src/pocketmine/level/format/io/LevelProvider.php index c914084fc..3c07f3023 100644 --- a/src/pocketmine/level/format/io/LevelProvider.php +++ b/src/pocketmine/level/format/io/LevelProvider.php @@ -93,16 +93,17 @@ interface LevelProvider{ public function saveChunk(Chunk $chunk) : void; /** - * Loads a chunk (usually from disk storage) and returns it. If the chunk does not exist, null is returned, or an - * empty Chunk if $create is specified. + * Loads a chunk (usually from disk storage) and returns it. If the chunk does not exist, null is returned. * - * @param int $chunkX - * @param int $chunkZ - * @param bool $create + * @param int $chunkX + * @param int $chunkZ * * @return null|Chunk + * + * @throws \Exception any of a range of exceptions that could be thrown while reading chunks. See individual + * implementations for details. */ - public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : ?Chunk; + public function loadChunk(int $chunkX, int $chunkZ) : ?Chunk; /** * @return string diff --git a/src/pocketmine/level/format/io/exception/CorruptedChunkException.php b/src/pocketmine/level/format/io/exception/CorruptedChunkException.php new file mode 100644 index 000000000..9c0705fbd --- /dev/null +++ b/src/pocketmine/level/format/io/exception/CorruptedChunkException.php @@ -0,0 +1,30 @@ +db->get($index . self::TAG_VERSION)); + $chunkVersion = ord($this->db->get($index . self::TAG_VERSION)); - $binaryStream = new BinaryStream(); + $binaryStream = new BinaryStream(); - switch($chunkVersion){ - case 7: //MCPE 1.2 (???) - case 4: //MCPE 1.1 - //TODO: check beds - case 3: //MCPE 1.0 - for($y = 0; $y < Chunk::MAX_SUBCHUNKS; ++$y){ - if(($data = $this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y))) === false){ - continue; - } - - $binaryStream->setBuffer($data, 0); - $subChunkVersion = $binaryStream->getByte(); - - switch($subChunkVersion){ - case 0: - $blocks = $binaryStream->get(4096); - $blockData = $binaryStream->get(2048); - if($chunkVersion < 4){ - $blockSkyLight = $binaryStream->get(2048); - $blockLight = $binaryStream->get(2048); - }else{ - //Mojang didn't bother changing the subchunk version when they stopped saving sky light -_- - $blockSkyLight = ""; - $blockLight = ""; - $lightPopulated = false; - } - - $subChunks[$y] = new SubChunk($blocks, $blockData, $blockSkyLight, $blockLight); - break; - default: - throw new UnsupportedChunkFormatException("don't know how to decode LevelDB subchunk format version $subChunkVersion"); - } + switch($chunkVersion){ + case 7: //MCPE 1.2 (???) + case 4: //MCPE 1.1 + //TODO: check beds + case 3: //MCPE 1.0 + for($y = 0; $y < Chunk::MAX_SUBCHUNKS; ++$y){ + if(($data = $this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y))) === false){ + continue; } - $binaryStream->setBuffer($this->db->get($index . self::TAG_DATA_2D), 0); + $binaryStream->setBuffer($data, 0); + $subChunkVersion = $binaryStream->getByte(); - $heightMap = array_values(unpack("v*", $binaryStream->get(512))); - $biomeIds = $binaryStream->get(256); - break; - case 2: // < MCPE 1.0 - $binaryStream->setBuffer($this->db->get($index . self::TAG_LEGACY_TERRAIN)); - $fullIds = $binaryStream->get(32768); - $fullData = $binaryStream->get(16384); - $fullSkyLight = $binaryStream->get(16384); - $fullBlockLight = $binaryStream->get(16384); + switch($subChunkVersion){ + case 0: + $blocks = $binaryStream->get(4096); + $blockData = $binaryStream->get(2048); + if($chunkVersion < 4){ + $blockSkyLight = $binaryStream->get(2048); + $blockLight = $binaryStream->get(2048); + }else{ + //Mojang didn't bother changing the subchunk version when they stopped saving sky light -_- + $blockSkyLight = ""; + $blockLight = ""; + $lightPopulated = false; + } - for($yy = 0; $yy < 8; ++$yy){ - $subOffset = ($yy << 4); - $ids = ""; - for($i = 0; $i < 256; ++$i){ - $ids .= substr($fullIds, $subOffset, 16); - $subOffset += 128; - } - $data = ""; - $subOffset = ($yy << 3); - for($i = 0; $i < 256; ++$i){ - $data .= substr($fullData, $subOffset, 8); - $subOffset += 64; - } - $skyLight = ""; - $subOffset = ($yy << 3); - for($i = 0; $i < 256; ++$i){ - $skyLight .= substr($fullSkyLight, $subOffset, 8); - $subOffset += 64; - } - $blockLight = ""; - $subOffset = ($yy << 3); - for($i = 0; $i < 256; ++$i){ - $blockLight .= substr($fullBlockLight, $subOffset, 8); - $subOffset += 64; - } - $subChunks[$yy] = new SubChunk($ids, $data, $skyLight, $blockLight); + $subChunks[$y] = new SubChunk($blocks, $blockData, $blockSkyLight, $blockLight); + break; + default: + //TODO: set chunks read-only so the version on disk doesn't get overwritten + throw new UnsupportedChunkFormatException("don't know how to decode LevelDB subchunk format version $subChunkVersion"); } - - $heightMap = array_values(unpack("C*", $binaryStream->get(256))); - $biomeIds = ChunkUtils::convertBiomeColors(array_values(unpack("N*", $binaryStream->get(1024)))); - break; - default: - throw new UnsupportedChunkFormatException("don't know how to decode chunk format version $chunkVersion"); - } - - $nbt = new LittleEndianNBTStream(); - - /** @var CompoundTag[] $entities */ - $entities = []; - if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false and strlen($entityData) > 0){ - $entities = $nbt->read($entityData, true); - if(!is_array($entities)){ - $entities = [$entities]; } - } - /** @var CompoundTag $entityNBT */ - foreach($entities as $entityNBT){ - if($entityNBT->hasTag("id", IntTag::class)){ - $entityNBT->setInt("id", $entityNBT->getInt("id") & 0xff); //remove type flags - TODO: use these instead of removing them) + $binaryStream->setBuffer($this->db->get($index . self::TAG_DATA_2D), 0); + + $heightMap = array_values(unpack("v*", $binaryStream->get(512))); + $biomeIds = $binaryStream->get(256); + break; + case 2: // < MCPE 1.0 + $binaryStream->setBuffer($this->db->get($index . self::TAG_LEGACY_TERRAIN)); + $fullIds = $binaryStream->get(32768); + $fullData = $binaryStream->get(16384); + $fullSkyLight = $binaryStream->get(16384); + $fullBlockLight = $binaryStream->get(16384); + + for($yy = 0; $yy < 8; ++$yy){ + $subOffset = ($yy << 4); + $ids = ""; + for($i = 0; $i < 256; ++$i){ + $ids .= substr($fullIds, $subOffset, 16); + $subOffset += 128; + } + $data = ""; + $subOffset = ($yy << 3); + for($i = 0; $i < 256; ++$i){ + $data .= substr($fullData, $subOffset, 8); + $subOffset += 64; + } + $skyLight = ""; + $subOffset = ($yy << 3); + for($i = 0; $i < 256; ++$i){ + $skyLight .= substr($fullSkyLight, $subOffset, 8); + $subOffset += 64; + } + $blockLight = ""; + $subOffset = ($yy << 3); + for($i = 0; $i < 256; ++$i){ + $blockLight .= substr($fullBlockLight, $subOffset, 8); + $subOffset += 64; + } + $subChunks[$yy] = new SubChunk($ids, $data, $skyLight, $blockLight); } - } - - $tiles = []; - if(($tileData = $this->db->get($index . self::TAG_BLOCK_ENTITY)) !== false and strlen($tileData) > 0){ - $tiles = $nbt->read($tileData, true); - if(!is_array($tiles)){ - $tiles = [$tiles]; - } - } - - $extraData = []; - if(($extraRawData = $this->db->get($index . self::TAG_BLOCK_EXTRA_DATA)) !== false and strlen($extraRawData) > 0){ - $binaryStream->setBuffer($extraRawData, 0); - $count = $binaryStream->getLInt(); - for($i = 0; $i < $count; ++$i){ - $key = $binaryStream->getLInt(); - $value = $binaryStream->getLShort(); - $extraData[$key] = $value; - } - } - - $chunk = new Chunk( - $chunkX, - $chunkZ, - $subChunks, - $entities, - $tiles, - $biomeIds, - $heightMap, - $extraData - ); - - //TODO: tile ticks, biome states (?) - - $chunk->setGenerated(true); - $chunk->setPopulated(true); - $chunk->setLightPopulated($lightPopulated); - - return $chunk; - }catch(UnsupportedChunkFormatException $e){ - //TODO: set chunks read-only so the version on disk doesn't get overwritten - - $logger = MainLogger::getLogger(); - $logger->error("Failed to decode LevelDB chunk: " . $e->getMessage()); - - return null; - }catch(\Throwable $t){ - $logger = MainLogger::getLogger(); - $logger->error("LevelDB chunk decode error"); - $logger->logException($t); - - return null; + $heightMap = array_values(unpack("C*", $binaryStream->get(256))); + $biomeIds = ChunkUtils::convertBiomeColors(array_values(unpack("N*", $binaryStream->get(1024)))); + break; + default: + //TODO: set chunks read-only so the version on disk doesn't get overwritten + throw new UnsupportedChunkFormatException("don't know how to decode chunk format version $chunkVersion"); } + + $nbt = new LittleEndianNBTStream(); + + /** @var CompoundTag[] $entities */ + $entities = []; + if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false and strlen($entityData) > 0){ + $entities = $nbt->read($entityData, true); + if(!is_array($entities)){ + $entities = [$entities]; + } + } + + /** @var CompoundTag $entityNBT */ + foreach($entities as $entityNBT){ + if($entityNBT->hasTag("id", IntTag::class)){ + $entityNBT->setInt("id", $entityNBT->getInt("id") & 0xff); //remove type flags - TODO: use these instead of removing them) + } + } + + $tiles = []; + if(($tileData = $this->db->get($index . self::TAG_BLOCK_ENTITY)) !== false and strlen($tileData) > 0){ + $tiles = $nbt->read($tileData, true); + if(!is_array($tiles)){ + $tiles = [$tiles]; + } + } + + $extraData = []; + if(($extraRawData = $this->db->get($index . self::TAG_BLOCK_EXTRA_DATA)) !== false and strlen($extraRawData) > 0){ + $binaryStream->setBuffer($extraRawData, 0); + $count = $binaryStream->getLInt(); + for($i = 0; $i < $count; ++$i){ + $key = $binaryStream->getLInt(); + $value = $binaryStream->getLShort(); + $extraData[$key] = $value; + } + } + + $chunk = new Chunk( + $chunkX, + $chunkZ, + $subChunks, + $entities, + $tiles, + $biomeIds, + $heightMap, + $extraData + ); + + //TODO: tile ticks, biome states (?) + + $chunk->setGenerated(true); + $chunk->setPopulated(true); + $chunk->setLightPopulated($lightPopulated); + + return $chunk; } protected function writeChunk(Chunk $chunk) : void{ diff --git a/src/pocketmine/level/format/io/region/Anvil.php b/src/pocketmine/level/format/io/region/Anvil.php index fec1056d9..7f9bc3208 100644 --- a/src/pocketmine/level/format/io/region/Anvil.php +++ b/src/pocketmine/level/format/io/region/Anvil.php @@ -33,7 +33,6 @@ use pocketmine\nbt\tag\ByteArrayTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntArrayTag; use pocketmine\nbt\tag\ListTag; -use pocketmine\utils\MainLogger; class Anvil extends McRegion{ @@ -99,45 +98,40 @@ class Anvil extends McRegion{ protected function nbtDeserialize(string $data){ $nbt = new BigEndianNBTStream(); - try{ - $chunk = $nbt->readCompressed($data); - if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){ - throw new ChunkException("Invalid NBT format"); - } - - $chunk = $chunk->getCompoundTag("Level"); - - $subChunks = []; - $subChunksTag = $chunk->getListTag("Sections") ?? []; - foreach($subChunksTag as $subChunk){ - if($subChunk instanceof CompoundTag){ - $subChunks[$subChunk->getByte("Y")] = $this->deserializeSubChunk($subChunk); - } - } - - if($chunk->hasTag("BiomeColors", IntArrayTag::class)){ - $biomeIds = ChunkUtils::convertBiomeColors($chunk->getIntArray("BiomeColors")); //Convert back to original format - }else{ - $biomeIds = $chunk->getByteArray("Biomes", "", true); - } - - $result = new Chunk( - $chunk->getInt("xPos"), - $chunk->getInt("zPos"), - $subChunks, - $chunk->hasTag("Entities", ListTag::class) ? $chunk->getListTag("Entities")->getValue() : [], - $chunk->hasTag("TileEntities", ListTag::class) ? $chunk->getListTag("TileEntities")->getValue() : [], - $biomeIds, - $chunk->getIntArray("HeightMap", []) - ); - $result->setLightPopulated($chunk->getByte("LightPopulated", 0) !== 0); - $result->setPopulated($chunk->getByte("TerrainPopulated", 0) !== 0); - $result->setGenerated(); - return $result; - }catch(\Throwable $e){ - MainLogger::getLogger()->logException($e); - return null; + $chunk = $nbt->readCompressed($data); + if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){ + throw new ChunkException("Invalid NBT format"); } + + $chunk = $chunk->getCompoundTag("Level"); + + $subChunks = []; + $subChunksTag = $chunk->getListTag("Sections") ?? []; + foreach($subChunksTag as $subChunk){ + if($subChunk instanceof CompoundTag){ + $subChunks[$subChunk->getByte("Y")] = $this->deserializeSubChunk($subChunk); + } + } + + if($chunk->hasTag("BiomeColors", IntArrayTag::class)){ + $biomeIds = ChunkUtils::convertBiomeColors($chunk->getIntArray("BiomeColors")); //Convert back to original format + }else{ + $biomeIds = $chunk->getByteArray("Biomes", "", true); + } + + $result = new Chunk( + $chunk->getInt("xPos"), + $chunk->getInt("zPos"), + $subChunks, + $chunk->hasTag("Entities", ListTag::class) ? $chunk->getListTag("Entities")->getValue() : [], + $chunk->hasTag("TileEntities", ListTag::class) ? $chunk->getListTag("TileEntities")->getValue() : [], + $biomeIds, + $chunk->getIntArray("HeightMap", []) + ); + $result->setLightPopulated($chunk->getByte("LightPopulated", 0) !== 0); + $result->setPopulated($chunk->getByte("TerrainPopulated", 0) !== 0); + $result->setGenerated(); + return $result; } protected function deserializeSubChunk(CompoundTag $subChunk) : SubChunk{ diff --git a/src/pocketmine/level/format/io/region/McRegion.php b/src/pocketmine/level/format/io/region/McRegion.php index bb0bce889..333d13dc8 100644 --- a/src/pocketmine/level/format/io/region/McRegion.php +++ b/src/pocketmine/level/format/io/region/McRegion.php @@ -111,80 +111,75 @@ class McRegion extends BaseLevelProvider{ */ protected function nbtDeserialize(string $data){ $nbt = new BigEndianNBTStream(); - try{ - $chunk = $nbt->readCompressed($data); - if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){ - throw new ChunkException("Invalid NBT format"); - } - - $chunk = $chunk->getCompoundTag("Level"); - - $subChunks = []; - $fullIds = $chunk->hasTag("Blocks", ByteArrayTag::class) ? $chunk->getByteArray("Blocks") : str_repeat("\x00", 32768); - $fullData = $chunk->hasTag("Data", ByteArrayTag::class) ? $chunk->getByteArray("Data") : str_repeat("\x00", 16384); - $fullSkyLight = $chunk->hasTag("SkyLight", ByteArrayTag::class) ? $chunk->getByteArray("SkyLight") : str_repeat("\xff", 16384); - $fullBlockLight = $chunk->hasTag("BlockLight", ByteArrayTag::class) ? $chunk->getByteArray("BlockLight") : str_repeat("\x00", 16384); - - for($y = 0; $y < 8; ++$y){ - $offset = ($y << 4); - $ids = ""; - for($i = 0; $i < 256; ++$i){ - $ids .= substr($fullIds, $offset, 16); - $offset += 128; - } - $data = ""; - $offset = ($y << 3); - for($i = 0; $i < 256; ++$i){ - $data .= substr($fullData, $offset, 8); - $offset += 64; - } - $skyLight = ""; - $offset = ($y << 3); - for($i = 0; $i < 256; ++$i){ - $skyLight .= substr($fullSkyLight, $offset, 8); - $offset += 64; - } - $blockLight = ""; - $offset = ($y << 3); - for($i = 0; $i < 256; ++$i){ - $blockLight .= substr($fullBlockLight, $offset, 8); - $offset += 64; - } - $subChunks[$y] = new SubChunk($ids, $data, $skyLight, $blockLight); - } - - if($chunk->hasTag("BiomeColors", IntArrayTag::class)){ - $biomeIds = ChunkUtils::convertBiomeColors($chunk->getIntArray("BiomeColors")); //Convert back to original format - }elseif($chunk->hasTag("Biomes", ByteArrayTag::class)){ - $biomeIds = $chunk->getByteArray("Biomes"); - }else{ - $biomeIds = ""; - } - - $heightMap = []; - if($chunk->hasTag("HeightMap", ByteArrayTag::class)){ - $heightMap = array_values(unpack("C*", $chunk->getByteArray("HeightMap"))); - }elseif($chunk->hasTag("HeightMap", IntArrayTag::class)){ - $heightMap = $chunk->getIntArray("HeightMap"); #blameshoghicp - } - - $result = new Chunk( - $chunk->getInt("xPos"), - $chunk->getInt("zPos"), - $subChunks, - $chunk->hasTag("Entities", ListTag::class) ? $chunk->getListTag("Entities")->getValue() : [], - $chunk->hasTag("TileEntities", ListTag::class) ? $chunk->getListTag("TileEntities")->getValue() : [], - $biomeIds, - $heightMap - ); - $result->setLightPopulated($chunk->getByte("LightPopulated", 0) !== 0); - $result->setPopulated($chunk->getByte("TerrainPopulated", 0) !== 0); - $result->setGenerated(true); - return $result; - }catch(\Throwable $e){ - MainLogger::getLogger()->logException($e); - return null; + $chunk = $nbt->readCompressed($data); + if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){ + throw new ChunkException("Invalid NBT format"); } + + $chunk = $chunk->getCompoundTag("Level"); + + $subChunks = []; + $fullIds = $chunk->hasTag("Blocks", ByteArrayTag::class) ? $chunk->getByteArray("Blocks") : str_repeat("\x00", 32768); + $fullData = $chunk->hasTag("Data", ByteArrayTag::class) ? $chunk->getByteArray("Data") : str_repeat("\x00", 16384); + $fullSkyLight = $chunk->hasTag("SkyLight", ByteArrayTag::class) ? $chunk->getByteArray("SkyLight") : str_repeat("\xff", 16384); + $fullBlockLight = $chunk->hasTag("BlockLight", ByteArrayTag::class) ? $chunk->getByteArray("BlockLight") : str_repeat("\x00", 16384); + + for($y = 0; $y < 8; ++$y){ + $offset = ($y << 4); + $ids = ""; + for($i = 0; $i < 256; ++$i){ + $ids .= substr($fullIds, $offset, 16); + $offset += 128; + } + $data = ""; + $offset = ($y << 3); + for($i = 0; $i < 256; ++$i){ + $data .= substr($fullData, $offset, 8); + $offset += 64; + } + $skyLight = ""; + $offset = ($y << 3); + for($i = 0; $i < 256; ++$i){ + $skyLight .= substr($fullSkyLight, $offset, 8); + $offset += 64; + } + $blockLight = ""; + $offset = ($y << 3); + for($i = 0; $i < 256; ++$i){ + $blockLight .= substr($fullBlockLight, $offset, 8); + $offset += 64; + } + $subChunks[$y] = new SubChunk($ids, $data, $skyLight, $blockLight); + } + + if($chunk->hasTag("BiomeColors", IntArrayTag::class)){ + $biomeIds = ChunkUtils::convertBiomeColors($chunk->getIntArray("BiomeColors")); //Convert back to original format + }elseif($chunk->hasTag("Biomes", ByteArrayTag::class)){ + $biomeIds = $chunk->getByteArray("Biomes"); + }else{ + $biomeIds = ""; + } + + $heightMap = []; + if($chunk->hasTag("HeightMap", ByteArrayTag::class)){ + $heightMap = array_values(unpack("C*", $chunk->getByteArray("HeightMap"))); + }elseif($chunk->hasTag("HeightMap", IntArrayTag::class)){ + $heightMap = $chunk->getIntArray("HeightMap"); #blameshoghicp + } + + $result = new Chunk( + $chunk->getInt("xPos"), + $chunk->getInt("zPos"), + $subChunks, + $chunk->hasTag("Entities", ListTag::class) ? $chunk->getListTag("Entities")->getValue() : [], + $chunk->hasTag("TileEntities", ListTag::class) ? $chunk->getListTag("TileEntities")->getValue() : [], + $biomeIds, + $heightMap + ); + $result->setLightPopulated($chunk->getByte("LightPopulated", 0) !== 0); + $result->setPopulated($chunk->getByte("TerrainPopulated", 0) !== 0); + $result->setGenerated(true); + return $result; } public static function getProviderName() : string{ diff --git a/src/pocketmine/level/format/io/region/RegionLoader.php b/src/pocketmine/level/format/io/region/RegionLoader.php index 9eeebe09f..feb35844d 100644 --- a/src/pocketmine/level/format/io/region/RegionLoader.php +++ b/src/pocketmine/level/format/io/region/RegionLoader.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\level\format\io\region; use pocketmine\level\format\ChunkException; +use pocketmine\level\format\io\exception\CorruptedChunkException; use pocketmine\utils\Binary; use pocketmine\utils\MainLogger; @@ -95,10 +96,18 @@ class RegionLoader{ return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0); } + /** + * @param int $x + * @param int $z + * + * @return null|string + * @throws \InvalidArgumentException if invalid coordinates are given + * @throws CorruptedChunkException if chunk corruption is detected + */ public function readChunk(int $x, int $z) : ?string{ $index = self::getChunkOffset($x, $z); if($index < 0 or $index >= 4096){ - return null; + throw new \InvalidArgumentException("Invalid chunk position in region, expected x/z in range 0-31, got x=$x, z=$z"); } $this->lastUsed = time(); @@ -115,27 +124,26 @@ class RegionLoader{ if($length >= self::MAX_SECTOR_LENGTH){ $this->locationTable[$index][0] = ++$this->lastSector; $this->locationTable[$index][1] = 1; - MainLogger::getLogger()->error("Corrupted chunk header detected"); + throw new CorruptedChunkException("Corrupted chunk header detected (sector count larger than max)"); } return null; } if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors - MainLogger::getLogger()->error("Corrupted bigger chunk detected"); + MainLogger::getLogger()->error("Corrupted bigger chunk detected (bigger than number of sectors given in header)"); $this->locationTable[$index][1] = $length >> 12; $this->writeLocationIndex($index); }elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){ - MainLogger::getLogger()->error("Invalid compression type"); - return null; + throw new CorruptedChunkException("Invalid compression type (got $compression, expected " . self::COMPRESSION_ZLIB . " or " . self::COMPRESSION_GZIP . ")"); } $chunkData = fread($this->filePointer, $length - 1); - if($chunkData !== false){ - return $chunkData; - }else{ - MainLogger::getLogger()->error("Corrupted chunk detected"); - return null; + if($chunkData === false){ + throw new CorruptedChunkException("Corrupted chunk detected (failed to read chunk data from disk)"); + } + + return $chunkData; } public function chunkExists(int $x, int $z) : bool{