Skip to content

Commit 859f4a6

Browse files
committed
allow reading specific parts of an NBT structure as raw data to prevent excessive memory usage from large numbers of small child tags
1 parent 3cb087c commit 859f4a6

17 files changed

+454
-15
lines changed

src/Tag/ArrayValueTag.php

+24
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use ArrayAccess;
66
use Aternos\Nbt\IO\Reader\Reader;
77
use Aternos\Nbt\IO\Writer\Writer;
8+
use BadMethodCallException;
89
use Countable;
910
use Exception;
1011
use Iterator;
@@ -28,13 +29,26 @@ public function writeContent(Writer $writer): static
2829
return $this;
2930
}
3031

32+
/**
33+
* @inheritDoc
34+
*/
3135
protected function readContent(Reader $reader): static
3236
{
3337
$length = $reader->getDeserializer()->readLengthPrefix()->getValue();
3438
$this->valueArray = $this->readValues($reader, $length);
3539
return $this;
3640
}
3741

42+
/**
43+
* @inheritDoc
44+
*/
45+
protected static function readContentRaw(Reader $reader, TagOptions $options): string
46+
{
47+
$length = $reader->getDeserializer()->readLengthPrefix();
48+
$valueData = static::readValuesRaw($reader, $length->getValue());
49+
return $length->getRawData() . $valueData;
50+
}
51+
3852
/**
3953
* @param Writer $writer
4054
* @return string
@@ -48,6 +62,16 @@ abstract protected function writeValues(Writer $writer): string;
4862
*/
4963
abstract protected function readValues(Reader $reader, int $length): array;
5064

65+
/**
66+
* @param Reader $reader
67+
* @param int $length
68+
* @return string
69+
*/
70+
protected static function readValuesRaw(Reader $reader, int $length): string
71+
{
72+
throw new BadMethodCallException("Not implemented");
73+
}
74+
5175
/**
5276
* @param $value
5377
* @return bool

src/Tag/ByteArrayTag.php

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ protected function readValues(Reader $reader, int $length): array
3434
return $values;
3535
}
3636

37+
/**
38+
* @inheritDoc
39+
*/
40+
protected static function readValuesRaw(Reader $reader, int $length): string
41+
{
42+
$result = "";
43+
for ($i = 0; $i < $length; $i++) {
44+
$result .= $reader->getDeserializer()->readByte()->getRawData();
45+
}
46+
return $result;
47+
}
48+
3749
/**
3850
* @inheritDoc
3951
*/

src/Tag/ByteTag.php

+8
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,12 @@ protected function readContent(Reader $reader): static
2626
$this->value = $reader->getDeserializer()->readByte()->getValue();
2727
return $this;
2828
}
29+
30+
/**
31+
* @inheritDoc
32+
*/
33+
protected static function readContentRaw(Reader $reader, TagOptions $options): string
34+
{
35+
return $reader->getDeserializer()->readByte()->getRawData();
36+
}
2937
}

src/Tag/CompoundTag.php

+53-3
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,39 @@ class CompoundTag extends Tag implements Iterator, ArrayAccess, Countable
1717
* @var Tag[]
1818
*/
1919
protected array $valueArray = [];
20+
protected ?string $rawContent = null;
21+
protected ?int $rawContentFormat = null;
22+
23+
/**
24+
* @return bool
25+
*/
26+
public function isRaw(): bool
27+
{
28+
return $this->rawContent !== null;
29+
}
2030

2131
/**
2232
* @inheritDoc
2333
* @throws Exception
2434
*/
2535
public function writeContent(Writer $writer): static
2636
{
37+
if($this->isRaw()) {
38+
if($this->rawContentFormat !== $writer->getFormat()) {
39+
throw new Exception("Cannot change format of raw compound tag");
40+
}
41+
$writer->write($this->rawContent);
42+
return $this;
43+
}
44+
2745
$writtenNames = [];
2846
foreach ($this->valueArray as $value) {
2947
if (in_array($value->getName(), $writtenNames)) {
3048
throw new Exception("Duplicate key '" . $value->getName() . "' in compound tag");
3149
}
3250
$value->writeData($writer, true);
3351
}
34-
(new EndTag())->writeData($writer);
52+
(new EndTag($this->options))->writeData($writer);
3553
return $this;
3654
}
3755

