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;
 
  185    protected static ?PacketPool $instance = 
null;
 
  187    public static function getInstance() : self{
 
  188        if(self::$instance === null){
 
  189            self::$instance = 
new self;
 
  191        return self::$instance;
 
  195    protected \SplFixedArray $pool;
 
  197    public function __construct(){
 
  198        $this->pool = new \SplFixedArray(%d);
 
  202    public function registerPacket(Packet $packet) : void{
 
  203        $this->pool[$packet->pid()] = clone $packet;
 
  206    public function getPacketById(
int $pid) : ?Packet{
 
  207        return isset($this->pool[$pid]) ? clone $this->pool[$pid] : null;
 
  213    public function getPacket(
string $buffer) : ?Packet{
 
  214        return $this->getPacketById(VarInt::unpackUnsignedInt($buffer) & 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