PocketMine-MP 5.30.2 git-98f04176111e5ecab5e8814ffc69d992bfb64939
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
79
80class %s extends DataPacket{
81 public const NETWORK_ID = ProtocolInfo::%s;
82
86 public static function create() : self{
87 $result = new self;
88 //TODO: add fields
89 return $result;
90 }
91
92 protected function decodePayload(PacketSerializer $in) : void{
93 //TODO
94 }
95
96 protected function encodePayload(PacketSerializer $out) : void{
97 //TODO
98 }
99
100 public function handle(PacketHandlerInterface $handler) : bool{
101 return $handler->handle%s($this);
102 }
103}
104
105CODE;
106
107const PACKET_HANDLER_TRAIT_TEMPLATE = <<<'CODE'
108<?php
109
110/*
111 * This file is part of BedrockProtocol.
112 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
113 *
114 * BedrockProtocol is free software: you can redistribute it and/or modify
115 * it under the terms of the GNU Lesser General Public License as published by
116 * the Free Software Foundation, either version 3 of the License, or
117 * (at your option) any later version.
118 */
119
120declare(strict_types=1);
121
122namespace pocketmine\network\mcpe\protocol;
123
130trait PacketHandlerDefaultImplTrait{
131
132%s
133}
134
135CODE;
136
137const PACKET_HANDLER_INTERFACE_TEMPLATE = <<<'CODE'
138<?php
139
140/*
141 * This file is part of BedrockProtocol.
142 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
143 *
144 * BedrockProtocol is free software: you can redistribute it and/or modify
145 * it under the terms of the GNU Lesser General Public License as published by
146 * the Free Software Foundation, either version 3 of the License, or
147 * (at your option) any later version.
148 */
149
150declare(strict_types=1);
151
152namespace pocketmine\network\mcpe\protocol;
153
157interface PacketHandlerInterface{
158%s
159}
160
161CODE;
162
163const PACKET_POOL_TEMPLATE = <<<'CODE'
164<?php
165
166/*
167 * This file is part of BedrockProtocol.
168 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
169 *
170 * BedrockProtocol is free software: you can redistribute it and/or modify
171 * it under the terms of the GNU Lesser General Public License as published by
172 * the Free Software Foundation, either version 3 of the License, or
173 * (at your option) any later version.
174 */
175
176declare(strict_types=1);
177
178namespace pocketmine\network\mcpe\protocol;
179
182
183class PacketPool{
184 protected static ?PacketPool $instance = null;
185
186 public static function getInstance() : self{
187 if(self::$instance === null){
188 self::$instance = new self;
189 }
190 return self::$instance;
191 }
192
194 protected \SplFixedArray $pool;
195
196 public function __construct(){
197 $this->pool = new \SplFixedArray(%d);
198%s
199 }
200
201 public function registerPacket(Packet $packet) : void{
202 $this->pool[$packet->pid()] = clone $packet;
203 }
204
205 public function getPacketById(int $pid) : ?Packet{
206 return isset($this->pool[$pid]) ? clone $this->pool[$pid] : null;
207 }
208
212 public function getPacket(string $buffer) : ?Packet{
213 $offset = 0;
214 return $this->getPacketById(Binary::readUnsignedVarInt($buffer, $offset) & DataPacket::PID_MASK);
215 }
216}
217
218CODE;
219
220const PROTOCOL_INFO_TEMPLATE = <<<'CODE'
221<?php
222
223/*
224 * This file is part of BedrockProtocol.
225 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
226 *
227 * BedrockProtocol is free software: you can redistribute it and/or modify
228 * it under the terms of the GNU Lesser General Public License as published by
229 * the Free Software Foundation, either version 3 of the License, or
230 * (at your option) any later version.
231 */
232
233declare(strict_types=1);
234
235namespace pocketmine\network\mcpe\protocol;
236
240final class ProtocolInfo{
241
242 private function __construct(){
243 //NOOP
244 }
245
255 public const CURRENT_PROTOCOL = %d;
257 public const MINECRAFT_VERSION = '%s';
259 public const MINECRAFT_VERSION_NETWORK = '%s';
260
261%s
262}
263
264CODE;
265
266const CPP_NAMESPACE_SEPARATOR = "::";
267
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");
275 }
276 return $split;
277}
278
279function rchop(string $string, string $substring) : string{
280 if(str_ends_with($string, $substring)){
281 return substr($string, 0, -strlen($substring));
282 }
283 return $string;
284}
285
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;
299 }
300 }
301}
302
307function check_removed_packets(array $packetToIdList, string $packetsDir) : void{
308 $existing = scandir($packetsDir);
309 if($existing === false){
310 return;
311 }
312
313 //use ::class constants here so that they are checked for existence
314 $ignoredClasses = array_fill_keys([
315 DataPacket::class,
316 PacketPool::class,
317 Packet::class,
318 PacketDecodeException::class,
319 PacketHandlerDefaultImplTrait::class,
320 PacketHandlerInterface::class,
321 ClientboundPacket::class,
322 ServerboundPacket::class,
323 ], true);
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])){
328 continue;
329 }
330 if(!isset($packetToIdList[$packetName])){
331 echo "!!! Removed packet: $packetName" . PHP_EOL;
332 }
333 }
334 }
335}
336
341function generate_protocol_info(array $packetToIdList, int $protocolVersion, int $major, int $minor, int $patch, int $revision, bool $beta, string $packetsDir) : void{
342 $consts = "";
343 $last = 0;
344
345 foreach($packetToIdList as $name => $id){
346 if($id !== $last + 1){
347 $consts .= "\n";
348 }
349
350 $last = $id;
351 $consts .= sprintf(
352 "\tpublic const %s = %s;\n",
353 strtoupper(implode("_", split_upper($name))),
354 "0x" . str_pad(dechex($id), 2, "0", STR_PAD_LEFT)
355 );
356 }
357
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,
362 $protocolVersion,
363 $gameVersion,
364 $gameVersionNetwork,
365 $consts
366 ));
367
368 echo "Recreated ProtocolInfo" . PHP_EOL;
369}
370
375function generate_packet_pool(array $packetToIdList, string $packetsDir) : void{
376 $entries = "";
377
378 foreach($packetToIdList as $name => $id){
379 $entries .= sprintf("\n\t\t\$this->registerPacket(new %s());", $name);
380 }
381
382 $poolSize = (int) (ceil(max($packetToIdList) / 256) * 256);
383 file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketPool.php", sprintf(
384 PACKET_POOL_TEMPLATE,
385 $poolSize,
386 $entries
387 ));
388 echo "Recreated PacketPool\n";
389}
390
395function generate_packet_handler_classes(array $packetToIdList, string $packetsDir) : void{
396 $interfaceFunctions = [];
397 $traitFunctions = [];
398
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);
403 }
404
405 file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketHandlerInterface.php", sprintf(
406 PACKET_HANDLER_INTERFACE_TEMPLATE,
407 implode("\n\n", $interfaceFunctions)
408 ));
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)
413 ));
414 echo "Recreated PacketHandlerDefaultImplTrait" . PHP_EOL;
415}
416
417if(count($argv) < 2){
418 fwrite(STDERR, "Please provide an input protocol_info.json file" . PHP_EOL);
419 exit(1);
420}
421
422$rawData = file_get_contents($argv[1]);
423if($rawData === false){
424 fwrite(STDERR, "Couldn't read data from " . $argv[1] . PHP_EOL);
425 exit(1);
426}
427
428try{
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);
432 exit(1);
433}
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);
436 exit(1);
437}
438
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);
448 exit(1);
449}
450
451echo "Generating code basics for version $major.$minor.$patch.$revision " . ($beta ? "beta" : "") . " (protocol $protocolVersion)" . PHP_EOL;
452
453$packetToIdList = [];
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);
457 exit(1);
458 }
459 $namespaceSeparatorPos = strrpos($name, CPP_NAMESPACE_SEPARATOR);
460 if($namespaceSeparatorPos !== false){
461 //TODO: namespaced packet - discard namespace for now and just hope this is OK? This would be a real pain to
462 //deal with otherwise :(
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));
465 }
466 $packetToIdList[$name] = $id;
467}
468asort($packetToIdList, SORT_NUMERIC);
469
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);
476
477echo "Done" . PHP_EOL;