FastNBT

repository·master·Indexed 43 days ago

https://github.com/owengage/fastnbt

A high-performance Rust library for serializing and deserializing Minecraft: Java Edition's NBT format using Serde. The project includes fastnbt for core NBT handling with zero-copy deserialization, fastsnbt for stringified NBT (sNBT), and FastAnvil for rendering Minecraft world maps and accessing chunk, block, and biome data.

Tokens
23.1K
Snippets
87
Records
108
Agent score
54%

What's inside FastNBT

  1. Use fastsnbt for sNBT (serde) serialization and deserialization

    master

    fastsnbt is a fast serde-based crate for handling Minecraft: Java Edition's sNBT format. It supports zero-copy deserialization where possible using the from_str method.

    It is designed to work closely with the fastnbt crate, acting as an extension for sNBT-specific tasks. For core NBT types like Value, refer to the fastnbt documentation.

  2. Core concepts of fastnbt NBT handling

    master

    The fastnbt crate provides several mechanisms for working with NBT data:

    • Zero-copy deserialization: Supported via from_bytes to minimize allocations.
    • The Value type: A universal type for serializing or deserializing any NBT structure. It is designed to correctly preserve the exact NBT structure.
    • The nbt! macro: Provides an easy way to construct Value instances.
    • Specialized Array types: To handle NBT's array requirements efficiently, use the dedicated ByteArray, IntArray, and LongArray types.
    • The borrow module: Contains additional types specifically designed to avoid allocations during processing.
  3. Install FastNBT and FastAnvil libraries

    master

    To use these libraries in your Rust project, add them to your Cargo.toml dependencies.

    Note: The version numbers below are examples; check crates.io for the latest versions.

    [dependencies]
    fastnbt = "2"
    fastanvil = "0.32"
  4. Use CurrentJavaChunk for map rendering data

    master

    CurrentJavaChunk is a specialized struct designed to provide the data necessary to render maps within fastanvil.

    Note: This type is not intended for full chunk data access. If you require more complete data (e.g., for full world editing or deep inspection), you should implement your own chunk struct with serde traits or use [fastnbt::Value].

    It implements the Chunk trait, providing methods to query surface height, biomes, and blocks at specific coordinates.

    // This struct is typically used via deserialization from Minecraft NBT data
    // and provides access to chunk information for rendering purposes.
  5. How NameSerializer works for NBT serialization

    master

    In NBT (Named Binary Tag) format, fields must be written in the order of tag, name, and then value. However, determining the tag (the type identifier) often requires knowing the type of the value being serialized, which isn't known until the value serialization process begins.

    To solve this, NameSerializer is used as a temporary buffer for the field's name. The serialization workflow is:

    1. Start serializing the value.
    2. The value serialization process writes the tag to the main buffer.
    3. The NameSerializer then writes the saved name to the main buffer.
    4. Finally, the value itself is written to the main buffer.

    NameSerializer only handles writing the name; it does not write the tag or the length of the name.

  6. Specialized Array Serialization for NBT

    master

    The fastsnbt serializer detects specific key patterns to use optimized NBT array formats instead of standard list formats. This is triggered when a key matches specific internal tokens:

    • Byte Arrays: Triggered by BYTE_ARRAY_TOKEN_STR. Uses prefix B;.
    • Int Arrays: Triggered by INT_ARRAY_TOKEN_STR. Uses prefix I;.
    • Long Arrays: Triggered by LONG_ARRAY_TOKEN_STR. Uses prefix L;.

    When these tokens are matched, the serializer uses an ArraySerializer with the appropriate stride (1 for bytes, 4 for ints, 8 for longs) to produce the compact NBT array syntax.

  7. How NameSerializer works in SNBT serialization

    master

    In fastsnbt, the NameSerializer is an internal component used to serialize the names of fields within SNBT (Stringified NBT) data.

    Because SNBT requires data to be written in a specific order (tag, name, value), and the tag (which includes the type ID and length) often depends on the size of the value, the serializer cannot always write everything in a single pass. NameSerializer solves this by allowing the name to be written to a separate buffer or at a different time than the main value.

    Key Constraints:

    • The NameSerializer is strictly designed to serialize string-like names.
    • If you attempt to use it to serialize non-string types (such as integers, booleans, or complex structures), it will return a bespoke error: field must be string-like, found {ty}.
  8. Use NBT array types for sequences

    master

    In the NBT format, there is a distinction between NBT Lists and NBT Arrays. To preserve this distinction during (de)serialization, do not use Vec for NBT arrays. Instead, use the specialized types provided by fastnbt:

    • ByteArray (for i8 arrays)
    • IntArray (for i32 arrays)
    • LongArray (for i64 arrays)

    All these types implement Deref into a slice. For zero-copy versions, see the borrow module. Standard NBT Lists should still be captured using Vec or other suitable containers.

    use fastnbt::LongArray;
    use serde::Deserialize;
    
    #[derive(Deserialize)]
    pub struct Section {
        pub block_states: Option<LongArray>,
        pub y: i8,
    }
  9. Use the Region struct to handle Minecraft Anvil files

    master

    The Region struct is the primary interface for interacting with Minecraft's Anvil region format. It can be initialized with any type that implements Read, Write, and Seek (such as a file) to facilitate reading and writing chunk data.

    Note on Chunk Data: The crate provides a JavaChunk for deserializing chunks from Minecraft versions 1.15 through 1.18 into structs. However, this is a partial deserialization; it does not capture all information (e.g., entities are lost) and is not suitable for serializing back into a region. For full (de)serialization, you should define your own chunk structures using the fastnbt crate.

    // Example conceptual usage (requires implementing Read/Write/Seek on your source)
    // let region = Region::new(file_handle);
    // let chunk = region.get_chunk(x, z)?;