diff --git a/changelogs/5.34.md b/changelogs/5.34.md index f21e02e1c..eb2d728c1 100644 --- a/changelogs/5.34.md +++ b/changelogs/5.34.md @@ -102,3 +102,10 @@ Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if - `FastChunkSerializer` (used for transmitting chunks between threads) - GS4 Query - Auxiliary read-only data loading in the `pocketmine\data\bedrock` package + +# 5.34.1 +Released 26th September 2025. + +## Fixes +- Player login JSON processing no longer bails out on unexpected extra properties. A warning will now be logged instead (@dktapps). +- Fixed container drop issues when an ender crystal explosion causes another ender crystal nearby to explode (@dktapps, @kostamax27). diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 93e593133..a80ab6c5b 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,7 +31,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.34.1"; + public const BASE_VERSION = "5.34.2"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; diff --git a/src/network/mcpe/handler/LoginPacketHandler.php b/src/network/mcpe/handler/LoginPacketHandler.php index aa7c1da7a..0ab39420c 100644 --- a/src/network/mcpe/handler/LoginPacketHandler.php +++ b/src/network/mcpe/handler/LoginPacketHandler.php @@ -56,6 +56,7 @@ use function is_object; use function json_decode; use function md5; use function ord; +use function var_export; use const JSON_THROW_ON_ERROR; /** @@ -114,7 +115,7 @@ class LoginPacketHandler extends PacketHandler{ throw new PacketHandlingException("Unexpected type for self-signed certificate chain: " . gettype($chainData) . ", expected object"); } try{ - $chain = $this->defaultJsonMapper()->map($chainData, new LegacyAuthChain()); + $chain = $this->defaultJsonMapper("Self-signed auth chain JSON")->map($chainData, new LegacyAuthChain()); }catch(\JsonMapper_Exception $e){ throw PacketHandlingException::wrap($e, "Error mapping self-signed certificate chain"); } @@ -132,7 +133,7 @@ class LoginPacketHandler extends PacketHandler{ } try{ - $claims = $this->defaultJsonMapper()->map($claimsArray["extraData"], new LegacyAuthIdentityData()); + $claims = $this->defaultJsonMapper("Self-signed auth JWT 'extraData'")->map($claimsArray["extraData"], new LegacyAuthIdentityData()); }catch(\JsonMapper_Exception $e){ throw PacketHandlingException::wrap($e, "Error mapping self-signed certificate extraData"); } @@ -244,7 +245,7 @@ class LoginPacketHandler extends PacketHandler{ throw new PacketHandlingException("Unexpected type for auth info data: " . gettype($authInfoJson) . ", expected object"); } - $mapper = $this->defaultJsonMapper(); + $mapper = $this->defaultJsonMapper("Root authentication info JSON"); try{ $clientData = $mapper->map($authInfoJson, new AuthenticationInfo()); }catch(\JsonMapper_Exception $e){ @@ -258,7 +259,7 @@ class LoginPacketHandler extends PacketHandler{ * @throws PacketHandlingException */ protected function mapXboxTokenHeader(array $headerArray) : XboxAuthJwtHeader{ - $mapper = $this->defaultJsonMapper(); + $mapper = $this->defaultJsonMapper("OpenID JWT header"); try{ $header = $mapper->map($headerArray, new XboxAuthJwtHeader()); }catch(\JsonMapper_Exception $e){ @@ -272,7 +273,7 @@ class LoginPacketHandler extends PacketHandler{ * @throws PacketHandlingException */ protected function mapXboxTokenBody(array $bodyArray) : XboxAuthJwtBody{ - $mapper = $this->defaultJsonMapper(); + $mapper = $this->defaultJsonMapper("OpenID JWT body"); try{ $header = $mapper->map($bodyArray, new XboxAuthJwtBody()); }catch(\JsonMapper_Exception $e){ @@ -291,7 +292,7 @@ class LoginPacketHandler extends PacketHandler{ throw PacketHandlingException::wrap($e); } - $mapper = $this->defaultJsonMapper(); + $mapper = $this->defaultJsonMapper("ClientData JWT body"); try{ $clientData = $mapper->map($clientDataClaims, new ClientData()); }catch(\JsonMapper_Exception $e){ @@ -329,12 +330,21 @@ class LoginPacketHandler extends PacketHandler{ $this->server->getAsyncPool()->submitTask(new ProcessLegacyLoginTask($legacyCertificate, $clientDataJwt, rootAuthKeyDer: null, authRequired: $authRequired, onCompletion: $this->authCallback)); } - private function defaultJsonMapper() : \JsonMapper{ + private function defaultJsonMapper(string $logContext) : \JsonMapper{ $mapper = new \JsonMapper(); $mapper->bExceptionOnMissingData = true; - $mapper->bExceptionOnUndefinedProperty = true; + $mapper->undefinedPropertyHandler = $this->warnUndefinedJsonPropertyHandler($logContext); $mapper->bStrictObjectTypeChecking = true; $mapper->bEnforceMapType = false; return $mapper; } + + /** + * @phpstan-return \Closure(object, string, mixed) : void + */ + private function warnUndefinedJsonPropertyHandler(string $context) : \Closure{ + return fn(object $object, string $name, mixed $value) => $this->session->getLogger()->warning( + "$context: Unexpected JSON property for " . (new \ReflectionClass($object))->getShortName() . ": " . $name . " = " . var_export($value, return: true) + ); + } } diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index d98d8e9ad..c7f5f99cf 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -62,6 +62,20 @@ class ResourcePacksPacketHandler extends PacketHandler{ */ private const MAX_CONCURRENT_CHUNK_REQUESTS = 1; + /** + * All data/resource_packs/chemistry* packs need to be listed here to get chemistry blocks to render + * correctly, unfortunately there doesn't seem to be a better way to do this + */ + private const CHEMISTRY_RESOURCE_PACKS = [ + ["b41c2785-c512-4a49-af56-3a87afd47c57", "1.21.30"], + ["a4df0cb3-17be-4163-88d7-fcf7002b935d", "1.21.20"], + ["d19adffe-a2e1-4b02-8436-ca4583368c89", "1.21.10"], + ["85d5603d-2824-4b21-8044-34f441f4fce1", "1.21.0"], + ["e977cd13-0a11-4618-96fb-03dfe9c43608", "1.20.60"], + ["0674721c-a0aa-41a1-9ba8-1ed33ea3e7ed", "1.20.50"], + ["0fba4063-dba1-4281-9b89-ff9390653530", "1.0.0"], + ]; + /** * @var ResourcePack[] * @phpstan-var array @@ -200,8 +214,10 @@ class ResourcePacksPacketHandler extends PacketHandler{ return new ResourcePackStackEntry($pack->getPackId(), $pack->getPackVersion(), ""); //TODO: subpacks }, $this->resourcePackStack); - //we support chemistry blocks by default, the client should already have this installed - $stack[] = new ResourcePackStackEntry("0fba4063-dba1-4281-9b89-ff9390653530", "1.0.0", ""); + //we support chemistry blocks by default, the client should already have these installed + foreach(self::CHEMISTRY_RESOURCE_PACKS as [$uuid, $version]){ + $stack[] = new ResourcePackStackEntry($uuid, $version, ""); + } //we don't force here, because it doesn't have user-facing effects //but it does have an annoying side-effect when true: it makes diff --git a/src/utils/Timezone.php b/src/utils/Timezone.php index 6723b12eb..849daadc5 100644 --- a/src/utils/Timezone.php +++ b/src/utils/Timezone.php @@ -26,9 +26,11 @@ namespace pocketmine\utils; use function abs; use function date_default_timezone_set; use function date_parse; +use function escapeshellarg; use function exec; use function file_get_contents; -use function implode; +use function floor; +use function hexdec; use function ini_get; use function ini_set; use function is_array; @@ -37,6 +39,7 @@ use function json_decode; use function parse_ini_file; use function preg_match; use function readlink; +use function sprintf; use function str_contains; use function str_replace; use function str_starts_with; @@ -105,40 +108,67 @@ abstract class Timezone{ public static function detectSystemTimezone() : string|false{ switch(Utils::getOS()){ case Utils::OS_WINDOWS: - $regex = '/(UTC)(\+*\-*\d*\d*\:*\d*\d*)/'; + $keyPath = 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation'; /* - * wmic timezone get Caption - * Get the timezone offset + * Get the timezone offset through the registry * * Sample Output var_dump - * array(3) { - * [0] => - * string(7) "Caption" - * [1] => - * string(20) "(UTC+09:30) Adelaide" - * [2] => - * string(0) "" - * } + * array(13) { + * [0]=> + * string(0) "" + * [1]=> + * string(71) "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation" + * [2]=> + * string(35) " Bias REG_DWORD 0xfffffe20" + * [3]=> + * string(43) " DaylightBias REG_DWORD 0xffffffc4" + * [4]=> + * string(45) " DaylightName REG_SZ @tzres.dll,-571" + * [5]=> + * string(67) " DaylightStart REG_BINARY 00000000000000000000000000000000" + * [6]=> + * string(36) " StandardBias REG_DWORD 0x0" + * [7]=> + * string(45) " StandardName REG_SZ @tzres.dll,-572" + * [8]=> + * string(67) " StandardStart REG_BINARY 00000000000000000000000000000000" + * [9]=> + * string(52) " TimeZoneKeyName REG_SZ China Standard Time" + * [10]=> + * string(51) " DynamicDaylightTimeDisabled REG_DWORD 0x0" + * [11]=> + * string(45) " ActiveTimeBias REG_DWORD 0xfffffe20" + * [12]=> + * string(0) "" + * } */ - exec("wmic timezone get Caption", $output); + exec("reg query " . escapeshellarg($keyPath), $output); - $string = trim(implode("\n", $output)); + foreach($output as $line){ + if(preg_match('/ActiveTimeBias\s+REG_DWORD\s+0x([0-9a-fA-F]+)/', $line, $matches) > 0){ + $offsetMinutes = Binary::signInt((int) hexdec(trim($matches[1]))); - //Detect the Time Zone string - preg_match($regex, $string, $matches); + if($offsetMinutes === 0){ + return "UTC"; + } - if(!isset($matches[2])){ - return false; + $sign = $offsetMinutes <= 0 ? '+' : '-'; //windows timezone + and - are opposite + $absMinutes = abs($offsetMinutes); + $hours = floor($absMinutes / 60); + $minutes = $absMinutes % 60; + + $offset = sprintf( + "%s%02d:%02d", + $sign, + $hours, + $minutes + ); + + return self::parseOffset($offset); + } } - - $offset = $matches[2]; - - if($offset === ""){ - return "UTC"; - } - - return self::parseOffset($offset); + return false; case Utils::OS_LINUX: // Ubuntu / Debian. $data = @file_get_contents('/etc/timezone'); diff --git a/tests/phpstan/stubs/JsonMapper.stub b/tests/phpstan/stubs/JsonMapper.stub index e597a35ce..f129a3104 100644 --- a/tests/phpstan/stubs/JsonMapper.stub +++ b/tests/phpstan/stubs/JsonMapper.stub @@ -5,6 +5,9 @@ class JsonMapper_Exception extends \Exception{} class JsonMapper{ + /** @var ?\Closure(object, string, mixed) : void */ + public $undefinedPropertyHandler = null; + /** * @template TModel of object *