mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-10-16 03:51:37 +00:00
First pass ext-encoding support (high-level network I/O and read-only data) (#6799)
This implements ext-encoding only in high-level network I/O (only BedrockProtocol and stuff implemented in PM) and read-only data. This should net a significant performance advantage while being low-risk in the case of critical issues with the extension. Any problems affecting protocol won't do permanent damage while being fairly easy to debug. Next passes will integrate ext-encoding versions of RakLib, RakLibIpc and NBT, as well as generally using ext-encoding for writeable data.
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-date": "*",
|
||||
"ext-encoding": "~1.0.0",
|
||||
"ext-gmp": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-igbinary": "^3.0.1",
|
||||
@@ -36,7 +37,7 @@
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-data": "~6.0.0+bedrock-1.21.100",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100",
|
||||
"pocketmine/bedrock-protocol": "~41.0.0+bedrock-1.21.100",
|
||||
"pocketmine/bedrock-protocol": "~50.0.0+bedrock-1.21.100",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
|
16
composer.lock
generated
16
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1e7545f6cc226b31d54238602143ba78",
|
||||
"content-hash": "0d71d3fba23ba8c4734cac59b9e10129",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@@ -256,19 +256,20 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-protocol",
|
||||
"version": "41.0.0+bedrock-1.21.100",
|
||||
"version": "50.0.0+bedrock-1.21.100",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "920ac291fe1b0143b2ebc90b3374ddab0b8531bf"
|
||||
"reference": "2d7aa27a5537ae593fb1c39158648ea462fef72a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/920ac291fe1b0143b2ebc90b3374ddab0b8531bf",
|
||||
"reference": "920ac291fe1b0143b2ebc90b3374ddab0b8531bf",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2d7aa27a5537ae593fb1c39158648ea462fef72a",
|
||||
"reference": "2d7aa27a5537ae593fb1c39158648ea462fef72a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-encoding": "~1.0.0",
|
||||
"ext-json": "*",
|
||||
"php": "^8.1",
|
||||
"pocketmine/binaryutils": "^0.2.0",
|
||||
@@ -296,9 +297,9 @@
|
||||
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/41.0.0+bedrock-1.21.100"
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/50.0.0+bedrock-1.21.100"
|
||||
},
|
||||
"time": "2025-09-09T20:52:18+00:00"
|
||||
"time": "2025-09-20T23:09:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@@ -2797,6 +2798,7 @@
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-date": "*",
|
||||
"ext-encoding": "~1.0.0",
|
||||
"ext-gmp": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-igbinary": "^3.0.1",
|
||||
|
@@ -98,6 +98,7 @@ namespace pocketmine {
|
||||
"crypto" => "php-crypto",
|
||||
"ctype" => "ctype",
|
||||
"date" => "Date",
|
||||
"encoding" => "pmmp/ext-encoding",
|
||||
"gmp" => "GMP",
|
||||
"hash" => "Hash",
|
||||
"igbinary" => "igbinary",
|
||||
@@ -155,6 +156,12 @@ namespace pocketmine {
|
||||
}
|
||||
}
|
||||
|
||||
if(($encoding_version = phpversion("encoding")) !== false){
|
||||
if(version_compare($encoding_version, "1.0.0") < 0 || version_compare($encoding_version, "2.0.0") >= 0){
|
||||
$messages[] = "pmmp/ext-encoding ^1.0.0 is required, while you have $encoding_version.";
|
||||
}
|
||||
}
|
||||
|
||||
if(extension_loaded("pocketmine")){
|
||||
$messages[] = "The native PocketMine extension is no longer supported.";
|
||||
}
|
||||
|
@@ -26,6 +26,8 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pmmp\encoding\BE;
|
||||
use pmmp\encoding\LE;
|
||||
use pocketmine\block\tile\Spawnable;
|
||||
use pocketmine\block\tile\Tile;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
@@ -49,7 +51,6 @@ use pocketmine\math\Vector3;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\Position;
|
||||
@@ -98,9 +99,10 @@ class Block{
|
||||
* of operations required to compute the state ID (micro optimization).
|
||||
*/
|
||||
private static function computeStateIdXorMask(int $typeId) : int{
|
||||
//TODO: the mixed byte order here is probably a mistake, but it doesn't break anything for now
|
||||
return
|
||||
$typeId << self::INTERNAL_STATE_DATA_BITS |
|
||||
(Binary::readLong(hash('xxh3', Binary::writeLLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK);
|
||||
(BE::unpackSignedLong(hash('xxh3', LE::packSignedLong($typeId), binary: true)) & self::INTERNAL_STATE_DATA_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -23,10 +23,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\crafting;
|
||||
|
||||
use pmmp\encoding\ByteBufferWriter;
|
||||
use pmmp\encoding\VarInt;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\DestructorCallbackTrait;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use function array_shift;
|
||||
@@ -114,11 +115,13 @@ class CraftingManager{
|
||||
}
|
||||
|
||||
private static function hashOutput(Item $output) : string{
|
||||
$write = new BinaryStream();
|
||||
$write->putVarInt($output->getStateId());
|
||||
$write->put((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag())));
|
||||
$write = new ByteBufferWriter();
|
||||
VarInt::writeSignedInt($write, $output->getStateId());
|
||||
//TODO: the NBT serializer allocates its own ByteBufferWriter, we should change the API in the future to
|
||||
//allow passing our own to avoid this extra allocation
|
||||
$write->writeByteArray((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag())));
|
||||
|
||||
return $write->getBuffer();
|
||||
return $write->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -23,11 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\data\bedrock\block\upgrade;
|
||||
|
||||
use pmmp\encoding\ByteBufferReader;
|
||||
use pmmp\encoding\DataDecodeException;
|
||||
use pmmp\encoding\VarInt;
|
||||
use pocketmine\data\bedrock\block\BlockStateData;
|
||||
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\utils\BinaryDataException;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
|
||||
/**
|
||||
* Handles translating legacy 1.12 block ID/meta into modern blockstates.
|
||||
@@ -84,25 +85,25 @@ final class BlockIdMetaUpgrader{
|
||||
public static function loadFromString(string $data, LegacyBlockIdToStringIdMap $idMap, BlockStateUpgrader $blockStateUpgrader) : self{
|
||||
$mappingTable = [];
|
||||
|
||||
$legacyStateMapReader = new BinaryStream($data);
|
||||
$legacyStateMapReader = new ByteBufferReader($data);
|
||||
$nbtReader = new LittleEndianNbtSerializer();
|
||||
|
||||
$idCount = $legacyStateMapReader->getUnsignedVarInt();
|
||||
$idCount = VarInt::readUnsignedInt($legacyStateMapReader);
|
||||
for($idIndex = 0; $idIndex < $idCount; $idIndex++){
|
||||
$id = $legacyStateMapReader->get($legacyStateMapReader->getUnsignedVarInt());
|
||||
$id = $legacyStateMapReader->readByteArray(VarInt::readUnsignedInt($legacyStateMapReader));
|
||||
|
||||
$metaCount = $legacyStateMapReader->getUnsignedVarInt();
|
||||
$metaCount = VarInt::readUnsignedInt($legacyStateMapReader);
|
||||
for($metaIndex = 0; $metaIndex < $metaCount; $metaIndex++){
|
||||
$meta = $legacyStateMapReader->getUnsignedVarInt();
|
||||
$meta = VarInt::readUnsignedInt($legacyStateMapReader);
|
||||
|
||||
$offset = $legacyStateMapReader->getOffset();
|
||||
$state = $nbtReader->read($legacyStateMapReader->getBuffer(), $offset)->mustGetCompoundTag();
|
||||
$state = $nbtReader->read($legacyStateMapReader->getData(), $offset)->mustGetCompoundTag();
|
||||
$legacyStateMapReader->setOffset($offset);
|
||||
$mappingTable[$id][$meta] = $blockStateUpgrader->upgrade(BlockStateData::fromNbt($state));
|
||||
}
|
||||
}
|
||||
if(!$legacyStateMapReader->feof()){
|
||||
throw new BinaryDataException("Unexpected trailing data in legacy state map data");
|
||||
if($legacyStateMapReader->getUnreadLength() > 0){
|
||||
throw new DataDecodeException("Unexpected trailing data in legacy state map data");
|
||||
}
|
||||
|
||||
return new self($mappingTable, $idMap);
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use pmmp\encoding\ByteBufferWriter;
|
||||
use pocketmine\network\mcpe\compression\CompressBatchPromise;
|
||||
use pocketmine\network\mcpe\compression\Compressor;
|
||||
use pocketmine\network\mcpe\convert\TypeConverter;
|
||||
@@ -33,7 +34,6 @@ use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\network\mcpe\serializer\ChunkSerializer;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\thread\NonThreadSafeValue;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\io\FastChunkSerializer;
|
||||
use function chr;
|
||||
@@ -73,11 +73,11 @@ class ChunkRequestTask extends AsyncTask{
|
||||
$converter = TypeConverter::getInstance();
|
||||
$payload = ChunkSerializer::serializeFullChunk($chunk, $dimensionId, $converter->getBlockTranslator(), $this->tiles);
|
||||
|
||||
$stream = new BinaryStream();
|
||||
$stream = new ByteBufferWriter();
|
||||
PacketBatch::encodePackets($stream, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $dimensionId, $subCount, false, null, $payload)]);
|
||||
|
||||
$compressor = $this->compressor->deserialize();
|
||||
$this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($stream->getBuffer()));
|
||||
$this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($stream->getData()));
|
||||
}
|
||||
|
||||
public function onCompletion() : void{
|
||||
|
@@ -23,9 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use pmmp\encoding\BE;
|
||||
use pmmp\encoding\Byte;
|
||||
use pmmp\encoding\ByteBufferReader;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\Utils;
|
||||
use function base64_decode;
|
||||
use function base64_encode;
|
||||
@@ -133,17 +134,17 @@ final class JwtUtils{
|
||||
return self::ASN1_SEQUENCE_TAG . chr(strlen($sequence)) . $sequence;
|
||||
}
|
||||
|
||||
private static function signaturePartFromAsn1(BinaryStream $stream) : string{
|
||||
$prefix = $stream->get(1);
|
||||
private static function signaturePartFromAsn1(ByteBufferReader $stream) : string{
|
||||
$prefix = $stream->readByteArray(1);
|
||||
if($prefix !== self::ASN1_INTEGER_TAG){
|
||||
throw new \InvalidArgumentException("Expected an ASN.1 INTEGER tag, got " . bin2hex($prefix));
|
||||
}
|
||||
//we can assume the length is 1 byte here - if it were larger than 127, more complex logic would be needed
|
||||
$length = $stream->getByte();
|
||||
$length = Byte::readUnsigned($stream);
|
||||
if($length > self::SIGNATURE_PART_LENGTH + 1){ //each part may have an extra leading 0 byte to prevent it being interpreted as a negative number
|
||||
throw new \InvalidArgumentException("Expected at most 49 bytes for signature R or S, got $length");
|
||||
}
|
||||
$part = $stream->get($length);
|
||||
$part = $stream->readByteArray($length);
|
||||
return str_pad(ltrim($part, "\x00"), self::SIGNATURE_PART_LENGTH, "\x00", STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
@@ -159,11 +160,11 @@ final class JwtUtils{
|
||||
throw new \InvalidArgumentException("Invalid DER signature, expected $length sequence bytes, got " . strlen($parts));
|
||||
}
|
||||
|
||||
$stream = new BinaryStream($parts);
|
||||
$stream = new ByteBufferReader($parts);
|
||||
$rRaw = self::signaturePartFromAsn1($stream);
|
||||
$sRaw = self::signaturePartFromAsn1($stream);
|
||||
|
||||
if(!$stream->feof()){
|
||||
if($stream->getUnreadLength() > 0){
|
||||
throw new \InvalidArgumentException("Invalid DER signature, unexpected trailing sequence data");
|
||||
}
|
||||
|
||||
@@ -250,7 +251,7 @@ final class JwtUtils{
|
||||
return chr($length);
|
||||
}
|
||||
|
||||
$lengthBytes = ltrim(Binary::writeInt($length), "\x00");
|
||||
$lengthBytes = ltrim(BE::packUnsignedInt($length), "\x00");
|
||||
|
||||
return chr(0x80 | strlen($lengthBytes)) . $lengthBytes;
|
||||
}
|
||||
|
@@ -23,6 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use pmmp\encoding\ByteBufferReader;
|
||||
use pmmp\encoding\ByteBufferWriter;
|
||||
use pmmp\encoding\DataDecodeException;
|
||||
use pocketmine\entity\effect\EffectInstance;
|
||||
use pocketmine\event\player\PlayerDuplicateLoginEvent;
|
||||
use pocketmine\event\player\PlayerResourcePackOfferEvent;
|
||||
@@ -70,7 +73,6 @@ use pocketmine\network\mcpe\protocol\PlayerStartItemCooldownPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayStatusPacket;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\ServerboundPacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
|
||||
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
|
||||
@@ -109,8 +111,6 @@ use pocketmine\promise\PromiseResolver;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\BinaryDataException;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\world\format\io\GlobalItemDataHandlers;
|
||||
@@ -401,7 +401,7 @@ class NetworkSession{
|
||||
}
|
||||
|
||||
try{
|
||||
$stream = new BinaryStream($decompressed);
|
||||
$stream = new ByteBufferReader($decompressed);
|
||||
foreach(PacketBatch::decodeRaw($stream) as $buffer){
|
||||
$this->gamePacketLimiter->decrement();
|
||||
$packet = $this->packetPool->getPacket($buffer);
|
||||
@@ -421,7 +421,7 @@ class NetworkSession{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}catch(PacketDecodeException|BinaryDataException $e){
|
||||
}catch(PacketDecodeException|DataDecodeException $e){
|
||||
$this->logger->logException($e);
|
||||
throw PacketHandlingException::wrap($e, "Packet batch decode error");
|
||||
}
|
||||
@@ -453,14 +453,14 @@ class NetworkSession{
|
||||
$decodeTimings = Timings::getDecodeDataPacketTimings($packet);
|
||||
$decodeTimings->startTiming();
|
||||
try{
|
||||
$stream = PacketSerializer::decoder($buffer, 0);
|
||||
$stream = new ByteBufferReader($buffer);
|
||||
try{
|
||||
$packet->decode($stream);
|
||||
}catch(PacketDecodeException $e){
|
||||
throw PacketHandlingException::wrap($e);
|
||||
}
|
||||
if(!$stream->feof()){
|
||||
$remains = substr($stream->getBuffer(), $stream->getOffset());
|
||||
if($stream->getUnreadLength() > 0){
|
||||
$remains = substr($stream->getData(), $stream->getOffset());
|
||||
$this->logger->debug("Still " . strlen($remains) . " bytes unread in " . $packet->getName() . ": " . bin2hex($remains));
|
||||
}
|
||||
}finally{
|
||||
@@ -478,7 +478,7 @@ class NetworkSession{
|
||||
$handlerTimings->startTiming();
|
||||
try{
|
||||
if($this->handler === null || !$packet->handle($this->handler)){
|
||||
$this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer()));
|
||||
$this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getData()));
|
||||
}
|
||||
}finally{
|
||||
$handlerTimings->stopTiming();
|
||||
@@ -530,8 +530,10 @@ class NetworkSession{
|
||||
if($ackReceiptResolver !== null){
|
||||
$this->sendBufferAckPromises[] = $ackReceiptResolver;
|
||||
}
|
||||
$writer = new ByteBufferWriter();
|
||||
foreach($packets as $evPacket){
|
||||
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder(), $evPacket));
|
||||
$writer->clear(); //memory reuse let's gooooo
|
||||
$this->addToSendBuffer(self::encodePacketTimed($writer, $evPacket));
|
||||
}
|
||||
if($immediate){
|
||||
$this->flushGamePacketQueue();
|
||||
@@ -564,12 +566,12 @@ class NetworkSession{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function encodePacketTimed(PacketSerializer $serializer, ClientboundPacket $packet) : string{
|
||||
public static function encodePacketTimed(ByteBufferWriter $serializer, ClientboundPacket $packet) : string{
|
||||
$timings = Timings::getEncodeDataPacketTimings($packet);
|
||||
$timings->startTiming();
|
||||
try{
|
||||
$packet->encode($serializer);
|
||||
return $serializer->getBuffer();
|
||||
return $serializer->getData();
|
||||
}finally{
|
||||
$timings->stopTiming();
|
||||
}
|
||||
@@ -591,13 +593,13 @@ class NetworkSession{
|
||||
$syncMode = false;
|
||||
}
|
||||
|
||||
$stream = new BinaryStream();
|
||||
$stream = new ByteBufferWriter();
|
||||
PacketBatch::encodeRaw($stream, $this->sendBuffer);
|
||||
|
||||
if($this->enableCompression){
|
||||
$batch = $this->server->prepareBatch($stream->getBuffer(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer);
|
||||
$batch = $this->server->prepareBatch($stream->getData(), $this->compressor, $syncMode, Timings::$playerNetworkSendCompressSessionBuffer);
|
||||
}else{
|
||||
$batch = $stream->getBuffer();
|
||||
$batch = $stream->getData();
|
||||
}
|
||||
$this->sendBuffer = [];
|
||||
$ackPromises = $this->sendBufferAckPromises;
|
||||
|
@@ -23,12 +23,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe;
|
||||
|
||||
use pmmp\encoding\ByteBufferWriter;
|
||||
use pocketmine\event\server\DataPacketSendEvent;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use function count;
|
||||
use function log;
|
||||
use function spl_object_id;
|
||||
@@ -64,8 +63,10 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
|
||||
|
||||
$totalLength = 0;
|
||||
$packetBuffers = [];
|
||||
$writer = new ByteBufferWriter();
|
||||
foreach($packets as $packet){
|
||||
$buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder(), $packet);
|
||||
$writer->clear(); //memory reuse let's gooooo
|
||||
$buffer = NetworkSession::encodePacketTimed($writer, $packet);
|
||||
//varint length prefix + packet buffer
|
||||
$totalLength += (((int) log(strlen($buffer), 128)) + 1) + strlen($buffer);
|
||||
$packetBuffers[] = $buffer;
|
||||
@@ -77,9 +78,9 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
|
||||
$threshold = $compressor->getCompressionThreshold();
|
||||
if(count($compressorTargets) > 1 && $threshold !== null && $totalLength >= $threshold){
|
||||
//do not prepare shared batch unless we're sure it will be compressed
|
||||
$stream = new BinaryStream();
|
||||
$stream = new ByteBufferWriter();
|
||||
PacketBatch::encodeRaw($stream, $packetBuffers);
|
||||
$batchBuffer = $stream->getBuffer();
|
||||
$batchBuffer = $stream->getData();
|
||||
|
||||
$batch = $this->server->prepareBatch($batchBuffer, $compressor, timings: Timings::$playerNetworkSendCompressBroadcast);
|
||||
foreach($compressorTargets as $target){
|
||||
|
6
src/network/mcpe/cache/CraftingDataCache.php
vendored
6
src/network/mcpe/cache/CraftingDataCache.php
vendored
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\cache;
|
||||
|
||||
use pmmp\encoding\BE;
|
||||
use pocketmine\crafting\CraftingManager;
|
||||
use pocketmine\crafting\FurnaceType;
|
||||
use pocketmine\crafting\ShapedRecipe;
|
||||
@@ -41,7 +42,6 @@ use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShaped
|
||||
use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use function array_map;
|
||||
@@ -99,7 +99,7 @@ final class CraftingDataCache{
|
||||
};
|
||||
$recipesWithTypeIds[] = new ProtocolShapelessRecipe(
|
||||
CraftingDataPacket::ENTRY_SHAPELESS,
|
||||
Binary::writeInt($recipeNetId),
|
||||
BE::packUnsignedInt($recipeNetId), //TODO: this should probably be changed to something human-readable
|
||||
array_map($converter->coreRecipeIngredientToNet(...), $recipe->getIngredientList()),
|
||||
array_map($converter->coreItemStackToNet(...), $recipe->getResults()),
|
||||
$nullUUID,
|
||||
@@ -118,7 +118,7 @@ final class CraftingDataCache{
|
||||
}
|
||||
$recipesWithTypeIds[] = $r = new ProtocolShapedRecipe(
|
||||
CraftingDataPacket::ENTRY_SHAPED,
|
||||
Binary::writeInt($recipeNetId),
|
||||
BE::packUnsignedInt($recipeNetId), //TODO: this should probably be changed to something human-readable
|
||||
$inputs,
|
||||
array_map($converter->coreItemStackToNet(...), $recipe->getResults()),
|
||||
$nullUUID,
|
||||
|
@@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\convert;
|
||||
|
||||
use pmmp\encoding\ByteBufferReader;
|
||||
use pmmp\encoding\ByteBufferWriter;
|
||||
use pocketmine\block\tile\Container;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\crafting\ExactRecipeIngredient;
|
||||
@@ -44,7 +46,6 @@ use pocketmine\nbt\tag\Tag;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\nbt\UnexpectedTagTypeException;
|
||||
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData;
|
||||
@@ -312,7 +313,7 @@ class TypeConverter{
|
||||
$extraData = $id === $this->shieldRuntimeId ?
|
||||
new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) :
|
||||
new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []);
|
||||
$extraDataSerializer = PacketSerializer::encoder();
|
||||
$extraDataSerializer = new ByteBufferWriter();
|
||||
$extraData->write($extraDataSerializer);
|
||||
|
||||
return new ItemStack(
|
||||
@@ -320,7 +321,7 @@ class TypeConverter{
|
||||
$meta,
|
||||
$itemStack->getCount(),
|
||||
$blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
|
||||
$extraDataSerializer->getBuffer(),
|
||||
$extraDataSerializer->getData(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -359,7 +360,7 @@ class TypeConverter{
|
||||
}
|
||||
|
||||
public function deserializeItemStackExtraData(string $extraData, int $id) : ItemStackExtraData{
|
||||
$extraDataDeserializer = PacketSerializer::decoder($extraData, 0);
|
||||
$extraDataDeserializer = new ByteBufferReader($extraData);
|
||||
return $id === $this->shieldRuntimeId ?
|
||||
ItemStackExtraDataShield::read($extraDataDeserializer) :
|
||||
ItemStackExtraData::read($extraDataDeserializer);
|
||||
|
@@ -24,7 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\network\mcpe\encryption;
|
||||
|
||||
use Crypto\Cipher;
|
||||
use pocketmine\utils\Binary;
|
||||
use pmmp\encoding\LE;
|
||||
use function bin2hex;
|
||||
use function openssl_digest;
|
||||
use function openssl_error_string;
|
||||
@@ -104,7 +104,7 @@ class EncryptionContext{
|
||||
}
|
||||
|
||||
private function calculateChecksum(int $counter, string $payload) : string{
|
||||
$hash = openssl_digest(Binary::writeLLong($counter) . $payload . $this->key, self::CHECKSUM_ALGO, true);
|
||||
$hash = openssl_digest(LE::packUnsignedLong($counter) . $payload . $this->key, self::CHECKSUM_ALGO, true);
|
||||
if($hash === false){
|
||||
throw new \RuntimeException("openssl_digest() error: " . openssl_error_string());
|
||||
}
|
||||
|
@@ -23,16 +23,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\serializer;
|
||||
|
||||
use pmmp\encoding\Byte;
|
||||
use pmmp\encoding\ByteBufferWriter;
|
||||
use pmmp\encoding\VarInt;
|
||||
use pocketmine\block\tile\Spawnable;
|
||||
use pocketmine\data\bedrock\BiomeIds;
|
||||
use pocketmine\data\bedrock\LegacyBiomeIdToStringIdMap;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\network\mcpe\convert\BlockTranslator;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\PalettedBlockArray;
|
||||
use pocketmine\world\format\SubChunk;
|
||||
@@ -84,7 +84,7 @@ final class ChunkSerializer{
|
||||
* @phpstan-param DimensionIds::* $dimensionId
|
||||
*/
|
||||
public static function serializeFullChunk(Chunk $chunk, int $dimensionId, BlockTranslator $blockTranslator, ?string $tiles = null) : string{
|
||||
$stream = PacketSerializer::encoder();
|
||||
$stream = new ByteBufferWriter();
|
||||
|
||||
$subChunkCount = self::getSubChunkCount($chunk, $dimensionId);
|
||||
$writtenCount = 0;
|
||||
@@ -100,37 +100,34 @@ final class ChunkSerializer{
|
||||
self::serializeBiomePalette($chunk->getSubChunk($y)->getBiomeArray(), $biomeIdMap, $stream);
|
||||
}
|
||||
|
||||
$stream->putByte(0); //border block array count
|
||||
Byte::writeUnsigned($stream, 0); //border block array count
|
||||
//Border block entry format: 1 byte (4 bits X, 4 bits Z). These are however useless since they crash the regular client.
|
||||
|
||||
if($tiles !== null){
|
||||
$stream->put($tiles);
|
||||
$stream->writeByteArray($tiles);
|
||||
}else{
|
||||
$stream->put(self::serializeTiles($chunk));
|
||||
$stream->writeByteArray(self::serializeTiles($chunk));
|
||||
}
|
||||
return $stream->getBuffer();
|
||||
return $stream->getData();
|
||||
}
|
||||
|
||||
public static function serializeSubChunk(SubChunk $subChunk, BlockTranslator $blockTranslator, PacketSerializer $stream, bool $persistentBlockStates) : void{
|
||||
public static function serializeSubChunk(SubChunk $subChunk, BlockTranslator $blockTranslator, ByteBufferWriter $stream, bool $persistentBlockStates) : void{
|
||||
$layers = $subChunk->getBlockLayers();
|
||||
$stream->putByte(8); //version
|
||||
Byte::writeUnsigned($stream, 8); //version
|
||||
|
||||
$stream->putByte(count($layers));
|
||||
Byte::writeUnsigned($stream, count($layers));
|
||||
|
||||
$blockStateDictionary = $blockTranslator->getBlockStateDictionary();
|
||||
|
||||
foreach($layers as $blocks){
|
||||
$bitsPerBlock = $blocks->getBitsPerBlock();
|
||||
$words = $blocks->getWordArray();
|
||||
$stream->putByte(($bitsPerBlock << 1) | ($persistentBlockStates ? 0 : 1));
|
||||
$stream->put($words);
|
||||
Byte::writeUnsigned($stream, ($bitsPerBlock << 1) | ($persistentBlockStates ? 0 : 1));
|
||||
$stream->writeByteArray($words);
|
||||
$palette = $blocks->getPalette();
|
||||
|
||||
if($bitsPerBlock !== 0){
|
||||
//these LSHIFT by 1 uvarints are optimizations: the client expects zigzag varints here
|
||||
//but since we know they are always unsigned, we can avoid the extra fcall overhead of
|
||||
//zigzag and just shift directly.
|
||||
$stream->putUnsignedVarInt(count($palette) << 1); //yes, this is intentionally zigzag
|
||||
VarInt::writeSignedInt($stream, count($palette)); //yes, this is intentionally zigzag
|
||||
}
|
||||
if($persistentBlockStates){
|
||||
$nbtSerializer = new NetworkNbtSerializer();
|
||||
@@ -141,46 +138,43 @@ final class ChunkSerializer{
|
||||
$state = $blockTranslator->getFallbackStateData();
|
||||
}
|
||||
|
||||
$stream->put($nbtSerializer->write(new TreeRoot($state->toNbt())));
|
||||
$stream->writeByteArray($nbtSerializer->write(new TreeRoot($state->toNbt())));
|
||||
}
|
||||
}else{
|
||||
//we would use writeSignedIntArray() here, but the gains of writing in batch are negated by the cost of
|
||||
//allocating a temporary array for the mapped palette IDs, especially for small palettes
|
||||
foreach($palette as $p){
|
||||
$stream->put(Binary::writeUnsignedVarInt($blockTranslator->internalIdToNetworkId($p) << 1));
|
||||
VarInt::writeSignedInt($stream, $blockTranslator->internalIdToNetworkId($p));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function serializeBiomePalette(PalettedBlockArray $biomePalette, LegacyBiomeIdToStringIdMap $biomeIdMap, PacketSerializer $stream) : void{
|
||||
private static function serializeBiomePalette(PalettedBlockArray $biomePalette, LegacyBiomeIdToStringIdMap $biomeIdMap, ByteBufferWriter $stream) : void{
|
||||
$biomePaletteBitsPerBlock = $biomePalette->getBitsPerBlock();
|
||||
$stream->putByte(($biomePaletteBitsPerBlock << 1) | 1); //the last bit is non-persistence (like for blocks), though it has no effect on biomes since they always use integer IDs
|
||||
$stream->put($biomePalette->getWordArray());
|
||||
Byte::writeUnsigned($stream, ($biomePaletteBitsPerBlock << 1) | 1); //the last bit is non-persistence (like for blocks), though it has no effect on biomes since they always use integer IDs
|
||||
$stream->writeByteArray($biomePalette->getWordArray());
|
||||
|
||||
//these LSHIFT by 1 uvarints are optimizations: the client expects zigzag varints here
|
||||
//but since we know they are always unsigned, we can avoid the extra fcall overhead of
|
||||
//zigzag and just shift directly.
|
||||
$biomePaletteArray = $biomePalette->getPalette();
|
||||
if($biomePaletteBitsPerBlock !== 0){
|
||||
$stream->putUnsignedVarInt(count($biomePaletteArray) << 1);
|
||||
VarInt::writeSignedInt($stream, count($biomePaletteArray));
|
||||
}
|
||||
|
||||
foreach($biomePaletteArray as $p){
|
||||
if($biomeIdMap->legacyToString($p) === null){
|
||||
//make sure we aren't sending bogus biomes - the 1.18.0 client crashes if we do this
|
||||
$p = BiomeIds::OCEAN;
|
||||
}
|
||||
$stream->put(Binary::writeUnsignedVarInt($p << 1));
|
||||
//we would use writeSignedIntArray() here, but the gains of writing in batch are negated by the cost of
|
||||
//allocating a temporary array for the mapped palette IDs, especially for small palettes
|
||||
VarInt::writeSignedInt($stream, $biomeIdMap->legacyToString($p) !== null ? $p : BiomeIds::OCEAN);
|
||||
}
|
||||
}
|
||||
|
||||
public static function serializeTiles(Chunk $chunk) : string{
|
||||
$stream = new BinaryStream();
|
||||
$stream = new ByteBufferWriter();
|
||||
foreach($chunk->getTiles() as $tile){
|
||||
if($tile instanceof Spawnable){
|
||||
$stream->put($tile->getSerializedSpawnCompound()->getEncodedNbt());
|
||||
$stream->writeByteArray($tile->getSerializedSpawnCompound()->getEncodedNbt());
|
||||
}
|
||||
}
|
||||
|
||||
return $stream->getBuffer();
|
||||
return $stream->getData();
|
||||
}
|
||||
}
|
||||
|
@@ -27,16 +27,16 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace pocketmine\network\query;
|
||||
|
||||
use pmmp\encoding\BE;
|
||||
use pmmp\encoding\Byte;
|
||||
use pmmp\encoding\ByteBufferReader;
|
||||
use pmmp\encoding\ByteBufferWriter;
|
||||
use pmmp\encoding\DataDecodeException;
|
||||
use pocketmine\network\AdvancedNetworkInterface;
|
||||
use pocketmine\network\RawPacketHandler;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\BinaryDataException;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use function chr;
|
||||
use function hash;
|
||||
use function random_bytes;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
|
||||
class QueryHandler implements RawPacketHandler{
|
||||
@@ -80,51 +80,53 @@ class QueryHandler implements RawPacketHandler{
|
||||
}
|
||||
|
||||
public static function getTokenString(string $token, string $salt) : int{
|
||||
return Binary::readInt(substr(hash("sha512", $salt . ":" . $token, true), 7, 4));
|
||||
return BE::unpackSignedInt(substr(hash("sha512", $salt . ":" . $token, true), 7, 4));
|
||||
}
|
||||
|
||||
public function handle(AdvancedNetworkInterface $interface, string $address, int $port, string $packet) : bool{
|
||||
try{
|
||||
$stream = new BinaryStream($packet);
|
||||
$header = $stream->get(2);
|
||||
$stream = new ByteBufferReader($packet);
|
||||
$header = $stream->readByteArray(2);
|
||||
if($header !== "\xfe\xfd"){ //TODO: have this filtered by the regex filter we installed above
|
||||
return false;
|
||||
}
|
||||
$packetType = $stream->getByte();
|
||||
$sessionID = $stream->getInt();
|
||||
$packetType = Byte::readUnsigned($stream);
|
||||
$sessionID = BE::readUnsignedInt($stream);
|
||||
|
||||
switch($packetType){
|
||||
case self::HANDSHAKE: //Handshake
|
||||
$reply = chr(self::HANDSHAKE);
|
||||
$reply .= Binary::writeInt($sessionID);
|
||||
$reply .= self::getTokenString($this->token, $address) . "\x00";
|
||||
$writer = new ByteBufferWriter();
|
||||
Byte::writeUnsigned($writer, self::HANDSHAKE);
|
||||
BE::writeUnsignedInt($writer, $sessionID);
|
||||
$writer->writeByteArray(self::getTokenString($this->token, $address) . "\x00");
|
||||
|
||||
$interface->sendRawPacket($address, $port, $reply);
|
||||
$interface->sendRawPacket($address, $port, $writer->getData());
|
||||
|
||||
return true;
|
||||
case self::STATISTICS: //Stat
|
||||
$token = $stream->getInt();
|
||||
$token = BE::readUnsignedInt($stream);
|
||||
if($token !== ($t1 = self::getTokenString($this->token, $address)) && $token !== ($t2 = self::getTokenString($this->lastToken, $address))){
|
||||
$this->logger->debug("Bad token $token from $address $port, expected $t1 or $t2");
|
||||
|
||||
return true;
|
||||
}
|
||||
$reply = chr(self::STATISTICS);
|
||||
$reply .= Binary::writeInt($sessionID);
|
||||
$writer = new ByteBufferWriter();
|
||||
Byte::writeUnsigned($writer, self::STATISTICS);
|
||||
BE::writeUnsignedInt($writer, $sessionID);
|
||||
|
||||
$remaining = $stream->getRemaining();
|
||||
if(strlen($remaining) === 4){ //TODO: check this! according to the spec, this should always be here and always be FF FF FF 01
|
||||
$reply .= $this->server->getQueryInformation()->getLongQuery();
|
||||
$remaining = $stream->getUnreadLength();
|
||||
if($remaining === 4){ //TODO: check this! according to the spec, this should always be here and always be FF FF FF 01
|
||||
$writer->writeByteArray($this->server->getQueryInformation()->getLongQuery());
|
||||
}else{
|
||||
$reply .= $this->server->getQueryInformation()->getShortQuery();
|
||||
$writer->writeByteArray($this->server->getQueryInformation()->getShortQuery());
|
||||
}
|
||||
$interface->sendRawPacket($address, $port, $reply);
|
||||
$interface->sendRawPacket($address, $port, $writer->getData());
|
||||
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}catch(BinaryDataException $e){
|
||||
}catch(DataDecodeException $e){
|
||||
$this->logger->debug("Bad packet from $address $port: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
@@ -23,11 +23,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\query;
|
||||
|
||||
use pmmp\encoding\LE;
|
||||
use pocketmine\player\GameMode;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\plugin\Plugin;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\YmlServerProperties;
|
||||
use function array_map;
|
||||
@@ -236,6 +236,6 @@ final class QueryInfo{
|
||||
}
|
||||
|
||||
public function getShortQuery() : string{
|
||||
return $this->shortQueryCache ?? ($this->shortQueryCache = $this->serverName . "\x00" . $this->gametype . "\x00" . $this->map . "\x00" . $this->numPlayers . "\x00" . $this->maxPlayers . "\x00" . Binary::writeLShort($this->port) . $this->ip . "\x00");
|
||||
return $this->shortQueryCache ?? ($this->shortQueryCache = $this->serverName . "\x00" . $this->gametype . "\x00" . $this->map . "\x00" . $this->numPlayers . "\x00" . $this->maxPlayers . "\x00" . LE::packUnsignedShort($this->port) . $this->ip . "\x00");
|
||||
}
|
||||
}
|
||||
|
@@ -23,8 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\world\format\io;
|
||||
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pmmp\encoding\BE;
|
||||
use pmmp\encoding\Byte;
|
||||
use pmmp\encoding\ByteBufferReader;
|
||||
use pmmp\encoding\ByteBufferWriter;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\PalettedBlockArray;
|
||||
use pocketmine\world\format\SubChunk;
|
||||
@@ -45,15 +47,15 @@ final class FastChunkSerializer{
|
||||
//NOOP
|
||||
}
|
||||
|
||||
private static function serializePalettedArray(BinaryStream $stream, PalettedBlockArray $array) : void{
|
||||
private static function serializePalettedArray(ByteBufferWriter $stream, PalettedBlockArray $array) : void{
|
||||
$wordArray = $array->getWordArray();
|
||||
$palette = $array->getPalette();
|
||||
|
||||
$stream->putByte($array->getBitsPerBlock());
|
||||
$stream->put($wordArray);
|
||||
Byte::writeUnsigned($stream, $array->getBitsPerBlock());
|
||||
$stream->writeByteArray($wordArray);
|
||||
$serialPalette = pack("L*", ...$palette);
|
||||
$stream->putInt(strlen($serialPalette));
|
||||
$stream->put($serialPalette);
|
||||
BE::writeUnsignedInt($stream, strlen($serialPalette));
|
||||
$stream->writeByteArray($serialPalette);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,21 +63,20 @@ final class FastChunkSerializer{
|
||||
* TODO: tiles and entities
|
||||
*/
|
||||
public static function serializeTerrain(Chunk $chunk) : string{
|
||||
$stream = new BinaryStream();
|
||||
$stream->putByte(
|
||||
($chunk->isPopulated() ? self::FLAG_POPULATED : 0)
|
||||
);
|
||||
$stream = new ByteBufferWriter();
|
||||
Byte::writeUnsigned($stream, ($chunk->isPopulated() ? self::FLAG_POPULATED : 0));
|
||||
|
||||
//subchunks
|
||||
$subChunks = $chunk->getSubChunks();
|
||||
$count = count($subChunks);
|
||||
$stream->putByte($count);
|
||||
Byte::writeUnsigned($stream, $count);
|
||||
|
||||
foreach($subChunks as $y => $subChunk){
|
||||
$stream->putByte($y);
|
||||
$stream->putInt($subChunk->getEmptyBlockId());
|
||||
Byte::writeSigned($stream, $y);
|
||||
BE::writeUnsignedInt($stream, $subChunk->getEmptyBlockId());
|
||||
|
||||
$layers = $subChunk->getBlockLayers();
|
||||
$stream->putByte(count($layers));
|
||||
Byte::writeUnsigned($stream, count($layers));
|
||||
foreach($layers as $blocks){
|
||||
self::serializePalettedArray($stream, $blocks);
|
||||
}
|
||||
@@ -83,14 +84,15 @@ final class FastChunkSerializer{
|
||||
|
||||
}
|
||||
|
||||
return $stream->getBuffer();
|
||||
return $stream->getData();
|
||||
}
|
||||
|
||||
private static function deserializePalettedArray(BinaryStream $stream) : PalettedBlockArray{
|
||||
$bitsPerBlock = $stream->getByte();
|
||||
$words = $stream->get(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock));
|
||||
private static function deserializePalettedArray(ByteBufferReader $stream) : PalettedBlockArray{
|
||||
$bitsPerBlock = Byte::readUnsigned($stream);
|
||||
$words = $stream->readByteArray(PalettedBlockArray::getExpectedWordArraySize($bitsPerBlock));
|
||||
$paletteSize = BE::readUnsignedInt($stream);
|
||||
/** @var int[] $unpackedPalette */
|
||||
$unpackedPalette = unpack("L*", $stream->get($stream->getInt())); //unpack() will never fail here
|
||||
$unpackedPalette = unpack("L*", $stream->readByteArray($paletteSize)); //unpack() will never fail here
|
||||
$palette = array_values($unpackedPalette);
|
||||
|
||||
return PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
|
||||
@@ -100,20 +102,21 @@ final class FastChunkSerializer{
|
||||
* Deserializes a fast-serialized chunk
|
||||
*/
|
||||
public static function deserializeTerrain(string $data) : Chunk{
|
||||
$stream = new BinaryStream($data);
|
||||
$stream = new ByteBufferReader($data);
|
||||
|
||||
$flags = $stream->getByte();
|
||||
$flags = Byte::readUnsigned($stream);
|
||||
$terrainPopulated = (bool) ($flags & self::FLAG_POPULATED);
|
||||
|
||||
$subChunks = [];
|
||||
|
||||
$count = $stream->getByte();
|
||||
$count = Byte::readUnsigned($stream);
|
||||
for($subCount = 0; $subCount < $count; ++$subCount){
|
||||
$y = Binary::signByte($stream->getByte());
|
||||
$airBlockId = $stream->getInt();
|
||||
$y = Byte::readSigned($stream);
|
||||
//TODO: why the heck are we using big-endian here?
|
||||
$airBlockId = BE::readUnsignedInt($stream);
|
||||
|
||||
$layers = [];
|
||||
for($i = 0, $layerCount = $stream->getByte(); $i < $layerCount; ++$i){
|
||||
for($i = 0, $layerCount = Byte::readUnsigned($stream); $i < $layerCount; ++$i){
|
||||
$layers[] = self::deserializePalettedArray($stream);
|
||||
}
|
||||
$biomeArray = self::deserializePalettedArray($stream);
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\tools\generate_bedrock_data_from_packets;
|
||||
|
||||
use pmmp\encoding\ByteBufferReader;
|
||||
use pocketmine\crafting\json\FurnaceRecipeData;
|
||||
use pocketmine\crafting\json\ItemStackData;
|
||||
use pocketmine\crafting\json\PotionContainerChangeRecipeData;
|
||||
@@ -51,7 +52,6 @@ use pocketmine\network\mcpe\protocol\CreativeContentPacket;
|
||||
use pocketmine\network\mcpe\protocol\ItemRegistryPacket;
|
||||
use pocketmine\network\mcpe\protocol\PacketPool;
|
||||
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\StartGamePacket;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
|
||||
@@ -190,7 +190,7 @@ class ParserPacketHandler extends PacketHandler{
|
||||
|
||||
$rawExtraData = $itemStack->getRawExtraData();
|
||||
if($rawExtraData !== ""){
|
||||
$decoder = PacketSerializer::decoder($rawExtraData, 0);
|
||||
$decoder = new ByteBufferReader($rawExtraData);
|
||||
$extraData = $itemStringId === ItemTypeNames::SHIELD ? ItemStackExtraDataShield::read($decoder) : ItemStackExtraData::read($decoder);
|
||||
$nbt = $extraData->getNbt();
|
||||
if($nbt !== null && count($nbt) > 0){
|
||||
@@ -645,12 +645,13 @@ function main(array $argv) : int{
|
||||
fwrite(STDERR, "Unknown packet on line " . ($lineNum + 1) . ": " . $parts[1]);
|
||||
continue;
|
||||
}
|
||||
$serializer = PacketSerializer::decoder($raw, 0);
|
||||
$serializer = new ByteBufferReader($raw);
|
||||
|
||||
$pk->decode($serializer);
|
||||
$pk->handle($handler);
|
||||
if(!$serializer->feof()){
|
||||
echo "Packet on line " . ($lineNum + 1) . ": didn't read all data from " . get_class($pk) . " (stopped at offset " . $serializer->getOffset() . " of " . strlen($serializer->getBuffer()) . " bytes): " . bin2hex($serializer->getRemaining()) . "\n";
|
||||
$remaining = strlen($serializer->getData()) - $serializer->getOffset();
|
||||
if($remaining > 0){
|
||||
echo "Packet on line " . ($lineNum + 1) . ": didn't read all data from " . get_class($pk) . " (stopped at offset " . $serializer->getOffset() . " of " . strlen($serializer->getData()) . " bytes): " . bin2hex($serializer->readByteArray($remaining)) . "\n";
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
Reference in New Issue
Block a user