From 9c71f4fc1ce6ed1beeff9f55b8b209fa361983cb Mon Sep 17 00:00:00 2001
From: Dries C <15795262+dries-c@users.noreply.github.com>
Date: Wed, 18 Jun 2025 02:15:00 +0200
Subject: [PATCH] Assemble 1.21.90 (#6736)

---
 changelogs/5.29.md                            | 25 +++++++
 composer.json                                 |  4 +-
 composer.lock                                 | 26 +++----
 src/VersionInfo.php                           |  4 +-
 src/data/bedrock/item/ItemTypeNames.php       |  1 +
 .../mcpe/handler/LoginPacketHandler.php       | 71 +++++++++++++++++--
 .../mcpe/handler/PreSpawnPacketHandler.php    |  3 +-
 .../handler/ResourcePacksPacketHandler.php    |  3 +-
 src/world/format/io/data/BedrockWorldData.php |  4 +-
 9 files changed, 115 insertions(+), 26 deletions(-)
 create mode 100644 changelogs/5.29.md

diff --git a/changelogs/5.29.md b/changelogs/5.29.md
new file mode 100644
index 000000000..cb6e50da3
--- /dev/null
+++ b/changelogs/5.29.md
@@ -0,0 +1,25 @@
+# 5.29.0
+Released 18th June 2025.
+
+This is a support release for Minecraft: Bedrock Edition 1.21.90.
+
+**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
+Do not update plugin minimum API versions unless you need new features added in this release.
+
+**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
+Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
+
+## General
+- Added support for Minecraft: Bedrock Edition 1.21.90.
+- Removed support for earlier versions.
+
+## Fixes
+- Fixed thread crashes sometimes not reporting proper cause information in crashdumps.
+- Fixed crash when a plugin replaced a player's held tool with a different tool with a damage exceeding the old tool's max damage during an action.
+- Fixed performance issue of `PlayerAuthInputPacket` input flags handling (broken change detection).
+- Fixed `BaseInventory->addItem()` triggering updates on empty slots when no items were added.
+- Fixed slow check in `SubChunk` block layer garbage collection.
+
+## Internals
+- `LoginPacketHandler->processLogin()` signature has changed. This will break any plugins overriding `LoginPacketHandler`. As noted above, this is _not_ covered by the API version guarantee.
+- Automated branch sync for `minor-next` and `major-next` is now triggered by `repository_dispatch` from a cron job in this repository instead of `RestrictedActions`. The `RestrictedActions` cron job was getting automatically disabled by GitHub due to repo inactivity.
diff --git a/composer.json b/composer.json
index 1935bc290..1decaac39 100644
--- a/composer.json
+++ b/composer.json
@@ -34,9 +34,9 @@
       "adhocore/json-comment": "~1.2.0",
       "netresearch/jsonmapper": "~v5.0.0",
       "pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
-      "pocketmine/bedrock-data": "~5.0.0+bedrock-1.21.80",
+      "pocketmine/bedrock-data": "~5.1.0+bedrock-1.21.90",
       "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
-      "pocketmine/bedrock-protocol": "~38.1.0+bedrock-1.21.80",
+      "pocketmine/bedrock-protocol": "~39.0.0+bedrock-1.21.90",
       "pocketmine/binaryutils": "^0.2.1",
       "pocketmine/callback-validator": "^1.0.2",
       "pocketmine/color": "^0.3.0",
diff --git a/composer.lock b/composer.lock
index cb60c7ace..b6db8fe7d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "69921783f476a0704fa1f8924b901a89",
+    "content-hash": "bde74cbb65c043a2acf6f62b5b328e67",
     "packages": [
         {
             "name": "adhocore/json-comment",
@@ -204,16 +204,16 @@
         },
         {
             "name": "pocketmine/bedrock-data",
-            "version": "5.0.0+bedrock-1.21.80",
+            "version": "5.1.0+bedrock-1.21.90",
             "source": {
                 "type": "git",
                 "url": "https://github.com/pmmp/BedrockData.git",
-                "reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080"
+                "reference": "89ed34957aeccc63e517aa849af593adae958e98"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/e38d5ea19f794ec5216e5f96742237e8c4e7f080",
-                "reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080",
+                "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/89ed34957aeccc63e517aa849af593adae958e98",
+                "reference": "89ed34957aeccc63e517aa849af593adae958e98",
                 "shasum": ""
             },
             "type": "library",
@@ -224,9 +224,9 @@
             "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
             "support": {
                 "issues": "https://github.com/pmmp/BedrockData/issues",
-                "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.80"
+                "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.90"
             },
-            "time": "2025-05-09T14:15:18+00:00"
+            "time": "2025-06-17T23:44:21+00:00"
         },
         {
             "name": "pocketmine/bedrock-item-upgrade-schema",
@@ -256,16 +256,16 @@
         },
         {
             "name": "pocketmine/bedrock-protocol",
-            "version": "38.1.0+bedrock-1.21.80",
+            "version": "39.0.0+bedrock-1.21.90",
             "source": {
                 "type": "git",
                 "url": "https://github.com/pmmp/BedrockProtocol.git",
-                "reference": "a1fa215563517050045309bb779a67f75843b867"
+                "reference": "2b088183d12fc003523400867ee398e3893899ed"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a1fa215563517050045309bb779a67f75843b867",
-                "reference": "a1fa215563517050045309bb779a67f75843b867",
+                "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2b088183d12fc003523400867ee398e3893899ed",
+                "reference": "2b088183d12fc003523400867ee398e3893899ed",
                 "shasum": ""
             },
             "require": {
@@ -296,9 +296,9 @@
             "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
             "support": {
                 "issues": "https://github.com/pmmp/BedrockProtocol/issues",
-                "source": "https://github.com/pmmp/BedrockProtocol/tree/38.1.0+bedrock-1.21.80"
+                "source": "https://github.com/pmmp/BedrockProtocol/tree/39.0.0+bedrock-1.21.90"
             },
-            "time": "2025-05-28T22:19:59+00:00"
+            "time": "2025-06-17T23:46:38+00:00"
         },
         {
             "name": "pocketmine/binaryutils",
diff --git a/src/VersionInfo.php b/src/VersionInfo.php
index 615024656..7033c2707 100644
--- a/src/VersionInfo.php
+++ b/src/VersionInfo.php
@@ -31,8 +31,8 @@ use function str_repeat;
 
 final class VersionInfo{
 	public const NAME = "PocketMine-MP";
-	public const BASE_VERSION = "5.28.3";
-	public const IS_DEVELOPMENT_BUILD = true;
+	public const BASE_VERSION = "5.29.0";
+	public const IS_DEVELOPMENT_BUILD = false;
 	public const BUILD_CHANNEL = "stable";
 
 	/**
diff --git a/src/data/bedrock/item/ItemTypeNames.php b/src/data/bedrock/item/ItemTypeNames.php
index 5f86cde96..d2ab0996b 100644
--- a/src/data/bedrock/item/ItemTypeNames.php
+++ b/src/data/bedrock/item/ItemTypeNames.php
@@ -380,6 +380,7 @@ final class ItemTypeNames{
 	public const MUSIC_DISC_RELIC = "minecraft:music_disc_relic";
 	public const MUSIC_DISC_STAL = "minecraft:music_disc_stal";
 	public const MUSIC_DISC_STRAD = "minecraft:music_disc_strad";
+	public const MUSIC_DISC_TEARS = "minecraft:music_disc_tears";
 	public const MUSIC_DISC_WAIT = "minecraft:music_disc_wait";
 	public const MUSIC_DISC_WARD = "minecraft:music_disc_ward";
 	public const MUTTON = "minecraft:mutton";
diff --git a/src/network/mcpe/handler/LoginPacketHandler.php b/src/network/mcpe/handler/LoginPacketHandler.php
index c15753dad..5c467f2d4 100644
--- a/src/network/mcpe/handler/LoginPacketHandler.php
+++ b/src/network/mcpe/handler/LoginPacketHandler.php
@@ -33,6 +33,8 @@ use pocketmine\network\mcpe\JwtUtils;
 use pocketmine\network\mcpe\NetworkSession;
 use pocketmine\network\mcpe\protocol\LoginPacket;
 use pocketmine\network\mcpe\protocol\types\login\AuthenticationData;
+use pocketmine\network\mcpe\protocol\types\login\AuthenticationInfo;
+use pocketmine\network\mcpe\protocol\types\login\AuthenticationType;
 use pocketmine\network\mcpe\protocol\types\login\ClientData;
 use pocketmine\network\mcpe\protocol\types\login\ClientDataToSkinDataHelper;
 use pocketmine\network\mcpe\protocol\types\login\JwtChain;
@@ -42,7 +44,11 @@ use pocketmine\player\PlayerInfo;
 use pocketmine\player\XboxLivePlayerInfo;
 use pocketmine\Server;
 use Ramsey\Uuid\Uuid;
+use function gettype;
 use function is_array;
+use function is_object;
+use function json_decode;
+use const JSON_THROW_ON_ERROR;
 
 /**
  * Handles the initial login phase of the session. This handler is used as the initial state.
@@ -60,7 +66,9 @@ class LoginPacketHandler extends PacketHandler{
 	){}
 
 	public function handleLogin(LoginPacket $packet) : bool{
-		$extraData = $this->fetchAuthData($packet->chainDataJwt);
+		$authInfo = $this->parseAuthInfo($packet->authInfoJson);
+		$jwtChain = $this->parseJwtChain($authInfo->Certificate);
+		$extraData = $this->fetchAuthData($jwtChain);
 
 		if(!Player::isValidUserName($extraData->displayName)){
 			$this->session->disconnectWithError(KnownTranslationFactory::disconnectionScreen_invalidName());
@@ -139,11 +147,61 @@ class LoginPacketHandler extends PacketHandler{
 			return true;
 		}
 
-		$this->processLogin($packet, $ev->isAuthRequired());
+		$this->processLogin($authInfo->Token, AuthenticationType::from($authInfo->AuthenticationType), $jwtChain->chain, $packet->clientDataJwt, $ev->isAuthRequired());
 
 		return true;
 	}
 
+	/**
+	 * @throws PacketHandlingException
+	 */
+	protected function parseAuthInfo(string $authInfo) : AuthenticationInfo{
+		try{
+			$authInfoJson = json_decode($authInfo, associative: false, flags: JSON_THROW_ON_ERROR);
+		}catch(\JsonException $e){
+			throw PacketHandlingException::wrap($e);
+		}
+		if(!is_object($authInfoJson)){
+			throw new \RuntimeException("Unexpected type for auth info data: " . gettype($authInfoJson) . ", expected object");
+		}
+
+		$mapper = new \JsonMapper();
+		$mapper->bExceptionOnMissingData = true;
+		$mapper->bExceptionOnUndefinedProperty = true;
+		$mapper->bStrictObjectTypeChecking = true;
+		try{
+			$clientData = $mapper->map($authInfoJson, new AuthenticationInfo());
+		}catch(\JsonMapper_Exception $e){
+			throw PacketHandlingException::wrap($e);
+		}
+		return $clientData;
+	}
+
+	/**
+	 * @throws PacketHandlingException
+	 */
+	protected function parseJwtChain(string $chainDataJwt) : JwtChain{
+		try{
+			$jwtChainJson = json_decode($chainDataJwt, associative: false, flags: JSON_THROW_ON_ERROR);
+		}catch(\JsonException $e){
+			throw PacketHandlingException::wrap($e);
+		}
+		if(!is_object($jwtChainJson)){
+			throw new \RuntimeException("Unexpected type for JWT chain data: " . gettype($jwtChainJson) . ", expected object");
+		}
+
+		$mapper = new \JsonMapper();
+		$mapper->bExceptionOnMissingData = true;
+		$mapper->bExceptionOnUndefinedProperty = true;
+		$mapper->bStrictObjectTypeChecking = true;
+		try{
+			$clientData = $mapper->map($jwtChainJson, new JwtChain());
+		}catch(\JsonMapper_Exception $e){
+			throw PacketHandlingException::wrap($e);
+		}
+		return $clientData;
+	}
+
 	/**
 	 * @throws PacketHandlingException
 	 */
@@ -211,10 +269,15 @@ class LoginPacketHandler extends PacketHandler{
 	 * TODO: This is separated for the purposes of allowing plugins (like Specter) to hack it and bypass authentication.
 	 * In the future this won't be necessary.
 	 *
+	 * @param null|string[] $legacyCertificate
+	 *
 	 * @throws \InvalidArgumentException
 	 */
-	protected function processLogin(LoginPacket $packet, bool $authRequired) : void{
-		$this->server->getAsyncPool()->submitTask(new ProcessLoginTask($packet->chainDataJwt->chain, $packet->clientDataJwt, $authRequired, $this->authCallback));
+	protected function processLogin(string $token, AuthenticationType $authType, ?array $legacyCertificate, string $clientData, bool $authRequired) : void{
+		if($legacyCertificate === null){
+			throw new PacketHandlingException("Legacy certificate cannot be null");
+		}
+		$this->server->getAsyncPool()->submitTask(new ProcessLoginTask($legacyCertificate, $clientData, $authRequired, $this->authCallback));
 		$this->session->setHandler(null); //drop packets received during login verification
 	}
 }
diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php
index 9aa302c0c..e528a5201 100644
--- a/src/network/mcpe/handler/PreSpawnPacketHandler.php
+++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php
@@ -40,7 +40,6 @@ use pocketmine\network\mcpe\protocol\types\Experiments;
 use pocketmine\network\mcpe\protocol\types\LevelSettings;
 use pocketmine\network\mcpe\protocol\types\NetworkPermissions;
 use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings;
-use pocketmine\network\mcpe\protocol\types\ServerAuthMovementMode;
 use pocketmine\network\mcpe\protocol\types\SpawnSettings;
 use pocketmine\player\Player;
 use pocketmine\Server;
@@ -99,7 +98,7 @@ class PreSpawnPacketHandler extends PacketHandler{
 				$this->server->getMotd(),
 				"",
 				false,
-				new PlayerMovementSettings(ServerAuthMovementMode::SERVER_AUTHORITATIVE_V2, 0, false),
+				new PlayerMovementSettings(0, false),
 				0,
 				0,
 				"",
diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php
index a1df394da..a9ffae6f7 100644
--- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php
+++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php
@@ -120,7 +120,8 @@ class ResourcePacksPacketHandler extends PacketHandler{
 			hasAddons: false,
 			hasScripts: false,
 			worldTemplateId: Uuid::fromString(Uuid::NIL),
-			worldTemplateVersion: ""
+			worldTemplateVersion: "",
+			forceDisableVibrantVisuals: true,
 		));
 		$this->session->getLogger()->debug("Waiting for client to accept resource packs");
 	}
diff --git a/src/world/format/io/data/BedrockWorldData.php b/src/world/format/io/data/BedrockWorldData.php
index d39c17a47..a971920ec 100644
--- a/src/world/format/io/data/BedrockWorldData.php
+++ b/src/world/format/io/data/BedrockWorldData.php
@@ -51,11 +51,11 @@ use function time;
 class BedrockWorldData extends BaseNbtWorldData{
 
 	public const CURRENT_STORAGE_VERSION = 10;
-	public const CURRENT_STORAGE_NETWORK_VERSION = 800;
+	public const CURRENT_STORAGE_NETWORK_VERSION = 818;
 	public const CURRENT_CLIENT_VERSION_TARGET = [
 		1, //major
 		21, //minor
-		80, //patch
+		90, //patch
 		3, //revision
 		0 //is beta
 	];