Compare commits

..

24 Commits
3.5.5 ... 3.5.7

Author SHA1 Message Date
4b8e4123af Release 3.5.7 2019-01-22 22:13:35 +00:00
45a4252c26 RCON: Explicitly specify connection backlog size, fixes #2685
I believe this is caused by a bug in the linux kernel, since it only impacts certain machines I tested (one, to be specific). Whatever the case, setting a max backlog size is prudent anyway, and fixes the problem.
2019-01-22 22:11:32 +00:00
feaaa925a7 Fixed a series of denial-of-service bugs in RCON
Packets with a too-short payload would either cause the RCON thread to hang until the client disconnected, or crash the RCON thread entirely.

commit 90bb1894d7f87645b806f5fc67d1b877bb963180
Author: Dylan K. Taylor <odigiman@gmail.com>
Date:   Tue Jan 22 18:15:46 2019 +0000

    fix some bugs in RCON
2019-01-22 22:05:15 +00:00
5221db1178 Updated BinaryUtils dependency 2019-01-22 17:22:11 +00:00
47321114eb login: allow 60 seconds clock drift on login JWT
can everyone please just use NTP already?
2019-01-20 15:51:29 +00:00
a27c9409f1 ExperienceOrb: fixed wrong condition 2019-01-20 12:30:24 +00:00
854f851525 ExperienceOrb: don't follow players in other worlds 2019-01-20 12:30:09 +00:00
9003b38be3 Fixed a wtf in Player->removeWindow() 2019-01-19 19:14:21 +00:00
a6a93f822f 3.5.7 is next 2019-01-19 17:48:16 +00:00
d4851a8f1f Release 3.5.6 2019-01-19 17:33:07 +00:00
480a513f30 Sign: fixed asserts on loading NBT, closes #2636 2019-01-19 16:42:22 +00:00
4fd3bee360 Entity: Address fireticks crashdumps
This will now throw an exception at the source instead of crashing when the entity is saved, which should put the blame on the correct plugin responsible for this.
This also includes magic method hacks to preserve backwards compatibility, since the fireTicks field is now protected.
2019-01-19 16:05:10 +00:00
41fd7545e3 RegionLoader: Account for unexpected EOF when reading chunks, closes #2676 2019-01-19 15:49:19 +00:00
82dddde159 Remove dead code in /time command handler 2019-01-19 15:15:04 +00:00
bc709efb77 Prevent stupidity with /enchant 2019-01-19 15:14:51 +00:00
cd98e6a23e Updated PreProcessor submodule 2019-01-19 13:57:17 +00:00
cb591a98f4 NetworkBinaryStream: Skip item NBT which is too large when encoded
Encoded tags larger than 32KB overflow the length field, so we can't send these over network. However, it's unreasonable to randomly throw this burden off onto users by crashing their servers, so the next best solution is to just not send the NBT. This is also not an ideal solution (books and the like with too-large tags won't work on the client side) but it's better than crashing the server or client due to a protocol bug. Mojang have confirmed this will be resolved by a future MCPE release, so we'll just work around this problem until then.
2019-01-16 21:50:43 +00:00
e9d1af0aee Fixed stack trace encoding bug in CrashDump
it was encoding the real (full) trace, instead of emitting the printable version.
2019-01-16 18:56:56 +00:00
0f545c410a Bow: Improve consistency with vanilla (JE at least), closes #2660
This change causes the existing test I did to now report ~55-60 blocks, which is much closer to parity with vanilla.
2019-01-15 14:42:41 +00:00
1c2ed0836f Sync composer dependencies 2019-01-15 10:26:33 +00:00
6cf30dc813 build 7.3 on travis 2019-01-11 23:54:44 +00:00
f7d9247d39 Server: Permit NULL value entries in pocketmine.yml worlds
this permits listing worlds to load as keys without values.
2019-01-10 19:30:37 +00:00
3380aa3ac2 Config: Assert only whitespace precedes .properties key, fixes #commented properties not being skipped 2019-01-07 12:25:05 +00:00
6a9cad8fb7 3.5.6 is next 2019-01-06 20:18:32 +00:00
21 changed files with 172 additions and 74 deletions

View File

@ -2,13 +2,14 @@ language: php
php:
- 7.2
- 7.3
before_script:
# - pecl install channel://pecl.php.net/pthreads-3.1.6
- echo | pecl install channel://pecl.php.net/yaml-2.0.2
- echo | pecl install channel://pecl.php.net/yaml-2.0.4
- git clone https://github.com/pmmp/pthreads.git
- cd pthreads
- git checkout c8cfacda84f21032d6014b53e72bf345ac901dac
- git checkout 6ca019c58b4fa09ee2ff490f2444e34bef0773d0
- phpize
- ./configure
- make

26
composer.lock generated
View File

@ -92,16 +92,16 @@
},
{
"name": "pocketmine/binaryutils",
"version": "0.1.5",
"version": "0.1.8",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BinaryUtils.git",
"reference": "03361b0d78ef2b400a99e96406aa594a5bc1c4ed"
"reference": "33f511715d22418c03368b49b45a6c25d6b33806"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/03361b0d78ef2b400a99e96406aa594a5bc1c4ed",
"reference": "03361b0d78ef2b400a99e96406aa594a5bc1c4ed",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/33f511715d22418c03368b49b45a6c25d6b33806",
"reference": "33f511715d22418c03368b49b45a6c25d6b33806",
"shasum": ""
},
"require": {
@ -119,10 +119,10 @@
],
"description": "Classes and methods for conveniently handling binary data",
"support": {
"source": "https://github.com/pmmp/BinaryUtils/tree/0.1.5",
"source": "https://github.com/pmmp/BinaryUtils/tree/0.1.8",
"issues": "https://github.com/pmmp/BinaryUtils/issues"
},
"time": "2019-01-04T13:32:11+00:00"
"time": "2019-01-16T17:31:44+00:00"
},
{
"name": "pocketmine/math",
@ -160,16 +160,16 @@
},
{
"name": "pocketmine/nbt",
"version": "0.2.4",
"version": "0.2.5",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "05dddb51830fd8f3b6c93e553abe07643ec96fc5"
"reference": "0b290fa0f5b44835ebeea8146c9ac960cac833f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/05dddb51830fd8f3b6c93e553abe07643ec96fc5",
"reference": "05dddb51830fd8f3b6c93e553abe07643ec96fc5",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/0b290fa0f5b44835ebeea8146c9ac960cac833f5",
"reference": "0b290fa0f5b44835ebeea8146c9ac960cac833f5",
"shasum": ""
},
"require": {
@ -194,10 +194,10 @@
],
"description": "PHP library for working with Named Binary Tags",
"support": {
"source": "https://github.com/pmmp/NBT/tree/0.2.4",
"source": "https://github.com/pmmp/NBT/tree/0.2.5",
"issues": "https://github.com/pmmp/NBT/issues"
},
"time": "2019-01-04T15:28:44+00:00"
"time": "2019-01-07T17:28:16+00:00"
},
{
"name": "pocketmine/raklib",
@ -302,7 +302,7 @@
],
"description": "Standard library files required by PocketMine-MP and related projects",
"support": {
"source": "https://github.com/pmmp/SPL/tree/master"
"source": "https://github.com/pmmp/SPL/tree/0.3.2"
},
"time": "2018-08-12T15:17:39+00:00"
}

