13declare(strict_types=1);
15namespace pocketmine\network\mcpe\protocol\tools\generate_protocol_info;
21use pocketmine\network\mcpe\protocol\PacketHandlerDefaultImplTrait;
25use
function array_fill_keys;
31use
function file_exists;
32use
function file_get_contents;
33use
function file_put_contents;
39use
function is_string;
40use
function json_decode;
42use
function preg_split;
45use
function str_contains;
46use
function str_ends_with;
50use
function strtoupper;
52use
const DIRECTORY_SEPARATOR;
53use
const JSON_THROW_ON_ERROR;
55use
const PREG_SPLIT_DELIM_CAPTURE;
56use
const PREG_SPLIT_NO_EMPTY;
57use
const SORT_NUMERIC;
59use
const STR_PAD_LEFT;
61const DATA_PACKET_TEMPLATE = <<<
'CODE'
74declare(strict_types=1);
76namespace pocketmine\network\mcpe\protocol;
78use pmmp\encoding\ByteBufferReader;
79use pmmp\encoding\ByteBufferWriter;
81class %s extends DataPacket{
82 public const NETWORK_ID = ProtocolInfo::%s;
87 public static function create() : self{
93 protected function decodePayload(ByteBufferReader $in) : void{
97 protected function encodePayload(ByteBufferWriter $out) : void{
101 public function handle(PacketHandlerInterface $handler) : bool{
102 return $handler->handle%s($this);
108const PACKET_HANDLER_TRAIT_TEMPLATE = <<<
'CODE'
121declare(strict_types=1);
123namespace pocketmine\network\mcpe\protocol;
131trait PacketHandlerDefaultImplTrait{
138const PACKET_HANDLER_INTERFACE_TEMPLATE = <<<
'CODE'
151declare(strict_types=1);
153namespace pocketmine\network\mcpe\protocol;
158interface PacketHandlerInterface{
164const PACKET_POOL_TEMPLATE = <<<
'CODE'
177declare(strict_types=1);
179namespace pocketmine\network\mcpe\protocol;
181use pmmp\encoding\DataDecodeException;
182use pmmp\encoding\VarInt;
183use
function array_filter;
184use
function is_object;
187 protected static ?PacketPool $instance =
null;
189 public static function getInstance() : self{
190 if(self::$instance === null){
191 self::$instance =
new self;
193 return self::$instance;
197 protected \SplFixedArray $pool;
199 public function __construct(){
200 $this->pool = new \SplFixedArray(%d);
204 public function registerPacket(Packet $packet) : void{
205 $this->pool[$packet->pid()] = clone $packet;
208 public function getPacketById(
int $pid) : ?Packet{
209 return isset($this->pool[$pid]) ? clone $this->pool[$pid] : null;
215 public function getPacket(
string $buffer) : ?Packet{
216 return $this->getPacketById(VarInt::unpackUnsignedInt($buffer) & DataPacket::PID_MASK);
223 public function getAll() : array{
224 return array_filter($this->pool->toArray(), is_object(...));
230const PROTOCOL_INFO_TEMPLATE = <<<
'CODE'
243declare(strict_types=1);
245namespace pocketmine\network\mcpe\protocol;
250final class ProtocolInfo{
252 private function __construct(){
276const CPP_NAMESPACE_SEPARATOR =
"::";
281function split_upper(
string $string) : array{
282 $split = preg_split(
'/([A-Z][^A-Z]+)/', $string, flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
283 if($split ===
false){
284 throw new \Error(
"preg_split failed");
289function rchop(
string $string,
string $substring) : string{
290 if(str_ends_with($string, $substring)){
291 return substr($string, 0, -strlen($substring));
300function generate_new_packet_stubs(array $packetToIdList,
string $packetsDir) : void{
301 foreach($packetToIdList as $name => $id){
302 $packetFilePath = $packetsDir . DIRECTORY_SEPARATOR . $name .
'.php';
303 if(!file_exists($packetFilePath)){
304 echo
"!!! New packet: $name" . PHP_EOL;
305 $constName = strtoupper(implode(
'_', split_upper($name)));
306 $baseName = rchop($name,
'Packet');
307 file_put_contents($packetFilePath, sprintf(DATA_PACKET_TEMPLATE, $name, $constName, $baseName));
308 echo
"Created stub class for $name at $packetFilePath" . PHP_EOL;
317function check_removed_packets(array $packetToIdList,
string $packetsDir) : void{
318 $existing = scandir($packetsDir);
319 if($existing ===
false){
324 $ignoredClasses = array_fill_keys([
328 PacketDecodeException::class,
329 PacketHandlerDefaultImplTrait::class,
330 PacketHandlerInterface::class,
331 ClientboundPacket::class,
332 ServerboundPacket::class,
334 foreach($existing as $fileName){
335 if(str_ends_with($fileName,
".php")){
336 $packetName = substr($fileName, 0, -strlen(
".php"));
337 if(!str_contains($packetName,
"Packet") || isset($ignoredClasses[
"pocketmine\\network\\mcpe\\protocol\\" . $packetName])){
340 if(!isset($packetToIdList[$packetName])){
341 echo
"!!! Removed packet: $packetName" . PHP_EOL;
351function generate_protocol_info(array $packetToIdList,
int $protocolVersion,
int $major,
int $minor,
int $patch,
int $revision,
bool $beta,
string $packetsDir) : void{
355 foreach($packetToIdList as $name => $id){
356 if($id !== $last + 1){
362 "\tpublic const %s = %s;\n",
363 strtoupper(implode(
"_", split_upper($name))),
364 "0x" . str_pad(dechex($id), 2,
"0", STR_PAD_LEFT)
368 if($major === 1 && $minor >= 26){
371 $gameVersion = sprintf(
"v%d.%d%s", $minor, $patch, $beta ?
".$revision beta" :
"");
373 $gameVersion = sprintf(
"v%d.%d.%d%s", $major, $minor, $patch, $beta ?
".$revision beta" :
"");
375 $gameVersionNetwork = sprintf(
"%d.%d.%d%s", $major, $minor, $patch, $beta ?
".$revision" :
"");
376 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"ProtocolInfo.php", sprintf(
377 PROTOCOL_INFO_TEMPLATE,
384 echo
"Recreated ProtocolInfo" . PHP_EOL;
391function generate_packet_pool(array $packetToIdList,
string $packetsDir) : void{
394 foreach($packetToIdList as $name => $id){
395 $entries .= sprintf(
"\n\t\t\$this->registerPacket(new %s());", $name);
398 $poolSize = (int) (ceil(max($packetToIdList) / 256) * 256);
399 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"PacketPool.php", sprintf(
400 PACKET_POOL_TEMPLATE,
404 echo
"Recreated PacketPool\n";
411function generate_packet_handler_classes(array $packetToIdList,
string $packetsDir) : void{
412 $interfaceFunctions = [];
413 $traitFunctions = [];
415 foreach($packetToIdList as $name => $id){
416 $baseName = rchop($name,
"Packet");
417 $interfaceFunctions[] = sprintf(
"\tpublic function handle%s(%s \$packet) : bool;", $baseName, $name);
418 $traitFunctions[] = sprintf(
"\tpublic function handle%s(%s \$packet) : bool{\n\t\treturn false;\n\t}", $baseName, $name);
421 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"PacketHandlerInterface.php", sprintf(
422 PACKET_HANDLER_INTERFACE_TEMPLATE,
423 implode(
"\n\n", $interfaceFunctions)
425 echo
"Recreated PacketHandlerInterface" . PHP_EOL;
426 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"PacketHandlerDefaultImplTrait.php", sprintf(
427 PACKET_HANDLER_TRAIT_TEMPLATE,
428 implode(
"\n\n", $traitFunctions)
430 echo
"Recreated PacketHandlerDefaultImplTrait" . PHP_EOL;
434 fwrite(STDERR,
"Please provide an input protocol_info.json file" . PHP_EOL);
438$rawData = file_get_contents($argv[1]);
439if($rawData ===
false){
440 fwrite(STDERR,
"Couldn't read data from " . $argv[1] . PHP_EOL);
445 $json = json_decode($rawData, associative:
true, flags: JSON_THROW_ON_ERROR);
446}
catch(\JsonException $e){
447 fwrite(STDERR,
"Error decoding input file: " . $e->getMessage() . PHP_EOL);
450if(!is_array($json) || count($json) !== 2 || !is_array($json[
"version"] ??
null) || !is_array($json[
"packets"] ??
null)){
451 fwrite(STDERR,
"Invalid input file, expected 2 objects: \"version\" and \"packets\"" . PHP_EOL);
455$versionInfo = $json[
"version"];
456$major = $versionInfo[
"major"] ??
null;
457$minor = $versionInfo[
"minor"] ??
null;
458$patch = $versionInfo[
"patch"] ??
null;
459$revision = $versionInfo[
"revision"] ??
null;
460$beta = $versionInfo[
"beta"] ??
null;
461$protocolVersion = $versionInfo[
"protocol_version"] ??
null;
462if(!is_int($major) || !is_int($minor) || !is_int($patch) || !is_int($revision) || !is_bool($beta) || !is_int($protocolVersion)){
463 fwrite(STDERR,
"Invalid version info, expected \"major\" (int), \"minor\" (int), \"patch\" (int), \"revision\" (int), \"beta\" (bool) and \"protocol_version\" (int)" . PHP_EOL);
467echo
"Generating code basics for version $major.$minor.$patch.$revision " . ($beta ?
"beta" :
"") .
" (protocol $protocolVersion)" . PHP_EOL;
470foreach($json[
"packets"] as $name => $id){
471 if(!is_string($name) || !is_int($id)){
472 fwrite(STDERR,
"Invalid packet entry \"$name\", expected string => int" . PHP_EOL);
475 $namespaceSeparatorPos = strrpos($name, CPP_NAMESPACE_SEPARATOR);
476 if($namespaceSeparatorPos !==
false){
479 echo
"Warning: Discarded C++ namespace for $name - this might result in class name conflicts" . PHP_EOL;
480 $name = substr($name, $namespaceSeparatorPos + strlen(CPP_NAMESPACE_SEPARATOR));
482 $packetToIdList[$name] = $id;
484asort($packetToIdList, SORT_NUMERIC);
486$packetsDir = dirname(__DIR__) .
'/src/';
487generate_protocol_info($packetToIdList, $protocolVersion, $major, $minor, $patch, $revision, $beta, $packetsDir);
488generate_packet_pool($packetToIdList, $packetsDir);
489generate_packet_handler_classes($packetToIdList, $packetsDir);
490check_removed_packets($packetToIdList, $packetsDir);
491generate_new_packet_stubs($packetToIdList, $packetsDir);
493echo
"Done" . PHP_EOL;
const MINECRAFT_VERSION_NETWORK