61 public static function isValid(
string $path) : bool{
62 if(file_exists(Path::join($path,
"level.dat")) && is_dir($regionPath = Path::join($path,
"region"))){
63 foreach(scandir($regionPath, SCANDIR_SORT_NONE) as $file){
64 $extPos = strrpos($file,
".");
65 if($extPos !==
false && substr($file, $extPos + 1) === static::getRegionFileExtension()){
76 protected array $regions = [];
79 return new
JavaWorldData(Path::join($this->getPath(),
"level.dat"));
83 $limit = time() - 300;
84 foreach($this->regions as $index => $region){
85 if($region->lastUsed <= $limit){
87 unset($this->regions[$index]);
98 public static function getRegionIndex(
int $chunkX,
int $chunkZ, &$regionX, &$regionZ) : void{
99 $regionX = $chunkX >> 5;
100 $regionZ = $chunkZ >> 5;
103 protected function getRegion(
int $regionX,
int $regionZ) : ?
RegionLoader{
104 return $this->regions[morton2d_encode($regionX, $regionZ)] ?? null;
111 return Path::join($this->path,
"region",
"r.$regionX.$regionZ." . static::getRegionFileExtension());
114 protected function loadRegion(
int $regionX,
int $regionZ) :
RegionLoader{
115 if(!isset($this->regions[$index = morton2d_encode($regionX, $regionZ)])){
116 $path = $this->pathToRegion($regionX, $regionZ);
119 $this->regions[$index] = RegionLoader::loadExisting($path);
120 }
catch(CorruptedRegionException $e){
121 $this->logger->error(
"Corrupted region file detected: " . $e->getMessage());
123 $backupPath = $path .
".bak." . time();
124 rename($path, $backupPath);
125 $this->logger->error(
"Corrupted region file has been backed up to " . $backupPath);
127 $this->regions[$index] = RegionLoader::createNew($path);
130 return $this->regions[$index];
133 protected function unloadRegion(
int $regionX,
int $regionZ) : void{
134 if(isset($this->regions[$hash = morton2d_encode($regionX, $regionZ)])){
135 $this->regions[$hash]->close();
136 unset($this->regions[$hash]);
141 foreach($this->regions as $index => $region){
143 unset($this->regions[$index]);
157 if($list->count() === 0){
160 if($list->getTagType() !== NBT::TAG_Compound){
161 throw new CorruptedChunkException(
"Expected TAG_List<TAG_Compound> for '$context'");
164 foreach($list as $tag){
165 if(!($tag instanceof CompoundTag)){
167 throw new CorruptedChunkException(
"Expected TAG_List<TAG_Compound> for '$context'");
174 protected static function readFixedSizeByteArray(
CompoundTag $chunk,
string $tagName,
int $length) : string{
175 $tag = $chunk->getTag($tagName);
176 if(!($tag instanceof ByteArrayTag)){
183 if(strlen($data) !== $length){
184 throw new CorruptedChunkException(
"Expected '$tagName' payload to have exactly $length bytes, but have " . strlen($data));
193 $regionX = $regionZ = null;
194 self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ);
195 assert(is_int($regionX) && is_int($regionZ));
197 if(!file_exists($this->pathToRegion($regionX, $regionZ))){
201 $chunkData = $this->loadRegion($regionX, $regionZ)->readChunk($chunkX & 0x1f, $chunkZ & 0x1f);
202 if($chunkData !==
null){
203 return $this->deserializeChunk($chunkData,
new \
PrefixedLogger($this->logger,
"Loading chunk x=$chunkX z=$chunkZ"));
209 private function createRegionIterator() : \RegexIterator{
210 return new \RegexIterator(
211 new \FilesystemIterator(
212 Path::join($this->path,
'region'),
213 \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
215 '/\/r\.(-?\d+)\.(-?\d+)\.' . static::getRegionFileExtension() .
'$/',
216 \RegexIterator::GET_MATCH
221 $iterator = $this->createRegionIterator();
223 foreach($iterator as $region){
224 $regionX = ((int) $region[1]);
225 $regionZ = ((int) $region[2]);
229 for($chunkX = $rX; $chunkX < $rX + 32; ++$chunkX){
230 for($chunkZ = $rZ; $chunkZ < $rZ + 32; ++$chunkZ){
232 $chunk = $this->loadChunk($chunkX, $chunkZ);
234 yield [$chunkX, $chunkZ] => $chunk;
240 if($logger !==
null){
241 $logger->error(
"Skipped corrupted chunk $chunkX $chunkZ (" . $e->getMessage() .
")");
247 $this->unloadRegion($regionX, $regionZ);
253 foreach($this->createRegionIterator() as $region){
254 $regionX = ((int) $region[1]);
255 $regionZ = ((int) $region[2]);
256 $count += $this->loadRegion($regionX, $regionZ)->calculateChunkCount();
257 $this->unloadRegion($regionX, $regionZ);