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;
49use
function strtoupper;
51use
const DIRECTORY_SEPARATOR;
52use
const JSON_THROW_ON_ERROR;
54use
const PREG_SPLIT_DELIM_CAPTURE;
55use
const PREG_SPLIT_NO_EMPTY;
56use
const SORT_NUMERIC;
58use
const STR_PAD_LEFT;
60const DATA_PACKET_TEMPLATE = <<<
'CODE'
73declare(strict_types=1);
75namespace pocketmine\network\mcpe\protocol;
79class %s extends DataPacket{
80 public const NETWORK_ID = ProtocolInfo::%s;
85 public static function create() : self{
91 protected function decodePayload(PacketSerializer $in) : void{
95 protected function encodePayload(PacketSerializer $out) : void{
99 public function handle(PacketHandlerInterface $handler) : bool{
100 return $handler->handle%s($this);
106const PACKET_HANDLER_TRAIT_TEMPLATE = <<<
'CODE'
119declare(strict_types=1);
121namespace pocketmine\network\mcpe\protocol;
129trait PacketHandlerDefaultImplTrait{
136const PACKET_HANDLER_INTERFACE_TEMPLATE = <<<
'CODE'
149declare(strict_types=1);
151namespace pocketmine\network\mcpe\protocol;
156interface PacketHandlerInterface{
162const PACKET_POOL_TEMPLATE = <<<
'CODE'
175declare(strict_types=1);
177namespace pocketmine\network\mcpe\protocol;
183 protected static ?PacketPool $instance =
null;
185 public static function getInstance() : self{
186 if(self::$instance === null){
187 self::$instance =
new self;
189 return self::$instance;
193 protected \SplFixedArray $pool;
195 public function __construct(){
196 $this->pool = new \SplFixedArray(%d);
200 public function registerPacket(Packet $packet) : void{
201 $this->pool[$packet->pid()] = clone $packet;
204 public function getPacketById(
int $pid) : ?Packet{
205 return isset($this->pool[$pid]) ? clone $this->pool[$pid] : null;
211 public function getPacket(
string $buffer) : ?Packet{
213 return $this->getPacketById(Binary::readUnsignedVarInt($buffer, $offset) & DataPacket::PID_MASK);
219const PROTOCOL_INFO_TEMPLATE = <<<
'CODE'
232declare(strict_types=1);
234namespace pocketmine\network\mcpe\protocol;
239final class ProtocolInfo{
241 private function __construct(){
268function split_upper(
string $string) : array{
269 $split = preg_split(
'/([A-Z][^A-Z]*)/', $string, flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
270 if($split ===
false){
271 throw new \Error(
"preg_split failed");
276function rchop(
string $string,
string $substring) : string{
277 if(str_ends_with($string, $substring)){
278 return substr($string, 0, -strlen($substring));
287function generate_new_packet_stubs(array $packetToIdList,
string $packetsDir) : void{
288 foreach($packetToIdList as $name => $id){
289 $packetFilePath = $packetsDir . DIRECTORY_SEPARATOR . $name .
'.php';
290 if(!file_exists($packetFilePath)){
291 echo
"!!! New packet: $name" . PHP_EOL;
292 $constName = strtoupper(implode(
'_', split_upper($name)));
293 $baseName = rchop($name,
'Packet');
294 file_put_contents($packetFilePath, sprintf(DATA_PACKET_TEMPLATE, $name, $constName, $baseName));
295 echo
"Created stub class for $name at $packetFilePath" . PHP_EOL;
304function check_removed_packets(array $packetToIdList,
string $packetsDir) : void{
305 $existing = scandir($packetsDir);
306 if($existing ===
false){
311 $ignoredClasses = array_fill_keys([
315 PacketDecodeException::class,
316 PacketHandlerDefaultImplTrait::class,
317 PacketHandlerInterface::class,
318 ClientboundPacket::class,
319 ServerboundPacket::class,
321 foreach($existing as $fileName){
322 if(str_ends_with($fileName,
".php")){
323 $packetName = substr($fileName, 0, -strlen(
".php"));
324 if(!str_contains($packetName,
"Packet") || isset($ignoredClasses[
"pocketmine\\network\\mcpe\\protocol\\" . $packetName])){
327 if(!isset($packetToIdList[$packetName])){
328 echo
"!!! Removed packet: $packetName" . PHP_EOL;
338function generate_protocol_info(array $packetToIdList,
int $protocolVersion,
int $major,
int $minor,
int $patch,
int $revision,
bool $beta,
string $packetsDir) : void{
342 foreach($packetToIdList as $name => $id){
343 if($id !== $last + 1){
349 "\tpublic const %s = %s;\n",
350 strtoupper(implode(
"_", split_upper($name))),
351 "0x" . str_pad(dechex($id), 2,
"0", STR_PAD_LEFT)
355 $gameVersion = sprintf(
"v%d.%d.%d%s", $major, $minor, $patch, $beta ?
".$revision beta" :
"");
356 $gameVersionNetwork = sprintf(
"%d.%d.%d%s", $major, $minor, $patch, $beta ?
".$revision" :
"");
357 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"ProtocolInfo.php", sprintf(
358 PROTOCOL_INFO_TEMPLATE,
365 echo
"Recreated ProtocolInfo" . PHP_EOL;
372function generate_packet_pool(array $packetToIdList,
string $packetsDir) : void{
375 foreach($packetToIdList as $name => $id){
376 $entries .= sprintf(
"\n\t\t\$this->registerPacket(new %s());", $name);
379 $poolSize = (int) (ceil(max($packetToIdList) / 256) * 256);
380 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"PacketPool.php", sprintf(
381 PACKET_POOL_TEMPLATE,
385 echo
"Recreated PacketPool\n";
392function generate_packet_handler_classes(array $packetToIdList,
string $packetsDir) : void{
393 $interfaceFunctions = [];
394 $traitFunctions = [];
396 foreach($packetToIdList as $name => $id){
397 $baseName = rchop($name,
"Packet");
398 $interfaceFunctions[] = sprintf(
"\tpublic function handle%s(%s \$packet) : bool;", $baseName, $name);
399 $traitFunctions[] = sprintf(
"\tpublic function handle%s(%s \$packet) : bool{\n\t\treturn false;\n\t}", $baseName, $name);
402 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"PacketHandlerInterface.php", sprintf(
403 PACKET_HANDLER_INTERFACE_TEMPLATE,
404 implode(
"\n\n", $interfaceFunctions)
406 echo
"Recreated PacketHandlerInterface" . PHP_EOL;
407 file_put_contents($packetsDir . DIRECTORY_SEPARATOR .
"PacketHandlerDefaultImplTrait.php", sprintf(
408 PACKET_HANDLER_TRAIT_TEMPLATE,
409 implode(
"\n\n", $traitFunctions)
411 echo
"Recreated PacketHandlerDefaultImplTrait" . PHP_EOL;
415 fwrite(STDERR,
"Please provide an input protocol_info.json file" . PHP_EOL);
419$rawData = file_get_contents($argv[1]);
420if($rawData ===
false){
421 fwrite(STDERR,
"Couldn't read data from " . $argv[1] . PHP_EOL);
426 $json = json_decode($rawData, associative:
true, flags: JSON_THROW_ON_ERROR);
427}
catch(\JsonException $e){
428 fwrite(STDERR,
"Error decoding input file: " . $e->getMessage() . PHP_EOL);
431if(!is_array($json) || count($json) !== 2 || !is_array($json[
"version"] ??
null) || !is_array($json[
"packets"] ??
null)){
432 fwrite(STDERR,
"Invalid input file, expected 2 objects: \"version\" and \"packets\"" . PHP_EOL);
436$versionInfo = $json[
"version"];
437$major = $versionInfo[
"major"] ??
null;
438$minor = $versionInfo[
"minor"] ??
null;
439$patch = $versionInfo[
"patch"] ??
null;
440$revision = $versionInfo[
"revision"] ??
null;
441$beta = $versionInfo[
"beta"] ??
null;
442$protocolVersion = $versionInfo[
"protocol_version"] ??
null;
443if(!is_int($major) || !is_int($minor) || !is_int($patch) || !is_int($revision) || !is_bool($beta) || !is_int($protocolVersion)){
444 fwrite(STDERR,
"Invalid version info, expected \"major\" (int), \"minor\" (int), \"patch\" (int), \"revision\" (int), \"beta\" (bool) and \"protocol_version\" (int)" . PHP_EOL);
448echo
"Generating code basics for version $major.$minor.$patch.$revision " . ($beta ?
"beta" :
"") .
" (protocol $protocolVersion)" . PHP_EOL;
451foreach($json[
"packets"] as $name => $id){
452 if(!is_string($name) || !is_int($id)){
453 fwrite(STDERR,
"Invalid packet entry \"$name\", expected string => int" . PHP_EOL);
456 $packetToIdList[$name] = $id;
458asort($packetToIdList, SORT_NUMERIC);
460$packetsDir = dirname(__DIR__) .
'/src/';
461generate_protocol_info($packetToIdList, $protocolVersion, $major, $minor, $patch, $revision, $beta, $packetsDir);
462generate_packet_pool($packetToIdList, $packetsDir);
463generate_packet_handler_classes($packetToIdList, $packetsDir);
464check_removed_packets($packetToIdList, $packetsDir);
465generate_new_packet_stubs($packetToIdList, $packetsDir);
467echo
"Done" . PHP_EOL;
const MINECRAFT_VERSION_NETWORK