13declare(strict_types=1);
15namespace pocketmine\network\mcpe\protocol;
27use
function array_search;
32 public const NETWORK_ID = ProtocolInfo::AVAILABLE_COMMANDS_PACKET;
45 public const ARG_TYPE_FLOAT = ArgTypes::VAL;
46 public const ARG_TYPE_VALUE = ArgTypes::RVAL;
47 public const ARG_TYPE_WILDCARD_INT = ArgTypes::WILDCARDINT;
48 public const ARG_TYPE_OPERATOR = ArgTypes::OPERATOR;
49 public const ARG_TYPE_COMPARE_OPERATOR = ArgTypes::COMPAREOPERATOR;
50 public const ARG_TYPE_TARGET = ArgTypes::SELECTION;
52 public const ARG_TYPE_WILDCARD_TARGET = ArgTypes::WILDCARDSELECTION;
54 public const ARG_TYPE_FILEPATH = ArgTypes::PATHCOMMAND;
56 public const ARG_TYPE_FULL_INTEGER_RANGE = ArgTypes::FULLINTEGERRANGE;
58 public const ARG_TYPE_EQUIPMENT_SLOT = ArgTypes::EQUIPMENTSLOTENUM;
59 public const ARG_TYPE_STRING = ArgTypes::ID;
61 public const ARG_TYPE_INT_POSITION = ArgTypes::POSITION;
62 public const ARG_TYPE_POSITION = ArgTypes::POSITION_FLOAT;
64 public const ARG_TYPE_MESSAGE = ArgTypes::MESSAGE_ROOT;
66 public const ARG_TYPE_RAWTEXT = ArgTypes::RAWTEXT;
68 public const ARG_TYPE_JSON = ArgTypes::JSON_OBJECT;
70 public const ARG_TYPE_BLOCK_STATES = ArgTypes::BLOCK_STATE_ARRAY;
72 public const ARG_TYPE_COMMAND = ArgTypes::SLASHCOMMAND;
83 public const ARG_FLAG_SOFT_ENUM = 0x4000000;
85 public const HARDCODED_ENUM_NAMES = [
93 public array $commandData = [];
100 public array $hardcodedEnums = [];
107 public array $softEnums = [];
113 public array $enumConstraints = [];
122 public static function create(array $commandData, array $hardcodedEnums, array $softEnums, array $enumConstraints) : self{
124 $result->commandData = $commandData;
125 $result->hardcodedEnums = $hardcodedEnums;
126 $result->softEnums = $softEnums;
127 $result->enumConstraints = $enumConstraints;
134 for($i = 0, $enumValuesCount = $in->
getUnsignedVarInt(); $i < $enumValuesCount; ++$i){
139 $chainedSubcommandValueNames = [];
141 $chainedSubcommandValueNames[] = $in->
getString();
153 $enums[] = $enum = $this->getEnum($enumValues, $in);
159 if(isset(self::HARDCODED_ENUM_NAMES[$enum->getName()])){
160 $this->hardcodedEnums[] = $enum;
164 $chainedSubCommandData = [];
169 $valueName = $chainedSubcommandValueNames[$in->
getLShort()];
171 $values[] =
new ChainedSubCommandValue($valueName, $valueType);
173 $chainedSubCommandData[] =
new ChainedSubCommandData($name, $values);
177 $this->commandData[] = $this->getCommandData($enums, $postfixes, $chainedSubCommandData, $in);
181 $this->softEnums[] = $this->getSoftEnum($in);
184 $this->initSoftEnumsInCommandData();
187 $this->enumConstraints[] = $this->getEnumConstraint($enums, $enumValues, $in);
196 foreach($this->commandData as $datum){
197 foreach($datum->getOverloads() as $overload){
198 foreach($overload->getParameters() as $parameter){
199 if(($parameter->paramType & self::ARG_FLAG_SOFT_ENUM) !== 0){
200 $index = $parameter->paramType & 0xffff;
201 $parameter->enum = $this->softEnums[$index] ??
null;
202 if($parameter->enum ===
null){
203 throw new PacketDecodeException(
"deserializing $datum->name parameter $parameter->paramName: expected soft enum at $index, but got none");
218 $enumName = $in->getString();
221 $listSize = count($enumValueList);
224 $index = $this->getEnumValueIndex($listSize, $in);
225 if(!isset($enumValueList[$index])){
229 $enumValues[] = $enumValueList[$index];
239 $enumName = $in->getString();
247 return new CommandEnum($enumName, $enumValues,
true);
254 $out->putString($enum->getName());
258 $listSize = count($enumValueMap);
259 foreach($values as $value){
260 if(!isset($enumValueMap[$value])){
261 throw new \LogicException(
"Enum value '$value' doesn't have a value index");
263 $this->putEnumValueIndex($enumValueMap[$value], $listSize, $out);
267 protected function putSoftEnum(CommandEnum $enum, PacketSerializer $out) : void{
268 $out->putString($enum->getName());
270 $values = $enum->getValues();
271 $out->putUnsignedVarInt(count($values));
272 foreach($values as $value){
273 $out->putString($value);
281 if($valueCount < 256){
283 }elseif($valueCount < 65536){
290 protected function putEnumValueIndex(
int $index,
int $valueCount, PacketSerializer $out) : void{
291 if($valueCount < 256){
292 $out->putByte($index);
293 }elseif($valueCount < 65536){
294 $out->putLShort($index);
296 $out->putLInt($index);
309 $valueIndex = $in->getLInt();
310 if(!isset($enumValues[$valueIndex])){
314 if(!isset($enums[$enumIndex])){
317 $enum = $enums[$enumIndex];
318 $valueOffset = array_search($enumValues[$valueIndex], $enum->getValues(),
true);
319 if($valueOffset ===
false){
320 throw new PacketDecodeException(
"Value \"" . $enumValues[$valueIndex] .
"\" does not belong to enum \"" . $enum->getName() .
"\"");
325 $constraintIds[] = $in->
getByte();
328 return new CommandEnumConstraint($enum, $valueOffset, $constraintIds);
336 $out->putLInt($enumValueIndexes[$constraint->getAffectedValue()]);
337 $out->putLInt($enumIndexes[$constraint->getEnum()->getName()]);
353 $name = $in->getString();
357 $aliases = $enums[$in->
getLInt()] ??
null;
359 $chainedSubCommandData = [];
362 $chainedSubCommandData[] = $allChainedSubCommandData[$index] ??
throw new PacketDecodeException(
"Unknown chained subcommand data index $index");
366 for($overloadIndex = 0, $overloadCount = $in->
getUnsignedVarInt(); $overloadIndex < $overloadCount; ++$overloadIndex){
369 for($paramIndex = 0, $paramCount = $in->
getUnsignedVarInt(); $paramIndex < $paramCount; ++$paramIndex){
371 $parameter->paramName = $in->
getString();
372 $parameter->paramType = $in->
getLInt();
373 $parameter->isOptional = $in->
getBool();
374 $parameter->flags = $in->
getByte();
376 if(($parameter->paramType & self::ARG_FLAG_ENUM) !== 0){
377 $index = ($parameter->paramType & 0xffff);
378 $parameter->enum = $enums[$index] ??
null;
379 if($parameter->enum ===
null){
380 throw new PacketDecodeException(
"deserializing $name parameter $parameter->paramName: expected enum at $index, but got none");
382 }elseif(($parameter->paramType & self::ARG_FLAG_POSTFIX) !== 0){
383 $index = ($parameter->paramType & 0xffff);
384 $parameter->postfix = $postfixes[$index] ??
null;
385 if($parameter->postfix ===
null){
386 throw new PacketDecodeException(
"deserializing $name parameter $parameter->paramName: expected postfix at $index, but got none");
388 }elseif(($parameter->paramType & self::ARG_FLAG_VALID) === 0){
389 throw new PacketDecodeException(
"deserializing $name parameter $parameter->paramName: Invalid parameter type 0x" . dechex($parameter->paramType));
392 $parameters[$paramIndex] = $parameter;
394 $overloads[$overloadIndex] =
new CommandOverload($isChaining, $parameters);
397 return new CommandData($name, $description, $flags, $permission, $aliases, $overloads, $chainedSubCommandData);
407 $out->putString($data->name);
408 $out->putString($data->description);
409 $out->putLShort($data->flags);
410 $out->putByte($data->permission);
412 if($data->aliases !==
null){
413 $out->putLInt($enumIndexes[$data->aliases->getName()] ?? -1);
419 foreach($data->chainedSubCommandData as $chainedSubCommandData){
420 $index = $chainedSubCommandDataIndexes[$chainedSubCommandData->getName()] ??
421 throw new \LogicException(
"Chained subcommand data {$chainedSubCommandData->getName()} does not have an index (this should be impossible)");
422 $out->putLShort($index);
426 foreach($data->overloads as $overload){
427 $out->putBool($overload->isChaining());
429 foreach($overload->getParameters() as $parameter){
430 $out->putString($parameter->paramName);
432 if($parameter->enum !==
null){
433 if($parameter->enum->isSoft()){
434 $type = self::ARG_FLAG_SOFT_ENUM | self::ARG_FLAG_VALID | ($softEnumIndexes[$parameter->enum->getName()] ?? -1);
436 $type = self::ARG_FLAG_ENUM | self::ARG_FLAG_VALID | ($enumIndexes[$parameter->enum->getName()] ?? -1);
438 }elseif($parameter->postfix !==
null){
439 if(!isset($postfixIndexes[$parameter->postfix])){
440 throw new \LogicException(
"Postfix '$parameter->postfix' not in postfixes array");
442 $type = self::ARG_FLAG_POSTFIX | $postfixIndexes[$parameter->postfix];
444 $type = $parameter->paramType;
447 $out->putLInt($type);
448 $out->putBool($parameter->isOptional);
449 $out->putByte($parameter->flags);
459 $enumValueIndexes = [];
464 $postfixIndexes = [];
486 $softEnumIndexes = [];
492 $allChainedSubCommandData = [];
497 $chainedSubCommandDataIndexes = [];
503 $chainedSubCommandValueNameIndexes = [];
505 $addEnumFn =
static function(
CommandEnum $enum) use (
506 &$enums, &$softEnums, &$enumIndexes, &$softEnumIndexes, &$enumValueIndexes
508 $enumName = $enum->getName();
511 if(!isset($softEnumIndexes[$enumName])){
512 $softEnums[$softEnumIndexes[$enumName] = count($softEnumIndexes)] = $enum;
515 foreach($enum->getValues() as $str){
516 $enumValueIndexes[$str] = $enumValueIndexes[$str] ?? count($enumValueIndexes);
518 if(!isset($enumIndexes[$enumName])){
519 $enums[$enumIndexes[$enumName] = count($enumIndexes)] = $enum;
523 foreach($this->hardcodedEnums as $enum){
526 foreach($this->softEnums as $enum){
529 foreach($this->commandData as $commandData){
530 if($commandData->aliases !==
null){
531 $addEnumFn($commandData->aliases);
533 foreach($commandData->overloads as $overload){
534 foreach($overload->getParameters() as $parameter){
535 if($parameter->enum !==
null){
536 $addEnumFn($parameter->enum);
539 if($parameter->postfix !==
null){
540 $postfixIndexes[$parameter->postfix] = $postfixIndexes[$parameter->postfix] ?? count($postfixIndexes);
544 foreach($commandData->chainedSubCommandData as $chainedSubCommandData){
545 if(!isset($allChainedSubCommandData[$chainedSubCommandData->getName()])){
546 $allChainedSubCommandData[$chainedSubCommandData->getName()] = $chainedSubCommandData;
547 $chainedSubCommandDataIndexes[$chainedSubCommandData->getName()] = count($chainedSubCommandDataIndexes);
549 foreach($chainedSubCommandData->getValues() as $value){
550 $chainedSubCommandValueNameIndexes[$value->getName()] ??= count($chainedSubCommandValueNameIndexes);
557 foreach($enumValueIndexes as $enumValue => $index){
558 $out->putString((
string) $enumValue);
562 foreach($chainedSubCommandValueNameIndexes as $chainedSubCommandValueName => $index){
563 $out->putString((
string) $chainedSubCommandValueName);
567 foreach($postfixIndexes as $postfix => $index){
568 $out->putString((
string) $postfix);
572 foreach($enums as $enum){
573 $this->putEnum($enum, $enumValueIndexes, $out);
577 foreach($allChainedSubCommandData as $chainedSubCommandData){
578 $out->putString($chainedSubCommandData->getName());
580 foreach($chainedSubCommandData->getValues() as $value){
581 $valueNameIndex = $chainedSubCommandValueNameIndexes[$value->getName()] ??
582 throw new \LogicException(
"Chained subcommand value name index for \"" . $value->getName() .
"\" not found (this should never happen)");
583 $out->putLShort($valueNameIndex);
584 $out->putLShort($value->getType());
589 foreach($this->commandData as $data){
590 $this->putCommandData($data, $enumIndexes, $softEnumIndexes, $postfixIndexes, $chainedSubCommandDataIndexes, $out);
594 foreach($softEnums as $enum){
595 $this->putSoftEnum($enum, $out);
599 foreach($this->enumConstraints as $constraint){
600 $this->putEnumConstraint($constraint, $enumIndexes, $enumValueIndexes, $out);
605 return $handler->handleAvailableCommands($this);
getEnumConstraint(array $enums, array $enumValues, PacketSerializer $in)
handle(PacketHandlerInterface $handler)
putEnum(CommandEnum $enum, array $enumValueMap, PacketSerializer $out)
getEnumValueIndex(int $valueCount, PacketSerializer $in)
encodePayload(PacketSerializer $out)
getSoftEnum(PacketSerializer $in)
putEnumConstraint(CommandEnumConstraint $constraint, array $enumIndexes, array $enumValueIndexes, PacketSerializer $out)
static create(array $commandData, array $hardcodedEnums, array $softEnums, array $enumConstraints)
initSoftEnumsInCommandData()
getEnum(array $enumValueList, PacketSerializer $in)
putCommandData(CommandData $data, array $enumIndexes, array $softEnumIndexes, array $postfixIndexes, array $chainedSubCommandDataIndexes, PacketSerializer $out)
getCommandData(array $enums, array $postfixes, array $allChainedSubCommandData, PacketSerializer $in)
decodePayload(PacketSerializer $in)
putUnsignedVarInt(int $v)