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{