PocketMine-MP 5.42.2 git-40e2639b20bdc4ddba49d9f7f5fa0d5e92aa266f
Loading...
Searching...
No Matches
generate-protocol-info.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\tools\generate_protocol_info;
16
21use pocketmine\network\mcpe\protocol\PacketHandlerDefaultImplTrait;
25use function array_fill_keys;
26use function asort;
27use function ceil;
28use function count;
29use function dechex;
30use function dirname;
31use function file_exists;
32use function file_get_contents;
33use function file_put_contents;
34use function fwrite;
35use function implode;
36use function is_array;
37use function is_bool;
38use function is_int;
39use function is_string;
40use function json_decode;
41use function max;
42use function preg_split;
43use function scandir;
44use function sprintf;
45use function str_contains;
46use function str_ends_with;
47use function str_pad;
48use function strlen;
49use function strrpos;
50use function strtoupper;
51use function substr;
52use const DIRECTORY_SEPARATOR;
53use const JSON_THROW_ON_ERROR;
54use const PHP_EOL;
55use const PREG_SPLIT_DELIM_CAPTURE;
56use const PREG_SPLIT_NO_EMPTY;
57use const SORT_NUMERIC;
58use const STDERR;
59use const STR_PAD_LEFT;
60
61const DATA_PACKET_TEMPLATE = <<<'CODE'
62<?php
63
64/*
65 * This file is part of BedrockProtocol.
66 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
67 *
68 * BedrockProtocol is free software: you can redistribute it and/or modify
69 * it under the terms of the GNU Lesser General Public License as published by
70 * the Free Software Foundation, either version 3 of the License, or
71 * (at your option) any later version.
72 */
73
74declare(strict_types=1);
75
76namespace pocketmine\network\mcpe\protocol;
77
78use pmmp\encoding\ByteBufferReader;
79use pmmp\encoding\ByteBufferWriter;
80
81class %s extends DataPacket{
82 public const NETWORK_ID = ProtocolInfo::%s;
83
87 public static function create() : self{
88 $result = new self;
89 //TODO: add fields
90 return $result;
91 }
92
93 protected function decodePayload(ByteBufferReader $in) : void{
94 //TODO
95 }
96
97 protected function encodePayload(ByteBufferWriter $out) : void{
98 //TODO
99 }
100
101 public function handle(PacketHandlerInterface $handler) : bool{
102 return $handler->handle%s($this);
103 }
104}
105
106CODE;
107
108const PACKET_HANDLER_TRAIT_TEMPLATE = <<<'CODE'
109<?php
110
111/*
112 * This file is part of BedrockProtocol.
113 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
114 *
115 * BedrockProtocol is free software: you can redistribute it and/or modify
116 * it under the terms of the GNU Lesser General Public License as published by
117 * the Free Software Foundation, either version 3 of the License, or
118 * (at your option) any later version.
119 */
120
121declare(strict_types=1);
122
123namespace pocketmine\network\mcpe\protocol;
124
131trait PacketHandlerDefaultImplTrait{
132
133%s
134}
135
136CODE;
137
138const PACKET_HANDLER_INTERFACE_TEMPLATE = <<<'CODE'
139<?php
140
141/*
142 * This file is part of BedrockProtocol.
143 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
144 *
145 * BedrockProtocol is free software: you can redistribute it and/or modify
146 * it under the terms of the GNU Lesser General Public License as published by
147 * the Free Software Foundation, either version 3 of the License, or
148 * (at your option) any later version.
149 */
150
151declare(strict_types=1);
152
153namespace pocketmine\network\mcpe\protocol;
154
158interface PacketHandlerInterface{
159%s
160}
161
162CODE;
163
164const PACKET_POOL_TEMPLATE = <<<'CODE'
165<?php
166
167/*
168 * This file is part of BedrockProtocol.
169 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
170 *
171 * BedrockProtocol is free software: you can redistribute it and/or modify
172 * it under the terms of the GNU Lesser General Public License as published by
173 * the Free Software Foundation, either version 3 of the License, or
174 * (at your option) any later version.
175 */
176
177declare(strict_types=1);
178
179namespace pocketmine\network\mcpe\protocol;
180
181use pmmp\encoding\DataDecodeException;
182use pmmp\encoding\VarInt;
183use function array_filter;
184use function is_object;
185
186class PacketPool{
187 protected static ?PacketPool $instance = null;
188
189 public static function getInstance() : self{
190 if(self::$instance === null){
191 self::$instance = new self;
192 }
193 return self::$instance;
194 }
195
197 protected \SplFixedArray $pool;
198
199 public function __construct(){
200 $this->pool = new \SplFixedArray(%d);
201%s
202 }
203
204 public function registerPacket(Packet $packet) : void{
205 $this->pool[$packet->pid()] = clone $packet;
206 }
207
208 public function getPacketById(int $pid) : ?Packet{
209 return isset($this->pool[$pid]) ? clone $this->pool[$pid] : null;
210 }
211
215 public function getPacket(string $buffer) : ?Packet{
216 return $this->getPacketById(VarInt::unpackUnsignedInt($buffer) & DataPacket::PID_MASK);
217 }
218
223 public function getAll() : array{
224 return array_filter($this->pool->toArray(), is_object(...));
225 }
226}
227
228CODE;
229
230const PROTOCOL_INFO_TEMPLATE = <<<'CODE'
231<?php
232
233/*
234 * This file is part of BedrockProtocol.
235 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
236 *
237 * BedrockProtocol is free software: you can redistribute it and/or modify
238 * it under the terms of the GNU Lesser General Public License as published by
239 * the Free Software Foundation, either version 3 of the License, or
240 * (at your option) any later version.
241 */
242
243declare(strict_types=1);
244
245namespace pocketmine\network\mcpe\protocol;
246
250final class ProtocolInfo{
251
252 private function __construct(){
253 //NOOP
254 }
255
265 public const CURRENT_PROTOCOL = %d;
267 public const MINECRAFT_VERSION = '%s';
269 public const MINECRAFT_VERSION_NETWORK = '%s';
270
271%s
272}
273
274CODE;
275
276const CPP_NAMESPACE_SEPARATOR = "::";
277
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");
285 }
286 return $split;
287}
288
289function rchop(string $string, string $substring) : string{
290 if(str_ends_with($string, $substring)){
291 return substr($string, 0, -strlen($substring));
292 }
293 return $string;
294}
295
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;
309 }
310 }
311}
312
317function check_removed_packets(array $packetToIdList, string $packetsDir) : void{
318 $existing = scandir($packetsDir);
319 if($existing === false){
320 return;
321 }
322
323 //use ::class constants here so that they are checked for existence
324 $ignoredClasses = array_fill_keys([
325 DataPacket::class,
326 PacketPool::class,
327 Packet::class,
328 PacketDecodeException::class,
329 PacketHandlerDefaultImplTrait::class,
330 PacketHandlerInterface::class,
331 ClientboundPacket::class,
332 ServerboundPacket::class,
333 ], true);
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])){
338 continue;
339 }
340 if(!isset($packetToIdList[$packetName])){
341 echo "!!! Removed packet: $packetName" . PHP_EOL;
342 }
343 }
344 }
345}
346
351function generate_protocol_info(array $packetToIdList, int $protocolVersion, int $major, int $minor, int $patch, int $revision, bool $beta, string $packetsDir) : void{
352 $consts = "";
353 $last = 0;
354
355 foreach($packetToIdList as $name => $id){
356 if($id !== $last + 1){
357 $consts .= "\n";
358 }
359
360 $last = $id;
361 $consts .= sprintf(
362 "\tpublic const %s = %s;\n",
363 strtoupper(implode("_", split_upper($name))),
364 "0x" . str_pad(dechex($id), 2, "0", STR_PAD_LEFT)
365 );
366 }
367
368 if($major === 1 && $minor >= 26){
369 //major version is no longer displayed as of 1.26.0, presumably because the 1 is meaningless
370 //however it is still shown in the network version for BC reasons
371 $gameVersion = sprintf("v%d.%d%s", $minor, $patch, $beta ? ".$revision beta" : "");
372 }else{
373 $gameVersion = sprintf("v%d.%d.%d%s", $major, $minor, $patch, $beta ? ".$revision beta" : "");
374 }
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,
378 $protocolVersion,
379 $gameVersion,
380 $gameVersionNetwork,
381 $consts
382 ));
383
384 echo "Recreated ProtocolInfo" . PHP_EOL;
385}
386
391function generate_packet_pool(array $packetToIdList, string $packetsDir) : void{
392 $entries = "";
393
394 foreach($packetToIdList as $name => $id){
395 $entries .= sprintf("\n\t\t\$this->registerPacket(new %s());", $name);
396 }
397
398 $poolSize = (int) (ceil(max($packetToIdList) / 256) * 256);
399 file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketPool.php", sprintf(
400 PACKET_POOL_TEMPLATE,
401 $poolSize,
402 $entries
403 ));
404 echo "Recreated PacketPool\n";
405}
406
411function generate_packet_handler_classes(array $packetToIdList, string $packetsDir) : void{
412 $interfaceFunctions = [];
413 $traitFunctions = [];
414
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);
419 }
420
421 file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketHandlerInterface.php", sprintf(
422 PACKET_HANDLER_INTERFACE_TEMPLATE,
423 implode("\n\n", $interfaceFunctions)
424 ));
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)
429 ));
430 echo "Recreated PacketHandlerDefaultImplTrait" . PHP_EOL;
431}
432
433if(count($argv) < 2){
434 fwrite(STDERR, "Please provide an input protocol_info.json file" . PHP_EOL);
435 exit(1);
436}
437
438$rawData = file_get_contents($argv[1]);
439if($rawData === false){
440 fwrite(STDERR, "Couldn't read data from " . $argv[1] . PHP_EOL);
441 exit(1);
442}
443
444try{
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);
448 exit(1);
449}
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);
452 exit(1);
453}
454
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);
464 exit(1);
465}
466
467echo "Generating code basics for version $major.$minor.$patch.$revision " . ($beta ? "beta" : "") . " (protocol $protocolVersion)" . PHP_EOL;
468
469$packetToIdList = [];
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);
473 exit(1);
474 }
475 $namespaceSeparatorPos = strrpos($name, CPP_NAMESPACE_SEPARATOR);
476 if($namespaceSeparatorPos !== false){
477 //TODO: namespaced packet - discard namespace for now and just hope this is OK? This would be a real pain to
478 //deal with otherwise :(
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));
481 }
482 $packetToIdList[$name] = $id;
483}
484asort($packetToIdList, SORT_NUMERIC);
485
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);
492
493echo "Done" . PHP_EOL;