diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 1b1521cdc..cc6b4abe4 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -3366,11 +3366,6 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade return false; } - public function handleUnknown(UnknownPacket $packet) : bool{ - $this->server->getLogger()->debug("Received unknown packet from " . $this->getName() . ": 0x" . bin2hex($packet->buffer)); - return true; - } - /** * Called when a packet is received from the client. This method will call DataPacketReceiveEvent. * @@ -3390,7 +3385,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade } $this->server->getPluginManager()->callEvent($ev = new DataPacketReceiveEvent($this, $packet)); if(!$ev->isCancelled() and !$packet->handle($this)){ - $this->server->getLogger()->debug("Unhandled " . get_class($packet) . " received from " . $this->getName() . ": 0x" . bin2hex($packet->buffer)); + $this->server->getLogger()->debug("Unhandled " . $packet->getName() . " received from " . $this->getName() . ": 0x" . bin2hex($packet->buffer)); } $timings->stopTiming(); diff --git a/src/pocketmine/lang/locale b/src/pocketmine/lang/locale index b30ca5e3b..2ab4d9172 160000 --- a/src/pocketmine/lang/locale +++ b/src/pocketmine/lang/locale @@ -1 +1 @@ -Subproject commit b30ca5e3bdb65c446e22bf777d1dcb04d78b6f7d +Subproject commit 2ab4d9172eba05cb7c6b8c98639db2ba9e60472c diff --git a/src/pocketmine/level/format/io/region/CorruptedRegionException.php b/src/pocketmine/level/format/io/region/CorruptedRegionException.php new file mode 100644 index 000000000..7749a3f5e --- /dev/null +++ b/src/pocketmine/level/format/io/region/CorruptedRegionException.php @@ -0,0 +1,28 @@ +regions[$index = Level::chunkHash($x, $z)])){ $this->regions[$index] = new RegionLoader($this, $x, $z, static::REGION_FILE_EXTENSION); + try{ + $this->regions[$index]->open(); + }catch(CorruptedRegionException $e){ + $logger = $this->level->getServer()->getLogger(); + $logger->error("Corrupted region file detected: " . $e->getMessage()); + + $this->regions[$index]->close(false); //Do not write anything to the file + + $path = $this->regions[$index]->getFilePath(); + $backupPath = $path . ".bak." . time(); + rename($path, $backupPath); + $logger->error("Corrupted region file has been backed up to " . $backupPath); + + $this->regions[$index] = new RegionLoader($this, $x, $z, static::REGION_FILE_EXTENSION); + $this->regions[$index]->open(); //this will create a new empty region to replace the corrupted one + } } } diff --git a/src/pocketmine/level/format/io/region/RegionException.php b/src/pocketmine/level/format/io/region/RegionException.php new file mode 100644 index 000000000..d0238daac --- /dev/null +++ b/src/pocketmine/level/format/io/region/RegionException.php @@ -0,0 +1,28 @@ +z = $regionZ; $this->levelProvider = $level; $this->filePath = $this->levelProvider->getPath() . "region/r.$regionX.$regionZ.$fileExtension"; + } + + public function open(){ $exists = file_exists($this->filePath); if(!$exists){ touch($this->filePath); + }else{ + $fileSize = filesize($this->filePath); + if($fileSize > self::MAX_REGION_FILE_SIZE){ + throw new CorruptedRegionException("Corrupted oversized region file found, should be a maximum of " . self::MAX_REGION_FILE_SIZE . " bytes, got " . $fileSize . " bytes"); + }elseif($fileSize % 4096 !== 0){ + throw new CorruptedRegionException("Region file should be padded to a multiple of 4KiB"); + } } + $this->filePointer = fopen($this->filePath, "r+b"); stream_set_read_buffer($this->filePointer, 1024 * 16); //16KB stream_set_write_buffer($this->filePointer, 1024 * 16); //16KB @@ -170,9 +186,20 @@ class RegionLoader{ return $x + ($z << 5); } - public function close(){ - $this->writeLocationTable(); - fclose($this->filePointer); + /** + * Writes the region header and closes the file + * + * @param bool $writeHeader + */ + public function close(bool $writeHeader = true){ + if(is_resource($this->filePointer)){ + if($writeHeader){ + $this->writeLocationTable(); + } + + fclose($this->filePointer); + } + $this->levelProvider = null; } @@ -255,14 +282,34 @@ class RegionLoader{ fseek($this->filePointer, 0); $this->lastSector = 1; - $data = unpack("N*", fread($this->filePointer, 4 * 1024 * 2)); //1024 records * 4 bytes * 2 times + $headerRaw = fread($this->filePointer, self::REGION_HEADER_LENGTH); + if(($len = strlen($headerRaw)) !== self::REGION_HEADER_LENGTH){ + throw new CorruptedRegionException("Invalid region file header, expected " . self::REGION_HEADER_LENGTH . " bytes, got " . $len . " bytes"); + } + + $data = unpack("N*", $headerRaw); + $usedOffsets = []; for($i = 0; $i < 1024; ++$i){ $index = $data[$i + 1]; + $offset = $index >> 8; + if($offset !== 0){ + fseek($this->filePointer, ($offset << 12)); + if(fgetc($this->filePointer) === false){ //Try and read from the location + throw new CorruptedRegionException("Region file location offset points to invalid location"); + }elseif(isset($usedOffsets[$offset])){ + throw new CorruptedRegionException("Found two chunk offsets pointing to the same location"); + }else{ + $usedOffsets[$offset] = true; + } + } + $this->locationTable[$i] = [$index >> 8, $index & 0xff, $data[1024 + $i + 1]]; if(($this->locationTable[$i][0] + $this->locationTable[$i][1] - 1) > $this->lastSector){ $this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1] - 1; } } + + fseek($this->filePointer, 0); } private function writeLocationTable(){ @@ -300,4 +347,7 @@ class RegionLoader{ return $this->z; } + public function getFilePath() : string{ + return $this->filePath; + } } diff --git a/src/pocketmine/network/mcpe/NetworkSession.php b/src/pocketmine/network/mcpe/NetworkSession.php index 4eeed453d..b9d56098e 100644 --- a/src/pocketmine/network/mcpe/NetworkSession.php +++ b/src/pocketmine/network/mcpe/NetworkSession.php @@ -292,6 +292,4 @@ interface NetworkSession{ public function handleStopSound(StopSoundPacket $packet) : bool; public function handleSetTitle(SetTitlePacket $packet) : bool; - - public function handleUnknown(UnknownPacket $packet) : bool; } \ No newline at end of file diff --git a/src/pocketmine/network/mcpe/protocol/DataPacket.php b/src/pocketmine/network/mcpe/protocol/DataPacket.php index a320f5898..f530eb179 100644 --- a/src/pocketmine/network/mcpe/protocol/DataPacket.php +++ b/src/pocketmine/network/mcpe/protocol/DataPacket.php @@ -40,6 +40,10 @@ abstract class DataPacket extends BinaryStream{ return $this::NETWORK_ID; } + public function getName() : string{ + return (new \ReflectionClass($this))->getShortName(); + } + public function canBeBatched() : bool{ return true; } @@ -52,6 +56,16 @@ abstract class DataPacket extends BinaryStream{ abstract public function decode(); + /** + * Performs handling for this packet. Usually you'll want an appropriately named method in the NetworkSession for this. + * + * This method returns a bool to indicate whether the packet was handled or not. If the packet was unhandled, a debug message will be logged with a hexdump of the packet. + * Typically this method returns the return value of the handler in the supplied NetworkSession. See other packets for examples how to implement this. + * + * @param NetworkSession $session + * + * @return bool true if the packet was handled successfully, false if not. + */ abstract public function handle(NetworkSession $session) : bool; public function reset(){ diff --git a/src/pocketmine/network/mcpe/protocol/UnknownPacket.php b/src/pocketmine/network/mcpe/protocol/UnknownPacket.php index 87034a35e..ebedebe64 100644 --- a/src/pocketmine/network/mcpe/protocol/UnknownPacket.php +++ b/src/pocketmine/network/mcpe/protocol/UnknownPacket.php @@ -37,6 +37,10 @@ class UnknownPacket extends DataPacket{ return self::NETWORK_ID; } + public function getName() : string{ + return "unknown packet"; + } + public function decode(){ $this->offset -= 1; //Rewind one byte so we can read the PID $this->payload = $this->get(true); @@ -48,6 +52,6 @@ class UnknownPacket extends DataPacket{ } public function handle(NetworkSession $session) : bool{ - return $session->handleUnknown($this); + return false; } } \ No newline at end of file