diff options
Diffstat (limited to 'docs/handbook/libnbtplusplus/overview.md')
| -rw-r--r-- | docs/handbook/libnbtplusplus/overview.md | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/docs/handbook/libnbtplusplus/overview.md b/docs/handbook/libnbtplusplus/overview.md new file mode 100644 index 0000000000..b2144a2bdc --- /dev/null +++ b/docs/handbook/libnbtplusplus/overview.md @@ -0,0 +1,422 @@ +# libnbt++ Overview + +## What is libnbt++? + +libnbt++ is a free C++ library for reading, writing, and manipulating Minecraft's **Named Binary Tag (NBT)** file format. It provides a modern C++11 interface for working with NBT data, supporting both compressed and uncompressed files, big-endian (Java Edition) and little-endian (Bedrock/Pocket Edition) byte orders, and full tag hierarchy manipulation. + +The library lives under the `nbt` namespace and provides strongly-typed tag classes that mirror the NBT specification exactly. It was originally created by ljfa-ag and is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0-or-later). + +libnbt++3 is a complete rewrite of the older libnbt++2, designed to eliminate boilerplate code and provide a more natural C++ syntax for NBT operations. + +--- + +## The NBT Format + +NBT (Named Binary Tag) is a binary serialization format invented by Markus "Notch" Persson for Minecraft. It is used throughout the game to store: + +- World save data (level.dat) +- Chunk data (region files) +- Player inventories and statistics +- Structure files +- Server configuration + +### Binary Structure + +An NBT file consists of a single named root tag, which is always a **Compound** tag. The binary layout is: + +``` +[tag type byte] [name length (2 bytes, big-endian)] [name (UTF-8)] [payload] +``` + +Each tag type has a specific format for its payload, and compound/list tags recursively contain other tags. + +### Compression + +NBT files in Minecraft are typically compressed with either **gzip** (most common for `.dat` files) or **zlib/deflate** (used in chunk data within region files). libnbt++ supports both through its optional zlib integration. + +--- + +## Tag Types + +The NBT format defines 13 tag types, represented in libnbt++ by the `tag_type` enum class defined in `include/tag.h`: + +```cpp +enum class tag_type : int8_t { + End = 0, // Marks the end of a compound tag + Byte = 1, // Signed 8-bit integer + Short = 2, // Signed 16-bit integer + Int = 3, // Signed 32-bit integer + Long = 4, // Signed 64-bit integer + Float = 5, // 32-bit IEEE 754 floating point + Double = 6, // 64-bit IEEE 754 floating point + Byte_Array = 7, // Array of signed bytes + String = 8, // UTF-8 string (max 65535 bytes) + List = 9, // Ordered list of unnamed tags (same type) + Compound = 10, // Collection of named tags (any type) + Int_Array = 11, // Array of signed 32-bit integers + Long_Array = 12, // Array of signed 64-bit integers + Null = -1 // Internal: denotes empty value objects +}; +``` + +The `Null` type (value -1) is an internal sentinel used by libnbt++ to represent uninitialized `value` objects; it does not appear in the NBT specification. + +The `End` type (value 0) is only valid within compound tags to mark their end; it is never used as a standalone tag. + +### Tag Type Validation + +The function `is_valid_type()` checks whether an integer value is a valid tag type: + +```cpp +bool is_valid_type(int type, bool allow_end = false); +``` + +It returns `true` when `type` falls between 1 and 12 (inclusive), or between 0 and 12 if `allow_end` is `true`. + +--- + +## C++ Tag Classes + +Each NBT tag type maps to a concrete C++ class in the `nbt` namespace. The classes are organized using templates for related types: + +| NBT Type | ID | C++ Class | Underlying Type | Header | +|-------------|----|--------------------|------------------------|--------------------| +| Byte | 1 | `tag_byte` | `tag_primitive<int8_t>` | `tag_primitive.h` | +| Short | 2 | `tag_short` | `tag_primitive<int16_t>`| `tag_primitive.h` | +| Int | 3 | `tag_int` | `tag_primitive<int32_t>`| `tag_primitive.h` | +| Long | 4 | `tag_long` | `tag_primitive<int64_t>`| `tag_primitive.h` | +| Float | 5 | `tag_float` | `tag_primitive<float>` | `tag_primitive.h` | +| Double | 6 | `tag_double` | `tag_primitive<double>` | `tag_primitive.h` | +| Byte_Array | 7 | `tag_byte_array` | `tag_array<int8_t>` | `tag_array.h` | +| String | 8 | `tag_string` | `tag_string` | `tag_string.h` | +| List | 9 | `tag_list` | `tag_list` | `tag_list.h` | +| Compound | 10 | `tag_compound` | `tag_compound` | `tag_compound.h` | +| Int_Array | 11 | `tag_int_array` | `tag_array<int32_t>` | `tag_array.h` | +| Long_Array | 12 | `tag_long_array` | `tag_array<int64_t>` | `tag_array.h` | + +The typedef names (`tag_byte`, `tag_short`, etc.) are the intended public API. The underlying template classes (`tag_primitive<T>`, `tag_array<T>`) should not be used directly. + +--- + +## Library Features + +### Modern C++11 Design + +- **Move semantics**: Tags support move construction and move assignment for efficient transfers +- **Smart pointers**: `std::unique_ptr<tag>` is used throughout for ownership management +- **Initializer lists**: Compounds and lists can be constructed with brace-enclosed initializer lists +- **Type-safe conversions**: The `value` class provides explicit conversions with `std::bad_cast` on type mismatch +- **Templates**: `tag_primitive<T>` and `tag_array<T>` use templates to avoid code duplication + +### Convenient Syntax + +Creating complex NBT structures is straightforward: + +```cpp +#include <nbt_tags.h> + +nbt::tag_compound root{ + {"playerName", "Steve"}, + {"health", int16_t(20)}, + {"position", nbt::tag_list{1.0, 64.5, -3.2}}, + {"inventory", nbt::tag_list::of<nbt::tag_compound>({ + {{"id", "minecraft:diamond_sword"}, {"count", int8_t(1)}}, + {{"id", "minecraft:apple"}, {"count", int8_t(64)}} + })}, + {"scores", nbt::tag_int_array{100, 250, 380}} +}; +``` + +### The value Class + +The `value` class (`include/value.h`) acts as a type-erased wrapper around `std::unique_ptr<tag>`. It enables: + +- Implicit numeric conversions (widening only): `int8_t` → `int16_t` → `int32_t` → `int64_t` → `float` → `double` +- Direct string assignment +- Subscript access: `compound["key"]` for compounds, `list[index]` for lists +- Chained access: `root["nested"]["deep"]["value"]` + +### I/O System + +Reading and writing NBT data uses the stream-based API: + +```cpp +#include <nbt_tags.h> +#include <io/stream_reader.h> +#include <io/stream_writer.h> +#include <fstream> + +// Reading +std::ifstream file("level.dat", std::ios::binary); +auto [name, compound] = nbt::io::read_compound(file); + +// Writing +std::ofstream out("output.nbt", std::ios::binary); +nbt::io::write_tag("Level", *compound, out); +``` + +The I/O system supports both big-endian (Java Edition default) and little-endian (Bedrock Edition) byte orders via the `endian::endian` enum: + +```cpp +// Reading Bedrock Edition data +auto pair = nbt::io::read_compound(file, endian::little); +``` + +### Zlib Compression Support + +When built with `NBT_USE_ZLIB=ON` (the default), the library provides stream wrappers for transparent compression/decompression: + +```cpp +#include <io/izlibstream.h> +#include <io/ozlibstream.h> + +// Reading gzip-compressed NBT +std::ifstream gzfile("level.dat", std::ios::binary); +zlib::izlibstream decompressed(gzfile); +auto pair = nbt::io::read_compound(decompressed); + +// Writing gzip-compressed NBT +std::ofstream outfile("output.dat", std::ios::binary); +zlib::ozlibstream compressed(outfile, Z_DEFAULT_COMPRESSION, true /* gzip */); +nbt::io::write_tag("Level", root, compressed); +compressed.close(); +``` + +### Visitor Pattern + +The library implements the Visitor pattern through `nbt_visitor` and `const_nbt_visitor` base classes, with 12 overloads (one per concrete tag type). The JSON formatter (`text::json_formatter`) is an example of a visitor that pretty-prints tag trees for debugging. + +### Polymorphic Operations + +All tag classes support: + +- **`clone()`** — Deep-copies the tag, returning `std::unique_ptr<tag>` +- **`move_clone()`** — Moves the tag into a new `unique_ptr` +- **`assign(tag&&)`** — Move-assigns from another tag of the same type +- **`get_type()`** — Returns the `tag_type` enum value +- **`operator==` / `operator!=`** — Deep equality comparison +- **`operator<<`** — JSON-like formatted output via `text::json_formatter` + +### Factory Construction + +Tags can be constructed dynamically by type: + +```cpp +auto t = nbt::tag::create(nbt::tag_type::Int); // Default-constructed tag_int(0) +auto t = nbt::tag::create(nbt::tag_type::Float, 3.14f); // Numeric tag_float(3.14) +``` + +--- + +## Namespace Organization + +| Namespace | Contents | +|----------------|-------------------------------------------------------------| +| `nbt` | All tag classes, `value`, `value_initializer`, helpers | +| `nbt::detail` | CRTP base class, primitive/array type traits (internal) | +| `nbt::io` | `stream_reader`, `stream_writer`, free functions | +| `nbt::text` | `json_formatter` for pretty-printing | +| `endian` | Endianness-aware binary read/write functions | +| `zlib` | zlib stream wrappers (`izlibstream`, `ozlibstream`) | + +--- + +## File Organization + +### Public Headers (`include/`) + +| File | Purpose | +|--------------------------|-------------------------------------------------------------| +| `tag.h` | `tag` base class, `tag_type` enum, `is_valid_type()` | +| `tagfwd.h` | Forward declarations for all tag classes | +| `nbt_tags.h` | Convenience header — includes all tag headers | +| `tag_primitive.h` | `tag_primitive<T>` template and `tag_byte`..`tag_double` typedefs | +| `tag_string.h` | `tag_string` class | +| `tag_array.h` | `tag_array<T>` template and `tag_byte_array`..`tag_long_array` | +| `tag_list.h` | `tag_list` class | +| `tag_compound.h` | `tag_compound` class | +| `value.h` | `value` type-erased tag wrapper | +| `value_initializer.h` | `value_initializer` — implicit conversions for function params | +| `crtp_tag.h` | CRTP base template implementing polymorphic dispatch | +| `primitive_detail.h` | Type traits mapping C++ types to `tag_type` values | +| `nbt_visitor.h` | `nbt_visitor` and `const_nbt_visitor` base classes | +| `endian_str.h` | Endianness-aware binary I/O functions | +| `make_unique.h` | `nbt::make_unique<T>()` helper (C++11 polyfill) | +| `io/stream_reader.h` | `stream_reader` class and `read_compound()`/`read_tag()` | +| `io/stream_writer.h` | `stream_writer` class and `write_tag()` | +| `io/izlibstream.h` | `izlibstream` for decompression (requires zlib) | +| `io/ozlibstream.h` | `ozlibstream` for compression (requires zlib) | +| `io/zlib_streambuf.h` | `zlib_streambuf` base class, `zlib_error` exception | +| `text/json_formatter.h` | `json_formatter` for pretty-printing tags | + +### Source Files (`src/`) + +| File | Purpose | +|---------------------------|------------------------------------------| +| `tag.cpp` | `tag` methods, `tag_primitive` explicit instantiations, operators | +| `tag_compound.cpp` | `tag_compound` methods, binary I/O | +| `tag_list.cpp` | `tag_list` methods, initializer lists, binary I/O | +| `tag_string.cpp` | `tag_string` read/write payload | +| `value.cpp` | `value` assignment operators, conversions | +| `value_initializer.cpp` | `value_initializer` constructors | +| `endian_str.cpp` | Big/little endian read/write implementations | +| `io/stream_reader.cpp` | `stream_reader` methods, format parsing | +| `io/stream_writer.cpp` | `stream_writer` methods, format output | +| `io/izlibstream.cpp` | `inflate_streambuf` implementation | +| `io/ozlibstream.cpp` | `deflate_streambuf` implementation | +| `text/json_formatter.cpp` | `json_formatter` visitor implementation | + +--- + +## Quick Start Examples + +### Reading an NBT File + +```cpp +#include <nbt_tags.h> +#include <io/stream_reader.h> +#include <fstream> +#include <iostream> + +int main() { + std::ifstream file("level.dat", std::ios::binary); + if (!file) return 1; + + auto [name, root] = nbt::io::read_compound(file); + std::cout << "Root tag: " << name << "\n"; + std::cout << *root << std::endl; // JSON-formatted output + + return 0; +} +``` + +### Reading a Compressed File + +```cpp +#include <nbt_tags.h> +#include <io/stream_reader.h> +#include <io/izlibstream.h> +#include <fstream> + +int main() { + std::ifstream file("level.dat", std::ios::binary); + zlib::izlibstream decompressed(file); // Auto-detects gzip/zlib + auto [name, root] = nbt::io::read_compound(decompressed); + return 0; +} +``` + +### Creating and Writing NBT Data + +```cpp +#include <nbt_tags.h> +#include <io/stream_writer.h> +#include <fstream> + +int main() { + nbt::tag_compound data{ + {"name", "World1"}, + {"seed", int64_t(123456789)}, + {"spawnX", int32_t(0)}, + {"spawnY", int32_t(64)}, + {"spawnZ", int32_t(0)}, + {"gameType", int32_t(0)}, + {"raining", int8_t(0)}, + {"version", nbt::tag_compound{ + {"id", int32_t(19133)}, + {"name", "1.20.4"}, + {"snapshot", int8_t(0)} + }} + }; + + std::ofstream out("output.nbt", std::ios::binary); + nbt::io::write_tag("", data, out); + return 0; +} +``` + +### Modifying Existing Data + +```cpp +auto [name, root] = nbt::io::read_compound(file); + +// Modify values using operator[] +(*root)["playerName"] = std::string("Alex"); +(*root)["health"] = int16_t(20); + +// Add a new nested compound +root->put("newSection", nbt::tag_compound{ + {"key1", int32_t(42)}, + {"key2", "hello"} +}); + +// Remove a tag +root->erase("oldSection"); + +// Check if a key exists +if (root->has_key("inventory", nbt::tag_type::List)) { + auto& inv = root->at("inventory").as<nbt::tag_list>(); + inv.push_back(nbt::tag_compound{{"id", "minecraft:stone"}, {"count", int8_t(1)}}); +} +``` + +### Iterating Over Tags + +```cpp +// Iterating a compound +for (const auto& [key, val] : *root) { + std::cout << key << ": type=" << val.get_type() << "\n"; +} + +// Iterating a list +auto& list = root->at("items").as<nbt::tag_list>(); +for (size_t i = 0; i < list.size(); ++i) { + std::cout << "Item " << i << ": " << list[i].get() << "\n"; +} +``` + +--- + +## Error Handling + +libnbt++ uses exceptions for error reporting: + +| Exception | Thrown When | +|------------------------|----------------------------------------------------------| +| `nbt::io::input_error` | Read failure: invalid tag type, unexpected EOF, corruption | +| `std::bad_cast` | Type mismatch in `value` conversions or `tag::assign()` | +| `std::out_of_range` | Invalid key in `tag_compound::at()` or index in `tag_list::at()` | +| `std::invalid_argument`| Invalid tag type to `tag::create()`, type mismatch in list operations | +| `std::length_error` | String > 65535 bytes, array > INT32_MAX elements | +| `zlib::zlib_error` | zlib decompression/compression failure | +| `std::bad_alloc` | zlib memory allocation failure | + +Stream state flags (`failbit`, `badbit`) are also set on the underlying `std::istream`/`std::ostream` when errors occur. + +--- + +## Thread Safety + +libnbt++ provides no thread safety guarantees beyond those of the C++ standard library. Tag objects should not be accessed concurrently from multiple threads without external synchronization. Reading from separate `stream_reader` instances using independent streams is safe. + +--- + +## Platform Requirements + +- C++11 compatible compiler (GCC 4.8+, Clang 3.3+, MSVC 2015+) +- CMake 3.15 or later +- zlib (optional, for compressed NBT support) +- IEEE 754 floating point (enforced via `static_assert`) +- 8-bit bytes (enforced via `static_assert` on `CHAR_BIT`) + +The library uses `memcpy`-based type punning (not `reinterpret_cast`) for float/double endian conversions, ensuring defined behavior across compilers. + +--- + +## License + +libnbt++ is licensed under the **GNU Lesser General Public License v3.0 or later** (LGPL-3.0-or-later). This means: + +- You can link against libnbt++ from proprietary software +- Modifications to libnbt++ itself must be released under LGPL +- The full license text is in `COPYING` and `COPYING.LESSER` |
