From 4eb9dacd3cc219eeaff0c9cbda80683e3c02d9ba Mon Sep 17 00:00:00 2001 From: dohwi <78037515+dohwi@users.noreply.github.com> Date: Mon, 24 Jul 2023 20:16:56 +0900 Subject: [PATCH 1/3] Remove unnecessary HorizontalFacingTrait (#5930) FacingOppositePlacingPlayerTrait already includes HorizontalFacingTrait, so we don't need to include it twice. --- src/block/CarvedPumpkin.php | 2 -- src/block/ChemistryTable.php | 2 -- src/block/Chest.php | 2 -- src/block/EndPortalFrame.php | 2 -- src/block/EnderChest.php | 2 -- src/block/Furnace.php | 2 -- src/block/GlazedTerracotta.php | 2 -- src/block/Lectern.php | 2 -- src/block/Loom.php | 2 -- src/block/Stonecutter.php | 2 -- 10 files changed, 20 deletions(-) diff --git a/src/block/CarvedPumpkin.php b/src/block/CarvedPumpkin.php index 5fc73d088..98f3c2307 100644 --- a/src/block/CarvedPumpkin.php +++ b/src/block/CarvedPumpkin.php @@ -24,9 +24,7 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; class CarvedPumpkin extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; } diff --git a/src/block/ChemistryTable.php b/src/block/ChemistryTable.php index 27fb63674..058e40288 100644 --- a/src/block/ChemistryTable.php +++ b/src/block/ChemistryTable.php @@ -24,14 +24,12 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; final class ChemistryTable extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ //TODO diff --git a/src/block/Chest.php b/src/block/Chest.php index 45c190505..270c696c3 100644 --- a/src/block/Chest.php +++ b/src/block/Chest.php @@ -25,7 +25,6 @@ namespace pocketmine\block; use pocketmine\block\tile\Chest as TileChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\event\block\ChestPairEvent; use pocketmine\item\Item; @@ -36,7 +35,6 @@ use pocketmine\player\Player; class Chest extends Transparent{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; /** * @return AxisAlignedBB[] diff --git a/src/block/EndPortalFrame.php b/src/block/EndPortalFrame.php index 08c903f11..612cf3723 100644 --- a/src/block/EndPortalFrame.php +++ b/src/block/EndPortalFrame.php @@ -24,14 +24,12 @@ declare(strict_types=1); namespace pocketmine\block; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; class EndPortalFrame extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; protected bool $eye = false; diff --git a/src/block/EnderChest.php b/src/block/EnderChest.php index 68c2805f9..26596eac9 100644 --- a/src/block/EnderChest.php +++ b/src/block/EnderChest.php @@ -26,7 +26,6 @@ namespace pocketmine\block; use pocketmine\block\inventory\EnderChestInventory; use pocketmine\block\tile\EnderChest as TileEnderChest; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -36,7 +35,6 @@ use pocketmine\player\Player; class EnderChest extends Transparent{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function getLightLevel() : int{ return 7; diff --git a/src/block/Furnace.php b/src/block/Furnace.php index d943f8cc6..fbff73c93 100644 --- a/src/block/Furnace.php +++ b/src/block/Furnace.php @@ -25,7 +25,6 @@ namespace pocketmine\block; use pocketmine\block\tile\Furnace as TileFurnace; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\crafting\FurnaceType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -35,7 +34,6 @@ use function mt_rand; class Furnace extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; protected FurnaceType $furnaceType; diff --git a/src/block/GlazedTerracotta.php b/src/block/GlazedTerracotta.php index 5568703c2..b49347aef 100644 --- a/src/block/GlazedTerracotta.php +++ b/src/block/GlazedTerracotta.php @@ -26,12 +26,10 @@ namespace pocketmine\block; use pocketmine\block\utils\ColoredTrait; use pocketmine\block\utils\DyeColor; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; class GlazedTerracotta extends Opaque{ use ColoredTrait; use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ $this->color = DyeColor::BLACK(); diff --git a/src/block/Lectern.php b/src/block/Lectern.php index a80426acf..d9f07d22b 100644 --- a/src/block/Lectern.php +++ b/src/block/Lectern.php @@ -25,7 +25,6 @@ namespace pocketmine\block; use pocketmine\block\tile\Lectern as TileLectern; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\data\runtime\RuntimeDataDescriber; use pocketmine\item\Item; @@ -39,7 +38,6 @@ use function count; class Lectern extends Transparent{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; protected int $viewedPage = 0; protected ?WritableBookBase $book = null; diff --git a/src/block/Loom.php b/src/block/Loom.php index a10b57723..d3dd4f3c7 100644 --- a/src/block/Loom.php +++ b/src/block/Loom.php @@ -25,14 +25,12 @@ namespace pocketmine\block; use pocketmine\block\inventory\LoomInventory; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\player\Player; final class Loom extends Opaque{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ diff --git a/src/block/Stonecutter.php b/src/block/Stonecutter.php index 7736381e4..eb7dc68c2 100644 --- a/src/block/Stonecutter.php +++ b/src/block/Stonecutter.php @@ -25,7 +25,6 @@ namespace pocketmine\block; use pocketmine\block\inventory\StonecutterInventory; use pocketmine\block\utils\FacesOppositePlacingPlayerTrait; -use pocketmine\block\utils\HorizontalFacingTrait; use pocketmine\block\utils\SupportType; use pocketmine\item\Item; use pocketmine\math\AxisAlignedBB; @@ -35,7 +34,6 @@ use pocketmine\player\Player; class Stonecutter extends Transparent{ use FacesOppositePlacingPlayerTrait; - use HorizontalFacingTrait; public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{ if($player !== null){ From b078e01b65a9a45386ff23633d927362ea9c58a4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 24 Jul 2023 13:35:32 +0100 Subject: [PATCH 2/3] JwtUtils: handle DER <-> raw signature conversion in-house, drop fgrosse/phpasn1 dependency normally I would hesitate to reinvent the wheel, but we only need a tiny subset of the ASN.1 spec which is trivial to implement by itself. I'd rather this than depend on another library that could introduce security vulnerabilities (I'm looking at you, jsonmapper). closes #5935 --- composer.json | 1 - composer.lock | 78 +------------------- src/network/mcpe/JwtUtils.php | 131 ++++++++++++++++++++++------------ 3 files changed, 85 insertions(+), 125 deletions(-) diff --git a/composer.json b/composer.json index ea312ac14..608c88459 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,6 @@ "ext-zlib": ">=1.2.11", "composer-runtime-api": "^2.0", "adhocore/json-comment": "~1.2.0", - "fgrosse/phpasn1": "~2.5.0", "pocketmine/netresearch-jsonmapper": "~v4.2.1000", "pocketmine/bedrock-block-upgrade-schema": "~3.1.0+bedrock-1.20.10", "pocketmine/bedrock-data": "~2.4.0+bedrock-1.20.10", diff --git a/composer.lock b/composer.lock index 53807f312..2a4521d26 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": "74166e6c2f09b356c83a951efef349f2", + "content-hash": "fa059375785ed5b842af30c6b99c572f", "packages": [ { "name": "adhocore/json-comment", @@ -120,82 +120,6 @@ ], "time": "2023-01-15T23:15:59+00:00" }, - { - "name": "fgrosse/phpasn1", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/fgrosse/PHPASN1.git", - "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b", - "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "~2.0", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" - }, - "suggest": { - "ext-bcmath": "BCmath is the fallback extension for big integer calculations", - "ext-curl": "For loading OID information from the web if they have not bee defined statically", - "ext-gmp": "GMP is the preferred extension for big integer calculations", - "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "FG\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Friedrich Große", - "email": "friedrich.grosse@gmail.com", - "homepage": "https://github.com/FGrosse", - "role": "Author" - }, - { - "name": "All contributors", - "homepage": "https://github.com/FGrosse/PHPASN1/contributors" - } - ], - "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", - "homepage": "https://github.com/FGrosse/PHPASN1", - "keywords": [ - "DER", - "asn.1", - "asn1", - "ber", - "binary", - "decoding", - "encoding", - "x.509", - "x.690", - "x509", - "x690" - ], - "support": { - "issues": "https://github.com/fgrosse/PHPASN1/issues", - "source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0" - }, - "abandoned": true, - "time": "2022-12-19T11:08:26+00:00" - }, { "name": "pocketmine/bedrock-block-upgrade-schema", "version": "3.1.0", diff --git a/src/network/mcpe/JwtUtils.php b/src/network/mcpe/JwtUtils.php index a3cebbd73..259a602d4 100644 --- a/src/network/mcpe/JwtUtils.php +++ b/src/network/mcpe/JwtUtils.php @@ -23,28 +23,26 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; -use FG\ASN1\Exception\ParserException; -use FG\ASN1\Universal\Integer; -use FG\ASN1\Universal\Sequence; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\BinaryStream; use pocketmine\utils\Utils; use function base64_decode; use function base64_encode; +use function bin2hex; +use function chr; use function count; use function explode; -use function gmp_export; -use function gmp_import; -use function gmp_init; -use function gmp_strval; use function is_array; use function json_decode; use function json_encode; use function json_last_error_msg; +use function ltrim; use function openssl_error_string; use function openssl_pkey_get_details; use function openssl_pkey_get_public; use function openssl_sign; use function openssl_verify; +use function ord; use function preg_match; use function rtrim; use function sprintf; @@ -54,8 +52,7 @@ use function str_replace; use function str_split; use function strlen; use function strtr; -use const GMP_BIG_ENDIAN; -use const GMP_MSW_FIRST; +use function substr; use const JSON_THROW_ON_ERROR; use const OPENSSL_ALGO_SHA384; use const STR_PAD_LEFT; @@ -63,6 +60,12 @@ use const STR_PAD_LEFT; final class JwtUtils{ public const BEDROCK_SIGNING_KEY_CURVE_NAME = "secp384r1"; + private const ASN1_INTEGER_TAG = "\x02"; + private const ASN1_SEQUENCE_TAG = "\x30"; + + private const SIGNATURE_PART_LENGTH = 48; + private const SIGNATURE_ALGORITHM = OPENSSL_ALGO_SHA384; + /** * @return string[] * @phpstan-return array{string, string, string} @@ -98,30 +101,84 @@ final class JwtUtils{ return [$header, $body, $signature]; } + private static function signaturePartToAsn1(string $part) : string{ + if(strlen($part) !== self::SIGNATURE_PART_LENGTH){ + throw new JwtException("R and S for a SHA384 signature must each be exactly 48 bytes, but have " . strlen($part) . " bytes"); + } + $part = ltrim($part, "\x00"); + if(ord($part[0]) >= 128){ + //ASN.1 integers with a leading 1 bit are considered negative - add a leading 0 byte to prevent this + //ECDSA signature R and S values are always positive + $part = "\x00" . $part; + } + + //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed + return self::ASN1_INTEGER_TAG . chr(strlen($part)) . $part; + } + + private static function rawSignatureToDer(string $rawSignature) : string{ + if(strlen($rawSignature) !== self::SIGNATURE_PART_LENGTH * 2){ + throw new JwtException("JWT signature has unexpected length, expected 96, got " . strlen($rawSignature)); + } + + [$rString, $sString] = str_split($rawSignature, self::SIGNATURE_PART_LENGTH); + $sequence = self::signaturePartToAsn1($rString) . self::signaturePartToAsn1($sString); + + //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed + return self::ASN1_SEQUENCE_TAG . chr(strlen($sequence)) . $sequence; + } + + private static function signaturePartFromAsn1(BinaryStream $stream) : string{ + $prefix = $stream->get(1); + if($prefix !== self::ASN1_INTEGER_TAG){ + throw new \InvalidArgumentException("Expected an ASN.1 INTEGER tag, got " . bin2hex($prefix)); + } + //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed + $length = $stream->getByte(); + if($length > self::SIGNATURE_PART_LENGTH + 1){ //each part may have an extra leading 0 byte to prevent it being interpreted as a negative number + throw new \InvalidArgumentException("Expected at most 49 bytes for signature R or S, got $length"); + } + $part = $stream->get($length); + return str_pad(ltrim($part, "\x00"), self::SIGNATURE_PART_LENGTH, "\x00", STR_PAD_LEFT); + } + + private static function rawSignatureFromDer(string $derSignature) : string{ + if($derSignature[0] !== self::ASN1_SEQUENCE_TAG){ + throw new \InvalidArgumentException("Invalid DER signature, expected ASN.1 SEQUENCE tag, got " . bin2hex($derSignature[0])); + } + + //we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed + $length = ord($derSignature[1]); + $parts = substr($derSignature, 2, $length); + if(strlen($parts) !== $length){ + throw new \InvalidArgumentException("Invalid DER signature, expected $length sequence bytes, got " . strlen($parts)); + } + + $stream = new BinaryStream($parts); + $rRaw = self::signaturePartFromAsn1($stream); + $sRaw = self::signaturePartFromAsn1($stream); + + if(!$stream->feof()){ + throw new \InvalidArgumentException("Invalid DER signature, unexpected trailing sequence data"); + } + + return $rRaw . $sRaw; + } + /** * @throws JwtException */ public static function verify(string $jwt, \OpenSSLAsymmetricKey $signingKey) : bool{ [$header, $body, $signature] = self::split($jwt); - $plainSignature = self::b64UrlDecode($signature); - if(strlen($plainSignature) !== 96){ - throw new JwtException("JWT signature has unexpected length, expected 96, got " . strlen($plainSignature)); - } - - [$rString, $sString] = str_split($plainSignature, 48); - $convert = fn(string $str) => gmp_strval(gmp_import($str, 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST), 10); - - $sequence = new Sequence( - new Integer($convert($rString)), - new Integer($convert($sString)) - ); + $rawSignature = self::b64UrlDecode($signature); + $derSignature = self::rawSignatureToDer($rawSignature); $v = openssl_verify( $header . '.' . $body, - $sequence->getBinary(), + $derSignature, $signingKey, - OPENSSL_ALGO_SHA384 + self::SIGNATURE_ALGORITHM ); switch($v){ case 0: return false; @@ -140,33 +197,13 @@ final class JwtUtils{ openssl_sign( $jwtBody, - $rawDerSig, + $derSignature, $signingKey, - OPENSSL_ALGO_SHA384 + self::SIGNATURE_ALGORITHM ); - try{ - $asnObject = Sequence::fromBinary($rawDerSig); - }catch(ParserException $e){ - throw new AssumptionFailedError("Failed to parse OpenSSL signature: " . $e->getMessage(), 0, $e); - } - if(count($asnObject) !== 2){ - throw new AssumptionFailedError("OpenSSL produced invalid signature, expected exactly 2 parts"); - } - [$r, $s] = [$asnObject[0], $asnObject[1]]; - if(!($r instanceof Integer) || !($s instanceof Integer)){ - throw new AssumptionFailedError("OpenSSL produced invalid signature, expected 2 INTEGER parts"); - } - $rString = $r->getContent(); - $sString = $s->getContent(); - - $toBinary = fn($str) => str_pad( - gmp_export(gmp_init($str, 10), 1, GMP_BIG_ENDIAN | GMP_MSW_FIRST), - 48, - "\x00", - STR_PAD_LEFT - ); - $jwtSig = JwtUtils::b64UrlEncode($toBinary($rString) . $toBinary($sString)); + $rawSignature = self::rawSignatureFromDer($derSignature); + $jwtSig = self::b64UrlEncode($rawSignature); return "$jwtBody.$jwtSig"; } From 2a4909d328db8f7b71e47835f23f9072f6814c6b Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 24 Jul 2023 16:44:01 +0100 Subject: [PATCH 3/3] Fixed missing handling for some ContainerUIIds SMITHING_TABLE_TEMPLATE is new in 1.20 HORSE_EQUIP was always present, but somehow got overlooked when building up that big ugly switch table --- composer.json | 2 +- composer.lock | 14 +++++++------- .../handler/ItemStackContainerIdTranslator.php | 2 ++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index e3c8d3e1f..999ad29c9 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "pocketmine/bedrock-block-upgrade-schema": "~3.1.0+bedrock-1.20.10", "pocketmine/bedrock-data": "~2.4.0+bedrock-1.20.10", "pocketmine/bedrock-item-upgrade-schema": "~1.4.0+bedrock-1.20.10", - "pocketmine/bedrock-protocol": "~23.0.0+bedrock-1.20.10", + "pocketmine/bedrock-protocol": "~23.0.2+bedrock-1.20.10", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/classloader": "^0.2.0", diff --git a/composer.lock b/composer.lock index cae00386d..4c90391e4 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": "4524eed9bd1a33e650413fbf41382e20", + "content-hash": "2acf1299aa8b354c44495ae048aa8893", "packages": [ { "name": "adhocore/json-comment", @@ -276,16 +276,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "23.0.1+bedrock-1.20.10", + "version": "23.0.2+bedrock-1.20.10", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "db48400736799cc3833a2644a02e308992a98fa8" + "reference": "69a309a2dd7dcf3ec8c316385b866397e8c2cbfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/db48400736799cc3833a2644a02e308992a98fa8", - "reference": "db48400736799cc3833a2644a02e308992a98fa8", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/69a309a2dd7dcf3ec8c316385b866397e8c2cbfd", + "reference": "69a309a2dd7dcf3ec8c316385b866397e8c2cbfd", "shasum": "" }, "require": { @@ -317,9 +317,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/23.0.1+bedrock-1.20.10" + "source": "https://github.com/pmmp/BedrockProtocol/tree/23.0.2+bedrock-1.20.10" }, - "time": "2023-07-18T21:07:24+00:00" + "time": "2023-07-24T15:35:36+00:00" }, { "name": "pocketmine/binaryutils", diff --git a/src/network/mcpe/handler/ItemStackContainerIdTranslator.php b/src/network/mcpe/handler/ItemStackContainerIdTranslator.php index f9ea9ef5f..e9356dd73 100644 --- a/src/network/mcpe/handler/ItemStackContainerIdTranslator.php +++ b/src/network/mcpe/handler/ItemStackContainerIdTranslator.php @@ -70,6 +70,7 @@ final class ItemStackContainerIdTranslator{ ContainerUIIds::MATERIAL_REDUCER_OUTPUT, ContainerUIIds::SMITHING_TABLE_INPUT, ContainerUIIds::SMITHING_TABLE_MATERIAL, + ContainerUIIds::SMITHING_TABLE_TEMPLATE, ContainerUIIds::STONECUTTER_INPUT, ContainerUIIds::TRADE2_INGREDIENT1, ContainerUIIds::TRADE2_INGREDIENT2, @@ -84,6 +85,7 @@ final class ItemStackContainerIdTranslator{ ContainerUIIds::FURNACE_FUEL, ContainerUIIds::FURNACE_INGREDIENT, ContainerUIIds::FURNACE_RESULT, + ContainerUIIds::HORSE_EQUIP, ContainerUIIds::LEVEL_ENTITY, //chest ContainerUIIds::SHULKER_BOX, ContainerUIIds::SMOKER_INGREDIENT => [$currentWindowId, $slotId],