mirror of
https://github.com/pmmp/BedrockProtocol.git
synced 2024-11-23 13:46:18 +00:00
68d818091f
this now takes a JSON file as input, as provided by protocol_info_dumper.py in bds-modding-devkit.
468 lines
14 KiB
PHP
468 lines
14 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of BedrockProtocol.
|
|
* Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
|
|
*
|
|
* BedrockProtocol is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace pocketmine\network\mcpe\protocol\tools\generate_protocol_info;
|
|
|
|
use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
|
use pocketmine\network\mcpe\protocol\DataPacket;
|
|
use pocketmine\network\mcpe\protocol\Packet;
|
|
use pocketmine\network\mcpe\protocol\PacketDecodeException;
|
|
use pocketmine\network\mcpe\protocol\PacketHandlerDefaultImplTrait;
|
|
use pocketmine\network\mcpe\protocol\PacketHandlerInterface;
|
|
use pocketmine\network\mcpe\protocol\PacketPool;
|
|
use pocketmine\network\mcpe\protocol\ServerboundPacket;
|
|
use function array_fill_keys;
|
|
use function asort;
|
|
use function ceil;
|
|
use function count;
|
|
use function dechex;
|
|
use function dirname;
|
|
use function file_exists;
|
|
use function file_get_contents;
|
|
use function file_put_contents;
|
|
use function fwrite;
|
|
use function implode;
|
|
use function is_array;
|
|
use function is_bool;
|
|
use function is_int;
|
|
use function is_string;
|
|
use function json_decode;
|
|
use function max;
|
|
use function preg_split;
|
|
use function scandir;
|
|
use function sprintf;
|
|
use function str_contains;
|
|
use function str_ends_with;
|
|
use function str_pad;
|
|
use function strlen;
|
|
use function strtoupper;
|
|
use function substr;
|
|
use const DIRECTORY_SEPARATOR;
|
|
use const JSON_THROW_ON_ERROR;
|
|
use const PHP_EOL;
|
|
use const PREG_SPLIT_DELIM_CAPTURE;
|
|
use const PREG_SPLIT_NO_EMPTY;
|
|
use const SORT_NUMERIC;
|
|
use const STDERR;
|
|
use const STR_PAD_LEFT;
|
|
|
|
const DATA_PACKET_TEMPLATE = <<<'CODE'
|
|
<?php
|
|
|
|
/*
|
|
* This file is part of BedrockProtocol.
|
|
* Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
|
|
*
|
|
* BedrockProtocol is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace pocketmine\network\mcpe\protocol;
|
|
|
|
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
|
|
|
class %s extends DataPacket{
|
|
public const NETWORK_ID = ProtocolInfo::%s;
|
|
|
|
/**
|
|
* @generate-create-func
|
|
*/
|
|
public static function create() : self{
|
|
$result = new self;
|
|
//TODO: add fields
|
|
return $result;
|
|
}
|
|
|
|
protected function decodePayload(PacketSerializer $in) : void{
|
|
//TODO
|
|
}
|
|
|
|
protected function encodePayload(PacketSerializer $out) : void{
|
|
//TODO
|
|
}
|
|
|
|
public function handle(PacketHandlerInterface $handler) : bool{
|
|
return $handler->handle%s($this);
|
|
}
|
|
}
|
|
|
|
CODE;
|
|
|
|
const PACKET_HANDLER_TRAIT_TEMPLATE = <<<'CODE'
|
|
<?php
|
|
|
|
/*
|
|
* This file is part of BedrockProtocol.
|
|
* Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
|
|
*
|
|
* BedrockProtocol is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace pocketmine\network\mcpe\protocol;
|
|
|
|
/**
|
|
* This trait provides default implementations for all packet handlers, so that you don't have to manually write
|
|
* handlers for every single packet even if you don't care about them.
|
|
*
|
|
* This file is automatically generated. Do not edit it manually.
|
|
*/
|
|
trait PacketHandlerDefaultImplTrait{
|
|
|
|
%s
|
|
}
|
|
|
|
CODE;
|
|
|
|
const PACKET_HANDLER_INTERFACE_TEMPLATE = <<<'CODE'
|
|
<?php
|
|
|
|
/*
|
|
* This file is part of BedrockProtocol.
|
|
* Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
|
|
*
|
|
* BedrockProtocol is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace pocketmine\network\mcpe\protocol;
|
|
|
|
/**
|
|
* This class is an automatically generated stub. Do not edit it manually.
|
|
*/
|
|
interface PacketHandlerInterface{
|
|
%s
|
|
}
|
|
|
|
CODE;
|
|
|
|
const PACKET_POOL_TEMPLATE = <<<'CODE'
|
|
<?php
|
|
|
|
/*
|
|
* This file is part of BedrockProtocol.
|
|
* Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
|
|
*
|
|
* BedrockProtocol is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace pocketmine\network\mcpe\protocol;
|
|
|
|
use pocketmine\utils\Binary;
|
|
use pocketmine\utils\BinaryDataException;
|
|
|
|
class PacketPool{
|
|
protected static ?PacketPool $instance = null;
|
|
|
|
public static function getInstance() : self{
|
|
if(self::$instance === null){
|
|
self::$instance = new self;
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/** @var \SplFixedArray<Packet> */
|
|
protected \SplFixedArray $pool;
|
|
|
|
public function __construct(){
|
|
$this->pool = new \SplFixedArray(%d);
|
|
%s
|
|
}
|
|
|
|
public function registerPacket(Packet $packet) : void{
|
|
$this->pool[$packet->pid()] = clone $packet;
|
|
}
|
|
|
|
public function getPacketById(int $pid) : ?Packet{
|
|
return isset($this->pool[$pid]) ? clone $this->pool[$pid] : null;
|
|
}
|
|
|
|
/**
|
|
* @throws BinaryDataException
|
|
*/
|
|
public function getPacket(string $buffer) : ?Packet{
|
|
$offset = 0;
|
|
return $this->getPacketById(Binary::readUnsignedVarInt($buffer, $offset) & DataPacket::PID_MASK);
|
|
}
|
|
}
|
|
|
|
CODE;
|
|
|
|
const PROTOCOL_INFO_TEMPLATE = <<<'CODE'
|
|
<?php
|
|
|
|
/*
|
|
* This file is part of BedrockProtocol.
|
|
* Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
|
|
*
|
|
* BedrockProtocol is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace pocketmine\network\mcpe\protocol;
|
|
|
|
/**
|
|
* Version numbers and packet IDs for the current Minecraft PE protocol
|
|
*/
|
|
final class ProtocolInfo{
|
|
|
|
private function __construct(){
|
|
//NOOP
|
|
}
|
|
|
|
/**
|
|
* NOTE TO DEVELOPERS
|
|
* Do not waste your time or ours submitting pull requests changing game and/or protocol version numbers.
|
|
* Pull requests changing game and/or protocol version numbers will be closed.
|
|
*
|
|
* This file is generated automatically, do not edit it manually.
|
|
*/
|
|
|
|
/** Actual Minecraft: PE protocol version */
|
|
public const CURRENT_PROTOCOL = %d;
|
|
/** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */
|
|
public const MINECRAFT_VERSION = '%s';
|
|
/** Version number sent to clients in ping responses. */
|
|
public const MINECRAFT_VERSION_NETWORK = '%s';
|
|
|
|
%s
|
|
}
|
|
|
|
CODE;
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
function split_upper(string $string) : array{
|
|
$split = preg_split('/([A-Z][^A-Z]*)/', $string, flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
|
if($split === false){
|
|
throw new \Error("preg_split failed");
|
|
}
|
|
return $split;
|
|
}
|
|
|
|
function rchop(string $string, string $substring) : string{
|
|
if(str_ends_with($string, $substring)){
|
|
return substr($string, 0, -strlen($substring));
|
|
}
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* @param string[] $packetToIdList
|
|
* @phpstan-param array<string, int> $packetToIdList
|
|
*/
|
|
function generate_new_packet_stubs(array $packetToIdList, string $packetsDir) : void{
|
|
foreach($packetToIdList as $name => $id){
|
|
$packetFilePath = $packetsDir . DIRECTORY_SEPARATOR . $name . '.php';
|
|
if(!file_exists($packetFilePath)){
|
|
echo "!!! New packet: $name" . PHP_EOL;
|
|
$constName = strtoupper(implode('_', split_upper($name)));
|
|
$baseName = rchop($name, 'Packet');
|
|
file_put_contents($packetFilePath, sprintf(DATA_PACKET_TEMPLATE, $name, $constName, $baseName));
|
|
echo "Created stub class for $name at $packetFilePath" . PHP_EOL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string[] $packetToIdList
|
|
* @phpstan-param array<string, int> $packetToIdList
|
|
*/
|
|
function check_removed_packets(array $packetToIdList, string $packetsDir) : void{
|
|
$existing = scandir($packetsDir);
|
|
if($existing === false){
|
|
return;
|
|
}
|
|
|
|
//use ::class constants here so that they are checked for existence
|
|
$ignoredClasses = array_fill_keys([
|
|
DataPacket::class,
|
|
PacketPool::class,
|
|
Packet::class,
|
|
PacketDecodeException::class,
|
|
PacketHandlerDefaultImplTrait::class,
|
|
PacketHandlerInterface::class,
|
|
ClientboundPacket::class,
|
|
ServerboundPacket::class,
|
|
], true);
|
|
foreach($existing as $fileName){
|
|
if(str_ends_with($fileName, ".php")){
|
|
$packetName = substr($fileName, 0, -strlen(".php"));
|
|
if(!str_contains($packetName, "Packet") || isset($ignoredClasses["pocketmine\\network\\mcpe\\protocol\\" . $packetName])){
|
|
continue;
|
|
}
|
|
if(!isset($packetToIdList[$packetName])){
|
|
echo "!!! Removed packet: $packetName" . PHP_EOL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string[] $packetToIdList
|
|
* @phpstan-param array<string, int> $packetToIdList
|
|
*/
|
|
function generate_protocol_info(array $packetToIdList, int $protocolVersion, int $major, int $minor, int $patch, int $revision, bool $beta, string $packetsDir) : void{
|
|
$consts = "";
|
|
$last = 0;
|
|
|
|
foreach($packetToIdList as $name => $id){
|
|
if($id !== $last + 1){
|
|
$consts .= "\n";
|
|
}
|
|
|
|
$last = $id;
|
|
$consts .= sprintf(
|
|
"\tpublic const %s = %s;\n",
|
|
strtoupper(implode("_", split_upper($name))),
|
|
"0x" . str_pad(dechex($id), 2, "0", STR_PAD_LEFT)
|
|
);
|
|
}
|
|
|
|
$gameVersion = sprintf("v%d.%d.%d%s", $major, $minor, $patch, $beta ? ".$revision beta" : "");
|
|
$gameVersionNetwork = sprintf("%d.%d.%d%s", $major, $minor, $patch, $beta ? ".$revision" : "");
|
|
file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "ProtocolInfo.php", sprintf(
|
|
PROTOCOL_INFO_TEMPLATE,
|
|
$protocolVersion,
|
|
$gameVersion,
|
|
$gameVersionNetwork,
|
|
$consts
|
|
));
|
|
|
|
echo "Recreated ProtocolInfo" . PHP_EOL;
|
|
}
|
|
|
|
/**
|
|
* @param string[] $packetToIdList
|
|
* @phpstan-param array<string, int> $packetToIdList
|
|
*/
|
|
function generate_packet_pool(array $packetToIdList, string $packetsDir) : void{
|
|
$entries = "";
|
|
|
|
foreach($packetToIdList as $name => $id){
|
|
$entries .= sprintf("\n\t\t\$this->registerPacket(new %s());", $name);
|
|
}
|
|
|
|
$poolSize = (int) (ceil(max($packetToIdList) / 256) * 256);
|
|
file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketPool.php", sprintf(
|
|
PACKET_POOL_TEMPLATE,
|
|
$poolSize,
|
|
$entries
|
|
));
|
|
echo "Recreated PacketPool\n";
|
|
}
|
|
|
|
/**
|
|
* @param string[] $packetToIdList
|
|
* @phpstan-param array<string, int> $packetToIdList
|
|
*/
|
|
function generate_packet_handler_classes(array $packetToIdList, string $packetsDir) : void{
|
|
$interfaceFunctions = [];
|
|
$traitFunctions = [];
|
|
|
|
foreach($packetToIdList as $name => $id){
|
|
$baseName = rchop($name, "Packet");
|
|
$interfaceFunctions[] = sprintf("\tpublic function handle%s(%s \$packet) : bool;", $baseName, $name);
|
|
$traitFunctions[] = sprintf("\tpublic function handle%s(%s \$packet) : bool{\n\t\treturn false;\n\t}", $baseName, $name);
|
|
}
|
|
|
|
file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketHandlerInterface.php", sprintf(
|
|
PACKET_HANDLER_INTERFACE_TEMPLATE,
|
|
implode("\n\n", $interfaceFunctions)
|
|
));
|
|
echo "Recreated PacketHandlerInterface" . PHP_EOL;
|
|
file_put_contents($packetsDir . DIRECTORY_SEPARATOR . "PacketHandlerDefaultImplTrait.php", sprintf(
|
|
PACKET_HANDLER_TRAIT_TEMPLATE,
|
|
implode("\n\n", $traitFunctions)
|
|
));
|
|
echo "Recreated PacketHandlerDefaultImplTrait" . PHP_EOL;
|
|
}
|
|
|
|
if(count($argv) < 2){
|
|
fwrite(STDERR, "Please provide an input protocol_info.json file" . PHP_EOL);
|
|
exit(1);
|
|
}
|
|
|
|
$rawData = file_get_contents($argv[1]);
|
|
if($rawData === false){
|
|
fwrite(STDERR, "Couldn't read data from " . $argv[1] . PHP_EOL);
|
|
exit(1);
|
|
}
|
|
|
|
try{
|
|
$json = json_decode($rawData, associative: true, flags: JSON_THROW_ON_ERROR);
|
|
}catch(\JsonException $e){
|
|
fwrite(STDERR, "Error decoding input file: " . $e->getMessage() . PHP_EOL);
|
|
exit(1);
|
|
}
|
|
if(!is_array($json) || count($json) !== 2 || !is_array($json["version"] ?? null) || !is_array($json["packets"] ?? null)){
|
|
fwrite(STDERR, "Invalid input file, expected 2 objects: \"version\" and \"packets\"" . PHP_EOL);
|
|
exit(1);
|
|
}
|
|
|
|
$versionInfo = $json["version"];
|
|
$major = $versionInfo["major"] ?? null;
|
|
$minor = $versionInfo["minor"] ?? null;
|
|
$patch = $versionInfo["patch"] ?? null;
|
|
$revision = $versionInfo["revision"] ?? null;
|
|
$beta = $versionInfo["beta"] ?? null;
|
|
$protocolVersion = $versionInfo["protocol_version"] ?? null;
|
|
if(!is_int($major) || !is_int($minor) || !is_int($patch) || !is_int($revision) || !is_bool($beta) || !is_int($protocolVersion)){
|
|
fwrite(STDERR, "Invalid version info, expected \"major\" (int), \"minor\" (int), \"patch\" (int), \"revision\" (int), \"beta\" (bool) and \"protocol_version\" (int)" . PHP_EOL);
|
|
exit(1);
|
|
}
|
|
|
|
echo "Generating code basics for version $major.$minor.$patch.$revision " . ($beta ? "beta" : "") . " (protocol $protocolVersion)" . PHP_EOL;
|
|
|
|
$packetToIdList = [];
|
|
foreach($json["packets"] as $name => $id){
|
|
if(!is_string($name) || !is_int($id)){
|
|
fwrite(STDERR, "Invalid packet entry \"$name\", expected string => int" . PHP_EOL);
|
|
exit(1);
|
|
}
|
|
$packetToIdList[$name] = $id;
|
|
}
|
|
asort($packetToIdList, SORT_NUMERIC);
|
|
|
|
$packetsDir = dirname(__DIR__) . '/src/';
|
|
generate_protocol_info($packetToIdList, $protocolVersion, $major, $minor, $patch, $revision, $beta, $packetsDir);
|
|
generate_packet_pool($packetToIdList, $packetsDir);
|
|
generate_packet_handler_classes($packetToIdList, $packetsDir);
|
|
check_removed_packets($packetToIdList, $packetsDir);
|
|
generate_new_packet_stubs($packetToIdList, $packetsDir);
|
|
|
|
echo "Done" . PHP_EOL;
|