From 965c19375f91769c99038fbf69058a4ee6deb6f1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 18 Nov 2017 16:44:21 +0000 Subject: [PATCH] NBT: Split up concerns of endianness and varint NBT into their own classes, separate stream handling from NBT class The remaining methods, constants and fields in the NBT class now pertain to generic NBT functionality (except for the matchList()/matchTree() methods, but that's a job for another time). All NBT I/O specific logic has now been moved to NBTStream and its descendents. --- src/pocketmine/Player.php | 6 +- src/pocketmine/Server.php | 5 +- src/pocketmine/item/Item.php | 5 +- .../level/format/io/BaseLevelProvider.php | 6 +- .../level/format/io/leveldb/LevelDB.php | 12 +- .../level/format/io/region/Anvil.php | 5 +- .../level/format/io/region/McRegion.php | 7 +- src/pocketmine/nbt/BigEndianNBTStream.php | 83 ++++++ src/pocketmine/nbt/LittleEndianNBTStream.php | 83 ++++++ src/pocketmine/nbt/NBT.php | 274 +----------------- src/pocketmine/nbt/NBTStream.php | 267 +++++++++++++++++ .../nbt/NetworkLittleEndianNBTStream.php | 70 +++++ src/pocketmine/nbt/tag/ByteArrayTag.php | 9 +- src/pocketmine/nbt/tag/ByteTag.php | 5 +- src/pocketmine/nbt/tag/CompoundTag.php | 11 +- src/pocketmine/nbt/tag/DoubleTag.php | 5 +- src/pocketmine/nbt/tag/EndTag.php | 5 +- src/pocketmine/nbt/tag/FloatTag.php | 5 +- src/pocketmine/nbt/tag/IntArrayTag.php | 11 +- src/pocketmine/nbt/tag/IntTag.php | 9 +- src/pocketmine/nbt/tag/ListTag.php | 13 +- src/pocketmine/nbt/tag/LongTag.php | 9 +- src/pocketmine/nbt/tag/ShortTag.php | 5 +- src/pocketmine/nbt/tag/StringTag.php | 9 +- src/pocketmine/nbt/tag/Tag.php | 6 +- src/pocketmine/tile/Spawnable.php | 5 +- 26 files changed, 588 insertions(+), 342 deletions(-) create mode 100644 src/pocketmine/nbt/BigEndianNBTStream.php create mode 100644 src/pocketmine/nbt/LittleEndianNBTStream.php create mode 100644 src/pocketmine/nbt/NBTStream.php create mode 100644 src/pocketmine/nbt/NetworkLittleEndianNBTStream.php diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index e73825f44..ed2afbde8 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -91,7 +91,7 @@ use pocketmine\level\WeakPosition; use pocketmine\math\AxisAlignedBB; use pocketmine\math\Vector3; use pocketmine\metadata\MetadataValue; -use pocketmine\nbt\NBT; +use pocketmine\nbt\NetworkLittleEndianNBTStream; use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\DoubleTag; @@ -2834,8 +2834,8 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $t = $this->level->getTile($pos); if($t instanceof Spawnable){ - $nbt = new NBT(NBT::LITTLE_ENDIAN); - $nbt->read($packet->namedtag, false, true); + $nbt = new NetworkLittleEndianNBTStream(); + $nbt->read($packet->namedtag); $nbt = $nbt->getData(); if(!$t->updateCompoundTag($nbt, $this)){ $t->spawnTo($this); diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index e8ac70fb2..c8a14d98f 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -67,6 +67,7 @@ use pocketmine\level\LevelException; use pocketmine\metadata\EntityMetadataStore; use pocketmine\metadata\LevelMetadataStore; use pocketmine\metadata\PlayerMetadataStore; +use pocketmine\nbt\BigEndianNBTStream; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\CompoundTag; @@ -749,7 +750,7 @@ class Server{ if($this->shouldSavePlayerData()){ if(file_exists($path . "$name.dat")){ try{ - $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt = new BigEndianNBTStream(); $nbt->readCompressed(file_get_contents($path . "$name.dat")); return $nbt->getData(); @@ -815,7 +816,7 @@ class Server{ $this->pluginManager->callEvent($ev); if(!$ev->isCancelled()){ - $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt = new BigEndianNBTStream(); try{ $nbt->setData($ev->getSaveData()); diff --git a/src/pocketmine/item/Item.php b/src/pocketmine/item/Item.php index e1b477d2d..5aef7797a 100644 --- a/src/pocketmine/item/Item.php +++ b/src/pocketmine/item/Item.php @@ -34,6 +34,7 @@ use pocketmine\item\enchantment\Enchantment; use pocketmine\item\enchantment\EnchantmentInstance; use pocketmine\level\Level; use pocketmine\math\Vector3; +use pocketmine\nbt\LittleEndianNBTStream; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\CompoundTag; @@ -64,7 +65,7 @@ class Item implements ItemIds, \JsonSerializable{ } if(self::$cachedParser === null){ - self::$cachedParser = new NBT(NBT::LITTLE_ENDIAN); + self::$cachedParser = new LittleEndianNBTStream(); } self::$cachedParser->read($tag); @@ -79,7 +80,7 @@ class Item implements ItemIds, \JsonSerializable{ private static function writeCompoundTag(CompoundTag $tag) : string{ if(self::$cachedParser === null){ - self::$cachedParser = new NBT(NBT::LITTLE_ENDIAN); + self::$cachedParser = new LittleEndianNBTStream(); } self::$cachedParser->setData($tag); diff --git a/src/pocketmine/level/format/io/BaseLevelProvider.php b/src/pocketmine/level/format/io/BaseLevelProvider.php index 806f1f0db..62d757375 100644 --- a/src/pocketmine/level/format/io/BaseLevelProvider.php +++ b/src/pocketmine/level/format/io/BaseLevelProvider.php @@ -29,7 +29,7 @@ use pocketmine\level\generator\Generator; use pocketmine\level\Level; use pocketmine\level\LevelException; use pocketmine\math\Vector3; -use pocketmine\nbt\NBT; +use pocketmine\nbt\BigEndianNBTStream; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\StringTag; use pocketmine\scheduler\AsyncTask; @@ -48,7 +48,7 @@ abstract class BaseLevelProvider implements LevelProvider{ if(!file_exists($this->path)){ mkdir($this->path, 0777, true); } - $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt = new BigEndianNBTStream(); $nbt->readCompressed(file_get_contents($this->getPath() . "level.dat")); $levelData = $nbt->getData()->getCompoundTag("Data"); if($levelData !== null){ @@ -120,7 +120,7 @@ abstract class BaseLevelProvider implements LevelProvider{ } public function saveLevelData(){ - $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt = new BigEndianNBTStream(); $nbt->setData(new CompoundTag("", [ $this->levelData ])); diff --git a/src/pocketmine/level/format/io/leveldb/LevelDB.php b/src/pocketmine/level/format/io/leveldb/LevelDB.php index d360268c5..b59021bc9 100644 --- a/src/pocketmine/level/format/io/leveldb/LevelDB.php +++ b/src/pocketmine/level/format/io/leveldb/LevelDB.php @@ -32,7 +32,7 @@ use pocketmine\level\generator\Flat; use pocketmine\level\generator\Generator; use pocketmine\level\Level; use pocketmine\level\LevelException; -use pocketmine\nbt\NBT; +use pocketmine\nbt\LittleEndianNBTStream; use pocketmine\nbt\tag\{ ByteTag, CompoundTag, FloatTag, IntTag, LongTag, StringTag }; @@ -94,7 +94,7 @@ class LevelDB extends BaseLevelProvider{ if(!file_exists($this->path)){ mkdir($this->path, 0777, true); } - $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt = new LittleEndianNBTStream(); $nbt->read(substr(file_get_contents($this->getPath() . "level.dat"), 8)); $levelData = $nbt->getData(); if($levelData instanceof CompoundTag){ @@ -217,7 +217,7 @@ class LevelDB extends BaseLevelProvider{ new StringTag("generatorOptions", $options["preset"] ?? "") ]); - $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt = new LittleEndianNBTStream(); $nbt->setData($levelData); $buffer = $nbt->write(); file_put_contents($path . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); @@ -247,7 +247,7 @@ class LevelDB extends BaseLevelProvider{ $this->levelData->setInt("NetworkVersion", ProtocolInfo::CURRENT_PROTOCOL); $this->levelData->setInt("StorageVersion", self::CURRENT_STORAGE_VERSION); - $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt = new LittleEndianNBTStream(); $nbt->setData($this->levelData); $buffer = $nbt->write(); file_put_contents($this->getPath() . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); @@ -416,7 +416,7 @@ class LevelDB extends BaseLevelProvider{ throw new UnsupportedChunkFormatException("don't know how to decode chunk format version $chunkVersion"); } - $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt = new LittleEndianNBTStream(); $entities = []; if(($entityData = $this->db->get($index . self::TAG_ENTITY)) !== false and strlen($entityData) > 0){ @@ -553,7 +553,7 @@ class LevelDB extends BaseLevelProvider{ */ private function writeTags(array $targets, string $index){ if(!empty($targets)){ - $nbt = new NBT(NBT::LITTLE_ENDIAN); + $nbt = new LittleEndianNBTStream(); $nbt->setData($targets); $this->db->put($index, $nbt->write()); }else{ diff --git a/src/pocketmine/level/format/io/region/Anvil.php b/src/pocketmine/level/format/io/region/Anvil.php index 7e6b27b43..e6e3ac085 100644 --- a/src/pocketmine/level/format/io/region/Anvil.php +++ b/src/pocketmine/level/format/io/region/Anvil.php @@ -27,6 +27,7 @@ use pocketmine\level\format\Chunk; use pocketmine\level\format\ChunkException; use pocketmine\level\format\io\ChunkUtils; use pocketmine\level\format\SubChunk; +use pocketmine\nbt\BigEndianNBTStream; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\ByteArrayTag; use pocketmine\nbt\tag\CompoundTag; @@ -83,7 +84,7 @@ class Anvil extends McRegion{ //TODO: TileTicks - $writer = new NBT(NBT::BIG_ENDIAN); + $writer = new BigEndianNBTStream(); $nbt->setName("Level"); $writer->setData(new CompoundTag("", [$nbt])); @@ -100,7 +101,7 @@ class Anvil extends McRegion{ } public function nbtDeserialize(string $data){ - $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt = new BigEndianNBTStream(); try{ $nbt->readCompressed($data); diff --git a/src/pocketmine/level/format/io/region/McRegion.php b/src/pocketmine/level/format/io/region/McRegion.php index addfc9b33..d073c86b0 100644 --- a/src/pocketmine/level/format/io/region/McRegion.php +++ b/src/pocketmine/level/format/io/region/McRegion.php @@ -30,6 +30,7 @@ use pocketmine\level\format\io\ChunkUtils; use pocketmine\level\format\SubChunk; use pocketmine\level\generator\Generator; use pocketmine\level\Level; +use pocketmine\nbt\BigEndianNBTStream; use pocketmine\nbt\NBT; use pocketmine\nbt\tag\{ ByteArrayTag, ByteTag, CompoundTag, IntArrayTag, IntTag, ListTag, LongTag, StringTag @@ -102,7 +103,7 @@ class McRegion extends BaseLevelProvider{ $nbt->setTag(new ListTag("TileEntities", $tiles, NBT::TAG_Compound)); - $writer = new NBT(NBT::BIG_ENDIAN); + $writer = new BigEndianNBTStream(); $nbt->setName("Level"); $writer->setData(new CompoundTag("", [$nbt])); @@ -115,7 +116,7 @@ class McRegion extends BaseLevelProvider{ * @return Chunk|null */ public function nbtDeserialize(string $data){ - $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt = new BigEndianNBTStream(); try{ $nbt->readCompressed($data); @@ -258,7 +259,7 @@ class McRegion extends BaseLevelProvider{ new StringTag("LevelName", $name), new CompoundTag("GameRules", []) ]); - $nbt = new NBT(NBT::BIG_ENDIAN); + $nbt = new BigEndianNBTStream(); $nbt->setData(new CompoundTag("", [ $levelData ])); diff --git a/src/pocketmine/nbt/BigEndianNBTStream.php b/src/pocketmine/nbt/BigEndianNBTStream.php new file mode 100644 index 000000000..9c70eb27a --- /dev/null +++ b/src/pocketmine/nbt/BigEndianNBTStream.php @@ -0,0 +1,83 @@ +get(2)); + } + + public function getSignedShort() : int{ + return Binary::readSignedShort($this->get(2)); + } + + public function putShort($v){ + $this->buffer .= Binary::writeShort($v); + } + + public function getInt() : int{ + return Binary::readInt($this->get(4)); + } + + public function putInt($v){ + $this->buffer .= Binary::writeInt($v); + } + + public function getLong() : int{ + return Binary::readLong($this->get(8)); + } + + public function putLong($v){ + $this->buffer .= Binary::writeLong($v); + } + + public function getFloat() : float{ + return Binary::readFloat($this->get(4)); + } + + public function putFloat($v){ + $this->buffer .= Binary::writeFloat($v); + } + + public function getDouble() : float{ + return Binary::readDouble($this->get(8)); + } + + public function putDouble($v){ + $this->buffer .= Binary::writeDouble($v); + } + + public function getIntArray() : array{ + $len = $this->getInt(); + return array_values(unpack("N*", $this->get($len * 4))); + } + + public function putIntArray(array $array) : void{ + $this->putInt(count($array)); + $this->put(pack("N*", ...$array)); + } +} diff --git a/src/pocketmine/nbt/LittleEndianNBTStream.php b/src/pocketmine/nbt/LittleEndianNBTStream.php new file mode 100644 index 000000000..fe1a6ea30 --- /dev/null +++ b/src/pocketmine/nbt/LittleEndianNBTStream.php @@ -0,0 +1,83 @@ +get(2)); + } + + public function getSignedShort() : int{ + return Binary::readSignedLShort($this->get(2)); + } + + public function putShort($v){ + $this->put(Binary::writeLShort($v)); + } + + public function getInt() : int{ + return Binary::readLInt($this->get(4)); + } + + public function putInt($v){ + $this->put(Binary::writeLInt($v)); + } + + public function getLong() : int{ + return Binary::readLLong($this->get(8)); + } + + public function putLong($v){ + $this->put(Binary::writeLLong($v)); + } + + public function getFloat() : float{ + return Binary::readLFloat($this->get(4)); + } + + public function putFloat($v){ + $this->put(Binary::writeLFloat($v)); + } + + public function getDouble() : float{ + return Binary::readLDouble($this->get(8)); + } + + public function putDouble($v){ + $this->put(Binary::writeLDouble($v)); + } + + public function getIntArray() : array{ + $len = $this->getInt(); + return array_values(unpack("V*", $this->get($len * 4))); + } + + public function putIntArray(array $array) : void{ + $this->putInt(count($array)); + $this->put(pack("V*", ...$array)); + } +} diff --git a/src/pocketmine/nbt/NBT.php b/src/pocketmine/nbt/NBT.php index debc56946..8675be59a 100644 --- a/src/pocketmine/nbt/NBT.php +++ b/src/pocketmine/nbt/NBT.php @@ -36,22 +36,11 @@ use pocketmine\nbt\tag\IntArrayTag; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\LongTag; -use pocketmine\nbt\tag\NamedTag; use pocketmine\nbt\tag\ShortTag; use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\Tag; -#ifndef COMPILE -use pocketmine\utils\Binary; -#endif - - -#include - -/** - * Named Binary Tag encoder/decoder - */ -class NBT{ +abstract class NBT{ public const LITTLE_ENDIAN = 0; public const BIG_ENDIAN = 1; @@ -68,11 +57,6 @@ class NBT{ public const TAG_Compound = 10; public const TAG_IntArray = 11; - public $buffer; - public $offset; - public $endianness; - private $data; - /** * @param int $type * @@ -172,260 +156,4 @@ class NBT{ return true; } - - public function get($len){ - if($len < 0){ - $this->offset = strlen($this->buffer) - 1; - return ""; - }elseif($len === true){ - return substr($this->buffer, $this->offset); - } - - return $len === 1 ? $this->buffer{$this->offset++} : substr($this->buffer, ($this->offset += $len) - $len, $len); - } - - public function put($v){ - $this->buffer .= $v; - } - - public function feof() : bool{ - return !isset($this->buffer{$this->offset}); - } - - public function __construct($endianness = self::LITTLE_ENDIAN){ - $this->offset = 0; - $this->endianness = $endianness & 0x01; - } - - public function read($buffer, $doMultiple = false, bool $network = false){ - $this->offset = 0; - $this->buffer = $buffer; - $this->data = $this->readTag($network); - if($doMultiple and $this->offset < strlen($this->buffer)){ - $this->data = [$this->data]; - do{ - $this->data[] = $this->readTag($network); - }while($this->offset < strlen($this->buffer)); - } - $this->buffer = ""; - } - - public function readCompressed($buffer){ - $this->read(zlib_decode($buffer)); - } - - /** - * @param bool $network - * - * @return string|bool - */ - public function write(bool $network = false){ - $this->offset = 0; - $this->buffer = ""; - - if($this->data instanceof CompoundTag){ - $this->writeTag($this->data, $network); - - return $this->buffer; - }elseif(is_array($this->data)){ - foreach($this->data as $tag){ - $this->writeTag($tag, $network); - } - return $this->buffer; - } - - return false; - } - - public function writeCompressed($compression = ZLIB_ENCODING_GZIP, $level = 7){ - if(($write = $this->write()) !== false){ - return zlib_encode($write, $compression, $level); - } - - return false; - } - - public function readTag(bool $network = false){ - if($this->feof()){ - return new EndTag(); - } - - $tagType = $this->getByte(); - $tag = self::createTag($tagType); - - if($tag instanceof NamedTag){ - $tag->setName($this->getString($network)); - $tag->read($this, $network); - } - - return $tag; - } - - public function writeTag(Tag $tag, bool $network = false){ - $this->putByte($tag->getType()); - if($tag instanceof NamedTag){ - $this->putString($tag->getName(), $network); - } - $tag->write($this, $network); - } - - public function getByte() : int{ - return Binary::readByte($this->get(1)); - } - - public function getSignedByte() : int{ - return Binary::readSignedByte($this->get(1)); - } - - public function putByte($v){ - $this->buffer .= Binary::writeByte($v); - } - - public function getShort() : int{ - return $this->endianness === self::BIG_ENDIAN ? Binary::readShort($this->get(2)) : Binary::readLShort($this->get(2)); - } - - public function getSignedShort() : int{ - return $this->endianness === self::BIG_ENDIAN ? Binary::readSignedShort($this->get(2)) : Binary::readSignedLShort($this->get(2)); - } - - public function putShort($v){ - $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeShort($v) : Binary::writeLShort($v); - } - - public function getInt(bool $network = false) : int{ - if($network === true){ - return Binary::readVarInt($this->buffer, $this->offset); - } - return $this->endianness === self::BIG_ENDIAN ? Binary::readInt($this->get(4)) : Binary::readLInt($this->get(4)); - } - - public function putInt($v, bool $network = false){ - if($network === true){ - $this->buffer .= Binary::writeVarInt($v); - }else{ - $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeInt($v) : Binary::writeLInt($v); - } - } - - public function getLong(bool $network = false) : int{ - if($network){ - return Binary::readVarLong($this->buffer, $this->offset); - } - return $this->endianness === self::BIG_ENDIAN ? Binary::readLong($this->get(8)) : Binary::readLLong($this->get(8)); - } - - public function putLong($v, bool $network = false){ - if($network){ - $this->buffer .= Binary::writeVarLong($v); - }else{ - $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeLong($v) : Binary::writeLLong($v); - } - } - - public function getFloat() : float{ - return $this->endianness === self::BIG_ENDIAN ? Binary::readFloat($this->get(4)) : Binary::readLFloat($this->get(4)); - } - - public function putFloat($v){ - $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeFloat($v) : Binary::writeLFloat($v); - } - - public function getDouble() : float{ - return $this->endianness === self::BIG_ENDIAN ? Binary::readDouble($this->get(8)) : Binary::readLDouble($this->get(8)); - } - - public function putDouble($v){ - $this->buffer .= $this->endianness === self::BIG_ENDIAN ? Binary::writeDouble($v) : Binary::writeLDouble($v); - } - - public function getString(bool $network = false){ - $len = $network ? Binary::readUnsignedVarInt($this->buffer, $this->offset) : $this->getShort(); - return $this->get($len); - } - - public function putString($v, bool $network = false){ - if($network === true){ - $this->put(Binary::writeUnsignedVarInt(strlen($v))); - }else{ - $this->putShort(strlen($v)); - } - $this->buffer .= $v; - } - - public function getArray() : array{ - $data = []; - self::toArray($data, $this->data); - return $data; - } - - private static function toArray(array &$data, Tag $tag){ - /** @var CompoundTag[]|ListTag[]|IntArrayTag[] $tag */ - foreach($tag as $key => $value){ - if($value instanceof CompoundTag or $value instanceof ListTag or $value instanceof IntArrayTag){ - $data[$key] = []; - self::toArray($data[$key], $value); - }else{ - $data[$key] = $value->getValue(); - } - } - } - - public static function fromArrayGuesser($key, $value){ - if(is_int($value)){ - return new IntTag($key, $value); - }elseif(is_float($value)){ - return new FloatTag($key, $value); - }elseif(is_string($value)){ - return new StringTag($key, $value); - }elseif(is_bool($value)){ - return new ByteTag($key, $value ? 1 : 0); - } - - return null; - } - - private static function fromArray(Tag $tag, array $data, callable $guesser){ - foreach($data as $key => $value){ - if(is_array($value)){ - $isNumeric = true; - $isIntArray = true; - foreach($value as $k => $v){ - if(!is_numeric($k)){ - $isNumeric = false; - break; - }elseif(!is_int($v)){ - $isIntArray = false; - } - } - $tag{$key} = $isNumeric ? ($isIntArray ? new IntArrayTag($key, []) : new ListTag($key, [])) : new CompoundTag($key, []); - self::fromArray($tag->{$key}, $value, $guesser); - }else{ - $v = call_user_func($guesser, $key, $value); - if($v instanceof Tag){ - $tag{$key} = $v; - } - } - } - } - - public function setArray(array $data, callable $guesser = null){ - $this->data = new CompoundTag("", []); - self::fromArray($this->data, $data, $guesser ?? [self::class, "fromArrayGuesser"]); - } - - /** - * @return CompoundTag|array - */ - public function getData(){ - return $this->data; - } - - /** - * @param CompoundTag|array $data - */ - public function setData($data){ - $this->data = $data; - } - } diff --git a/src/pocketmine/nbt/NBTStream.php b/src/pocketmine/nbt/NBTStream.php new file mode 100644 index 000000000..0ffaad878 --- /dev/null +++ b/src/pocketmine/nbt/NBTStream.php @@ -0,0 +1,267 @@ +offset = strlen($this->buffer) - 1; + return ""; + }elseif($len === true){ + return substr($this->buffer, $this->offset); + } + + return $len === 1 ? $this->buffer{$this->offset++} : substr($this->buffer, ($this->offset += $len) - $len, $len); + } + + public function put($v){ + $this->buffer .= $v; + } + + public function feof() : bool{ + return !isset($this->buffer{$this->offset}); + } + + public function __construct(){ + $this->offset = 0; + $this->buffer = ""; + } + + public function read($buffer, $doMultiple = false){ + $this->offset = 0; + $this->buffer = $buffer; + $this->data = $this->readTag(); + if($doMultiple and $this->offset < strlen($this->buffer)){ + $this->data = [$this->data]; + do{ + $this->data[] = $this->readTag(); + }while($this->offset < strlen($this->buffer)); + } + $this->buffer = ""; + } + + public function readCompressed($buffer){ + $this->read(zlib_decode($buffer)); + } + + /** + * @return bool|string + */ + public function write(){ + $this->offset = 0; + $this->buffer = ""; + + if($this->data instanceof CompoundTag){ + $this->writeTag($this->data); + + return $this->buffer; + }elseif(is_array($this->data)){ + foreach($this->data as $tag){ + $this->writeTag($tag); + } + return $this->buffer; + } + + return false; + } + + public function writeCompressed($compression = ZLIB_ENCODING_GZIP, $level = 7){ + if(($write = $this->write()) !== false){ + return zlib_encode($write, $compression, $level); + } + + return false; + } + + public function readTag(){ + if($this->feof()){ + return new EndTag(); + } + + $tagType = $this->getByte(); + $tag = NBT::createTag($tagType); + + if($tag instanceof NamedTag){ + $tag->setName($this->getString()); + $tag->read($this); + } + + return $tag; + } + + public function writeTag(Tag $tag){ + $this->putByte($tag->getType()); + if($tag instanceof NamedTag){ + $this->putString($tag->getName()); + } + $tag->write($this); + } + + public function getByte() : int{ + return Binary::readByte($this->get(1)); + } + + public function getSignedByte() : int{ + return Binary::readSignedByte($this->get(1)); + } + + public function putByte($v){ + $this->buffer .= Binary::writeByte($v); + } + + abstract public function getShort() : int; + + abstract public function getSignedShort() : int; + + abstract public function putShort($v); + + + abstract public function getInt() : int; + + abstract public function putInt($v); + + abstract public function getLong() : int; + + abstract public function putLong($v); + + + abstract public function getFloat() : float; + + abstract public function putFloat($v); + + + abstract public function getDouble() : float; + + abstract public function putDouble($v); + + public function getString(){ + return $this->get($this->getShort()); + } + + public function putString($v){ + $this->putShort(strlen($v)); + $this->put($v); + } + + abstract public function getIntArray() : array; + + abstract public function putIntArray(array $array) : void; + + + + public function getArray() : array{ + $data = []; + self::toArray($data, $this->data); + return $data; + } + + private static function toArray(array &$data, Tag $tag){ + /** @var CompoundTag[]|ListTag[]|IntArrayTag[] $tag */ + foreach($tag as $key => $value){ + if($value instanceof CompoundTag or $value instanceof ListTag or $value instanceof IntArrayTag){ + $data[$key] = []; + self::toArray($data[$key], $value); + }else{ + $data[$key] = $value->getValue(); + } + } + } + + public static function fromArrayGuesser($key, $value){ + if(is_int($value)){ + return new IntTag($key, $value); + }elseif(is_float($value)){ + return new FloatTag($key, $value); + }elseif(is_string($value)){ + return new StringTag($key, $value); + }elseif(is_bool($value)){ + return new ByteTag($key, $value ? 1 : 0); + } + + return null; + } + + private static function fromArray(Tag $tag, array $data, callable $guesser){ + foreach($data as $key => $value){ + if(is_array($value)){ + $isNumeric = true; + $isIntArray = true; + foreach($value as $k => $v){ + if(!is_numeric($k)){ + $isNumeric = false; + break; + }elseif(!is_int($v)){ + $isIntArray = false; + } + } + $tag{$key} = $isNumeric ? ($isIntArray ? new IntArrayTag($key, []) : new ListTag($key, [])) : new CompoundTag($key, []); + self::fromArray($tag->{$key}, $value, $guesser); + }else{ + $v = call_user_func($guesser, $key, $value); + if($v instanceof Tag){ + $tag{$key} = $v; + } + } + } + } + + public function setArray(array $data, callable $guesser = null){ + $this->data = new CompoundTag("", []); + self::fromArray($this->data, $data, $guesser ?? [self::class, "fromArrayGuesser"]); + } + + /** + * @return CompoundTag|array + */ + public function getData(){ + return $this->data; + } + + /** + * @param CompoundTag|array $data + */ + public function setData($data){ + $this->data = $data; + } +} diff --git a/src/pocketmine/nbt/NetworkLittleEndianNBTStream.php b/src/pocketmine/nbt/NetworkLittleEndianNBTStream.php new file mode 100644 index 000000000..61abc5de5 --- /dev/null +++ b/src/pocketmine/nbt/NetworkLittleEndianNBTStream.php @@ -0,0 +1,70 @@ +buffer, $this->offset); + } + + public function putInt($v){ + $this->put(Binary::writeVarInt($v)); + } + + public function getLong() : int{ + return Binary::readVarLong($this->buffer, $this->offset); + } + + public function putLong($v){ + $this->put(Binary::writeVarLong($v)); + } + + public function getString(){ + return $this->get(Binary::readUnsignedVarInt($this->buffer, $this->offset)); + } + + public function putString($v){ + $this->put(Binary::writeUnsignedVarInt(strlen($v)) . $v); + } + + public function getIntArray() : array{ + $len = $this->getInt(); //varint + $ret = []; + for($i = 0; $i < $len; ++$i){ + $ret[] = $this->getInt(); //varint + } + + return $ret; + } + + public function putIntArray(array $array) : void{ + $this->putInt(count($array)); //varint + foreach($array as $v){ + $this->putInt($v); //varint + } + } +} diff --git a/src/pocketmine/nbt/tag/ByteArrayTag.php b/src/pocketmine/nbt/tag/ByteArrayTag.php index 8d4555d33..0023958c7 100644 --- a/src/pocketmine/nbt/tag/ByteArrayTag.php +++ b/src/pocketmine/nbt/tag/ByteArrayTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -43,12 +44,12 @@ class ByteArrayTag extends NamedTag{ return NBT::TAG_ByteArray; } - public function read(NBT $nbt, bool $network = false) : void{ - $this->value = $nbt->get($nbt->getInt($network)); + public function read(NBTStream $nbt) : void{ + $this->value = $nbt->get($nbt->getInt()); } - public function write(NBT $nbt, bool $network = false) : void{ - $nbt->putInt(strlen($this->value), $network); + public function write(NBTStream $nbt) : void{ + $nbt->putInt(strlen($this->value)); $nbt->put($this->value); } diff --git a/src/pocketmine/nbt/tag/ByteTag.php b/src/pocketmine/nbt/tag/ByteTag.php index aca900764..dd93a7864 100644 --- a/src/pocketmine/nbt/tag/ByteTag.php +++ b/src/pocketmine/nbt/tag/ByteTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -43,11 +44,11 @@ class ByteTag extends NamedTag{ return NBT::TAG_Byte; } - public function read(NBT $nbt, bool $network = false) : void{ + public function read(NBTStream $nbt) : void{ $this->value = $nbt->getSignedByte(); } - public function write(NBT $nbt, bool $network = false) : void{ + public function write(NBTStream $nbt) : void{ $nbt->putByte($this->value); } diff --git a/src/pocketmine/nbt/tag/CompoundTag.php b/src/pocketmine/nbt/tag/CompoundTag.php index 1322e7472..e17f1f495 100644 --- a/src/pocketmine/nbt/tag/CompoundTag.php +++ b/src/pocketmine/nbt/tag/CompoundTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -427,23 +428,23 @@ class CompoundTag extends NamedTag implements \ArrayAccess{ return NBT::TAG_Compound; } - public function read(NBT $nbt, bool $network = false) : void{ + public function read(NBTStream $nbt) : void{ $this->value = []; do{ - $tag = $nbt->readTag($network); + $tag = $nbt->readTag(); if($tag instanceof NamedTag and $tag->__name !== ""){ $this->{$tag->__name} = $tag; } }while(!($tag instanceof EndTag) and !$nbt->feof()); } - public function write(NBT $nbt, bool $network = false) : void{ + public function write(NBTStream $nbt) : void{ foreach($this as $tag){ if($tag instanceof Tag and !($tag instanceof EndTag)){ - $nbt->writeTag($tag, $network); + $nbt->writeTag($tag); } } - $nbt->writeTag(new EndTag, $network); + $nbt->writeTag(new EndTag); } public function __toString(){ diff --git a/src/pocketmine/nbt/tag/DoubleTag.php b/src/pocketmine/nbt/tag/DoubleTag.php index bc3f1cf57..1ce8f91d5 100644 --- a/src/pocketmine/nbt/tag/DoubleTag.php +++ b/src/pocketmine/nbt/tag/DoubleTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -43,11 +44,11 @@ class DoubleTag extends NamedTag{ return NBT::TAG_Double; } - public function read(NBT $nbt, bool $network = false) : void{ + public function read(NBTStream $nbt) : void{ $this->value = $nbt->getDouble(); } - public function write(NBT $nbt, bool $network = false) : void{ + public function write(NBTStream $nbt) : void{ $nbt->putDouble($this->value); } diff --git a/src/pocketmine/nbt/tag/EndTag.php b/src/pocketmine/nbt/tag/EndTag.php index c063196d4..11d692131 100644 --- a/src/pocketmine/nbt/tag/EndTag.php +++ b/src/pocketmine/nbt/tag/EndTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; class EndTag extends Tag{ @@ -31,11 +32,11 @@ class EndTag extends Tag{ return NBT::TAG_End; } - public function read(NBT $nbt, bool $network = false) : void{ + public function read(NBTStream $nbt) : void{ } - public function write(NBT $nbt, bool $network = false) : void{ + public function write(NBTStream $nbt) : void{ } } diff --git a/src/pocketmine/nbt/tag/FloatTag.php b/src/pocketmine/nbt/tag/FloatTag.php index 4855bd916..41424f7fe 100644 --- a/src/pocketmine/nbt/tag/FloatTag.php +++ b/src/pocketmine/nbt/tag/FloatTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -43,11 +44,11 @@ class FloatTag extends NamedTag{ return NBT::TAG_Float; } - public function read(NBT $nbt, bool $network = false) : void{ + public function read(NBTStream $nbt) : void{ $this->value = $nbt->getFloat(); } - public function write(NBT $nbt, bool $network = false) : void{ + public function write(NBTStream $nbt) : void{ $nbt->putFloat($this->value); } diff --git a/src/pocketmine/nbt/tag/IntArrayTag.php b/src/pocketmine/nbt/tag/IntArrayTag.php index bb1c9e8ea..30c61d4f9 100644 --- a/src/pocketmine/nbt/tag/IntArrayTag.php +++ b/src/pocketmine/nbt/tag/IntArrayTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -43,14 +44,12 @@ class IntArrayTag extends NamedTag{ return NBT::TAG_IntArray; } - public function read(NBT $nbt, bool $network = false) : void{ - $size = $nbt->getInt($network); - $this->value = array_values(unpack($nbt->endianness === NBT::LITTLE_ENDIAN ? "V*" : "N*", $nbt->get($size * 4))); + public function read(NBTStream $nbt) : void{ + $this->value = $nbt->getIntArray(); } - public function write(NBT $nbt, bool $network = false) : void{ - $nbt->putInt(count($this->value), $network); - $nbt->put(pack($nbt->endianness === NBT::LITTLE_ENDIAN ? "V*" : "N*", ...$this->value)); + public function write(NBTStream $nbt) : void{ + $nbt->putIntArray($this->value); } public function __toString(){ diff --git a/src/pocketmine/nbt/tag/IntTag.php b/src/pocketmine/nbt/tag/IntTag.php index 093cd9342..ed38d756a 100644 --- a/src/pocketmine/nbt/tag/IntTag.php +++ b/src/pocketmine/nbt/tag/IntTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -41,12 +42,12 @@ class IntTag extends NamedTag{ return NBT::TAG_Int; } - public function read(NBT $nbt, bool $network = false) : void{ - $this->value = $nbt->getInt($network); + public function read(NBTStream $nbt) : void{ + $this->value = $nbt->getInt(); } - public function write(NBT $nbt, bool $network = false) : void{ - $nbt->putInt($this->value, $network); + public function write(NBTStream $nbt) : void{ + $nbt->putInt($this->value); } /** diff --git a/src/pocketmine/nbt/tag/ListTag.php b/src/pocketmine/nbt/tag/ListTag.php index 6b3db2636..b7f723c88 100644 --- a/src/pocketmine/nbt/tag/ListTag.php +++ b/src/pocketmine/nbt/tag/ListTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -163,20 +164,20 @@ class ListTag extends NamedTag implements \ArrayAccess, \Countable{ return $this->tagType; } - public function read(NBT $nbt, bool $network = false) : void{ + public function read(NBTStream $nbt) : void{ $this->value = []; $this->tagType = $nbt->getByte(); - $size = $nbt->getInt($network); + $size = $nbt->getInt(); $tagBase = NBT::createTag($this->tagType); for($i = 0; $i < $size and !$nbt->feof(); ++$i){ $tag = clone $tagBase; - $tag->read($nbt, $network); + $tag->read($nbt); $this->{$i} = $tag; } } - public function write(NBT $nbt, bool $network = false) : void{ + public function write(NBTStream $nbt) : void{ if($this->tagType === NBT::TAG_End){ //previously empty list, try detecting type from tag children $id = NBT::TAG_End; foreach($this as $tag){ @@ -200,9 +201,9 @@ class ListTag extends NamedTag implements \ArrayAccess, \Countable{ $tags[] = $tag; } } - $nbt->putInt(count($tags), $network); + $nbt->putInt(count($tags)); foreach($tags as $tag){ - $tag->write($nbt, $network); + $tag->write($nbt); } } diff --git a/src/pocketmine/nbt/tag/LongTag.php b/src/pocketmine/nbt/tag/LongTag.php index 5fe001327..e435afefc 100644 --- a/src/pocketmine/nbt/tag/LongTag.php +++ b/src/pocketmine/nbt/tag/LongTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -43,12 +44,12 @@ class LongTag extends NamedTag{ return NBT::TAG_Long; } - public function read(NBT $nbt, bool $network = false) : void{ - $this->value = $nbt->getLong($network); + public function read(NBTStream $nbt) : void{ + $this->value = $nbt->getLong(); } - public function write(NBT $nbt, bool $network = false) : void{ - $nbt->putLong($this->value, $network); + public function write(NBTStream $nbt) : void{ + $nbt->putLong($this->value); } /** diff --git a/src/pocketmine/nbt/tag/ShortTag.php b/src/pocketmine/nbt/tag/ShortTag.php index f3b5b5705..6eabeb222 100644 --- a/src/pocketmine/nbt/tag/ShortTag.php +++ b/src/pocketmine/nbt/tag/ShortTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -43,11 +44,11 @@ class ShortTag extends NamedTag{ return NBT::TAG_Short; } - public function read(NBT $nbt, bool $network = false) : void{ + public function read(NBTStream $nbt) : void{ $this->value = $nbt->getSignedShort(); } - public function write(NBT $nbt, bool $network = false) : void{ + public function write(NBTStream $nbt) : void{ $nbt->putShort($this->value); } diff --git a/src/pocketmine/nbt/tag/StringTag.php b/src/pocketmine/nbt/tag/StringTag.php index f0ad886b8..53deb5d87 100644 --- a/src/pocketmine/nbt/tag/StringTag.php +++ b/src/pocketmine/nbt/tag/StringTag.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\nbt\tag; use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; #include @@ -43,12 +44,12 @@ class StringTag extends NamedTag{ return NBT::TAG_String; } - public function read(NBT $nbt, bool $network = false) : void{ - $this->value = $nbt->getString($network); + public function read(NBTStream $nbt) : void{ + $this->value = $nbt->getString(); } - public function write(NBT $nbt, bool $network = false) : void{ - $nbt->putString($this->value, $network); + public function write(NBTStream $nbt) : void{ + $nbt->putString($this->value); } /** diff --git a/src/pocketmine/nbt/tag/Tag.php b/src/pocketmine/nbt/tag/Tag.php index 74e4d7b78..f4233378e 100644 --- a/src/pocketmine/nbt/tag/Tag.php +++ b/src/pocketmine/nbt/tag/Tag.php @@ -26,7 +26,7 @@ declare(strict_types=1); */ namespace pocketmine\nbt\tag; -use pocketmine\nbt\NBT; +use pocketmine\nbt\NBTStream; abstract class Tag extends \stdClass{ @@ -42,9 +42,9 @@ abstract class Tag extends \stdClass{ $this->value = $value; } - abstract public function write(NBT $nbt, bool $network = false) : void; + abstract public function write(NBTStream $nbt) : void; - abstract public function read(NBT $nbt, bool $network = false) : void; + abstract public function read(NBTStream $nbt) : void; public function __toString(){ return (string) $this->value; diff --git a/src/pocketmine/tile/Spawnable.php b/src/pocketmine/tile/Spawnable.php index 598f6211c..427512577 100644 --- a/src/pocketmine/tile/Spawnable.php +++ b/src/pocketmine/tile/Spawnable.php @@ -25,6 +25,7 @@ namespace pocketmine\tile; use pocketmine\level\Level; use pocketmine\nbt\NBT; +use pocketmine\nbt\NetworkLittleEndianNBTStream; use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\protocol\BlockEntityDataPacket; use pocketmine\Player; @@ -92,11 +93,11 @@ abstract class Spawnable extends Tile{ final public function getSerializedSpawnCompound() : string{ if($this->spawnCompoundCache === null){ if(self::$nbtWriter === null){ - self::$nbtWriter = new NBT(NBT::LITTLE_ENDIAN); + self::$nbtWriter = new NetworkLittleEndianNBTStream(); } self::$nbtWriter->setData($this->getSpawnCompound()); - $this->spawnCompoundCache = self::$nbtWriter->write(true); + $this->spawnCompoundCache = self::$nbtWriter->write(); } return $this->spawnCompoundCache;