read($decompressed)->mustGetCompoundTag(); }catch(NbtDataException $e){ throw new CorruptedChunkException($e->getMessage(), 0, $e); } $chunk = $chunk->getTag("Level"); if(!($chunk instanceof CompoundTag)){ throw new CorruptedChunkException("'Level' key is missing from chunk NBT"); } $makeBiomeArray = function(string $biomeIds) : PalettedBlockArray{ if(strlen($biomeIds) !== 256){ throw new CorruptedChunkException("Expected biome array to be exactly 256 bytes, got " . strlen($biomeIds)); } //TODO: we may need to convert legacy biome IDs return ChunkUtils::extrapolate3DBiomes($biomeIds); }; if(($biomeColorsTag = $chunk->getTag("BiomeColors")) instanceof IntArrayTag){ $biomes3d = $makeBiomeArray(ChunkUtils::convertBiomeColors($biomeColorsTag->getValue())); //Convert back to original format }elseif(($biomesTag = $chunk->getTag("Biomes")) instanceof ByteArrayTag){ $biomes3d = $makeBiomeArray($biomesTag->getValue()); }else{ $biomes3d = new PalettedBlockArray(BiomeIds::OCEAN); } $subChunks = []; $subChunksTag = $chunk->getListTag("Sections", CompoundTag::class) ?? []; foreach($subChunksTag as $subChunk){ $y = $subChunk->getByte("Y"); $subChunks[$y] = $this->deserializeSubChunk($subChunk, clone $biomes3d, new \PrefixedLogger($logger, "Subchunk y=$y")); } for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){ if(!isset($subChunks[$y])){ $subChunks[$y] = new SubChunk(Block::EMPTY_STATE_ID, null, null, clone $biomes3d); } } return new LoadedChunkData( data: new ChunkData( $subChunks, $chunk->getByte("TerrainPopulated", 0) !== 0, ($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [], ($tilesTag = $chunk->getTag("TileEntities")) instanceof ListTag ? self::getCompoundList("TileEntities", $tilesTag) : [], ), upgraded: true, fixerFlags: LoadedChunkData::FIXER_FLAG_ALL ); } abstract protected function deserializeSubChunk(CompoundTag $subChunk, PalettedBlockArray $biomes3d, \Logger $logger) : SubChunk; }