From 82d361d75f319eeb07cc303a755f777b533d9885 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 16 May 2020 17:36:22 +0100 Subject: [PATCH] extract a BiomeArray unit from Chunk this now also properly validates data read from disk. --- src/world/format/BiomeArray.php | 67 +++++++++++++++++++ src/world/format/Chunk.php | 17 ++--- src/world/format/io/FastChunkSerializer.php | 5 +- src/world/format/io/leveldb/LevelDB.php | 11 +-- .../io/region/LegacyAnvilChunkTrait.php | 18 +++-- src/world/format/io/region/McRegion.php | 15 +++-- 6 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 src/world/format/BiomeArray.php diff --git a/src/world/format/BiomeArray.php b/src/world/format/BiomeArray.php new file mode 100644 index 000000000..4e0734611 --- /dev/null +++ b/src/world/format/BiomeArray.php @@ -0,0 +1,67 @@ +payload = $payload; + } + + private static function idx(int $x, int $z) : int{ + if($x >= 16 or $z >= 16){ + throw new \InvalidArgumentException("x and z must be in the range 0-15"); + } + return ($z << 4) | $x; + } + + public function get(int $x, int $z) : int{ + return ord($this->payload[self::idx($x, $z)]); + } + + public function set(int $x, int $z, int $biomeId) : void{ + if($biomeId < 0 or $biomeId >= 256){ + throw new \InvalidArgumentException("Biome ID must be in the range 0-255"); + } + $this->payload[self::idx($x, $z)] = chr($biomeId); + } + + /** + * @return string ZZZZXXXX key bits + */ + public function getData() : string{ + return $this->payload; + } +} diff --git a/src/world/format/Chunk.php b/src/world/format/Chunk.php index 1a07af6bc..a0b9e651a 100644 --- a/src/world/format/Chunk.php +++ b/src/world/format/Chunk.php @@ -87,7 +87,7 @@ class Chunk{ */ protected $heightMap; - /** @var string */ + /** @var BiomeArray */ protected $biomeIds; /** @var CompoundTag[]|null */ @@ -102,7 +102,7 @@ class Chunk{ * @param CompoundTag[] $tiles * @param int[] $heightMap */ - public function __construct(int $chunkX, int $chunkZ, array $subChunks = [], ?array $entities = null, ?array $tiles = null, string $biomeIds = "", array $heightMap = []){ + public function __construct(int $chunkX, int $chunkZ, array $subChunks = [], ?array $entities = null, ?array $tiles = null, ?BiomeArray $biomeIds = null, array $heightMap = []){ $this->x = $chunkX; $this->z = $chunkZ; @@ -120,12 +120,7 @@ class Chunk{ $this->heightMap = \SplFixedArray::fromArray(array_fill(0, 256, $val)); } - if(strlen($biomeIds) === 256){ - $this->biomeIds = $biomeIds; - }else{ - assert($biomeIds === "", "Wrong BiomeIds value count, expected 256, got " . strlen($biomeIds)); - $this->biomeIds = str_repeat("\x00", 256); - } + $this->biomeIds = $biomeIds ?? new BiomeArray(str_repeat("\x00", 256)); $this->NBTtiles = $tiles; $this->NBTentities = $entities; @@ -359,7 +354,7 @@ class Chunk{ * @return int 0-255 */ public function getBiomeId(int $x, int $z) : int{ - return ord($this->biomeIds[($z << 4) | $x]); + return $this->biomeIds->get($x, $z); } /** @@ -370,7 +365,7 @@ class Chunk{ * @param int $biomeId 0-255 */ public function setBiomeId(int $x, int $z, int $biomeId) : void{ - $this->biomeIds[($z << 4) | $x] = chr($biomeId & 0xff); + $this->biomeIds->set($x, $z, $biomeId); $this->dirtyFlags |= self::DIRTY_FLAG_BIOMES; } @@ -550,7 +545,7 @@ class Chunk{ } public function getBiomeIdArray() : string{ - return $this->biomeIds; + return $this->biomeIds->getData(); } /** diff --git a/src/world/format/io/FastChunkSerializer.php b/src/world/format/io/FastChunkSerializer.php index 76aa5b77e..d2ff75755 100644 --- a/src/world/format/io/FastChunkSerializer.php +++ b/src/world/format/io/FastChunkSerializer.php @@ -25,6 +25,7 @@ namespace pocketmine\world\format\io; use pocketmine\block\BlockLegacyIds; use pocketmine\utils\BinaryStream; +use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\LightArray; use pocketmine\world\format\PalettedBlockArray; @@ -111,7 +112,7 @@ final class FastChunkSerializer{ $terrainGenerated = (bool) ($flags & 1); $subChunks = []; - $biomeIds = ""; + $biomeIds = null; $heightMap = []; if($terrainGenerated){ $count = $stream->getByte(); @@ -132,7 +133,7 @@ final class FastChunkSerializer{ ); } - $biomeIds = $stream->get(256); + $biomeIds = new BiomeArray($stream->get(256)); if($lightPopulated){ $heightMap = array_values(unpack("S*", $stream->get(512))); } diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php index 7bd49dac7..1aea0231d 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -33,6 +33,7 @@ use pocketmine\utils\Binary; use pocketmine\utils\BinaryDataException; use pocketmine\utils\BinaryStream; use pocketmine\utils\Utils; +use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\BaseWorldProvider; use pocketmine\world\format\io\ChunkUtils; @@ -232,8 +233,8 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ /** @var SubChunk[] $subChunks */ $subChunks = []; - /** @var string $biomeIds */ - $biomeIds = ""; + /** @var BiomeArray|null $biomeArray */ + $biomeArray = null; $chunkVersion = ord($this->db->get($index . self::TAG_VERSION)); $hasBeenUpgraded = $chunkVersion < self::CURRENT_LEVEL_CHUNK_VERSION; @@ -326,7 +327,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ try{ $binaryStream->get(512); //heightmap, discard it - $biomeIds = $binaryStream->get(256); + $biomeArray = new BiomeArray($binaryStream->get(256)); //never throws }catch(BinaryDataException $e){ throw new CorruptedChunkException($e->getMessage(), 0, $e); } @@ -360,7 +361,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ try{ $binaryStream->get(256); //heightmap, discard it - $biomeIds = ChunkUtils::convertBiomeColors(array_values(unpack("N*", $binaryStream->get(1024)))); + $biomeArray = new BiomeArray(ChunkUtils::convertBiomeColors(array_values(unpack("N*", $binaryStream->get(1024))))); //never throws }catch(BinaryDataException $e){ throw new CorruptedChunkException($e->getMessage(), 0, $e); } @@ -398,7 +399,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ $subChunks, $entities, $tiles, - $biomeIds + $biomeArray ); //TODO: tile ticks, biome states (?) diff --git a/src/world/format/io/region/LegacyAnvilChunkTrait.php b/src/world/format/io/region/LegacyAnvilChunkTrait.php index 204edff3b..bc06f7ed0 100644 --- a/src/world/format/io/region/LegacyAnvilChunkTrait.php +++ b/src/world/format/io/region/LegacyAnvilChunkTrait.php @@ -25,9 +25,11 @@ namespace pocketmine\world\format\io\region; use pocketmine\nbt\BigEndianNbtSerializer; use pocketmine\nbt\NbtDataException; +use pocketmine\nbt\tag\ByteArrayTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\IntArrayTag; use pocketmine\nbt\tag\ListTag; +use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\ChunkUtils; use pocketmine\world\format\io\exception\CorruptedChunkException; @@ -72,10 +74,18 @@ trait LegacyAnvilChunkTrait{ } } + $makeBiomeArray = function(string $biomeIds) : BiomeArray{ + try{ + return new BiomeArray($biomeIds); + }catch(\InvalidArgumentException $e){ + throw new CorruptedChunkException($e->getMessage(), 0, $e); + } + }; + $biomeArray = null; if($chunk->hasTag("BiomeColors", IntArrayTag::class)){ - $biomeIds = ChunkUtils::convertBiomeColors($chunk->getIntArray("BiomeColors")); //Convert back to original format - }else{ - $biomeIds = $chunk->getByteArray("Biomes", ""); + $biomeArray = $makeBiomeArray(ChunkUtils::convertBiomeColors($chunk->getIntArray("BiomeColors"))); //Convert back to original format + }elseif($chunk->hasTag("Biomes", ByteArrayTag::class)){ + $biomeArray = $makeBiomeArray($chunk->getByteArray("Biomes")); } $result = new Chunk( @@ -84,7 +94,7 @@ trait LegacyAnvilChunkTrait{ $subChunks, $chunk->hasTag("Entities", ListTag::class) ? self::getCompoundList("Entities", $chunk->getListTag("Entities")) : [], $chunk->hasTag("TileEntities", ListTag::class) ? self::getCompoundList("TileEntities", $chunk->getListTag("TileEntities")) : [], - $biomeIds + $biomeArray ); $result->setPopulated($chunk->getByte("TerrainPopulated", 0) !== 0); $result->setGenerated(); diff --git a/src/world/format/io/region/McRegion.php b/src/world/format/io/region/McRegion.php index 634edde90..f6de24ea4 100644 --- a/src/world/format/io/region/McRegion.php +++ b/src/world/format/io/region/McRegion.php @@ -29,6 +29,7 @@ use pocketmine\nbt\NbtDataException; use pocketmine\nbt\tag\ByteArrayTag; use pocketmine\nbt\tag\IntArrayTag; use pocketmine\nbt\tag\ListTag; +use pocketmine\world\format\BiomeArray; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\ChunkUtils; use pocketmine\world\format\io\exception\CorruptedChunkException; @@ -69,12 +70,18 @@ class McRegion extends RegionWorldProvider{ $subChunks[$y] = new SubChunk(BlockLegacyIds::AIR << 4, [SubChunkConverter::convertSubChunkFromLegacyColumn($fullIds, $fullData, $y)]); } + $makeBiomeArray = function(string $biomeIds) : BiomeArray{ + try{ + return new BiomeArray($biomeIds); + }catch(\InvalidArgumentException $e){ + throw new CorruptedChunkException($e->getMessage(), 0, $e); + } + }; + $biomeIds = null; if($chunk->hasTag("BiomeColors", IntArrayTag::class)){ - $biomeIds = ChunkUtils::convertBiomeColors($chunk->getIntArray("BiomeColors")); //Convert back to original format + $biomeIds = $makeBiomeArray(ChunkUtils::convertBiomeColors($chunk->getIntArray("BiomeColors"))); //Convert back to original format }elseif($chunk->hasTag("Biomes", ByteArrayTag::class)){ - $biomeIds = $chunk->getByteArray("Biomes"); - }else{ - $biomeIds = ""; + $biomeIds = $makeBiomeArray($chunk->getByteArray("Biomes")); } $result = new Chunk(