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.
This commit is contained in:
Dylan K. Taylor 2018-03-01 12:28:11 +00:00
parent ae2e1fdd5a
commit 9d018e8d9e
8 changed files with 306 additions and 289 deletions

View File

@ -2706,16 +2706,24 @@ class Level implements ChunkManager, Metadatable{
$this->timings->syncChunkLoadDataTimer->startTiming(); $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(); $this->timings->syncChunkLoadDataTimer->stopTiming();
if($chunk === null){ if($chunk === null){
$this->timings->syncChunkLoadTimer->stopTiming(); $this->timings->syncChunkLoadTimer->stopTiming();
if($create){
throw new \InvalidStateException("Could not create new Chunk");
}
return false; return false;
} }

View File

@ -113,13 +113,8 @@ abstract class BaseLevelProvider implements LevelProvider{
file_put_contents($this->getPath() . "level.dat", $buffer); file_put_contents($this->getPath() . "level.dat", $buffer);
} }
public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : ?Chunk{ public function loadChunk(int $chunkX, int $chunkZ) : ?Chunk{
$chunk = $this->readChunk($chunkX, $chunkZ); return $this->readChunk($chunkX, $chunkZ);
if($chunk === null and $create){
$chunk = new Chunk($chunkX, $chunkZ);
}
return $chunk;
} }
public function saveChunk(Chunk $chunk) : void{ public function saveChunk(Chunk $chunk) : void{

View File

@ -93,16 +93,17 @@ interface LevelProvider{
public function saveChunk(Chunk $chunk) : void; 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 * Loads a chunk (usually from disk storage) and returns it. If the chunk does not exist, null is returned.
* empty Chunk if $create is specified.
* *
* @param int $chunkX * @param int $chunkX
* @param int $chunkZ * @param int $chunkZ
* @param bool $create
* *
* @return null|Chunk * @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 * @return string

View File

@ -0,0 +1,30 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\level\format\io\exception;
use pocketmine\level\format\ChunkException;
class CorruptedChunkException extends ChunkException{
}

View File

@ -39,7 +39,6 @@ use pocketmine\nbt\tag\{
use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\utils\Binary; use pocketmine\utils\Binary;
use pocketmine\utils\BinaryStream; use pocketmine\utils\BinaryStream;
use pocketmine\utils\MainLogger;
class LevelDB extends BaseLevelProvider{ class LevelDB extends BaseLevelProvider{
@ -263,6 +262,7 @@ class LevelDB extends BaseLevelProvider{
* @param int $chunkZ * @param int $chunkZ
* *
* @return Chunk|null * @return Chunk|null
* @throws UnsupportedChunkFormatException
*/ */
protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk{ protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk{
$index = LevelDB::chunkIndex($chunkX, $chunkZ); $index = LevelDB::chunkIndex($chunkX, $chunkZ);
@ -271,168 +271,154 @@ class LevelDB extends BaseLevelProvider{
return null; return null;
} }
try{ /** @var SubChunk[] $subChunks */
/** @var SubChunk[] $subChunks */ $subChunks = [];
$subChunks = [];
/** @var bool $lightPopulated */ /** @var bool $lightPopulated */
$lightPopulated = true; $lightPopulated = true;
$chunkVersion = ord($this->db->get($index . self::TAG_VERSION)); $chunkVersion = ord($this->db->get($index . self::TAG_VERSION));
$binaryStream = new BinaryStream(); $binaryStream = new BinaryStream();
switch($chunkVersion){ switch($chunkVersion){
case 7: //MCPE 1.2 (???) case 7: //MCPE 1.2 (???)
case 4: //MCPE 1.1 case 4: //MCPE 1.1
//TODO: check beds //TODO: check beds
case 3: //MCPE 1.0 case 3: //MCPE 1.0
for($y = 0; $y < Chunk::MAX_SUBCHUNKS; ++$y){ for($y = 0; $y < Chunk::MAX_SUBCHUNKS; ++$y){
if(($data = $this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y))) === false){ if(($data = $this->db->get($index . self::TAG_SUBCHUNK_PREFIX . chr($y))) === false){
continue; 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");
}
} }
$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))); switch($subChunkVersion){
$biomeIds = $binaryStream->get(256); case 0:
break; $blocks = $binaryStream->get(4096);
case 2: // < MCPE 1.0 $blockData = $binaryStream->get(2048);
$binaryStream->setBuffer($this->db->get($index . self::TAG_LEGACY_TERRAIN)); if($chunkVersion < 4){
$fullIds = $binaryStream->get(32768); $blockSkyLight = $binaryStream->get(2048);
$fullData = $binaryStream->get(16384); $blockLight = $binaryStream->get(2048);
$fullSkyLight = $binaryStream->get(16384); }else{
$fullBlockLight = $binaryStream->get(16384); //Mojang didn't bother changing the subchunk version when they stopped saving sky light -_-
$blockSkyLight = "";
$blockLight = "";
$lightPopulated = false;
}
for($yy = 0; $yy < 8; ++$yy){ $subChunks[$y] = new SubChunk($blocks, $blockData, $blockSkyLight, $blockLight);
$subOffset = ($yy << 4); break;
$ids = ""; default:
for($i = 0; $i < 256; ++$i){ //TODO: set chunks read-only so the version on disk doesn't get overwritten
$ids .= substr($fullIds, $subOffset, 16); throw new UnsupportedChunkFormatException("don't know how to decode LevelDB subchunk format version $subChunkVersion");
$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);
} }
$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 */ $binaryStream->setBuffer($this->db->get($index . self::TAG_DATA_2D), 0);
foreach($entities as $entityNBT){
if($entityNBT->hasTag("id", IntTag::class)){ $heightMap = array_values(unpack("v*", $binaryStream->get(512)));
$entityNBT->setInt("id", $entityNBT->getInt("id") & 0xff); //remove type flags - TODO: use these instead of removing them) $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{ protected function writeChunk(Chunk $chunk) : void{

View File

@ -33,7 +33,6 @@ use pocketmine\nbt\tag\ByteArrayTag;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntArrayTag; use pocketmine\nbt\tag\IntArrayTag;
use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\ListTag;
use pocketmine\utils\MainLogger;
class Anvil extends McRegion{ class Anvil extends McRegion{
@ -99,45 +98,40 @@ class Anvil extends McRegion{
protected function nbtDeserialize(string $data){ protected function nbtDeserialize(string $data){
$nbt = new BigEndianNBTStream(); $nbt = new BigEndianNBTStream();
try{ $chunk = $nbt->readCompressed($data);
$chunk = $nbt->readCompressed($data); if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){
if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){ throw new ChunkException("Invalid NBT format");
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 = $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{ protected function deserializeSubChunk(CompoundTag $subChunk) : SubChunk{

View File

@ -111,80 +111,75 @@ class McRegion extends BaseLevelProvider{
*/ */
protected function nbtDeserialize(string $data){ protected function nbtDeserialize(string $data){
$nbt = new BigEndianNBTStream(); $nbt = new BigEndianNBTStream();
try{ $chunk = $nbt->readCompressed($data);
$chunk = $nbt->readCompressed($data); if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){
if(!($chunk instanceof CompoundTag) or !$chunk->hasTag("Level")){ throw new ChunkException("Invalid NBT format");
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 = $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{ public static function getProviderName() : string{

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\level\format\io\region; namespace pocketmine\level\format\io\region;
use pocketmine\level\format\ChunkException; use pocketmine\level\format\ChunkException;
use pocketmine\level\format\io\exception\CorruptedChunkException;
use pocketmine\utils\Binary; use pocketmine\utils\Binary;
use pocketmine\utils\MainLogger; use pocketmine\utils\MainLogger;
@ -95,10 +96,18 @@ class RegionLoader{
return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0); 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{ public function readChunk(int $x, int $z) : ?string{
$index = self::getChunkOffset($x, $z); $index = self::getChunkOffset($x, $z);
if($index < 0 or $index >= 4096){ 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(); $this->lastUsed = time();
@ -115,27 +124,26 @@ class RegionLoader{
if($length >= self::MAX_SECTOR_LENGTH){ if($length >= self::MAX_SECTOR_LENGTH){
$this->locationTable[$index][0] = ++$this->lastSector; $this->locationTable[$index][0] = ++$this->lastSector;
$this->locationTable[$index][1] = 1; $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; return null;
} }
if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors 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->locationTable[$index][1] = $length >> 12;
$this->writeLocationIndex($index); $this->writeLocationIndex($index);
}elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){ }elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
MainLogger::getLogger()->error("Invalid compression type"); throw new CorruptedChunkException("Invalid compression type (got $compression, expected " . self::COMPRESSION_ZLIB . " or " . self::COMPRESSION_GZIP . ")");
return null;
} }
$chunkData = fread($this->filePointer, $length - 1); $chunkData = fread($this->filePointer, $length - 1);
if($chunkData !== false){ if($chunkData === false){
return $chunkData; throw new CorruptedChunkException("Corrupted chunk detected (failed to read chunk data from disk)");
}else{
MainLogger::getLogger()->error("Corrupted chunk detected");
return null;
} }
return $chunkData;
} }
public function chunkExists(int $x, int $z) : bool{ public function chunkExists(int $x, int $z) : bool{