@@ -41,12 +59,31 @@ public function writeContent(Writer $writer): static
4159
*/
4260
protected function readContent(Reader $reader): static
4361
{
44-
while (!(($tag = Tag::load($reader)) instanceof EndTag)) {
45-
$this->valueArray[] = $tag;
62+
if($this->options->shouldBeReadRaw($this)) {
63+
$this->rawContentFormat = $reader->getFormat();
64+
$this->rawContent = static::readContentRaw($reader, $this->options);
65+
return $this;
66+
}
67+
while (!(($tag = Tag::load($reader, $this->options, $this)) instanceof EndTag)) {
68+
$this->valueArray[] = $tag->setParentTag($this);
4669
}
4770
return $this;
4871
}
4972

73+
/**
74+
* @inheritDoc
75+
* @throws Exception
76+
*/
77+
protected static function readContentRaw(Reader $reader, TagOptions $options): string
78+
{
79+
$result = "";
80+
do {
81+
$tag = Tag::loadRaw($reader, $options);
82+
$result .= $tag->getData();
83+
} while ($tag->getTagType() !== TagType::TAG_End);
84+
return $result;
85+
}
86+
5087
/**
5188
* @param Tag $value
5289
* @inheritDoc
@@ -60,6 +97,9 @@ public function offsetSet($offset, $value): void
6097
if (!is_string($offset) && !is_null($offset)) {
6198
throw new Exception("Invalid CompoundTag key");
6299
}
100+
if($this->isRaw()) {
101+
throw new Exception("Raw compound tags cannot be modified");
102+
}
63103
if (is_null($offset) && is_null($value->getName())) {
64104
throw new Exception("Tags inside a CompoundTag must be named.");
65105
}
@@ -68,6 +108,7 @@ public function offsetSet($offset, $value): void
68108
} else {
69109
$offset = $value->getName();
70110
}
111+
$value->setParentTag($this);
71112
$this->offsetUnset($offset);
72113
$this->valueArray[] = $value;
73114
}
@@ -140,11 +181,16 @@ public function offsetGet($offset)
140181

141182
/**
142183
* @inheritDoc
184+
* @throws Exception
143185
*/
144186
public function offsetUnset($offset)
145187
{
188+
if($this->isRaw()) {
189+
throw new Exception("Raw compound tags cannot be modified");
190+
}
146191
foreach ($this->valueArray as $i => $val) {
147192
if ($val->getName() === $offset) {
193+
$val->setParentTag(null);
148194
unset($this->valueArray[$i]);
149195
break;
150196
}
@@ -164,6 +210,9 @@ public function count(): int
164210
*/
165211
protected function getValueString(): string
166212
{
213+
if($this->isRaw()) {
214+
return strlen($this->rawContent) . " bytes";
215+
}
167216
return $this->count() . " entr" . ($this->count() === 1 ? "y" : "ies") . "\n{\n" .
168217
$this->indent(implode(", \n", array_map("strval", array_values($this->valueArray)))) .
169218
"\n}";
@@ -199,6 +248,7 @@ public function set(?string $name, Tag $tag): CompoundTag
199248
/**
200249
* @param string $name
201250
* @return $this
251+
* @throws Exception
202252
*/
203253
public function delete(string $name): CompoundTag
204254
{

src/Tag/DoubleTag.php

+8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ protected function readContent(Reader $reader): static
3535
return $this;
3636
}
3737

38+
/**
39+
* @inheritDoc
40+
*/
41+
protected static function readContentRaw(Reader $reader, TagOptions $options): string
42+
{
43+
return $reader->getDeserializer()->readDouble()->getRawData();
44+
}
45+
3846
/**
3947
* @inheritDoc
4048
*/

src/Tag/EndTag.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Aternos\Nbt\Tag;
44

55
use Aternos\Nbt\IO\Reader\Reader;
6-
use Aternos\Nbt\Deserializer\NbtDeserializer;
76
use Aternos\Nbt\IO\Writer\Writer;
87

98
class EndTag extends Tag
@@ -29,7 +28,15 @@ protected function readContent(Reader $reader): static
2928
/**
3029
* @inheritDoc
3130
*/
32-
public function canBeNamed(): bool
31+
protected static function readContentRaw(Reader $reader, TagOptions $options): string
32+
{
33+
return "";
34+
}
35+
36+
/**
37+
* @inheritDoc
38+
*/
39+
public static function canBeNamed(): bool
3340
{
3441
return false;
3542
}

src/Tag/FloatTag.php

+8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ protected function readContent(Reader $reader): static
3535
return $this;
3636
}
3737

38+
/**
39+
* @inheritDoc
40+
*/
41+
protected static function readContentRaw(Reader $reader, TagOptions $options): string
42+
{
43+
return $reader->getDeserializer()->readFloat()->getRawData();
44+
}
45+
3846
/**
3947
* @inheritDoc
4048
*/

src/Tag/IntArrayTag.php

+12
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ protected function readValues(Reader $reader, int $length): array
3232
return $values;
3333
}
3434

35+
/**
36+
* @inheritDoc
37+
*/
38+
protected static function readValuesRaw(Reader $reader, int $length): string
39+
{
40+
$result = "";
41+
for ($i = 0; $i < $length; $i++) {
42+
$result .= $reader->getDeserializer()->readInt()->getRawData();
43+
}
44+
return $result;
45+
}
46+
3547
/**
3648
* @inheritDoc
3749
*/

src/Tag/IntTag.php

+8
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,12 @@ protected function readContent(Reader $reader): static
2626
$this->value = $reader->getDeserializer()->readInt()->getValue();
2727
return $this;
2828
}
29+
30+
/**
31+
* @inheritDoc
32+
*/
33+
protected static function readContentRaw(Reader $reader, TagOptions $options): string
34+
{
35+
return $reader->getDeserializer()->readInt()->getRawData();
36+
}
2937
}

