diff --git a/src/pocketmine/level/format/io/region/RegionLoader.php b/src/pocketmine/level/format/io/region/RegionLoader.php index e86d3c56f..980dbbbd5 100644 --- a/src/pocketmine/level/format/io/region/RegionLoader.php +++ b/src/pocketmine/level/format/io/region/RegionLoader.php @@ -40,6 +40,7 @@ use function fseek; use function ftruncate; use function fwrite; use function is_resource; +use function ksort; use function max; use function ord; use function pack; @@ -314,6 +315,18 @@ class RegionLoader{ } $usedOffsets[$offset] = $i; } + ksort($usedOffsets, SORT_NUMERIC); + $prevLocationIndex = null; + foreach($usedOffsets as $startOffset => $locationTableIndex){ + if($prevLocationIndex !== null){ + if($this->locationTable[$locationTableIndex]->overlaps($this->locationTable[$prevLocationIndex])){ + self::getChunkCoords($locationTableIndex, $chunkXX, $chunkZZ); + self::getChunkCoords($prevLocationIndex, $prevChunkXX, $prevChunkZZ); + throw new CorruptedRegionException("Overlapping chunks detected in region header (chunk1: x=$chunkXX,z=$chunkZZ, chunk2: x=$prevChunkXX,z=$prevChunkZZ)"); + } + } + $prevLocationIndex = $locationTableIndex; + } } private function writeLocationTable() : void{ diff --git a/src/pocketmine/level/format/io/region/RegionLocationTableEntry.php b/src/pocketmine/level/format/io/region/RegionLocationTableEntry.php index 167a9786a..3e97522f2 100644 --- a/src/pocketmine/level/format/io/region/RegionLocationTableEntry.php +++ b/src/pocketmine/level/format/io/region/RegionLocationTableEntry.php @@ -76,4 +76,17 @@ class RegionLocationTableEntry{ public function isNull() : bool{ return $this->firstSector === 0 or $this->sectorCount === 0; } + + public function overlaps(RegionLocationTableEntry $other) : bool{ + $overlapCheck = static function(RegionLocationTableEntry $entry1, RegionLocationTableEntry $entry2) : bool{ + $entry1Last = $entry1->getLastSector(); + $entry2Last = $entry2->getLastSector(); + + return ( + ($entry2->firstSector >= $entry1->firstSector and $entry2->firstSector <= $entry1Last) or + ($entry2Last >= $entry1->firstSector and $entry2Last <= $entry1Last) + ); + }; + return $overlapCheck($this, $other) or $overlapCheck($other, $this); + } } diff --git a/tests/phpunit/level/format/io/region/RegionLocationTableEntryTest.php b/tests/phpunit/level/format/io/region/RegionLocationTableEntryTest.php new file mode 100644 index 000000000..bf8550df4 --- /dev/null +++ b/tests/phpunit/level/format/io/region/RegionLocationTableEntryTest.php @@ -0,0 +1,52 @@ + + */ + public function overlapDataProvider() : \Generator{ + yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(2, 1, 0), true]; + yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(3, 1, 0), false]; + yield [new RegionLocationTableEntry(2, 2, 0), new RegionLocationTableEntry(3, 2, 0), true]; + yield [new RegionLocationTableEntry(2, 2, 0), new RegionLocationTableEntry(4, 2, 0), false]; + yield [new RegionLocationTableEntry(2, 2, 0), new RegionLocationTableEntry(2, 1, 0), true]; + yield [new RegionLocationTableEntry(2, 4, 0), new RegionLocationTableEntry(3, 1, 0), true]; + } + + /** + * @dataProvider overlapDataProvider + */ + public function testOverlap(RegionLocationTableEntry $entry1, RegionLocationTableEntry $entry2, bool $overlaps) : void{ + $stringify = function(RegionLocationTableEntry $entry) : string{ + return sprintf("entry first=%d last=%d size=%d", $entry->getFirstSector(), $entry->getLastSector(), $entry->getSectorCount()); + }; + self::assertSame($overlaps, $entry1->overlaps($entry2), $stringify($entry1) . " expected to " . ($overlaps ? "overlap" : "not overlap") . " with " . $stringify($entry2)); + self::assertSame($overlaps, $entry2->overlaps($entry1), $stringify($entry2) . " expected to " . ($overlaps ? "overlap" : "not overlap") . " with " . $stringify($entry1)); + } +}