View File

@ -44,6 +44,7 @@ use function implode;
use function is_dir;
use function is_resource;
use function json_encode;
use function json_last_error_msg;
use function max;
use function mkdir;
use function ob_end_clean;
@ -146,7 +147,11 @@ class CrashDump{
$this->addLine("----------------------REPORT THE DATA BELOW THIS LINE-----------------------");
$this->addLine();
$this->addLine("===BEGIN CRASH DUMP===");
$this->encodedData = zlib_encode(json_encode($this->data, JSON_UNESCAPED_SLASHES), ZLIB_ENCODING_DEFLATE, 9);
$json = json_encode($this->data, JSON_UNESCAPED_SLASHES);
if($json === false){
throw new \RuntimeException("Failed to encode crashdump JSON: " . json_last_error_msg());
}
$this->encodedData = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9);
foreach(str_split(base64_encode($this->encodedData), 76) as $line){
$this->addLine($line);
}
@ -238,6 +243,9 @@ class CrashDump{
}
if(isset($lastError)){
if(isset($lastError["trace"])){
$lastError["trace"] = Utils::printableTrace($lastError["trace"]);
}
$this->data["lastError"] = $lastError;
}

View File

@ -3881,13 +3881,13 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{
* @param Inventory $inventory
* @param bool $force Forces removal of permanent windows such as normal inventory, cursor
*
* @throws \BadMethodCallException if trying to remove a fixed inventory window without the `force` parameter as true
* @throws \InvalidArgumentException if trying to remove a fixed inventory window without the `force` parameter as true
*/
public function removeWindow(Inventory $inventory, bool $force = false){
$id = $this->windows[$hash = spl_object_hash($inventory)] ?? null;
if($id !== null and !$force and isset($this->permanentWindows[$id])){
throw new \BadMethodCallException("Cannot remove fixed window $id (" . get_class($inventory) . ") from " . $this->getName());
throw new \InvalidArgumentException("Cannot remove fixed window $id (" . get_class($inventory) . ") from " . $this->getName());
}
$inventory->close($this);

View File

@ -37,7 +37,7 @@ namespace pocketmine {
use pocketmine\wizard\SetupWizard;
const NAME = "PocketMine-MP";
const BASE_VERSION = "3.5.5";
const BASE_VERSION = "3.5.7";
const IS_DEVELOPMENT_BUILD = false;
const BUILD_NUMBER = 0;

View File

@ -1731,7 +1731,9 @@ class Server{
GeneratorManager::registerDefaultGenerators();
foreach((array) $this->getProperty("worlds", []) as $name => $options){
if(!is_array($options)){
if($options === null){
$options = [];
}elseif(!is_array($options)){
continue;
}
if(!$this->loadLevel($name)){

View File

@ -67,7 +67,7 @@ class Water extends Liquid{
public function onEntityCollide(Entity $entity) : void{
$entity->resetFallDistance();
if($entity->fireTicks > 0){
if($entity->isOnFire()){
$entity->extinguish();
}

View File

@ -77,7 +77,15 @@ class EnchantCommand extends VanillaCommand{
return true;
}
$item->addEnchantment(new EnchantmentInstance($enchantment, (int) ($args[2] ?? 1)));
$level = 1;
if(isset($args[2])){
$level = $this->getBoundedInt($sender, $args[2], 1, $enchantment->getMaxLevel());
if($level === null){
return false;
}
}
$item->addEnchantment(new EnchantmentInstance($enchantment, $level));
$player->getInventory()->setItemInHand($item);

View File

@ -55,9 +55,7 @@ class TimeCommand extends VanillaCommand{
return true;
}
foreach($sender->getServer()->getLevels() as $level){
$level->checkTime();
$level->startTime();
$level->checkTime();
}
Command::broadcastCommandMessage($sender, "Restarted the time");
return true;
@ -68,9 +66,7 @@ class TimeCommand extends VanillaCommand{
return true;
}
foreach($sender->getServer()->getLevels() as $level){
$level->checkTime();
$level->stopTime();
$level->checkTime();
}
Command::broadcastCommandMessage($sender, "Stopped the time");
return true;
@ -110,9 +106,7 @@ class TimeCommand extends VanillaCommand{
}
foreach($sender->getServer()->getLevels() as $level){
$level->checkTime();
$level->setTime($value);
$level->checkTime();
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.time.set", [$value]));
}elseif($args[0] === "add"){
@ -124,9 +118,7 @@ class TimeCommand extends VanillaCommand{
$value = $this->getInteger($sender, $args[1], 0);
foreach($sender->getServer()->getLevels() as $level){
$level->checkTime();
$level->setTime($level->getTime() + $value);
$level->checkTime();
}
Command::broadcastCommandMessage($sender, new TranslationContainer("commands.time.added", [$value]));
}else{

View File

@ -477,7 +477,7 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
/** @var int */
public $lastUpdate;
/** @var int */
public $fireTicks = 0;
protected $fireTicks = 0;
/** @var CompoundTag */
public $namedtag;
/** @var bool */
@ -1093,8 +1093,8 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public function setOnFire(int $seconds) : void{
$ticks = $seconds * 20;
if($ticks > $this->fireTicks){
$this->fireTicks = $ticks;
if($ticks > $this->getFireTicks()){
$this->setFireTicks($ticks);
}
$this->setGenericFlag(self::DATA_FLAG_ONFIRE, true);
@ -1109,8 +1109,12 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
/**
* @param int $fireTicks
* @throws \InvalidArgumentException
*/
public function setFireTicks(int $fireTicks) : void{
if($fireTicks < 0 or $fireTicks > 0x7fff){
throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks");
}
$this->fireTicks = $fireTicks;
}
@ -2187,4 +2191,47 @@ abstract class Entity extends Location implements Metadatable, EntityIds{
public function __toString(){
return (new \ReflectionClass($this))->getShortName() . "(" . $this->getId() . ")";
}
/**
* TODO: remove this BC hack in 4.0
*
* @param string $name
*
* @return mixed
* @throws \ErrorException
*/
public function __get($name){
if($name === "fireTicks"){
return $this->fireTicks;
}
throw new \ErrorException("Undefined property: " . get_class($this) . "::\$" . $name);
}
/**
* TODO: remove this BC hack in 4.0
*
* @param string $name
* @param mixed $value
*
* @throws \ErrorException
* @throws \InvalidArgumentException
*/
public function __set($name, $value){
if($name === "fireTicks"){
$this->setFireTicks($value);
}else{
throw new \ErrorException("Undefined property: " . get_class($this) . "::\$" . $name);
}
}
/**
* TODO: remove this BC hack in 4.0
*
* @param string $name
*
* @return bool
*/
public function __isset($name){
return $name === "fireTicks";
}
}

View File

@ -147,7 +147,7 @@ class ExperienceOrb extends Entity{
return null;
}
$entity = $this->server->findEntity($this->targetPlayerRuntimeId);
$entity = $this->level->getEntity($this->targetPlayerRuntimeId);
if($entity instanceof Human){
return $entity;
}
@ -156,7 +156,7 @@ class ExperienceOrb extends Entity{
}
public function setTargetPlayer(?Human $player) : void{
$this->targetPlayerRuntimeId = $player ? $player->getId() : null;
$this->targetPlayerRuntimeId = $player !== null ? $player->getId() : null;
}
public function entityBaseTick(int $tickDiff = 1) : bool{

View File

@ -319,7 +319,7 @@ abstract class Projectile extends Entity{
$entityHit->attack($ev);
if($this->fireTicks > 0){
if($this->isOnFire()){
$ev = new EntityCombustByEntityEvent($this, $entityHit, 5);
$ev->call();
if(!$ev->isCancelled()){

View File

@ -63,10 +63,10 @@ class Bow extends Tool{
$diff = $player->getItemUseDuration();
$p = $diff / 20;
$force = min((($p ** 2) + $p * 2) / 3, 1) * 2;
$baseForce = min((($p ** 2) + $p * 2) / 3, 1);
$entity = Entity::createEntity("Arrow", $player->getLevel(), $nbt, $player, $force == 2);
$entity = Entity::createEntity("Arrow", $player->getLevel(), $nbt, $player, $baseForce >= 1);
if($entity instanceof Projectile){
$infinity = $this->hasEnchantment(Enchantment::INFINITY);
if($entity instanceof ArrowEntity){
@ -83,9 +83,9 @@ class Bow extends Tool{
if($this->hasEnchantment(Enchantment::FLAME)){
$entity->setOnFire(intdiv($entity->getFireTicks(), 20) + 100);
}
$ev = new EntityShootBowEvent($player, $this, $entity, $force);
$ev = new EntityShootBowEvent($player, $this, $entity, $baseForce * 3);
if($force < 0.1 or $diff < 5){
if($baseForce < 0.1 or $diff < 5){
$ev->setCancelled();
}

View File

@ -46,6 +46,7 @@ use function str_pad;
use function stream_set_read_buffer;
use function stream_set_write_buffer;
use function strlen;
use function substr;
use function time;
use function touch;
use function unpack;
@ -134,8 +135,11 @@ class RegionLoader{
}
fseek($this->filePointer, $this->locationTable[$index][0] << 12);
$length = Binary::readInt(fread($this->filePointer, 4));
$compression = ord(fgetc($this->filePointer));
$prefix = fread($this->filePointer, 4);
if($prefix === false or strlen($prefix) !== 4){
throw new CorruptedChunkException("Corrupted chunk header detected (unexpected end of file reading length prefix)");
}
$length = Binary::readInt($prefix);
if($length <= 0 or $length > self::MAX_SECTOR_LENGTH){ //Not yet generated / corrupted
if($length >= self::MAX_SECTOR_LENGTH){
@ -150,17 +154,19 @@ class RegionLoader{
MainLogger::getLogger()->error("Corrupted bigger chunk detected (bigger than number of sectors given in header)");
$this->locationTable[$index][1] = $length >> 12;
$this->writeLocationIndex($index);
}elseif($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
}
$chunkData = fread($this->filePointer, $length);
if($chunkData === false or strlen($chunkData) !== $length){
throw new CorruptedChunkException("Corrupted chunk detected (unexpected end of file reading chunk data)");
}
$compression = ord($chunkData[0]);
if($compression !== self::COMPRESSION_ZLIB and $compression !== self::COMPRESSION_GZIP){
throw new CorruptedChunkException("Invalid compression type (got $compression, expected " . self::COMPRESSION_ZLIB . " or " . self::COMPRESSION_GZIP . ")");
}
$chunkData = fread($this->filePointer, $length - 1);
if($chunkData === false){
throw new CorruptedChunkException("Corrupted chunk detected (failed to read chunk data from disk)");
}
return $chunkData;
return substr($chunkData, 1);
}
public function chunkExists(int $x, int $z) : bool{

View File

@ -113,7 +113,17 @@ class NetworkBinaryStream extends BinaryStream{
$nbt = $item->getCompoundTag();
$nbtLen = strlen($nbt);
if($nbtLen > 32767){
throw new \InvalidArgumentException("NBT encoded length must be < 32768, got $nbtLen bytes");
/*
* TODO: Workaround bug in the protocol (overflow)
* Encoded tags larger than 32KB overflow the length field, so we can't send these over network.
* However, it's unreasonable to randomly throw this burden off onto users by crashing their servers, so the
* next best solution is to just not send the NBT. This is also not an ideal solution (books and the like
* with too-large tags won't work on the client side) but it's better than crashing the server or client due
* to a protocol bug. Mojang have confirmed this will be resolved by a future MCPE release, so we'll just
* work around this problem until then.
*/
$nbt = "";
$nbtLen = 0;
}
$this->putLShort($nbtLen);

View File

@ -45,6 +45,8 @@ class VerifyLoginTask extends AsyncTask{
public const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
private const CLOCK_DRIFT_MAX = 60;
/** @var LoginPacket */
private $packet;
@ -144,11 +146,11 @@ class VerifyLoginTask extends AsyncTask{
$claims = json_decode(base64_decode(strtr($payloadB64, '-_', '+/'), true), true);
$time = time();
if(isset($claims["nbf"]) and $claims["nbf"] > $time){
if(isset($claims["nbf"]) and $claims["nbf"] > $time + self::CLOCK_DRIFT_MAX){
throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.tooEarly");
}
if(isset($claims["exp"]) and $claims["exp"] < $time){
if(isset($claims["exp"]) and $claims["exp"] < $time - self::CLOCK_DRIFT_MAX){
throw new VerifyLoginException("%pocketmine.disconnect.invalidSession.tooLate");
}

View File

@ -74,7 +74,7 @@ class RCON{
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if($this->socket === false or !@socket_bind($this->socket, $interface, $port) or !@socket_listen($this->socket)){
if($this->socket === false or !@socket_bind($this->socket, $interface, $port) or !@socket_listen($this->socket, 5)){
throw new \RuntimeException(trim(socket_strerror(socket_last_error())));
}

View File

@ -29,7 +29,6 @@ use pocketmine\utils\Binary;
use function count;
use function ltrim;
use function microtime;
use function rtrim;
use function socket_accept;
use function socket_close;
use function socket_getpeername;
@ -37,11 +36,14 @@ use function socket_last_error;
use function socket_read;
use function socket_select;
use function socket_set_block;
use function socket_set_nonblock;
use function socket_set_option;
use function socket_shutdown;
use function socket_strerror;
use function socket_write;
use function str_replace;
use function strlen;
use function substr;
use function trim;
use const PTHREADS_INHERIT_NONE;
use const SO_KEEPALIVE;
@ -103,24 +105,41 @@ class RCONInstance extends Thread{
private function readPacket($client, ?int &$requestID, ?int &$packetType, ?string &$payload){
$d = @socket_read($client, 4);
if($this->stop){
return false;
}elseif($d === false){
if(socket_last_error($client) === SOCKET_ECONNRESET){ //client crashed, terminate connection
return false;
socket_getpeername($client, $ip, $port);
if($d === false){
$err = socket_last_error($client);
if($err !== SOCKET_ECONNRESET){
$this->logger->debug("Connection error with $ip $port: " . trim(socket_strerror($err)));
}
return false;
}
if(strlen($d) !== 4){
if($d !== ""){ //empty data is returned on disconnection
$this->logger->debug("Truncated packet from $ip $port (want 4 bytes, have " . strlen($d) . "), disconnecting");
}
return null;
}elseif($d === "" or strlen($d) < 4){
return false;
}
$size = Binary::readLInt($d);
if($size < 0 or $size > 65535){
$this->logger->debug("Packet with too-large length header $size from $ip $port, disconnecting");
return false;
}
$requestID = Binary::readLInt(socket_read($client, 4));
$packetType = Binary::readLInt(socket_read($client, 4));
$payload = rtrim(socket_read($client, $size + 2)); //Strip two null bytes
$buf = @socket_read($client, $size);
if($buf === false){
$err = socket_last_error($client);
if($err !== SOCKET_ECONNRESET){
$this->logger->debug("Connection error with $ip $port: " . trim(socket_strerror($err)));
}
return false;
}
if(strlen($buf) !== $size){
$this->logger->debug("Truncated packet from $ip $port (want $size bytes, have " . strlen($buf) . "), disconnecting");
return false;
}
$requestID = Binary::readLInt(substr($buf, 0, 4));
$packetType = Binary::readLInt(substr($buf, 4, 4));
$payload = substr($buf, 8, -2); //Strip two null bytes
return true;
}
@ -157,7 +176,7 @@ class RCONInstance extends Thread{
if(count($clients) >= $this->maxClients){
@socket_close($client);
}else{
socket_set_block($client);
socket_set_nonblock($client);
socket_set_option($client, SOL_SOCKET, SO_KEEPALIVE, 1);
$id = $nextClientId++;
@ -233,11 +252,13 @@ class RCONInstance extends Thread{
}
private function disconnectClient($client) : void{
socket_getpeername($client, $ip, $port);
@socket_set_option($client, SOL_SOCKET, SO_LINGER, ["l_onoff" => 1, "l_linger" => 1]);
@socket_shutdown($client, 2);
@socket_set_block($client);
@socket_read($client, 1);
@socket_close($client);
$this->logger->info("Disconnected client: /$ip:$port");
}
public function getThreadName() : string{

View File

@ -31,8 +31,6 @@ use pocketmine\utils\TextFormat;
use function array_map;
use function array_pad;
use function array_slice;
use function assert;
use function count;
use function explode;
use function implode;
use function sprintf;
@ -41,13 +39,16 @@ class Sign extends Spawnable{
public const TAG_TEXT_BLOB = "Text";
public const TAG_TEXT_LINE = "Text%d"; //sprintf()able
private static function fixTextBlob(string $blob) : array{
return array_slice(array_pad(explode("\n", $blob), 4, ""), 0, 4);
}
/** @var string[] */
protected $text = ["", "", "", ""];
protected function readSaveData(CompoundTag $nbt) : void{
if($nbt->hasTag(self::TAG_TEXT_BLOB, StringTag::class)){ //MCPE 1.2 save format
$this->text = array_pad(explode("\n", $nbt->getString(self::TAG_TEXT_BLOB)), 4, "");
assert(count($this->text) === 4, "Too many lines!");
$this->text = self::fixTextBlob($nbt->getString(self::TAG_TEXT_BLOB));
}else{
for($i = 1; $i <= 4; ++$i){
$textKey = sprintf(self::TAG_TEXT_LINE, $i);
@ -138,7 +139,7 @@ class Sign extends Spawnable{
}
if($nbt->hasTag(self::TAG_TEXT_BLOB, StringTag::class)){
$lines = array_slice(array_pad(explode("\n", $nbt->getString(self::TAG_TEXT_BLOB)), 4, ""), 0, 4);
$lines = self::fixTextBlob($nbt->getString(self::TAG_TEXT_BLOB));
}else{
return false;
}

View File

@ -564,7 +564,7 @@ class Config{
* @param string $content
*/
private function parseProperties(string $content){
if(preg_match_all('/([a-zA-Z0-9\-_\.]+)[ \t]*=([^\r\n]*)/u', $content, $matches) > 0){ //false or 0 matches
if(preg_match_all('/^\s*([a-zA-Z0-9\-_\.]+)[ \t]*=([^\r\n]*)/um', $content, $matches) > 0){ //false or 0 matches
foreach($matches[1] as $i => $k){
$v = trim($matches[2][$i]);
switch(strtolower($v)){