summaryrefslogtreecommitdiff
path: root/docs/handbook/libnbtplusplus/overview.md
blob: b2144a2bdc247fcffd288e37b5f21649a289c594 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
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`