Merge branch 'next-minor' into next-major

This commit is contained in:
Dylan K. Taylor 2022-12-24 17:23:30 +00:00
commit 2a81a421f3
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
8 changed files with 236 additions and 60 deletions

View File

@ -113,20 +113,12 @@ class MemoryManager{
if($m <= 0){
$defaultMemory = 0;
}else{
switch(mb_strtoupper($matches[2])){
case "K":
$defaultMemory = intdiv($m, 1024);
break;
case "M":
$defaultMemory = $m;
break;
case "G":
$defaultMemory = $m * 1024;
break;
default:
$defaultMemory = $m;
break;
}
$defaultMemory = match(mb_strtoupper($matches[2])){
"K" => intdiv($m, 1024),
"M" => $m,
"G" => $m * 1024,
default => $m,
};
}
}

View File

@ -49,10 +49,7 @@ use pocketmine\lang\KnownTranslationFactory;
use pocketmine\lang\Language;
use pocketmine\lang\LanguageNotFoundException;
use pocketmine\lang\Translatable;
use pocketmine\nbt\BigEndianNbtSerializer;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\CompressBatchTask;
use pocketmine\network\mcpe\compression\Compressor;
@ -72,9 +69,13 @@ use pocketmine\network\query\QueryInfo;
use pocketmine\network\upnp\UPnPNetworkInterface;
use pocketmine\permission\BanList;
use pocketmine\permission\DefaultPermissions;
use pocketmine\player\DatFilePlayerDataProvider;
use pocketmine\player\GameMode;
use pocketmine\player\OfflinePlayer;
use pocketmine\player\Player;
use pocketmine\player\PlayerDataLoadException;
use pocketmine\player\PlayerDataProvider;
use pocketmine\player\PlayerDataSaveException;
use pocketmine\player\PlayerInfo;
use pocketmine\plugin\PharPluginLoader;
use pocketmine\plugin\Plugin;
@ -161,12 +162,9 @@ use function time;
use function touch;
use function trim;
use function yaml_parse;
use function zlib_decode;
use function zlib_encode;
use const DIRECTORY_SEPARATOR;
use const PHP_EOL;
use const PHP_INT_MAX;
use const ZLIB_ENCODING_GZIP;
/**
* The class that manages everything
@ -251,6 +249,8 @@ class Server{
private string $dataPath;
private string $pluginPath;
private PlayerDataProvider $playerDataProvider;
/**
* @var string[]
* @phpstan-var array<string, string>
@ -481,49 +481,22 @@ class Server{
return $result;
}
private function getPlayerDataPath(string $username) : string{
return Path::join($this->getDataPath(), 'players', strtolower($username) . '.dat');
}
/**
* Returns whether the server has stored any saved data for this player.
*/
public function hasOfflinePlayerData(string $name) : bool{
return file_exists($this->getPlayerDataPath($name));
}
private function handleCorruptedPlayerData(string $name) : void{
$path = $this->getPlayerDataPath($name);
rename($path, $path . '.bak');
$this->logger->error($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_playerCorrupted($name)));
return $this->playerDataProvider->hasData($name);
}
public function getOfflinePlayerData(string $name) : ?CompoundTag{
return Timings::$syncPlayerDataLoad->time(function() use ($name) : ?CompoundTag{
$name = strtolower($name);
$path = $this->getPlayerDataPath($name);
if(file_exists($path)){
$contents = @file_get_contents($path);
if($contents === false){
throw new \RuntimeException("Failed to read player data file \"$path\" (permission denied?)");
}
$decompressed = @zlib_decode($contents);
if($decompressed === false){
$this->logger->debug("Failed to decompress raw player data for \"$name\"");
$this->handleCorruptedPlayerData($name);
return null;
}
try{
return (new BigEndianNbtSerializer())->read($decompressed)->mustGetCompoundTag();
}catch(NbtDataException $e){ //corrupt data
$this->logger->debug("Failed to decode NBT data for \"$name\": " . $e->getMessage());
$this->handleCorruptedPlayerData($name);
return null;
}
try{
return $this->playerDataProvider->loadData($name);
}catch(PlayerDataLoadException $e){
$this->logger->debug("Failed to load player data for $name: " . $e->getMessage());
$this->logger->error($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_playerCorrupted($name)));
return null;
}
return null;
});
}
@ -537,11 +510,9 @@ class Server{
if(!$ev->isCancelled()){
Timings::$syncPlayerDataSave->time(function() use ($name, $ev) : void{
$nbt = new BigEndianNbtSerializer();
$contents = Utils::assumeNotFalse(zlib_encode($nbt->write(new TreeRoot($ev->getSaveData())), ZLIB_ENCODING_GZIP), "zlib_encode() failed unexpectedly");
try{
Filesystem::safeFilePutContents($this->getPlayerDataPath($name), $contents);
}catch(\RuntimeException $e){
$this->playerDataProvider->saveData($name, $ev->getSaveData());
}catch(PlayerDataSaveException $e){
$this->logger->critical($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_data_saveError($name, $e->getMessage())));
$this->logger->logException($e);
}
@ -1004,6 +975,8 @@ class Server{
$this->queryInfo = new QueryInfo($this);
$this->playerDataProvider = new DatFilePlayerDataProvider(Path::join($this->dataPath, "players"));
register_shutdown_function([$this, "crashDump"]);
$loadErrorCount = 0;

View File

@ -154,7 +154,7 @@ final class EntityFactory{
}, ['Snowball', 'minecraft:snowball']);
$this->register(SplashPotion::class, function(World $world, CompoundTag $nbt) : SplashPotion{
$potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort("PotionId", PotionTypeIds::WATER));
$potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort(SplashPotion::TAG_POTION_ID, PotionTypeIds::WATER));
if($potionType === null){
throw new SavedDataLoadingException("No such potion type");
}

View File

@ -50,6 +50,8 @@ use function sqrt;
class SplashPotion extends Throwable{
public const TAG_POTION_ID = "PotionId"; //TAG_Short
public static function getNetworkTypeId() : string{ return EntityIds::SPLASH_POTION; }
protected bool $linger = false;
@ -64,7 +66,7 @@ class SplashPotion extends Throwable{
public function saveNBT() : CompoundTag{
$nbt = parent::saveNBT();
$nbt->setShort("PotionId", PotionTypeIdMap::getInstance()->toId($this->getPotionType()));
$nbt->setShort(self::TAG_POTION_ID, PotionTypeIdMap::getInstance()->toId($this->getPotionType()));
return $nbt;
}

View File

@ -0,0 +1,101 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\player;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\nbt\BigEndianNbtSerializer;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
use function file_exists;
use function file_get_contents;
use function rename;
use function strtolower;
use function zlib_decode;
use function zlib_encode;
use const ZLIB_ENCODING_GZIP;
/**
* Stores player data in a single .dat file per player. Each file is gzipped big-endian NBT.
*/
final class DatFilePlayerDataProvider implements PlayerDataProvider{
public function __construct(
private string $path
){}
private function getPlayerDataPath(string $username) : string{
return Path::join($this->path, strtolower($username) . '.dat');
}
private function handleCorruptedPlayerData(string $name) : void{
$path = $this->getPlayerDataPath($name);
rename($path, $path . '.bak');
}
public function hasData(string $name) : bool{
return file_exists($this->getPlayerDataPath($name));
}
public function loadData(string $name) : ?CompoundTag{
$name = strtolower($name);
$path = $this->getPlayerDataPath($name);
if(!file_exists($path)){
return null;
}
try{
$contents = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => file_get_contents($path));
}catch(\ErrorException $e){
throw new PlayerDataLoadException("Failed to read player data file \"$path\": " . $e->getMessage(), 0, $e);
}
try{
$decompressed = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => zlib_decode($contents));
}catch(\ErrorException $e){
$this->handleCorruptedPlayerData($name);
throw new PlayerDataLoadException("Failed to decompress raw player data for \"$name\": " . $e->getMessage(), 0, $e);
}
try{
return (new BigEndianNbtSerializer())->read($decompressed)->mustGetCompoundTag();
}catch(NbtDataException $e){ //corrupt data
$this->handleCorruptedPlayerData($name);
throw new PlayerDataLoadException("Failed to decode NBT data for \"$name\": " . $e->getMessage(), 0, $e);
}
}
public function saveData(string $name, CompoundTag $data) : void{
$nbt = new BigEndianNbtSerializer();
$contents = Utils::assumeNotFalse(zlib_encode($nbt->write(new TreeRoot($data)), ZLIB_ENCODING_GZIP), "zlib_encode() failed unexpectedly");
try{
Filesystem::safeFilePutContents($this->getPlayerDataPath($name), $contents);
}catch(\RuntimeException $e){
throw new PlayerDataSaveException("Failed to write player data file: " . $e->getMessage(), 0, $e);
}
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\player;
final class PlayerDataLoadException extends \RuntimeException{
}

View File

@ -0,0 +1,52 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\player;
use pocketmine\nbt\tag\CompoundTag;
/**
* Handles storage of player data. Implementations must treat player names in a case-insensitive manner.
*/
interface PlayerDataProvider{
/**
* Returns whether there are any data associated with the given player name.
*/
public function hasData(string $name) : bool;
/**
* Returns the data associated with the given player name, or null if there is no data.
* TODO: we need an async version of this
*
* @throws PlayerDataLoadException
*/
public function loadData(string $name) : ?CompoundTag;
/**
* Saves data for the give player name.
*
* @throws PlayerDataSaveException
*/
public function saveData(string $name, CompoundTag $data) : void;
}

View File

@ -0,0 +1,28 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\player;
final class PlayerDataSaveException extends \RuntimeException{
}