nbtx
Minecraft NBT parser and writer library.
Note that this library uses little-endian byte order by default which differs from most libraries that use big-endian byte order by default.
1# MIT License 2# 3# Copyright (c) 2025 Jonas da Silva 4# 5# Permission is hereby granted, free of charge, to any person obtaining a copy 6# of this software and associated documentation files (the "Software"), to deal 7# in the Software without restriction, including without limitation the rights 8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9# copies of the Software, and to permit persons to whom the Software is 10# furnished to do so, subject to the following conditions: 11# 12# The above copyright notice and this permission notice shall be included in all 13# copies or substantial portions of the Software. 14# 15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21# SOFTWARE. 22 23""" 24Minecraft NBT parser and writer library. 25 26Note that this library uses little-endian byte order by default which differs 27from most libraries that use big-endian byte order by default. 28""" 29 30from __future__ import annotations 31 32import struct 33from abc import ABC, abstractmethod 34from collections.abc import Iterator, Mapping, Sequence 35from dataclasses import dataclass, field 36from typing import IO, Any, Literal, Self, overload, override 37 38type Endianness = Literal["little"] | Literal["big"] 39 40PRETTY_INDENTATION = " " 41""" 42Indentation used when pretty printing tags, more specifically ones that contain 43nested tags. 44""" 45 46STRING_LENGTH_LIMIT = 32_767 47""" 48The maximum length for strings including string tag values and tag names. 49""" 50 51 52def _struct_code_for_endianness(endianness: Endianness) -> str: 53 return "<" if endianness == "little" else ">" 54 55 56def _unpack( 57 endianness: Endianness, 58 format: str, 59 buffer: bytes, 60) -> tuple[bytes, tuple[Any, ...]]: 61 """ 62 A wrapper around `struct.unpack` that advances the provided buffer. 63 """ 64 e = _struct_code_for_endianness(endianness) 65 fmt = e + format 66 size = struct.calcsize(fmt) 67 (data_buffer, advanced_buffer) = (buffer[:size], buffer[size:]) 68 data = struct.unpack(fmt, data_buffer) 69 return (advanced_buffer, data) 70 71 72def _pack( 73 endianness: Endianness, 74 format: str, 75 *data: Any, 76) -> bytes: 77 e = _struct_code_for_endianness(endianness) 78 fmt = e + format 79 return struct.pack(fmt, *data) 80 81 82def _read_id( 83 endianness: Endianness, buffer: bytes, *, compare: int | None = None 84) -> tuple[bytes, int]: 85 """ 86 Reads an NBT tag ID. 87 88 If `compare` is provided, then this function compares the read ID with that 89 value and raises a `ValueError` when there is a mismatch. 90 """ 91 (buffer, (id,)) = _unpack(endianness, "b", buffer) 92 assert isinstance(id, int) 93 if compare is not None and id != compare: 94 raise ValueError(f"read ID 0x{id:>02x}, but expected 0x{compare:>02x}") 95 return (buffer, id) 96 97 98def _write_id(endianness: Endianness, id: int, stream: IO[bytes]) -> None: 99 stream.write(_pack(endianness, "b", id)) 100 101 102def _read_byte(endianness: Endianness, buffer: bytes) -> tuple[bytes, int]: 103 """ 104 Reads an NBT encoded byte without the tag ID. 105 """ 106 (buffer, (value,)) = _unpack(endianness, "b", buffer) 107 assert isinstance(value, int) 108 return (buffer, value) 109 110 111def _write_byte(endianness: Endianness, byte: int, stream: IO[bytes]) -> None: 112 stream.write(_pack(endianness, "b", byte)) 113 114 115def _read_short(endianness: Endianness, buffer: bytes) -> tuple[bytes, int]: 116 """ 117 Reads an NBT encoded 16-bit signed integer without the tag ID. 118 """ 119 (buffer, (value,)) = _unpack(endianness, "h", buffer) 120 assert isinstance(value, int) 121 return (buffer, value) 122 123 124def _write_short(endianness: Endianness, integer: int, stream: IO[bytes]) -> None: 125 stream.write(_pack(endianness, "h", integer)) 126 127 128def _read_int(endianness: Endianness, buffer: bytes) -> tuple[bytes, int]: 129 """ 130 Reads an NBT encoded 32-bit signed integer without the tag ID. 131 """ 132 (buffer, (value,)) = _unpack(endianness, "i", buffer) 133 assert isinstance(value, int) 134 return (buffer, value) 135 136 137def _write_int(endianness: Endianness, integer: int, stream: IO[bytes]) -> None: 138 stream.write(_pack(endianness, "i", integer)) 139 140 141def _read_long(endianness: Endianness, buffer: bytes) -> tuple[bytes, int]: 142 """ 143 Reads an NBT encoded 64-bit signed integer without the tag ID. 144 """ 145 (buffer, (value,)) = _unpack(endianness, "q", buffer) 146 assert isinstance(value, int) 147 return (buffer, value) 148 149 150def _write_long(endianness: Endianness, integer: int, stream: IO[bytes]) -> None: 151 stream.write(_pack(endianness, "q", integer)) 152 153 154def _read_float(endianness: Endianness, buffer: bytes) -> tuple[bytes, float]: 155 """ 156 Reads an NBT encoded 32-bit floating point without the tag ID. 157 """ 158 (buffer, (value,)) = _unpack(endianness, "f", buffer) 159 assert isinstance(value, float) 160 return (buffer, value) 161 162 163def _write_float(endianness: Endianness, floating: float, stream: IO[bytes]) -> None: 164 stream.write(_pack(endianness, "f", floating)) 165 166 167def _read_double(endianness: Endianness, buffer: bytes) -> tuple[bytes, float]: 168 """ 169 Reads an NBT encoded 32-bit floating point without the tag ID. 170 """ 171 (buffer, (value,)) = _unpack(endianness, "d", buffer) 172 assert isinstance(value, float) 173 return (buffer, value) 174 175 176def _write_double(endianness: Endianness, floating: float, stream: IO[bytes]) -> None: 177 stream.write(_pack(endianness, "d", floating)) 178 179 180def _read_string(endianness: Endianness, buffer: bytes) -> tuple[bytes, str]: 181 """ 182 Reads a NBT encoded string without the tag ID. 183 184 A string starts with a 16-bit signed integer declaring the length of the 185 string followed by the bytes that form the UTF-8 string. 186 """ 187 (buffer, (length,)) = _unpack(endianness, "h", buffer) 188 if length < 0: 189 raise ValueError(f"unexpected length {length} for string") 190 data = bytes() 191 for _ in range(length): 192 (buffer, (byte,)) = _unpack(endianness, "c", buffer) 193 data += byte 194 return (buffer, data.decode("utf8")) 195 196 197def _write_string(endianness: Endianness, string: str, stream: IO[bytes]) -> None: 198 data = string.encode("utf8") 199 stream.write(_pack(endianness, "h", len(data))) 200 stream.write(data) 201 202 203def _read_byte_list( 204 endianness: Endianness, buffer: bytes 205) -> tuple[bytes, Sequence[int]]: 206 (buffer, length) = _read_int(endianness, buffer) 207 children = [] 208 for _ in range(length): 209 (buffer, byte) = _read_byte(endianness, buffer) 210 children.append(byte) 211 return (buffer, children) 212 213 214def _write_byte_list( 215 endianness: Endianness, children: Sequence[int], stream: IO[bytes] 216) -> None: 217 _write_int(endianness, len(children), stream) 218 for byte in children: 219 _write_byte(endianness, byte, stream) 220 221 222def _read_long_list( 223 endianness: Endianness, buffer: bytes 224) -> tuple[bytes, Sequence[int]]: 225 (buffer, length) = _read_int(endianness, buffer) 226 children = [] 227 for _ in range(length): 228 (buffer, long) = _read_long(endianness, buffer) 229 children.append(long) 230 return (buffer, children) 231 232 233def _write_long_list( 234 endianness: Endianness, children: Sequence[int], stream: IO[bytes] 235) -> None: 236 _write_int(endianness, len(children), stream) 237 for long in children: 238 _write_long(endianness, long, stream) 239 240 241def _read_int_list( 242 endianness: Endianness, buffer: bytes 243) -> tuple[bytes, Sequence[int]]: 244 (buffer, length) = _read_int(endianness, buffer) 245 children = [] 246 for _ in range(length): 247 (buffer, long) = _read_int(endianness, buffer) 248 children.append(long) 249 return (buffer, children) 250 251 252def _write_int_list( 253 endianness: Endianness, children: Sequence[int], stream: IO[bytes] 254) -> None: 255 _write_int(endianness, len(children), stream) 256 for integer in children: 257 _write_int(endianness, integer, stream) 258 259 260def _read_list( 261 endianness: Endianness, buffer: bytes 262) -> tuple[bytes, Sequence[Any], int]: 263 (buffer, child_id) = _read_byte(endianness, buffer) 264 (buffer, length) = _read_int(endianness, buffer) 265 children: list[Any] = [] 266 for _ in range(length): 267 match child_id: 268 case 0x01: 269 (buffer, byte_data) = _read_byte(endianness, buffer) 270 children.append(TagByte("", byte_data)) 271 case 0x02: 272 (buffer, short_data) = _read_short(endianness, buffer) 273 children.append(TagShort("", short_data)) 274 case 0x03: 275 (buffer, int_data) = _read_int(endianness, buffer) 276 children.append(TagInt("", int_data)) 277 case 0x04: 278 (buffer, long_data) = _read_long(endianness, buffer) 279 children.append(TagLong("", long_data)) 280 case 0x05: 281 (buffer, float_data) = _read_float(endianness, buffer) 282 children.append(TagFloat("", float_data)) 283 case 0x06: 284 (buffer, double_data) = _read_double(endianness, buffer) 285 children.append(TagDouble("", double_data)) 286 case 0x07: 287 (buffer, byte_list_data) = _read_byte_list(endianness, buffer) 288 children.append(TagByteList("", byte_list_data)) 289 case 0x08: 290 (buffer, string_data) = _read_string(endianness, buffer) 291 children.append(TagString("", string_data)) 292 case 0x09: 293 (buffer, list_data, sublist_child_id) = _read_list(endianness, buffer) 294 children.append(TagList("", list_data, child_id=sublist_child_id)) 295 case 0x0A: 296 (buffer, compound_data) = _read_compound(endianness, buffer) 297 children.append(TagCompound("", compound_data)) 298 case 0x0B: 299 (buffer, int_list) = _read_int_list(endianness, buffer) 300 children.append(TagIntList("", int_list)) 301 case 0x0C: 302 (buffer, long_list) = _read_long_list(endianness, buffer) 303 children.append(TagLongList("", long_list)) 304 case _: 305 raise FormatError("expected valid ID") 306 return (buffer, children, child_id) 307 308 309def _write_list( 310 endianness: Endianness, children: Sequence[Any], child_id: int, stream: IO[bytes] 311) -> None: 312 _write_byte(endianness, child_id, stream) 313 _write_int(endianness, len(children), stream) 314 for child in children: 315 match child.id(): 316 case 0x01: 317 _write_byte(endianness, child.value, stream) 318 case 0x02: 319 _write_short(endianness, child.value, stream) 320 case 0x03: 321 _write_int(endianness, child.value, stream) 322 case 0x04: 323 _write_long(endianness, child.value, stream) 324 case 0x05: 325 _write_float(endianness, child.value, stream) 326 case 0x06: 327 _write_double(endianness, child.value, stream) 328 case 0x07: 329 _write_byte_list(endianness, child.value, stream) 330 case 0x08: 331 _write_string(endianness, child.value, stream) 332 case 0x09: 333 _write_list(endianness, child.value, child.child_id, stream) 334 case 0x0A: 335 _write_compound(endianness, child.value, stream) 336 case 0x0B: 337 _write_int_list(endianness, child.value, stream) 338 case 0x0C: 339 _write_long_list(endianness, child.value, stream) 340 case _: 341 raise FormatError("expected valid ID") 342 343 344def _read_compound( 345 endianness: Endianness, buffer: bytes 346) -> tuple[bytes, Sequence[Tag[Any, Any]]]: 347 compound: list[Tag[Any, Any]] = [] 348 while True: 349 (new_buffer_if_end, child_id) = _read_id(endianness, buffer) 350 if child_id == 0x00: 351 buffer = new_buffer_if_end 352 break 353 tag = _tag_class_by_id(child_id) 354 (child, buffer) = tag._read(buffer, endianness=endianness) 355 for index, old_child in enumerate(compound): 356 if child.name == old_child.name: 357 raise ValueError( 358 f"duplicate key in compound at index {index}: {old_child.name}" 359 ) 360 compound.append(child) 361 return (buffer, compound) 362 363 364def _write_compound( 365 endianness: Endianness, children: Sequence[Tag[Any, Any]], stream: IO[bytes] 366) -> None: 367 for child in children: 368 child._write(stream, endianness=endianness) 369 stream.write(b"\0") 370 371 372def _tag_class_by_id(id: int) -> type[Tag[Any, Any]]: 373 if id == TagByte.id(): 374 return TagByte 375 if id == TagShort.id(): 376 return TagShort 377 if id == TagInt.id(): 378 return TagInt 379 if id == TagLong.id(): 380 return TagLong 381 if id == TagFloat.id(): 382 return TagFloat 383 if id == TagDouble.id(): 384 return TagDouble 385 if id == TagList.id(): 386 return TagList 387 if id == TagByteList.id(): 388 return TagByteList 389 if id == TagIntList.id(): 390 return TagIntList 391 if id == TagLongList.id(): 392 return TagLongList 393 if id == TagCompound.id(): 394 return TagCompound 395 if id == TagString.id(): 396 return TagString 397 raise ValueError(f"read ID 0x{id:>02x}, but expected 0x00/.../0x12") 398 399 400@dataclass 401class NBTException(Exception): 402 """ 403 Base exceptions for NBT related exceptions. 404 """ 405 406 pass 407 408 409@dataclass 410class UnexpectedNameException(NBTException): 411 """ 412 Exception raised when a named tag is used when an unnamed one was expected. 413 """ 414 415 def __str__(self) -> str: 416 return "unexpected name" 417 418 419@dataclass 420class ExpectedNameException(NBTException): 421 """ 422 Exception raised when an named tag is expected when an unnamed one was 423 provided. 424 """ 425 426 def __str__(self) -> str: 427 return "expected name" 428 429 430@dataclass 431class TypeConflictException(NBTException): 432 """ 433 Exception raised when there is a conflict of tags within a container. 434 """ 435 436 id_actual: int 437 id_expected: int 438 439 def __str__(self) -> str: 440 return f"conflicting type: {self.id_actual:>02x} vs {self.id_expected:>02x}" 441 442 443@dataclass 444class StringTooLongException(NBTException): 445 """ 446 Exception raised for string that exceed the limit of 32.767. 447 """ 448 449 length: int 450 451 def __str__(self) -> str: 452 return f"string exceeds limit: {self.length > STRING_LENGTH_LIMIT}" 453 454 455@dataclass 456class UnemptyBufferException(NBTException): 457 """ 458 Exception raised when a buffer was not empty after full parse. 459 """ 460 461 def __str__(self) -> str: 462 return "buffer was not empty after full parse" 463 464 465@dataclass 466class FormatError(NBTException): 467 """ 468 Exception raised when the input is not valid NBT format. 469 """ 470 471 message: str 472 473 def __str__(self) -> str: 474 return f"malformed data: {self.message}" 475 476 477@dataclass(frozen=True) 478class Tag[T, P](ABC): 479 """ 480 Base class for NBT tags. 481 """ 482 483 name: str 484 """ 485 The name of the tag. 486 487 Commonly the name is irrelevant such as in a list. In that case an empty 488 string must be used. 489 """ 490 491 value: T 492 """ 493 The value of the tag. 494 """ 495 496 @staticmethod 497 @abstractmethod 498 def id() -> int: 499 """ 500 The ID that represents the tag in binary format. 501 """ 502 503 @abstractmethod 504 def as_python(self) -> P: 505 """ 506 Returns a python representation of this tag. 507 """ 508 509 @classmethod 510 @abstractmethod 511 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Tag[T, P], bytes]: 512 """ 513 Reads bytes from a buffer and interprets them as this tag. 514 515 This is a low-level function. If you intend to read an NBT input 516 use `load` instead. 517 518 This process includes parsing the ID, the name and the value of the tag. 519 This function returns a tuple containing the tag instance and the 520 remaining bytes. 521 """ 522 523 @abstractmethod 524 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 525 """ 526 Writes bytes to a buffer. 527 528 This is a low-level function. If you intend to write NBT tags use 529 `dump` instead. 530 """ 531 532 @abstractmethod 533 def pretty(self) -> str: 534 """ 535 Returns a human readable pretty representation of the tag structure. 536 """ 537 538 539@dataclass(frozen=True) 540class TagByte(Tag[int, int]): 541 """ 542 NBT tag for a single byte. 543 """ 544 545 # docstr-coverage:inherited 546 @override 547 @staticmethod 548 def id() -> int: 549 return 0x01 550 551 # docstr-coverage:inherited 552 @override 553 def as_python(self) -> int: 554 return self.value 555 556 @override 557 @classmethod 558 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 559 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 560 (buffer, name) = _read_string(endianness, buffer) 561 (buffer, value) = _read_byte(endianness, buffer) 562 return (cls(name, value), buffer) 563 564 @override 565 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 566 _write_id(endianness, self.id(), stream) 567 _write_string(endianness, self.name, stream) 568 _write_byte(endianness, self.value, stream) 569 570 # docstr-coverage:inherited 571 @override 572 def pretty(self) -> str: 573 return f"Byte({self.name!r}): {self.value}" 574 575 576@dataclass(frozen=True) 577class TagShort(Tag[int, int]): 578 """ 579 NBT tag for a short integer (signed 16-bit integer). 580 """ 581 582 # docstr-coverage:inherited 583 @override 584 @staticmethod 585 def id() -> int: 586 return 0x02 587 588 # docstr-coverage:inherited 589 @override 590 def as_python(self) -> int: 591 return self.value 592 593 @override 594 @classmethod 595 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 596 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 597 (buffer, name) = _read_string(endianness, buffer) 598 (buffer, value) = _read_short(endianness, buffer) 599 return (cls(name, value), buffer) 600 601 @override 602 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 603 _write_id(endianness, self.id(), stream) 604 _write_string(endianness, self.name, stream) 605 _write_short(endianness, self.value, stream) 606 607 # docstr-coverage:inherited 608 @override 609 def pretty(self) -> str: 610 return f"Short({self.name!r}): {self.value}" 611 612 613@dataclass(frozen=True) 614class TagInt(Tag[int, int]): 615 """ 616 NBT tag for an integer (signed 32-bit integer). 617 """ 618 619 # docstr-coverage:inherited 620 @override 621 @staticmethod 622 def id() -> int: 623 return 0x03 624 625 # docstr-coverage:inherited 626 @override 627 def as_python(self) -> int: 628 return self.value 629 630 @override 631 @classmethod 632 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 633 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 634 (buffer, name) = _read_string(endianness, buffer) 635 (buffer, value) = _read_int(endianness, buffer) 636 return (cls(name, value), buffer) 637 638 @override 639 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 640 _write_id(endianness, self.id(), stream) 641 _write_string(endianness, self.name, stream) 642 _write_int(endianness, self.value, stream) 643 644 # docstr-coverage:inherited 645 @override 646 def pretty(self) -> str: 647 return f"Int({self.name!r}): {self.value}" 648 649 650@dataclass(frozen=True) 651class TagLong(Tag[int, int]): 652 """ 653 NBT tag for a long integer (signed 64-bit integer). 654 """ 655 656 # docstr-coverage:inherited 657 @override 658 @staticmethod 659 def id() -> int: 660 return 0x04 661 662 # docstr-coverage:inherited 663 @override 664 def as_python(self) -> int: 665 return self.value 666 667 @override 668 @classmethod 669 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 670 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 671 (buffer, name) = _read_string(endianness, buffer) 672 (buffer, value) = _read_long(endianness, buffer) 673 return (cls(name, value), buffer) 674 675 @override 676 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 677 _write_id(endianness, self.id(), stream) 678 _write_string(endianness, self.name, stream) 679 _write_long(endianness, self.value, stream) 680 681 # docstr-coverage:inherited 682 @override 683 def pretty(self) -> str: 684 return f"Long({self.name!r}): {self.value}" 685 686 687@dataclass(frozen=True) 688class TagFloat(Tag[float, float]): 689 """ 690 NBT tag for an integer (32-bit floating point). 691 """ 692 693 # docstr-coverage:inherited 694 @override 695 @staticmethod 696 def id() -> int: 697 return 0x05 698 699 # docstr-coverage:inherited 700 @override 701 def as_python(self) -> float: 702 return self.value 703 704 @override 705 @classmethod 706 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 707 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 708 (buffer, name) = _read_string(endianness, buffer) 709 (buffer, value) = _read_float(endianness, buffer) 710 return (cls(name, value), buffer) 711 712 @override 713 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 714 _write_id(endianness, self.id(), stream) 715 _write_string(endianness, self.name, stream) 716 _write_float(endianness, self.value, stream) 717 718 # docstr-coverage:inherited 719 @override 720 def pretty(self) -> str: 721 return f"Float({self.name!r}): {self.value}" 722 723 724@dataclass(frozen=True) 725class TagDouble(Tag[float, float]): 726 """ 727 NBT tag for an integer (64-bit floating point). 728 """ 729 730 # docstr-coverage:inherited 731 @override 732 @staticmethod 733 def id() -> int: 734 return 0x06 735 736 # docstr-coverage:inherited 737 @override 738 def as_python(self) -> float: 739 return self.value 740 741 @override 742 @classmethod 743 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 744 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 745 (buffer, name) = _read_string(endianness, buffer) 746 (buffer, value) = _read_double(endianness, buffer) 747 return (cls(name, value), buffer) 748 749 @override 750 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 751 _write_id(endianness, self.id(), stream) 752 _write_string(endianness, self.name, stream) 753 _write_double(endianness, self.value, stream) 754 755 # docstr-coverage:inherited 756 @override 757 def pretty(self) -> str: 758 return f"Double({self.name!r}): {self.value}" 759 760 761@dataclass(frozen=True) 762class TagString(Tag[str, str]): 763 """ 764 NBT tag for a UTF-8 encoded string. 765 """ 766 767 def __post_init__(self) -> None: 768 if len(self.value) > STRING_LENGTH_LIMIT: 769 raise StringTooLongException(len(self.value)) 770 771 # docstr-coverage:inherited 772 @override 773 @staticmethod 774 def id() -> int: 775 return 0x08 776 777 # docstr-coverage:inherited 778 @override 779 def as_python(self) -> str: 780 return self.value 781 782 @override 783 @classmethod 784 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 785 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 786 (buffer, name) = _read_string(endianness, buffer) 787 (buffer, string) = _read_string(endianness, buffer) 788 return (cls(name, string), buffer) 789 790 @override 791 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 792 _write_id(endianness, self.id(), stream) 793 _write_string(endianness, self.name, stream) 794 _write_string(endianness, self.value, stream) 795 796 # docstr-coverage:inherited 797 @override 798 def pretty(self) -> str: 799 return f"String({self.name!r}): {self.value!r}" 800 801 802@dataclass(frozen=True) 803class TagList[T, P](Tag[Sequence[Tag[T, P]], list[P]], Sequence[Tag[T, P]]): 804 """ 805 NBT tag for a list containing nameless tags of one kind. 806 """ 807 808 child_id: int = field(kw_only=True) 809 810 def __post_init__(self) -> None: 811 for child in self.value: 812 if child.id() != self.child_id: 813 raise TypeConflictException( 814 id_actual=child.id(), id_expected=self.child_id 815 ) 816 if child.name != "": 817 raise UnexpectedNameException() 818 819 # docstr-coverage:inherited 820 @override 821 @staticmethod 822 def id() -> int: 823 return 0x09 824 825 # docstr-coverage:inherited 826 @override 827 def as_python(self) -> list[P]: 828 return [tag.as_python() for tag in self.value] 829 830 @override 831 @classmethod 832 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 833 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 834 (buffer, name) = _read_string(endianness, buffer) 835 (buffer, children, child_id) = _read_list(endianness, buffer) 836 return (cls(name, children, child_id=child_id), buffer) 837 838 @override 839 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 840 _write_id(endianness, self.id(), stream) 841 _write_string(endianness, self.name, stream) 842 _write_list(endianness, self.value, self.child_id, stream) 843 844 # docstr-coverage:inherited 845 @override 846 def pretty(self) -> str: 847 string = "" 848 string += f"TagList({self.name!r}): {len(self.value)} entries\n" 849 string += "{\n" 850 for child in self.value: 851 for line in child.pretty().splitlines(): 852 string += f"{PRETTY_INDENTATION}{line}\n" 853 string += "}" 854 return string 855 856 @overload 857 def __getitem__(self, index: int) -> Tag[T, P]: ... 858 859 @overload 860 def __getitem__(self, index: slice[int, int, int]) -> Sequence[Tag[T, P]]: ... 861 862 @override 863 def __getitem__( 864 self, index: int | slice[int, int, int] 865 ) -> Tag[T, P] | Sequence[Tag[T, P]]: 866 return self.value[index] 867 868 @override 869 def __len__(self) -> int: 870 return len(self.value) 871 872 873@dataclass(frozen=True) 874class TagByteList(Tag[Sequence[int], Sequence[int]], Sequence[int]): 875 """ 876 NBT tag for a list of bytes. 877 """ 878 879 # docstr-coverage:inherited 880 @override 881 @staticmethod 882 def id() -> int: 883 return 0x07 884 885 # docstr-coverage:inherited 886 @override 887 def as_python(self) -> Sequence[int]: 888 return self.value 889 890 @override 891 @classmethod 892 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 893 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 894 (buffer, name) = _read_string(endianness, buffer) 895 (buffer, children) = _read_byte_list(endianness, buffer) 896 return (cls(name, children), buffer) 897 898 @override 899 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 900 _write_id(endianness, self.id(), stream) 901 _write_string(endianness, self.name, stream) 902 _write_byte_list(endianness, self.value, stream) 903 904 # docstr-coverage:inherited 905 @override 906 def pretty(self) -> str: 907 string = "" 908 string += f"ByteList({self.name!r}) : {len(self.value)} entries\n" 909 string += "{\n" 910 for long in self.value: 911 string += f"{PRETTY_INDENTATION}{long}\n" 912 string += "}" 913 return string 914 915 @overload 916 def __getitem__(self, index: int) -> int: ... 917 918 @overload 919 def __getitem__(self, index: slice[int, int, int]) -> Sequence[int]: ... 920 921 @override 922 def __getitem__(self, index: int | slice[int, int, int]) -> int | Sequence[int]: 923 return self.value[index] 924 925 @override 926 def __len__(self) -> int: 927 return len(self.value) 928 929 930@dataclass(frozen=True) 931class TagIntList(Tag[Sequence[int], Sequence[int]], Sequence[int]): 932 """ 933 NBT tag for a list of integers. 934 """ 935 936 # docstr-coverage:inherited 937 @override 938 @staticmethod 939 def id() -> int: 940 return 0x0B 941 942 # docstr-coverage:inherited 943 @override 944 def as_python(self) -> Sequence[int]: 945 return self.value 946 947 @override 948 @classmethod 949 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 950 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 951 (buffer, name) = _read_string(endianness, buffer) 952 (buffer, children) = _read_int_list(endianness, buffer) 953 return (cls(name, children), buffer) 954 955 @override 956 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 957 _write_id(endianness, self.id(), stream) 958 _write_string(endianness, self.name, stream) 959 _write_int_list(endianness, self.value, stream) 960 961 # docstr-coverage:inherited 962 @override 963 def pretty(self) -> str: 964 string = "" 965 string += f"IntList({self.name!r}) : {len(self.value)} entries\n" 966 string += "{\n" 967 for integer in self.value: 968 string += f"{PRETTY_INDENTATION}{integer}\n" 969 string += "}" 970 return string 971 972 @overload 973 def __getitem__(self, index: int) -> int: ... 974 975 @overload 976 def __getitem__(self, index: slice[int, int, int]) -> Sequence[int]: ... 977 978 @override 979 def __getitem__(self, index: int | slice[int, int, int]) -> int | Sequence[int]: 980 return self.value[index] 981 982 @override 983 def __len__(self) -> int: 984 return len(self.value) 985 986 987@dataclass(frozen=True) 988class TagLongList(Tag[Sequence[int], Sequence[int]], Sequence[int]): 989 """ 990 NBT tag for a list of long integers. 991 """ 992 993 # docstr-coverage:inherited 994 @override 995 @staticmethod 996 def id() -> int: 997 return 0x0C 998 999 # docstr-coverage:inherited 1000 @override 1001 def as_python(self) -> Sequence[int]: 1002 return self.value 1003 1004 @override 1005 @classmethod 1006 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 1007 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 1008 (buffer, name) = _read_string(endianness, buffer) 1009 (buffer, children) = _read_long_list(endianness, buffer) 1010 return (cls(name, children), buffer) 1011 1012 @override 1013 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 1014 _write_id(endianness, self.id(), stream) 1015 _write_string(endianness, self.name, stream) 1016 _write_long_list(endianness, self.value, stream) 1017 1018 # docstr-coverage:inherited 1019 @override 1020 def pretty(self) -> str: 1021 string = "" 1022 string += f"LongList({self.name!r}) : {len(self.value)} entries\n" 1023 string += "{\n" 1024 for byte in self.value: 1025 string += f"{PRETTY_INDENTATION}{byte}\n" 1026 string += "}" 1027 return string 1028 1029 @overload 1030 def __getitem__(self, index: int) -> int: ... 1031 1032 @overload 1033 def __getitem__(self, index: slice[int, int, int]) -> Sequence[int]: ... 1034 1035 @override 1036 def __getitem__(self, index: int | slice[int, int, int]) -> int | Sequence[int]: 1037 return self.value[index] 1038 1039 @override 1040 def __len__(self) -> int: 1041 return len(self.value) 1042 1043 1044@dataclass(frozen=True) 1045class TagCompound[T, P]( 1046 Tag[Sequence[Tag[T, P]], dict[str, P]], Mapping[str, Tag[T, P]] 1047): 1048 """ 1049 NBT tag for a compound (list of uniquely named tags). 1050 """ 1051 1052 def __post_init__(self) -> None: 1053 for child in self.value: 1054 if child.name == "": 1055 raise ExpectedNameException() 1056 1057 # docstr-coverage:inherited 1058 @override 1059 @staticmethod 1060 def id() -> int: 1061 return 0x0A 1062 1063 # docstr-coverage:inherited 1064 @override 1065 def as_python(self) -> dict[str, P]: 1066 result = {} 1067 for tag in self.value: 1068 result[tag.name] = tag.as_python() 1069 return result 1070 1071 @override 1072 @classmethod 1073 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 1074 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 1075 (buffer, name) = _read_string(endianness, buffer) 1076 (buffer, compound) = _read_compound(endianness, buffer) 1077 return (cls(name, compound), buffer) 1078 1079 @override 1080 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 1081 _write_id(endianness, self.id(), stream) 1082 _write_string(endianness, self.name, stream) 1083 _write_compound(endianness, self.value, stream) 1084 1085 # docstr-coverage:inherited 1086 @override 1087 def pretty(self) -> str: 1088 string = "" 1089 string += f"Compound({self.name!r}): {len(self.value)} entries\n" 1090 string += "{\n" 1091 for child in self.value: 1092 for line in child.pretty().splitlines(): 1093 string += f"{PRETTY_INDENTATION}{line}\n" 1094 string += "}" 1095 return string 1096 1097 @override 1098 def __getitem__(self, key: str) -> Tag[T, P]: 1099 for tag in self.value: 1100 if tag.name == key: 1101 return tag 1102 raise KeyError(f"{key!r}") 1103 1104 @override 1105 def __iter__(self) -> Iterator[str]: 1106 return (tag.name for tag in self.value) 1107 1108 @override 1109 def __len__(self) -> int: 1110 return len(self.value) 1111 1112 1113def load( 1114 file: IO[bytes], 1115 *, 1116 endianness: Endianness = "little", 1117 ignore_rest: bool = False, 1118) -> Tag[Any, Any]: 1119 """ 1120 Loads an NBT file and returns the contained NBT tag. 1121 1122 # Parameters 1123 1124 - `file` -- The file-like object to read the data from. 1125 - `endianness` -- The byte order to use during parsing. Java Edition usually 1126 uses big endian byte order and Bedrock Edition usually uses little endian 1127 byte order. 1128 - `ignore_rest` -- Whether to ignore remaining bytes after a full parse. By 1129 default, this function expects the buffer to be empty after an NBT tree 1130 has been serialized and raises an exception otherwise. 1131 """ 1132 buffer = file.read() 1133 (_, id) = _read_id(endianness, buffer) 1134 tag_cls = _tag_class_by_id(id) 1135 (tag, rest) = tag_cls._read(buffer, endianness=endianness) 1136 if rest and not ignore_rest: 1137 raise UnemptyBufferException() 1138 return tag 1139 1140 1141def dump( 1142 tag: Tag[Any, Any], stream: IO[bytes], *, endianness: Endianness = "little" 1143) -> None: 1144 """ 1145 Dumps an NBT tag to a file. 1146 1147 # Parameters 1148 1149 - `tag` -- The root tag to write to the stream. 1150 - `stream` -- The stream to write to. 1151 - `endianness` -- The byte order to use. Java Edition usually uses big 1152 endian byte order and Bedrock Edition usually uses little endian byte 1153 order. 1154 """ 1155 tag._write(stream, endianness=endianness)
Indentation used when pretty printing tags, more specifically ones that contain nested tags.
The maximum length for strings including string tag values and tag names.
401@dataclass 402class NBTException(Exception): 403 """ 404 Base exceptions for NBT related exceptions. 405 """ 406 407 pass
Base exceptions for NBT related exceptions.
410@dataclass 411class UnexpectedNameException(NBTException): 412 """ 413 Exception raised when a named tag is used when an unnamed one was expected. 414 """ 415 416 def __str__(self) -> str: 417 return "unexpected name"
Exception raised when a named tag is used when an unnamed one was expected.
420@dataclass 421class ExpectedNameException(NBTException): 422 """ 423 Exception raised when an named tag is expected when an unnamed one was 424 provided. 425 """ 426 427 def __str__(self) -> str: 428 return "expected name"
Exception raised when an named tag is expected when an unnamed one was provided.
431@dataclass 432class TypeConflictException(NBTException): 433 """ 434 Exception raised when there is a conflict of tags within a container. 435 """ 436 437 id_actual: int 438 id_expected: int 439 440 def __str__(self) -> str: 441 return f"conflicting type: {self.id_actual:>02x} vs {self.id_expected:>02x}"
Exception raised when there is a conflict of tags within a container.
444@dataclass 445class StringTooLongException(NBTException): 446 """ 447 Exception raised for string that exceed the limit of 32.767. 448 """ 449 450 length: int 451 452 def __str__(self) -> str: 453 return f"string exceeds limit: {self.length > STRING_LENGTH_LIMIT}"
Exception raised for string that exceed the limit of 32.767.
456@dataclass 457class UnemptyBufferException(NBTException): 458 """ 459 Exception raised when a buffer was not empty after full parse. 460 """ 461 462 def __str__(self) -> str: 463 return "buffer was not empty after full parse"
Exception raised when a buffer was not empty after full parse.
466@dataclass 467class FormatError(NBTException): 468 """ 469 Exception raised when the input is not valid NBT format. 470 """ 471 472 message: str 473 474 def __str__(self) -> str: 475 return f"malformed data: {self.message}"
Exception raised when the input is not valid NBT format.
478@dataclass(frozen=True) 479class Tag[T, P](ABC): 480 """ 481 Base class for NBT tags. 482 """ 483 484 name: str 485 """ 486 The name of the tag. 487 488 Commonly the name is irrelevant such as in a list. In that case an empty 489 string must be used. 490 """ 491 492 value: T 493 """ 494 The value of the tag. 495 """ 496 497 @staticmethod 498 @abstractmethod 499 def id() -> int: 500 """ 501 The ID that represents the tag in binary format. 502 """ 503 504 @abstractmethod 505 def as_python(self) -> P: 506 """ 507 Returns a python representation of this tag. 508 """ 509 510 @classmethod 511 @abstractmethod 512 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Tag[T, P], bytes]: 513 """ 514 Reads bytes from a buffer and interprets them as this tag. 515 516 This is a low-level function. If you intend to read an NBT input 517 use `load` instead. 518 519 This process includes parsing the ID, the name and the value of the tag. 520 This function returns a tuple containing the tag instance and the 521 remaining bytes. 522 """ 523 524 @abstractmethod 525 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 526 """ 527 Writes bytes to a buffer. 528 529 This is a low-level function. If you intend to write NBT tags use 530 `dump` instead. 531 """ 532 533 @abstractmethod 534 def pretty(self) -> str: 535 """ 536 Returns a human readable pretty representation of the tag structure. 537 """
Base class for NBT tags.
The name of the tag.
Commonly the name is irrelevant such as in a list. In that case an empty string must be used.
497 @staticmethod 498 @abstractmethod 499 def id() -> int: 500 """ 501 The ID that represents the tag in binary format. 502 """
The ID that represents the tag in binary format.
540@dataclass(frozen=True) 541class TagByte(Tag[int, int]): 542 """ 543 NBT tag for a single byte. 544 """ 545 546 # docstr-coverage:inherited 547 @override 548 @staticmethod 549 def id() -> int: 550 return 0x01 551 552 # docstr-coverage:inherited 553 @override 554 def as_python(self) -> int: 555 return self.value 556 557 @override 558 @classmethod 559 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 560 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 561 (buffer, name) = _read_string(endianness, buffer) 562 (buffer, value) = _read_byte(endianness, buffer) 563 return (cls(name, value), buffer) 564 565 @override 566 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 567 _write_id(endianness, self.id(), stream) 568 _write_string(endianness, self.name, stream) 569 _write_byte(endianness, self.value, stream) 570 571 # docstr-coverage:inherited 572 @override 573 def pretty(self) -> str: 574 return f"Byte({self.name!r}): {self.value}"
NBT tag for a single byte.
577@dataclass(frozen=True) 578class TagShort(Tag[int, int]): 579 """ 580 NBT tag for a short integer (signed 16-bit integer). 581 """ 582 583 # docstr-coverage:inherited 584 @override 585 @staticmethod 586 def id() -> int: 587 return 0x02 588 589 # docstr-coverage:inherited 590 @override 591 def as_python(self) -> int: 592 return self.value 593 594 @override 595 @classmethod 596 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 597 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 598 (buffer, name) = _read_string(endianness, buffer) 599 (buffer, value) = _read_short(endianness, buffer) 600 return (cls(name, value), buffer) 601 602 @override 603 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 604 _write_id(endianness, self.id(), stream) 605 _write_string(endianness, self.name, stream) 606 _write_short(endianness, self.value, stream) 607 608 # docstr-coverage:inherited 609 @override 610 def pretty(self) -> str: 611 return f"Short({self.name!r}): {self.value}"
NBT tag for a short integer (signed 16-bit integer).
614@dataclass(frozen=True) 615class TagInt(Tag[int, int]): 616 """ 617 NBT tag for an integer (signed 32-bit integer). 618 """ 619 620 # docstr-coverage:inherited 621 @override 622 @staticmethod 623 def id() -> int: 624 return 0x03 625 626 # docstr-coverage:inherited 627 @override 628 def as_python(self) -> int: 629 return self.value 630 631 @override 632 @classmethod 633 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 634 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 635 (buffer, name) = _read_string(endianness, buffer) 636 (buffer, value) = _read_int(endianness, buffer) 637 return (cls(name, value), buffer) 638 639 @override 640 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 641 _write_id(endianness, self.id(), stream) 642 _write_string(endianness, self.name, stream) 643 _write_int(endianness, self.value, stream) 644 645 # docstr-coverage:inherited 646 @override 647 def pretty(self) -> str: 648 return f"Int({self.name!r}): {self.value}"
NBT tag for an integer (signed 32-bit integer).
651@dataclass(frozen=True) 652class TagLong(Tag[int, int]): 653 """ 654 NBT tag for a long integer (signed 64-bit integer). 655 """ 656 657 # docstr-coverage:inherited 658 @override 659 @staticmethod 660 def id() -> int: 661 return 0x04 662 663 # docstr-coverage:inherited 664 @override 665 def as_python(self) -> int: 666 return self.value 667 668 @override 669 @classmethod 670 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 671 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 672 (buffer, name) = _read_string(endianness, buffer) 673 (buffer, value) = _read_long(endianness, buffer) 674 return (cls(name, value), buffer) 675 676 @override 677 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 678 _write_id(endianness, self.id(), stream) 679 _write_string(endianness, self.name, stream) 680 _write_long(endianness, self.value, stream) 681 682 # docstr-coverage:inherited 683 @override 684 def pretty(self) -> str: 685 return f"Long({self.name!r}): {self.value}"
NBT tag for a long integer (signed 64-bit integer).
688@dataclass(frozen=True) 689class TagFloat(Tag[float, float]): 690 """ 691 NBT tag for an integer (32-bit floating point). 692 """ 693 694 # docstr-coverage:inherited 695 @override 696 @staticmethod 697 def id() -> int: 698 return 0x05 699 700 # docstr-coverage:inherited 701 @override 702 def as_python(self) -> float: 703 return self.value 704 705 @override 706 @classmethod 707 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 708 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 709 (buffer, name) = _read_string(endianness, buffer) 710 (buffer, value) = _read_float(endianness, buffer) 711 return (cls(name, value), buffer) 712 713 @override 714 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 715 _write_id(endianness, self.id(), stream) 716 _write_string(endianness, self.name, stream) 717 _write_float(endianness, self.value, stream) 718 719 # docstr-coverage:inherited 720 @override 721 def pretty(self) -> str: 722 return f"Float({self.name!r}): {self.value}"
NBT tag for an integer (32-bit floating point).
725@dataclass(frozen=True) 726class TagDouble(Tag[float, float]): 727 """ 728 NBT tag for an integer (64-bit floating point). 729 """ 730 731 # docstr-coverage:inherited 732 @override 733 @staticmethod 734 def id() -> int: 735 return 0x06 736 737 # docstr-coverage:inherited 738 @override 739 def as_python(self) -> float: 740 return self.value 741 742 @override 743 @classmethod 744 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 745 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 746 (buffer, name) = _read_string(endianness, buffer) 747 (buffer, value) = _read_double(endianness, buffer) 748 return (cls(name, value), buffer) 749 750 @override 751 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 752 _write_id(endianness, self.id(), stream) 753 _write_string(endianness, self.name, stream) 754 _write_double(endianness, self.value, stream) 755 756 # docstr-coverage:inherited 757 @override 758 def pretty(self) -> str: 759 return f"Double({self.name!r}): {self.value}"
NBT tag for an integer (64-bit floating point).
762@dataclass(frozen=True) 763class TagString(Tag[str, str]): 764 """ 765 NBT tag for a UTF-8 encoded string. 766 """ 767 768 def __post_init__(self) -> None: 769 if len(self.value) > STRING_LENGTH_LIMIT: 770 raise StringTooLongException(len(self.value)) 771 772 # docstr-coverage:inherited 773 @override 774 @staticmethod 775 def id() -> int: 776 return 0x08 777 778 # docstr-coverage:inherited 779 @override 780 def as_python(self) -> str: 781 return self.value 782 783 @override 784 @classmethod 785 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 786 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 787 (buffer, name) = _read_string(endianness, buffer) 788 (buffer, string) = _read_string(endianness, buffer) 789 return (cls(name, string), buffer) 790 791 @override 792 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 793 _write_id(endianness, self.id(), stream) 794 _write_string(endianness, self.name, stream) 795 _write_string(endianness, self.value, stream) 796 797 # docstr-coverage:inherited 798 @override 799 def pretty(self) -> str: 800 return f"String({self.name!r}): {self.value!r}"
NBT tag for a UTF-8 encoded string.
803@dataclass(frozen=True) 804class TagList[T, P](Tag[Sequence[Tag[T, P]], list[P]], Sequence[Tag[T, P]]): 805 """ 806 NBT tag for a list containing nameless tags of one kind. 807 """ 808 809 child_id: int = field(kw_only=True) 810 811 def __post_init__(self) -> None: 812 for child in self.value: 813 if child.id() != self.child_id: 814 raise TypeConflictException( 815 id_actual=child.id(), id_expected=self.child_id 816 ) 817 if child.name != "": 818 raise UnexpectedNameException() 819 820 # docstr-coverage:inherited 821 @override 822 @staticmethod 823 def id() -> int: 824 return 0x09 825 826 # docstr-coverage:inherited 827 @override 828 def as_python(self) -> list[P]: 829 return [tag.as_python() for tag in self.value] 830 831 @override 832 @classmethod 833 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 834 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 835 (buffer, name) = _read_string(endianness, buffer) 836 (buffer, children, child_id) = _read_list(endianness, buffer) 837 return (cls(name, children, child_id=child_id), buffer) 838 839 @override 840 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 841 _write_id(endianness, self.id(), stream) 842 _write_string(endianness, self.name, stream) 843 _write_list(endianness, self.value, self.child_id, stream) 844 845 # docstr-coverage:inherited 846 @override 847 def pretty(self) -> str: 848 string = "" 849 string += f"TagList({self.name!r}): {len(self.value)} entries\n" 850 string += "{\n" 851 for child in self.value: 852 for line in child.pretty().splitlines(): 853 string += f"{PRETTY_INDENTATION}{line}\n" 854 string += "}" 855 return string 856 857 @overload 858 def __getitem__(self, index: int) -> Tag[T, P]: ... 859 860 @overload 861 def __getitem__(self, index: slice[int, int, int]) -> Sequence[Tag[T, P]]: ... 862 863 @override 864 def __getitem__( 865 self, index: int | slice[int, int, int] 866 ) -> Tag[T, P] | Sequence[Tag[T, P]]: 867 return self.value[index] 868 869 @override 870 def __len__(self) -> int: 871 return len(self.value)
NBT tag for a list containing nameless tags of one kind.
827 @override 828 def as_python(self) -> list[P]: 829 return [tag.as_python() for tag in self.value]
Returns a python representation of this tag.
846 @override 847 def pretty(self) -> str: 848 string = "" 849 string += f"TagList({self.name!r}): {len(self.value)} entries\n" 850 string += "{\n" 851 for child in self.value: 852 for line in child.pretty().splitlines(): 853 string += f"{PRETTY_INDENTATION}{line}\n" 854 string += "}" 855 return string
Returns a human readable pretty representation of the tag structure.
874@dataclass(frozen=True) 875class TagByteList(Tag[Sequence[int], Sequence[int]], Sequence[int]): 876 """ 877 NBT tag for a list of bytes. 878 """ 879 880 # docstr-coverage:inherited 881 @override 882 @staticmethod 883 def id() -> int: 884 return 0x07 885 886 # docstr-coverage:inherited 887 @override 888 def as_python(self) -> Sequence[int]: 889 return self.value 890 891 @override 892 @classmethod 893 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 894 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 895 (buffer, name) = _read_string(endianness, buffer) 896 (buffer, children) = _read_byte_list(endianness, buffer) 897 return (cls(name, children), buffer) 898 899 @override 900 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 901 _write_id(endianness, self.id(), stream) 902 _write_string(endianness, self.name, stream) 903 _write_byte_list(endianness, self.value, stream) 904 905 # docstr-coverage:inherited 906 @override 907 def pretty(self) -> str: 908 string = "" 909 string += f"ByteList({self.name!r}) : {len(self.value)} entries\n" 910 string += "{\n" 911 for long in self.value: 912 string += f"{PRETTY_INDENTATION}{long}\n" 913 string += "}" 914 return string 915 916 @overload 917 def __getitem__(self, index: int) -> int: ... 918 919 @overload 920 def __getitem__(self, index: slice[int, int, int]) -> Sequence[int]: ... 921 922 @override 923 def __getitem__(self, index: int | slice[int, int, int]) -> int | Sequence[int]: 924 return self.value[index] 925 926 @override 927 def __len__(self) -> int: 928 return len(self.value)
NBT tag for a list of bytes.
906 @override 907 def pretty(self) -> str: 908 string = "" 909 string += f"ByteList({self.name!r}) : {len(self.value)} entries\n" 910 string += "{\n" 911 for long in self.value: 912 string += f"{PRETTY_INDENTATION}{long}\n" 913 string += "}" 914 return string
Returns a human readable pretty representation of the tag structure.
931@dataclass(frozen=True) 932class TagIntList(Tag[Sequence[int], Sequence[int]], Sequence[int]): 933 """ 934 NBT tag for a list of integers. 935 """ 936 937 # docstr-coverage:inherited 938 @override 939 @staticmethod 940 def id() -> int: 941 return 0x0B 942 943 # docstr-coverage:inherited 944 @override 945 def as_python(self) -> Sequence[int]: 946 return self.value 947 948 @override 949 @classmethod 950 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 951 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 952 (buffer, name) = _read_string(endianness, buffer) 953 (buffer, children) = _read_int_list(endianness, buffer) 954 return (cls(name, children), buffer) 955 956 @override 957 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 958 _write_id(endianness, self.id(), stream) 959 _write_string(endianness, self.name, stream) 960 _write_int_list(endianness, self.value, stream) 961 962 # docstr-coverage:inherited 963 @override 964 def pretty(self) -> str: 965 string = "" 966 string += f"IntList({self.name!r}) : {len(self.value)} entries\n" 967 string += "{\n" 968 for integer in self.value: 969 string += f"{PRETTY_INDENTATION}{integer}\n" 970 string += "}" 971 return string 972 973 @overload 974 def __getitem__(self, index: int) -> int: ... 975 976 @overload 977 def __getitem__(self, index: slice[int, int, int]) -> Sequence[int]: ... 978 979 @override 980 def __getitem__(self, index: int | slice[int, int, int]) -> int | Sequence[int]: 981 return self.value[index] 982 983 @override 984 def __len__(self) -> int: 985 return len(self.value)
NBT tag for a list of integers.
963 @override 964 def pretty(self) -> str: 965 string = "" 966 string += f"IntList({self.name!r}) : {len(self.value)} entries\n" 967 string += "{\n" 968 for integer in self.value: 969 string += f"{PRETTY_INDENTATION}{integer}\n" 970 string += "}" 971 return string
Returns a human readable pretty representation of the tag structure.
988@dataclass(frozen=True) 989class TagLongList(Tag[Sequence[int], Sequence[int]], Sequence[int]): 990 """ 991 NBT tag for a list of long integers. 992 """ 993 994 # docstr-coverage:inherited 995 @override 996 @staticmethod 997 def id() -> int: 998 return 0x0C 999 1000 # docstr-coverage:inherited 1001 @override 1002 def as_python(self) -> Sequence[int]: 1003 return self.value 1004 1005 @override 1006 @classmethod 1007 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 1008 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 1009 (buffer, name) = _read_string(endianness, buffer) 1010 (buffer, children) = _read_long_list(endianness, buffer) 1011 return (cls(name, children), buffer) 1012 1013 @override 1014 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 1015 _write_id(endianness, self.id(), stream) 1016 _write_string(endianness, self.name, stream) 1017 _write_long_list(endianness, self.value, stream) 1018 1019 # docstr-coverage:inherited 1020 @override 1021 def pretty(self) -> str: 1022 string = "" 1023 string += f"LongList({self.name!r}) : {len(self.value)} entries\n" 1024 string += "{\n" 1025 for byte in self.value: 1026 string += f"{PRETTY_INDENTATION}{byte}\n" 1027 string += "}" 1028 return string 1029 1030 @overload 1031 def __getitem__(self, index: int) -> int: ... 1032 1033 @overload 1034 def __getitem__(self, index: slice[int, int, int]) -> Sequence[int]: ... 1035 1036 @override 1037 def __getitem__(self, index: int | slice[int, int, int]) -> int | Sequence[int]: 1038 return self.value[index] 1039 1040 @override 1041 def __len__(self) -> int: 1042 return len(self.value)
NBT tag for a list of long integers.
1020 @override 1021 def pretty(self) -> str: 1022 string = "" 1023 string += f"LongList({self.name!r}) : {len(self.value)} entries\n" 1024 string += "{\n" 1025 for byte in self.value: 1026 string += f"{PRETTY_INDENTATION}{byte}\n" 1027 string += "}" 1028 return string
Returns a human readable pretty representation of the tag structure.
1045@dataclass(frozen=True) 1046class TagCompound[T, P]( 1047 Tag[Sequence[Tag[T, P]], dict[str, P]], Mapping[str, Tag[T, P]] 1048): 1049 """ 1050 NBT tag for a compound (list of uniquely named tags). 1051 """ 1052 1053 def __post_init__(self) -> None: 1054 for child in self.value: 1055 if child.name == "": 1056 raise ExpectedNameException() 1057 1058 # docstr-coverage:inherited 1059 @override 1060 @staticmethod 1061 def id() -> int: 1062 return 0x0A 1063 1064 # docstr-coverage:inherited 1065 @override 1066 def as_python(self) -> dict[str, P]: 1067 result = {} 1068 for tag in self.value: 1069 result[tag.name] = tag.as_python() 1070 return result 1071 1072 @override 1073 @classmethod 1074 def _read(cls, buffer: bytes, *, endianness: Endianness) -> tuple[Self, bytes]: 1075 (buffer, _) = _read_id(endianness, buffer, compare=cls.id()) 1076 (buffer, name) = _read_string(endianness, buffer) 1077 (buffer, compound) = _read_compound(endianness, buffer) 1078 return (cls(name, compound), buffer) 1079 1080 @override 1081 def _write(self, stream: IO[bytes], *, endianness: Endianness) -> None: 1082 _write_id(endianness, self.id(), stream) 1083 _write_string(endianness, self.name, stream) 1084 _write_compound(endianness, self.value, stream) 1085 1086 # docstr-coverage:inherited 1087 @override 1088 def pretty(self) -> str: 1089 string = "" 1090 string += f"Compound({self.name!r}): {len(self.value)} entries\n" 1091 string += "{\n" 1092 for child in self.value: 1093 for line in child.pretty().splitlines(): 1094 string += f"{PRETTY_INDENTATION}{line}\n" 1095 string += "}" 1096 return string 1097 1098 @override 1099 def __getitem__(self, key: str) -> Tag[T, P]: 1100 for tag in self.value: 1101 if tag.name == key: 1102 return tag 1103 raise KeyError(f"{key!r}") 1104 1105 @override 1106 def __iter__(self) -> Iterator[str]: 1107 return (tag.name for tag in self.value) 1108 1109 @override 1110 def __len__(self) -> int: 1111 return len(self.value)
NBT tag for a compound (list of uniquely named tags).
1065 @override 1066 def as_python(self) -> dict[str, P]: 1067 result = {} 1068 for tag in self.value: 1069 result[tag.name] = tag.as_python() 1070 return result
Returns a python representation of this tag.
1087 @override 1088 def pretty(self) -> str: 1089 string = "" 1090 string += f"Compound({self.name!r}): {len(self.value)} entries\n" 1091 string += "{\n" 1092 for child in self.value: 1093 for line in child.pretty().splitlines(): 1094 string += f"{PRETTY_INDENTATION}{line}\n" 1095 string += "}" 1096 return string
Returns a human readable pretty representation of the tag structure.
1114def load( 1115 file: IO[bytes], 1116 *, 1117 endianness: Endianness = "little", 1118 ignore_rest: bool = False, 1119) -> Tag[Any, Any]: 1120 """ 1121 Loads an NBT file and returns the contained NBT tag. 1122 1123 # Parameters 1124 1125 - `file` -- The file-like object to read the data from. 1126 - `endianness` -- The byte order to use during parsing. Java Edition usually 1127 uses big endian byte order and Bedrock Edition usually uses little endian 1128 byte order. 1129 - `ignore_rest` -- Whether to ignore remaining bytes after a full parse. By 1130 default, this function expects the buffer to be empty after an NBT tree 1131 has been serialized and raises an exception otherwise. 1132 """ 1133 buffer = file.read() 1134 (_, id) = _read_id(endianness, buffer) 1135 tag_cls = _tag_class_by_id(id) 1136 (tag, rest) = tag_cls._read(buffer, endianness=endianness) 1137 if rest and not ignore_rest: 1138 raise UnemptyBufferException() 1139 return tag
Loads an NBT file and returns the contained NBT tag.
Parameters
file-- The file-like object to read the data from.endianness-- The byte order to use during parsing. Java Edition usually uses big endian byte order and Bedrock Edition usually uses little endian byte order.ignore_rest-- Whether to ignore remaining bytes after a full parse. By default, this function expects the buffer to be empty after an NBT tree has been serialized and raises an exception otherwise.
1142def dump( 1143 tag: Tag[Any, Any], stream: IO[bytes], *, endianness: Endianness = "little" 1144) -> None: 1145 """ 1146 Dumps an NBT tag to a file. 1147 1148 # Parameters 1149 1150 - `tag` -- The root tag to write to the stream. 1151 - `stream` -- The stream to write to. 1152 - `endianness` -- The byte order to use. Java Edition usually uses big 1153 endian byte order and Bedrock Edition usually uses little endian byte 1154 order. 1155 """ 1156 tag._write(stream, endianness=endianness)
Dumps an NBT tag to a file.
Parameters
tag-- The root tag to write to the stream.stream-- The stream to write to.endianness-- The byte order to use. Java Edition usually uses big endian byte order and Bedrock Edition usually uses little endian byte order.