PocketMine-MP 5.15.1 git-5ef247620a7c6301a849b54e5ef1009217729fc8
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
27use function array_search;
28use function count;
29use function dechex;
30
32 public const NETWORK_ID = ProtocolInfo::AVAILABLE_COMMANDS_PACKET;
33
38 public const ARG_FLAG_VALID = 0x100000;
39
44 public const ARG_TYPE_INT = ArgTypes::INT;
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;
51
52 public const ARG_TYPE_WILDCARD_TARGET = ArgTypes::WILDCARDSELECTION;
53
54 public const ARG_TYPE_FILEPATH = ArgTypes::PATHCOMMAND;
55
56 public const ARG_TYPE_FULL_INTEGER_RANGE = ArgTypes::FULLINTEGERRANGE;
57
58 public const ARG_TYPE_EQUIPMENT_SLOT = ArgTypes::EQUIPMENTSLOTENUM;
59 public const ARG_TYPE_STRING = ArgTypes::ID;
60
61 public const ARG_TYPE_INT_POSITION = ArgTypes::POSITION;
62 public const ARG_TYPE_POSITION = ArgTypes::POSITION_FLOAT;
63
64 public const ARG_TYPE_MESSAGE = ArgTypes::MESSAGE_ROOT;
65
66 public const ARG_TYPE_RAWTEXT = ArgTypes::RAWTEXT;
67
68 public const ARG_TYPE_JSON = ArgTypes::JSON_OBJECT;
69
70 public const ARG_TYPE_BLOCK_STATES = ArgTypes::BLOCK_STATE_ARRAY;
71
72 public const ARG_TYPE_COMMAND = ArgTypes::SLASHCOMMAND;
73
78 public const ARG_FLAG_ENUM = 0x200000;
79
81 public const ARG_FLAG_POSTFIX = 0x1000000;
82
83 public const ARG_FLAG_SOFT_ENUM = 0x4000000;
84
85 public const HARDCODED_ENUM_NAMES = [
86 "CommandName" => true
87 ];
88
93 public array $commandData = [];
94
100 public array $hardcodedEnums = [];
101
107 public array $softEnums = [];
108
113 public array $enumConstraints = [];
114
122 public static function create(array $commandData, array $hardcodedEnums, array $softEnums, array $enumConstraints) : self{
123 $result = new self;
124 $result->commandData = $commandData;
125 $result->hardcodedEnums = $hardcodedEnums;
126 $result->softEnums = $softEnums;
127 $result->enumConstraints = $enumConstraints;
128 return $result;
129 }
130
131 protected function decodePayload(PacketSerializer $in) : void{
133 $enumValues = [];
134 for($i = 0, $enumValuesCount = $in->getUnsignedVarInt(); $i < $enumValuesCount; ++$i){
135 $enumValues[] = $in->getString();
136 }
137
139 $chainedSubcommandValueNames = [];
140 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
141 $chainedSubcommandValueNames[] = $in->getString();
142 }
143
145 $postfixes = [];
146 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
147 $postfixes[] = $in->getString();
148 }
149
151 $enums = [];
152 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
153 $enums[] = $enum = $this->getEnum($enumValues, $in);
154 //TODO: Bedrock may provide some enums which are not referenced by any command, and can't reasonably be
155 //considered "hardcoded". This happens with various Edu command enums, and other enums which are probably
156 //intended to be used by commands which aren't present in public releases.
157 //We should probably store these somewhere, since we'll need them to be able to correctly re-encode the
158 //packet for testing.
159 if(isset(self::HARDCODED_ENUM_NAMES[$enum->getName()])){
160 $this->hardcodedEnums[] = $enum;
161 }
162 }
163
164 $chainedSubCommandData = [];
165 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
166 $name = $in->getString();
167 $values = [];
168 for($j = 0, $valueCount = $in->getUnsignedVarInt(); $j < $valueCount; ++$j){
169 $valueName = $chainedSubcommandValueNames[$in->getLShort()];
170 $valueType = $in->getLShort();
171 $values[] = new ChainedSubCommandValue($valueName, $valueType);
172 }
173 $chainedSubCommandData[] = new ChainedSubCommandData($name, $values);
174 }
175
176 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
177 $this->commandData[] = $this->getCommandData($enums, $postfixes, $chainedSubCommandData, $in);
178 }
179
180 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
181 $this->softEnums[] = $this->getSoftEnum($in);
182 }
183
184 $this->initSoftEnumsInCommandData();
185
186 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
187 $this->enumConstraints[] = $this->getEnumConstraint($enums, $enumValues, $in);
188 }
189 }
190
195 protected function initSoftEnumsInCommandData() : void{
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");
204 }
205 }
206 }
207 }
208 }
209 }
210
217 protected function getEnum(array $enumValueList, PacketSerializer $in) : CommandEnum{
218 $enumName = $in->getString();
219 $enumValues = [];
220
221 $listSize = count($enumValueList);
222
223 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
224 $index = $this->getEnumValueIndex($listSize, $in);
225 if(!isset($enumValueList[$index])){
226 throw new PacketDecodeException("Invalid enum value index $index");
227 }
228 //Get the enum value from the initial pile of mess
229 $enumValues[] = $enumValueList[$index];
230 }
231
232 return new CommandEnum($enumName, $enumValues);
233 }
234
238 protected function getSoftEnum(PacketSerializer $in) : CommandEnum{
239 $enumName = $in->getString();
240 $enumValues = [];
241
242 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
243 //Get the enum value from the initial pile of mess
244 $enumValues[] = $in->getString();
245 }
246
247 return new CommandEnum($enumName, $enumValues, true);
248 }
249
253 protected function putEnum(CommandEnum $enum, array $enumValueMap, PacketSerializer $out) : void{
254 $out->putString($enum->getName());
255
256 $values = $enum->getValues();
257 $out->putUnsignedVarInt(count($values));
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");
262 }
263 $this->putEnumValueIndex($enumValueMap[$value], $listSize, $out);
264 }
265 }
266
267 protected function putSoftEnum(CommandEnum $enum, PacketSerializer $out) : void{
268 $out->putString($enum->getName());
269
270 $values = $enum->getValues();
271 $out->putUnsignedVarInt(count($values));
272 foreach($values as $value){
273 $out->putString($value);
274 }
275 }
276
280 protected function getEnumValueIndex(int $valueCount, PacketSerializer $in) : int{
281 if($valueCount < 256){
282 return $in->getByte();
283 }elseif($valueCount < 65536){
284 return $in->getLShort();
285 }else{
286 return $in->getLInt();
287 }
288 }
289
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);
295 }else{
296 $out->putLInt($index);
297 }
298 }
299
307 protected function getEnumConstraint(array $enums, array $enumValues, PacketSerializer $in) : CommandEnumConstraint{
308 //wtf, what was wrong with an offset inside the enum? :(
309 $valueIndex = $in->getLInt();
310 if(!isset($enumValues[$valueIndex])){
311 throw new PacketDecodeException("Enum constraint refers to unknown enum value index $valueIndex");
312 }
313 $enumIndex = $in->getLInt();
314 if(!isset($enums[$enumIndex])){
315 throw new PacketDecodeException("Enum constraint refers to unknown enum index $enumIndex");
316 }
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() . "\"");
321 }
322
323 $constraintIds = [];
324 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
325 $constraintIds[] = $in->getByte();
326 }
327
328 return new CommandEnumConstraint($enum, $valueOffset, $constraintIds);
329 }
330
335 protected function putEnumConstraint(CommandEnumConstraint $constraint, array $enumIndexes, array $enumValueIndexes, PacketSerializer $out) : void{
336 $out->putLInt($enumValueIndexes[$constraint->getAffectedValue()]);
337 $out->putLInt($enumIndexes[$constraint->getEnum()->getName()]);
338 $out->putUnsignedVarInt(count($constraint->getConstraints()));
339 foreach($constraint->getConstraints() as $v){
340 $out->putByte($v);
341 }
342 }
343
352 protected function getCommandData(array $enums, array $postfixes, array $allChainedSubCommandData, PacketSerializer $in) : CommandData{
353 $name = $in->getString();
354 $description = $in->getString();
355 $flags = $in->getLShort();
356 $permission = $in->getByte();
357 $aliases = $enums[$in->getLInt()] ?? null;
358
359 $chainedSubCommandData = [];
360 for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){
361 $index = $in->getLShort();
362 $chainedSubCommandData[] = $allChainedSubCommandData[$index] ?? throw new PacketDecodeException("Unknown chained subcommand data index $index");
363 }
364 $overloads = [];
365
366 for($overloadIndex = 0, $overloadCount = $in->getUnsignedVarInt(); $overloadIndex < $overloadCount; ++$overloadIndex){
367 $parameters = [];
368 $isChaining = $in->getBool();
369 for($paramIndex = 0, $paramCount = $in->getUnsignedVarInt(); $paramIndex < $paramCount; ++$paramIndex){
370 $parameter = new CommandParameter();
371 $parameter->paramName = $in->getString();
372 $parameter->paramType = $in->getLInt();
373 $parameter->isOptional = $in->getBool();
374 $parameter->flags = $in->getByte();
375
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");
381 }
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");
387 }
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));
390 }
391
392 $parameters[$paramIndex] = $parameter;
393 }
394 $overloads[$overloadIndex] = new CommandOverload($isChaining, $parameters);
395 }
396
397 return new CommandData($name, $description, $flags, $permission, $aliases, $overloads, $chainedSubCommandData);
398 }
399
406 protected function putCommandData(CommandData $data, array $enumIndexes, array $softEnumIndexes, array $postfixIndexes, array $chainedSubCommandDataIndexes, PacketSerializer $out) : void{
407 $out->putString($data->name);
408 $out->putString($data->description);
409 $out->putLShort($data->flags);
410 $out->putByte($data->permission);
411
412 if($data->aliases !== null){
413 $out->putLInt($enumIndexes[$data->aliases->getName()] ?? -1);
414 }else{
415 $out->putLInt(-1);
416 }
417
418 $out->putUnsignedVarInt(count($data->chainedSubCommandData));
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);
423 }
424
425 $out->putUnsignedVarInt(count($data->overloads));
426 foreach($data->overloads as $overload){
427 $out->putBool($overload->isChaining());
428 $out->putUnsignedVarInt(count($overload->getParameters()));
429 foreach($overload->getParameters() as $parameter){
430 $out->putString($parameter->paramName);
431
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);
435 }else{
436 $type = self::ARG_FLAG_ENUM | self::ARG_FLAG_VALID | ($enumIndexes[$parameter->enum->getName()] ?? -1);
437 }
438 }elseif($parameter->postfix !== null){
439 if(!isset($postfixIndexes[$parameter->postfix])){
440 throw new \LogicException("Postfix '$parameter->postfix' not in postfixes array");
441 }
442 $type = self::ARG_FLAG_POSTFIX | $postfixIndexes[$parameter->postfix];
443 }else{
444 $type = $parameter->paramType;
445 }
446
447 $out->putLInt($type);
448 $out->putBool($parameter->isOptional);
449 $out->putByte($parameter->flags);
450 }
451 }
452 }
453
454 protected function encodePayload(PacketSerializer $out) : void{
459 $enumValueIndexes = [];
464 $postfixIndexes = [];
465
470 $enums = [];
475 $enumIndexes = [];
476
481 $softEnums = [];
486 $softEnumIndexes = [];
487
492 $allChainedSubCommandData = [];
497 $chainedSubCommandDataIndexes = [];
498
503 $chainedSubCommandValueNameIndexes = [];
504
505 $addEnumFn = static function(CommandEnum $enum) use (
506 &$enums, &$softEnums, &$enumIndexes, &$softEnumIndexes, &$enumValueIndexes
507 ) : void{
508 $enumName = $enum->getName();
509
510 if($enum->isSoft()){
511 if(!isset($softEnumIndexes[$enumName])){
512 $softEnums[$softEnumIndexes[$enumName] = count($softEnumIndexes)] = $enum;
513 }
514 }else{
515 foreach($enum->getValues() as $str){
516 $enumValueIndexes[$str] = $enumValueIndexes[$str] ?? count($enumValueIndexes); //latest index
517 }
518 if(!isset($enumIndexes[$enumName])){
519 $enums[$enumIndexes[$enumName] = count($enumIndexes)] = $enum;
520 }
521 }
522 };
523 foreach($this->hardcodedEnums as $enum){
524 $addEnumFn($enum);
525 }
526 foreach($this->softEnums as $enum){
527 $addEnumFn($enum);
528 }
529 foreach($this->commandData as $commandData){
530 if($commandData->aliases !== null){
531 $addEnumFn($commandData->aliases);
532 }
533 foreach($commandData->overloads as $overload){
534 foreach($overload->getParameters() as $parameter){
535 if($parameter->enum !== null){
536 $addEnumFn($parameter->enum);
537 }
538
539 if($parameter->postfix !== null){
540 $postfixIndexes[$parameter->postfix] = $postfixIndexes[$parameter->postfix] ?? count($postfixIndexes);
541 }
542 }
543 }
544 foreach($commandData->chainedSubCommandData as $chainedSubCommandData){
545 if(!isset($allChainedSubCommandData[$chainedSubCommandData->getName()])){
546 $allChainedSubCommandData[$chainedSubCommandData->getName()] = $chainedSubCommandData;
547 $chainedSubCommandDataIndexes[$chainedSubCommandData->getName()] = count($chainedSubCommandDataIndexes);
548
549 foreach($chainedSubCommandData->getValues() as $value){
550 $chainedSubCommandValueNameIndexes[$value->getName()] ??= count($chainedSubCommandValueNameIndexes);
551 }
552 }
553 }
554 }
555
556 $out->putUnsignedVarInt(count($enumValueIndexes));
557 foreach($enumValueIndexes as $enumValue => $index){
558 $out->putString((string) $enumValue); //stupid PHP key casting D:
559 }
560
561 $out->putUnsignedVarInt(count($chainedSubCommandValueNameIndexes));
562 foreach($chainedSubCommandValueNameIndexes as $chainedSubCommandValueName => $index){
563 $out->putString((string) $chainedSubCommandValueName); //stupid PHP key casting D:
564 }
565
566 $out->putUnsignedVarInt(count($postfixIndexes));
567 foreach($postfixIndexes as $postfix => $index){
568 $out->putString((string) $postfix); //stupid PHP key casting D:
569 }
570
571 $out->putUnsignedVarInt(count($enums));
572 foreach($enums as $enum){
573 $this->putEnum($enum, $enumValueIndexes, $out);
574 }
575
576 $out->putUnsignedVarInt(count($allChainedSubCommandData));
577 foreach($allChainedSubCommandData as $chainedSubCommandData){
578 $out->putString($chainedSubCommandData->getName());
579 $out->putUnsignedVarInt(count($chainedSubCommandData->getValues()));
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());
585 }
586 }
587
588 $out->putUnsignedVarInt(count($this->commandData));
589 foreach($this->commandData as $data){
590 $this->putCommandData($data, $enumIndexes, $softEnumIndexes, $postfixIndexes, $chainedSubCommandDataIndexes, $out);
591 }
592
593 $out->putUnsignedVarInt(count($softEnums));
594 foreach($softEnums as $enum){
595 $this->putSoftEnum($enum, $out);
596 }
597
598 $out->putUnsignedVarInt(count($this->enumConstraints));
599 foreach($this->enumConstraints as $constraint){
600 $this->putEnumConstraint($constraint, $enumIndexes, $enumValueIndexes, $out);
601 }
602 }
603
604 public function handle(PacketHandlerInterface $handler) : bool{
605 return $handler->handleAvailableCommands($this);
606 }
607}
getEnumConstraint(array $enums, array $enumValues, PacketSerializer $in)
putEnum(CommandEnum $enum, array $enumValueMap, PacketSerializer $out)
putEnumConstraint(CommandEnumConstraint $constraint, array $enumIndexes, array $enumValueIndexes, PacketSerializer $out)
static create(array $commandData, array $hardcodedEnums, array $softEnums, array $enumConstraints)
putCommandData(CommandData $data, array $enumIndexes, array $softEnumIndexes, array $postfixIndexes, array $chainedSubCommandDataIndexes, PacketSerializer $out)
getCommandData(array $enums, array $postfixes, array $allChainedSubCommandData, PacketSerializer $in)