src/Tag/ListTag.php

+50-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ protected function readValues(Reader $reader, int $length): array
7171
throw new Exception("Unknown ListTag content type " . $this->contentTagType);
7272
}
7373
for ($i = 0; $i < $length; $i++) {
74-
$values[] = (new $tagClass())->read($reader, false);
74+
$values[] = (new $tagClass($this->options))->setParentTag($this)->read($reader, false);
7575
}
7676
return $values;
7777
}
@@ -91,12 +91,38 @@ protected function checkArrayValue($value): bool
9191
return $value::TYPE === $this->contentTagType;
9292
}
9393

94+
/**
95+
* @inheritDoc
96+
*/
9497
protected function readContent(Reader $reader): static
9598
{
9699
$this->contentTagType = $reader->getDeserializer()->readByte()->getValue();
97100
return parent::readContent($reader);
98101
}
99102

103+
/**
104+
* @inheritDoc
105+
* @throws Exception
106+
*/
107+
protected static function readContentRaw(Reader $reader, TagOptions $options): string
108+
{
109+
$contentTagType = $reader->getDeserializer()->readByte();
110+
$length = $reader->getDeserializer()->readLengthPrefix();
111+
$valueData = "";
112+
113+
/** @var Tag $tagClass */
114+
$tagClass = Tag::getTagClass($contentTagType->getValue());
115+
if (is_null($tagClass)) {
116+
throw new Exception("Unknown ListTag content type " . $contentTagType->getValue());
117+
}
118+
$lengthVal = $length->getValue();
119+
for ($i = 0; $i < $lengthVal; $i++) {
120+
$valueData .= $tagClass::readRaw($reader, $options, false);
121+
}
122+
123+
return $contentTagType->getRawData() . $length->getRawData() . $valueData;
124+
}
125+
100126
/**
101127
* @inheritDoc
102128
*/
@@ -113,6 +139,29 @@ protected function getTagTypeString(): string
113139
return parent::getTagTypeString() . "<" . TagType::NAMES[$this->contentTagType] . ">";
114140
}
115141

142+
/**
143+
* @inheritDoc
144+
*/
145+
public function offsetSet($offset, $value)
146+
{
147+
/** @var Tag $previousValue */
148+
$previousValue = $this->valueArray[$offset] ?? null;
149+
parent::offsetSet($offset, $value);
150+
$value->setParentTag($this);
151+
$previousValue?->setParentTag(null);
152+
}
153+
154+
/**
155+
* @inheritDoc
156+
*/
157+
public function offsetUnset($offset)
158+
{
159+
/** @var Tag $previousValue */
160+
$previousValue = $this->valueArray[$offset] ?? null;
161+
$previousValue?->setParentTag(null);
162+
parent::offsetUnset($offset);
163+
}
164+
116165
/**
117166
* @inheritDoc
118167
*/

src/Tag/LongArrayTag.php

+12
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ protected function readValues(Reader $reader, int $length): array
4343
return $values;
4444
}
4545

46+
/**
47+
* @inheritDoc
48+
*/
49+
protected static function readValuesRaw(Reader $reader, int $length): string
50+
{
51+
$raw = "";
52+
for ($i = 0; $i < $length; $i++) {
53+
$raw = $reader->getDeserializer()->readLong()->getRawData();
54+
}
55+
return $raw;
56+
}
57+
4658
/**
4759
* @inheritDoc
4860
*/

0 commit comments

Comments
 (0)