PocketMine-MP 5.35.1 git-e32e836dad793a3a3c8ddd8927c00e112b1e576a
Loading...
Searching...
No Matches
AvailableCommandsPacket.php
1<?php
2
3/*
4 * This file is part of BedrockProtocol.
5 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
6 *
7 * BedrockProtocol is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 */
12
13declare(strict_types=1);
14
15namespace pocketmine\network\mcpe\protocol;
16
17use pmmp\encoding\Byte;
18use pmmp\encoding\ByteBufferReader;
19use pmmp\encoding\ByteBufferWriter;
20use pmmp\encoding\DataDecodeException;
21use pmmp\encoding\LE;
22use pmmp\encoding\VarInt;
32use function array_search;
33use function count;
34use function dechex;
35
37 public const NETWORK_ID = ProtocolInfo::AVAILABLE_COMMANDS_PACKET;
38
43 public const ARG_FLAG_VALID = 0x100000;
44
49 public const ARG_TYPE_INT = ArgTypes::INT;
50 public const ARG_TYPE_FLOAT = ArgTypes::VAL;
51 public const ARG_TYPE_VALUE = ArgTypes::RVAL;
52 public const ARG_TYPE_WILDCARD_INT = ArgTypes::WILDCARDINT;
53 public const ARG_TYPE_OPERATOR = ArgTypes::OPERATOR;
54 public const ARG_TYPE_COMPARE_OPERATOR = ArgTypes::COMPAREOPERATOR;
55 public const ARG_TYPE_TARGET = ArgTypes::SELECTION;
56
57 public const ARG_TYPE_WILDCARD_TARGET = ArgTypes::WILDCARDSELECTION;
58
59 public const ARG_TYPE_FILEPATH = ArgTypes::PATHCOMMAND;
60
61 public const ARG_TYPE_FULL_INTEGER_RANGE = ArgTypes::FULLINTEGERRANGE;
62
63 public const ARG_TYPE_EQUIPMENT_SLOT = ArgTypes::EQUIPMENTSLOTENUM;
64 public const ARG_TYPE_STRING = ArgTypes::ID;
65
66 public const ARG_TYPE_INT_POSITION = ArgTypes::POSITION;
67 public const ARG_TYPE_POSITION = ArgTypes::POSITION_FLOAT;
68
69 public const ARG_TYPE_MESSAGE = ArgTypes::MESSAGE_ROOT;
70
71 public const ARG_TYPE_RAWTEXT = ArgTypes::RAWTEXT;
72
73 public const ARG_TYPE_JSON = ArgTypes::JSON_OBJECT;
74
75 public const ARG_TYPE_BLOCK_STATES = ArgTypes::BLOCK_STATE_ARRAY;
76
77 public const ARG_TYPE_COMMAND = ArgTypes::CODEBUILDERARGS;
78
83 public const ARG_FLAG_ENUM = 0x200000;
84
86 public const ARG_FLAG_POSTFIX = 0x1000000;
87
88 public const ARG_FLAG_SOFT_ENUM = 0x4000000;
89
90 public const HARDCODED_ENUM_NAMES = [
91 "CommandName" => true
92 ];
93
98 public array $commandData = [];
99
105 public array $hardcodedEnums = [];
106
112 public array $softEnums = [];
113
118 public array $enumConstraints = [];
119
127 public static function create(array $commandData, array $hardcodedEnums, array $softEnums, array $enumConstraints) : self{
128 $result = new self;
129 $result->commandData = $commandData;
130 $result->hardcodedEnums = $hardcodedEnums;
131 $result->softEnums = $softEnums;
132 $result->enumConstraints = $enumConstraints;
133 return $result;
134 }
135
136 protected function decodePayload(ByteBufferReader $in) : void{
138 $enumValues = [];
139 for($i = 0, $enumValuesCount = VarInt::readUnsignedInt($in); $i < $enumValuesCount; ++$i){
140 $enumValues[] = CommonTypes::getString($in);
141 }
142
144 $chainedSubcommandValueNames = [];
145 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
146 $chainedSubcommandValueNames[] = CommonTypes::getString($in);
147 }
148
150 $postfixes = [];
151 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
152 $postfixes[] = CommonTypes::getString($in);
153 }
154
156 $enums = [];
157 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
158 $enums[] = $enum = $this->getEnum($enumValues, $in);
159 //TODO: Bedrock may provide some enums which are not referenced by any command, and can't reasonably be
160 //considered "hardcoded". This happens with various Edu command enums, and other enums which are probably
161 //intended to be used by commands which aren't present in public releases.
162 //We should probably store these somewhere, since we'll need them to be able to correctly re-encode the
163 //packet for testing.
164 if(isset(self::HARDCODED_ENUM_NAMES[$enum->getName()])){
165 $this->hardcodedEnums[] = $enum;
166 }
167 }
168
169 $chainedSubCommandData = [];
170 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
171 $name = CommonTypes::getString($in);
172 $values = [];
173 for($j = 0, $valueCount = VarInt::readUnsignedInt($in); $j < $valueCount; ++$j){
174 $valueName = $chainedSubcommandValueNames[LE::readUnsignedShort($in)];
175 $valueType = LE::readUnsignedShort($in);
176 $values[] = new ChainedSubCommandValue($valueName, $valueType);
177 }
178 $chainedSubCommandData[] = new ChainedSubCommandData($name, $values);
179 }
180
181 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
182 $this->commandData[] = $this->getCommandData($enums, $postfixes, $chainedSubCommandData, $in);
183 }
184
185 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
186 $this->softEnums[] = $this->getSoftEnum($in);
187 }
188
189 $this->initSoftEnumsInCommandData();
190
191 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
192 $this->enumConstraints[] = $this->getEnumConstraint($enums, $enumValues, $in);
193 }
194 }
195
200 protected function initSoftEnumsInCommandData() : void{
201 foreach($this->commandData as $datum){
202 foreach($datum->getOverloads() as $overload){
203 foreach($overload->getParameters() as $parameter){
204 if(($parameter->paramType & self::ARG_FLAG_SOFT_ENUM) !== 0){
205 $index = $parameter->paramType & 0xffff;
206 $parameter->enum = $this->softEnums[$index] ?? null;
207 if($parameter->enum === null){
208 throw new PacketDecodeException("deserializing $datum->name parameter $parameter->paramName: expected soft enum at $index, but got none");
209 }
210 }
211 }
212 }
213 }
214 }
215
222 protected function getEnum(array $enumValueList, ByteBufferReader $in) : CommandEnum{
223 $enumName = CommonTypes::getString($in);
224 $enumValues = [];
225
226 $listSize = count($enumValueList);
227
228 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
229 $index = $this->getEnumValueIndex($listSize, $in);
230 if(!isset($enumValueList[$index])){
231 throw new PacketDecodeException("Invalid enum value index $index");
232 }
233 //Get the enum value from the initial pile of mess
234 $enumValues[] = $enumValueList[$index];
235 }
236
237 return new CommandEnum($enumName, $enumValues);
238 }
239
243 protected function getSoftEnum(ByteBufferReader $in) : CommandEnum{
244 $enumName = CommonTypes::getString($in);
245 $enumValues = [];
246
247 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
248 //Get the enum value from the initial pile of mess
249 $enumValues[] = CommonTypes::getString($in);
250 }
251
252 return new CommandEnum($enumName, $enumValues, true);
253 }
254
258 protected function putEnum(CommandEnum $enum, array $enumValueMap, ByteBufferWriter $out) : void{
259 CommonTypes::putString($out, $enum->getName());
260
261 $values = $enum->getValues();
262 VarInt::writeUnsignedInt($out, count($values));
263 $listSize = count($enumValueMap);
264 foreach($values as $value){
265 if(!isset($enumValueMap[$value])){
266 throw new \LogicException("Enum value '$value' doesn't have a value index");
267 }
268 $this->putEnumValueIndex($enumValueMap[$value], $listSize, $out);
269 }
270 }
271
272 protected function putSoftEnum(CommandEnum $enum, ByteBufferWriter $out) : void{
273 CommonTypes::putString($out, $enum->getName());
274
275 $values = $enum->getValues();
276 VarInt::writeUnsignedInt($out, count($values));
277 foreach($values as $value){
278 CommonTypes::putString($out, $value);
279 }
280 }
281
285 protected function getEnumValueIndex(int $valueCount, ByteBufferReader $in) : int{
286 if($valueCount < 256){
287 return Byte::readUnsigned($in);
288 }elseif($valueCount < 65536){
289 return LE::readUnsignedShort($in);
290 }else{
291 return LE::readUnsignedInt($in);
292 }
293 }
294
295 protected function putEnumValueIndex(int $index, int $valueCount, ByteBufferWriter $out) : void{
296 if($valueCount < 256){
297 Byte::writeUnsigned($out, $index);
298 }elseif($valueCount < 65536){
299 LE::writeUnsignedShort($out, $index);
300 }else{
301 LE::writeUnsignedInt($out, $index);
302 }
303 }
304
312 protected function getEnumConstraint(array $enums, array $enumValues, ByteBufferReader $in) : CommandEnumConstraint{
313 //wtf, what was wrong with an offset inside the enum? :(
314 $valueIndex = LE::readUnsignedInt($in);
315 if(!isset($enumValues[$valueIndex])){
316 throw new PacketDecodeException("Enum constraint refers to unknown enum value index $valueIndex");
317 }
318 $enumIndex = LE::readUnsignedInt($in);
319 if(!isset($enums[$enumIndex])){
320 throw new PacketDecodeException("Enum constraint refers to unknown enum index $enumIndex");
321 }
322 $enum = $enums[$enumIndex];
323 $valueOffset = array_search($enumValues[$valueIndex], $enum->getValues(), true);
324 if($valueOffset === false){
325 throw new PacketDecodeException("Value \"" . $enumValues[$valueIndex] . "\" does not belong to enum \"" . $enum->getName() . "\"");
326 }
327
328 $constraintIds = [];
329 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
330 $constraintIds[] = Byte::readUnsigned($in);
331 }
332
333 return new CommandEnumConstraint($enum, $valueOffset, $constraintIds);
334 }
335
340 protected function putEnumConstraint(CommandEnumConstraint $constraint, array $enumIndexes, array $enumValueIndexes, ByteBufferWriter $out) : void{
341 LE::writeUnsignedInt($out, $enumValueIndexes[$constraint->getAffectedValue()]);
342 LE::writeUnsignedInt($out, $enumIndexes[$constraint->getEnum()->getName()]);
343 VarInt::writeUnsignedInt($out, count($constraint->getConstraints()));
344 foreach($constraint->getConstraints() as $v){
345 Byte::writeUnsigned($out, $v);
346 }
347 }
348
357 protected function getCommandData(array $enums, array $postfixes, array $allChainedSubCommandData, ByteBufferReader $in) : CommandData{
358 $name = CommonTypes::getString($in);
359 $description = CommonTypes::getString($in);
360 $flags = LE::readUnsignedShort($in);
361 $permission = Byte::readUnsigned($in);
362 $aliases = $enums[LE::readSignedInt($in)] ?? null;
363
364 $chainedSubCommandData = [];
365 for($i = 0, $count = VarInt::readUnsignedInt($in); $i < $count; ++$i){
366 $index = LE::readUnsignedShort($in);
367 $chainedSubCommandData[] = $allChainedSubCommandData[$index] ?? throw new PacketDecodeException("Unknown chained subcommand data index $index");
368 }
369 $overloads = [];
370
371 for($overloadIndex = 0, $overloadCount = VarInt::readUnsignedInt($in); $overloadIndex < $overloadCount; ++$overloadIndex){
372 $parameters = [];
373 $isChaining = CommonTypes::getBool($in);
374 for($paramIndex = 0, $paramCount = VarInt::readUnsignedInt($in); $paramIndex < $paramCount; ++$paramIndex){
375 $parameter = new CommandParameter();
376 $parameter->paramName = CommonTypes::getString($in);
377 $parameter->paramType = LE::readUnsignedInt($in);
378 $parameter->isOptional = CommonTypes::getBool($in);
379 $parameter->flags = Byte::readUnsigned($in);
380
381 if(($parameter->paramType & self::ARG_FLAG_ENUM) !== 0){
382 $index = ($parameter->paramType & 0xffff);
383 $parameter->enum = $enums[$index] ?? null;
384 if($parameter->enum === null){
385 throw new PacketDecodeException("deserializing $name parameter $parameter->paramName: expected enum at $index, but got none");
386 }
387 }elseif(($parameter->paramType & self::ARG_FLAG_POSTFIX) !== 0){
388 $index = ($parameter->paramType & 0xffff);
389 $parameter->postfix = $postfixes[$index] ?? null;
390 if($parameter->postfix === null){
391 throw new PacketDecodeException("deserializing $name parameter $parameter->paramName: expected postfix at $index, but got none");
392 }
393 }elseif(($parameter->paramType & self::ARG_FLAG_VALID) === 0){
394 throw new PacketDecodeException("deserializing $name parameter $parameter->paramName: Invalid parameter type 0x" . dechex($parameter->paramType));
395 }
396
397 $parameters[$paramIndex] = $parameter;
398 }
399 $overloads[$overloadIndex] = new CommandOverload($isChaining, $parameters);
400 }
401
402 return new CommandData($name, $description, $flags, $permission, $aliases, $overloads, $chainedSubCommandData);
403 }
404
411 protected function putCommandData(CommandData $data, array $enumIndexes, array $softEnumIndexes, array $postfixIndexes, array $chainedSubCommandDataIndexes, ByteBufferWriter $out) : void{
412 CommonTypes::putString($out, $data->name);
413 CommonTypes::putString($out, $data->description);
414 LE::writeUnsignedShort($out, $data->flags);
415 Byte::writeUnsigned($out, $data->permission);
416
417 if($data->aliases !== null){
418 LE::writeSignedInt($out, $enumIndexes[$data->aliases->getName()] ?? -1);
419 }else{
420 LE::writeSignedInt($out, -1);
421 }
422
423 VarInt::writeUnsignedInt($out, count($data->chainedSubCommandData));
424 foreach($data->chainedSubCommandData as $chainedSubCommandData){
425 $index = $chainedSubCommandDataIndexes[$chainedSubCommandData->getName()] ??
426 throw new \LogicException("Chained subcommand data {$chainedSubCommandData->getName()} does not have an index (this should be impossible)");
427 LE::writeUnsignedShort($out, $index);
428 }
429
430 VarInt::writeUnsignedInt($out, count($data->overloads));
431 foreach($data->overloads as $overload){
432 CommonTypes::putBool($out, $overload->isChaining());
433 VarInt::writeUnsignedInt($out, count($overload->getParameters()));
434 foreach($overload->getParameters() as $parameter){
435 CommonTypes::putString($out, $parameter->paramName);
436
437 if($parameter->enum !== null){
438 if($parameter->enum->isSoft()){
439 $type = self::ARG_FLAG_SOFT_ENUM | self::ARG_FLAG_VALID | ($softEnumIndexes[$parameter->enum->getName()] ?? -1);
440 }else{
441 $type = self::ARG_FLAG_ENUM | self::ARG_FLAG_VALID | ($enumIndexes[$parameter->enum->getName()] ?? -1);
442 }
443 }elseif($parameter->postfix !== null){
444 if(!isset($postfixIndexes[$parameter->postfix])){
445 throw new \LogicException("Postfix '$parameter->postfix' not in postfixes array");
446 }
447 $type = self::ARG_FLAG_POSTFIX | $postfixIndexes[$parameter->postfix];
448 }else{
449 $type = $parameter->paramType;
450 }
451
452 LE::writeUnsignedInt($out, $type);
453 CommonTypes::putBool($out, $parameter->isOptional);
454 Byte::writeUnsigned($out, $parameter->flags);
455 }
456 }
457 }
458
459 protected function encodePayload(ByteBufferWriter $out) : void{
464 $enumValueIndexes = [];
469 $postfixIndexes = [];
470
475 $enums = [];
480 $enumIndexes = [];
481
486 $softEnums = [];
491 $softEnumIndexes = [];
492
497 $allChainedSubCommandData = [];
502 $chainedSubCommandDataIndexes = [];
503
508 $chainedSubCommandValueNameIndexes = [];
509
510 $addEnumFn = static function(CommandEnum $enum) use (
511 &$enums, &$softEnums, &$enumIndexes, &$softEnumIndexes, &$enumValueIndexes
512 ) : void{
513 $enumName = $enum->getName();
514
515 if($enum->isSoft()){
516 if(!isset($softEnumIndexes[$enumName])){
517 $softEnums[$softEnumIndexes[$enumName] = count($softEnumIndexes)] = $enum;
518 }
519 }else{
520 foreach($enum->getValues() as $str){
521 $enumValueIndexes[$str] = $enumValueIndexes[$str] ?? count($enumValueIndexes); //latest index
522 }
523 if(!isset($enumIndexes[$enumName])){
524 $enums[$enumIndexes[$enumName] = count($enumIndexes)] = $enum;
525 }
526 }
527 };
528 foreach($this->hardcodedEnums as $enum){
529 $addEnumFn($enum);
530 }
531 foreach($this->softEnums as $enum){
532 $addEnumFn($enum);
533 }
534 foreach($this->commandData as $commandData){
535 if($commandData->aliases !== null){
536 $addEnumFn($commandData->aliases);
537 }
538 foreach($commandData->overloads as $overload){
539 foreach($overload->getParameters() as $parameter){
540 if($parameter->enum !== null){
541 $addEnumFn($parameter->enum);
542 }
543
544 if($parameter->postfix !== null){
545 $postfixIndexes[$parameter->postfix] = $postfixIndexes[$parameter->postfix] ?? count($postfixIndexes);
546 }
547 }
548 }
549 foreach($commandData->chainedSubCommandData as $chainedSubCommandData){
550 if(!isset($allChainedSubCommandData[$chainedSubCommandData->getName()])){
551 $allChainedSubCommandData[$chainedSubCommandData->getName()] = $chainedSubCommandData;
552 $chainedSubCommandDataIndexes[$chainedSubCommandData->getName()] = count($chainedSubCommandDataIndexes);
553
554 foreach($chainedSubCommandData->getValues() as $value){
555 $chainedSubCommandValueNameIndexes[$value->getName()] ??= count($chainedSubCommandValueNameIndexes);
556 }
557 }
558 }
559 }
560
561 VarInt::writeUnsignedInt($out, count($enumValueIndexes));
562 foreach($enumValueIndexes as $enumValue => $index){
563 CommonTypes::putString($out, (string) $enumValue); //stupid PHP key casting D:
564 }
565
566 VarInt::writeUnsignedInt($out, count($chainedSubCommandValueNameIndexes));
567 foreach($chainedSubCommandValueNameIndexes as $chainedSubCommandValueName => $index){
568 CommonTypes::putString($out, (string) $chainedSubCommandValueName); //stupid PHP key casting D:
569 }
570
571 VarInt::writeUnsignedInt($out, count($postfixIndexes));
572 foreach($postfixIndexes as $postfix => $index){
573 CommonTypes::putString($out, (string) $postfix); //stupid PHP key casting D:
574 }
575
576 VarInt::writeUnsignedInt($out, count($enums));
577 foreach($enums as $enum){
578 $this->putEnum($enum, $enumValueIndexes, $out);
579 }
580
581 VarInt::writeUnsignedInt($out, count($allChainedSubCommandData));
582 foreach($allChainedSubCommandData as $chainedSubCommandData){
583 CommonTypes::putString($out, $chainedSubCommandData->getName());
584 VarInt::writeUnsignedInt($out, count($chainedSubCommandData->getValues()));
585 foreach($chainedSubCommandData->getValues() as $value){
586 $valueNameIndex = $chainedSubCommandValueNameIndexes[$value->getName()] ??
587 throw new \LogicException("Chained subcommand value name index for \"" . $value->getName() . "\" not found (this should never happen)");
588 LE::writeUnsignedShort($out, $valueNameIndex);
589 LE::writeUnsignedShort($out, $value->getType());
590 }
591 }
592
593 VarInt::writeUnsignedInt($out, count($this->commandData));
594 foreach($this->commandData as $data){
595 $this->putCommandData($data, $enumIndexes, $softEnumIndexes, $postfixIndexes, $chainedSubCommandDataIndexes, $out);
596 }
597
598 VarInt::writeUnsignedInt($out, count($softEnums));
599 foreach($softEnums as $enum){
600 $this->putSoftEnum($enum, $out);
601 }
602
603 VarInt::writeUnsignedInt($out, count($this->enumConstraints));
604 foreach($this->enumConstraints as $constraint){
605 $this->putEnumConstraint($constraint, $enumIndexes, $enumValueIndexes, $out);
606 }
607 }
608
609 public function handle(PacketHandlerInterface $handler) : bool{
610 return $handler->handleAvailableCommands($this);
611 }
612}
getEnumConstraint(array $enums, array $enumValues, ByteBufferReader $in)
putEnum(CommandEnum $enum, array $enumValueMap, ByteBufferWriter $out)
putEnumConstraint(CommandEnumConstraint $constraint, array $enumIndexes, array $enumValueIndexes, ByteBufferWriter $out)
getCommandData(array $enums, array $postfixes, array $allChainedSubCommandData, ByteBufferReader $in)
static create(array $commandData, array $hardcodedEnums, array $softEnums, array $enumConstraints)
putCommandData(CommandData $data, array $enumIndexes, array $softEnumIndexes, array $postfixIndexes, array $chainedSubCommandDataIndexes, ByteBufferWriter $out)