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;
80class %s extends DataPacket{
81 public const NETWORK_ID = ProtocolInfo::%s;
86 public static function create() : self{
92 protected function decodePayload(PacketSerializer $in) : void{
96 protected function encodePayload(PacketSerializer $out) : void{
100 public function handle(PacketHandlerInterface $handler) : bool{
101 return $handler->handle%s($this);
107const PACKET_HANDLER_TRAIT_TEMPLATE = <<<
'CODE'
120declare(strict_types=1);
122namespace pocketmine\network\mcpe\protocol;
130trait PacketHandlerDefaultImplTrait{
137const PACKET_HANDLER_INTERFACE_TEMPLATE = <<<
'CODE'
150declare(strict_types=1);
152namespace pocketmine\network\mcpe\protocol;
157interface PacketHandlerInterface{
163const PACKET_POOL_TEMPLATE = <<<
'CODE'
176declare(strict_types=1);
178namespace pocketmine\network\mcpe\protocol;
184 protected static ?PacketPool $instance =
null;
186 public static function getInstance() : self{
187 if(self::$instance === null){
188 self::$instance =
new self;
190 return self::$instance;
194 protected \SplFixedArray $pool;
196 public function __construct(){
197 $this->pool = new \SplFixedArray(%d);
201 public function registerPacket(Packet $packet) : void{
202 $this->pool[$packet->pid()] = clone $packet;
205 public function getPacketById(
int $pid) : ?Packet{
206 return isset($this->pool[$pid]) ? clone $this->pool[$pid] : null;
212 public function getPacket(
string $buffer) : ?Packet{
214 return $this->getPacketById(Binary::readUnsignedVarInt($buffer, $offset) & DataPacket::PID_MASK);
220const PROTOCOL_INFO_TEMPLATE = <<<
'CODE'
233declare(strict_types=1);
235namespace pocketmine\network\mcpe\protocol;
240final class ProtocolInfo{
242 private function __construct(){
266const CPP_NAMESPACE_SEPARATOR =
"::";
271function split_upper(
string $string) : array{
272 $split = preg_split(
'/([A-Z][^A-Z]*)/', $string, flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
273 if($split ===
false){
274 throw new \Error(
"preg_split failed");
279function rchop(
string $string,
string $substring) : string{
280 if(str_ends_with($string, $substring)){
281 return substr($string, 0, -strlen($substring));
290function generate_new_packet_stubs(array $packetToIdList,
string $packetsDir) : void{
291 foreach($packetToIdList as $name => $id){
292 $packetFilePath = $packetsDir . DIRECTORY_SEPARATOR . $name .
'.php';
293 if(!file_exists($packetFilePath)){
294 echo
"!!! New packet: $name" . PHP_EOL;
295 $constName = strtoupper(implode(
'_', split_upper($name)));
296 $baseName = rchop($name,
'Packet');
297 file_put_contents($packetFilePath, sprintf(DATA_PACKET_TEMPLATE, $name, $constName, $baseName));
298 echo
"Created stub class for $name at $packetFilePath" . PHP_EOL;
307function check_removed_packets(array $packetToIdList,
string $packetsDir) : void{
308 $existing = scandir($packetsDir);
309 if($existing ===
false){
314 $ignoredClasses = array_fill_keys([
318 PacketDecodeException::class,
319 PacketHandlerDefaultImplTrait::class,
320 PacketHandlerInterface::class,
321 ClientboundPacket::class,
322 ServerboundPacket::class,
324 foreach($existing as $fileName){
325 if(str_ends_with($fileName,
".php")){
326 $packetName = substr($fileName, 0, -strlen(
".php"));
327 if(!str_contains($packetName,
"Packet") || isset($ignoredClasses[
"pocketmine\\network\\mcpe\\protocol\\" . $packetName])){
330 if(!isset($packetToIdList[$packetName])){
331 echo
"!!! Removed packet: $packetName" . PHP_EOL;
341function generate_protocol_info(array $packetToIdList,
int $protocolVersion,
int $major,
int $minor,
int $patch,
int $revision,
bool $beta,
string $packetsDir) : void{
345 foreach($packetToIdList as $name => $id){
346 if($id !== $last + 1){
352 "\tpublic const %s = %s;\n",
353 strtoupper(implode(
"_", split_upper($name))),
354 "0x" . str_pad(dechex($id), 2,
"0", STR_PAD_LEFT)
358 $gameVersion = sprintf(
"v%d.%d.%d%s", $major, $minor, $patch, $beta ?
".$revision beta" :
"");
359 $gameVersionNetwork = sprintf(
"%d.%d.%d%s", $major, $minor, $patch, $beta ?
".$revision" :
"");
360 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"ProtocolInfo.php", sprintf(
361 PROTOCOL_INFO_TEMPLATE,
368 echo
"Recreated ProtocolInfo" . PHP_EOL;
375function generate_packet_pool(array $packetToIdList,
string $packetsDir) : void{
378 foreach($packetToIdList as $name => $id){
379 $entries .= sprintf(
"\n\t\t\$this->registerPacket(new %s());", $name);
382 $poolSize = (int) (ceil(max($packetToIdList) / 256) * 256);
383 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"PacketPool.php", sprintf(
384 PACKET_POOL_TEMPLATE,
388 echo
"Recreated PacketPool\n";
395function generate_packet_handler_classes(array $packetToIdList,
string $packetsDir) : void{
396 $interfaceFunctions = [];
397 $traitFunctions = [];
399 foreach($packetToIdList as $name => $id){
400 $baseName = rchop($name,
"Packet");
401 $interfaceFunctions[] = sprintf(
"\tpublic function handle%s(%s \$packet) : bool;", $baseName, $name);
402 $traitFunctions[] = sprintf(
"\tpublic function handle%s(%s \$packet) : bool{\n\t\treturn false;\n\t}", $baseName, $name);
405 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"PacketHandlerInterface.php", sprintf(
406 PACKET_HANDLER_INTERFACE_TEMPLATE,
407 implode(
"\n\n", $interfaceFunctions)
409 echo
"Recreated PacketHandlerInterface" . PHP_EOL;
410 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"PacketHandlerDefaultImplTrait.php", sprintf(
411 PACKET_HANDLER_TRAIT_TEMPLATE,
412 implode(
"\n\n", $traitFunctions)
414 echo
"Recreated PacketHandlerDefaultImplTrait" . PHP_EOL;
418 fwrite(STDERR,
"Please provide an input protocol_info.json file" . PHP_EOL);
422$rawData = file_get_contents($argv[1]);
423if($rawData ===
false){
424 fwrite(STDERR,
"Couldn't read data from " . $argv[1] . PHP_EOL);
429 $json = json_decode($rawData, associative:
true, flags: JSON_THROW_ON_ERROR);
430}
catch(\JsonException $e){
431 fwrite(STDERR,
"Error decoding input file: " . $e->getMessage() . PHP_EOL);
434if(!is_array($json) || count($json) !== 2 || !is_array($json[
"version"] ??
null) || !is_array($json[
"packets"] ??
null)){
435 fwrite(STDERR,
"Invalid input file, expected 2 objects: \"version\" and \"packets\"" . PHP_EOL);
439$versionInfo = $json[
"version"];
440$major = $versionInfo[
"major"] ??
null;
441$minor = $versionInfo[
"minor"] ??
null;
442$patch = $versionInfo[
"patch"] ??
null;
443$revision = $versionInfo[
"revision"] ??
null;
444$beta = $versionInfo[
"beta"] ??
null;
445$protocolVersion = $versionInfo[
"protocol_version"] ??
null;
446if(!is_int($major) || !is_int($minor) || !is_int($patch) || !is_int($revision) || !is_bool($beta) || !is_int($protocolVersion)){
447 fwrite(STDERR,
"Invalid version info, expected \"major\" (int), \"minor\" (int), \"patch\" (int), \"revision\" (int), \"beta\" (bool) and \"protocol_version\" (int)" . PHP_EOL);
451echo
"Generating code basics for version $major.$minor.$patch.$revision " . ($beta ?
"beta" :
"") .
" (protocol $protocolVersion)" . PHP_EOL;
454foreach($json[
"packets"] as $name => $id){
455 if(!is_string($name) || !is_int($id)){
456 fwrite(STDERR,
"Invalid packet entry \"$name\", expected string => int" . PHP_EOL);
459 $namespaceSeparatorPos = strrpos($name, CPP_NAMESPACE_SEPARATOR);
460 if($namespaceSeparatorPos !==
false){
463 echo
"Warning: Discarded C++ namespace for $name - this might result in class name conflicts" . PHP_EOL;
464 $name = substr($name, $namespaceSeparatorPos + strlen(CPP_NAMESPACE_SEPARATOR));
466 $packetToIdList[$name] = $id;
468asort($packetToIdList, SORT_NUMERIC);
470$packetsDir = dirname(__DIR__) .
'/src/';
471generate_protocol_info($packetToIdList, $protocolVersion, $major, $minor, $patch, $revision, $beta, $packetsDir);
472generate_packet_pool($packetToIdList, $packetsDir);
473generate_packet_handler_classes($packetToIdList, $packetsDir);
474check_removed_packets($packetToIdList, $packetsDir);
475generate_new_packet_stubs($packetToIdList, $packetsDir);
477echo
"Done" . PHP_EOL;
const MINECRAFT_VERSION_NETWORK