Minecraft NBT (Named Binary Tag): C# parsing library

A structured binary format native to Minecraft for saving game data and transferring it over the network (in multiplayer), such as player data (<player>.dat; contains e.g. player's inventory and location), saved worlds (level.dat and Chunk format), list of saved multiplayer servers (servers.dat) and so on - see https://minecraft.gamepedia.com/NBT_format#Uses.

The entire file should be gzip-compressed (in accordance with the original specification NBT.txt by Notch), but can also be compressed with zlib or uncompressed.

This spec can only handle uncompressed NBT data, so be sure to first detect what type of data you are dealing with. You can use the Unix file command to do this (file-5.20 or later is required; older versions do not recognize zlib-compressed data and return application/octet-stream instead):

file --brief --mime-type input-unknown.nbt

If it says:

  • application/x-gzip or application/gzip (since file-5.37), you can decompress it by
    • gunzip -c input-gzip.nbt > output.nbt or
    • python3 -c "import sys, gzip; sys.stdout.buffer.write( gzip.decompress(sys.stdin.buffer.read()) )" < input-gzip.nbt > output.nbt
  • application/zlib, you can use
    • openssl zlib -d -in input-zlib.nbt -out output.nbt (does not work on most systems)
    • python3 -c "import sys, zlib; sys.stdout.buffer.write( zlib.decompress(sys.stdin.buffer.read()) )" < input-zlib.nbt > output.nbt
  • something else (especially image/x-pcx and application/octet-stream), it is most likely already uncompressed.

The file output.nbt generated by one of the above commands can already be processed with this Kaitai Struct specification.

This spec only implements the Java edition format. There is also a Bedrock edition NBT format, which uses little-endian encoding and has a few other differences, but it isn't as popular as the Java edition format.

Implementation note: strings in TAG_String are incorrectly decoded with standard UTF-8, while they are encoded in Modified UTF-8 (MUTF-8). That's because MUTF-8 is not supported natively by most target languages, and thus one must use external libraries to achieve a fully-compliant decoder. But decoding in standard UTF-8 is still better than nothing, and it usually works fine.

All Unicode code points with incompatible representations in MUTF-8 and UTF-8 are U+0000 (NUL), U+D800-U+DFFF (High and Low Surrogates) and U+10000-U+10FFFF (all Supplementary Planes; includes e.g. emoticons, pictograms). A MUTF-8-encoded string containing these code points cannot be successfully decoded as UTF-8. The behavior in this case depends on the target language - usually an exception is thrown, or the bytes that are not valid UTF-8 are replaced or ignored.

Sample files:

Application

Minecraft

File extension

["nbt", "dat", "schematic", "schem"]

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Minecraft NBT (Named Binary Tag) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C# generated by Kaitai Struct depends on the C# runtime library. You have to install it before you can parse data.

The C# runtime library is available in the NuGet Gallery. Installation instructions can also be found there.

Code

Parse a local file and get structure in memory:

var data = MinecraftNbt.FromFile("path/to/local/file.["nbt", "dat", "schematic", "schem"]");

Or parse structure from a byte array:

byte[] someArray = new byte[] { ... };
var data = new MinecraftNbt(new KaitaiStream(someArray));

After that, one can get various attributes from the structure by accessing properties like:

data.RootCheck // => get root check

C# source code to parse Minecraft NBT (Named Binary Tag)

MinecraftNbt.cs

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

using System.Collections.Generic;

namespace Kaitai
{

    /// <summary>
    /// A structured binary format native to Minecraft for saving game data and transferring
    /// it over the network (in multiplayer), such as player data
    /// ([`&lt;player&gt;.dat`](https://minecraft.gamepedia.com/Player.dat_format); contains
    /// e.g. player's inventory and location), saved worlds
    /// ([`level.dat`](
    ///   https://minecraft.gamepedia.com/Java_Edition_level_format#level.dat_format
    /// ) and [Chunk format](https://minecraft.gamepedia.com/Chunk_format#NBT_structure)),
    /// list of saved multiplayer servers
    /// ([`servers.dat`](https://minecraft.gamepedia.com/Servers.dat_format)) and so on -
    /// see &lt;https://minecraft.gamepedia.com/NBT_format#Uses&gt;.
    /// 
    /// The entire file should be _gzip_-compressed (in accordance with the original
    /// specification [NBT.txt](
    ///   https://web.archive.org/web/20110723210920/https://www.minecraft.net/docs/NBT.txt
    /// ) by Notch), but can also be compressed with _zlib_ or uncompressed.
    /// 
    /// This spec can only handle uncompressed NBT data, so be sure to first detect
    /// what type of data you are dealing with. You can use the Unix `file` command
    /// to do this (`file-5.20` or later is required; older versions do not recognize
    /// _zlib_-compressed data and return `application/octet-stream` instead):
    /// 
    /// ```shell
    /// file --brief --mime-type input-unknown.nbt
    /// ```
    /// 
    /// If it says:
    /// 
    ///   * `application/x-gzip` or `application/gzip` (since `file-5.37`), you can decompress it by
    ///     * `gunzip -c input-gzip.nbt &gt; output.nbt` or
    ///     * `python3 -c &quot;import sys, gzip; sys.stdout.buffer.write(
    ///       gzip.decompress(sys.stdin.buffer.read()) )&quot; &lt; input-gzip.nbt &gt; output.nbt`
    ///   * `application/zlib`, you can use
    ///     * `openssl zlib -d -in input-zlib.nbt -out output.nbt` (does not work on most systems)
    ///     * `python3 -c &quot;import sys, zlib; sys.stdout.buffer.write(
    ///       zlib.decompress(sys.stdin.buffer.read()) )&quot; &lt; input-zlib.nbt &gt; output.nbt`
    ///   * something else (especially `image/x-pcx` and `application/octet-stream`),
    ///     it is most likely already uncompressed.
    /// 
    /// The file `output.nbt` generated by one of the above commands can already be
    /// processed with this Kaitai Struct specification.
    /// 
    /// This spec **only** implements the Java edition format. There is also
    /// a [Bedrock edition](https://wiki.vg/NBT#Bedrock_edition) NBT format,
    /// which uses little-endian encoding and has a few other differences, but it isn't
    /// as popular as the Java edition format.
    /// 
    /// **Implementation note:** strings in `TAG_String` are incorrectly decoded with
    /// standard UTF-8, while they are encoded in [**Modified UTF-8**](
    ///   https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8
    /// ) (MUTF-8). That's because MUTF-8 is not supported natively by most target
    /// languages, and thus one must use external libraries to achieve a fully-compliant
    /// decoder. But decoding in standard UTF-8 is still better than nothing, and
    /// it usually works fine.
    /// 
    /// All Unicode code points with incompatible representations in MUTF-8 and UTF-8 are
    /// U+0000 (_NUL_), U+D800-U+DFFF (_High_ and _Low Surrogates_) and U+10000-U+10FFFF
    /// (all _Supplementary_ Planes; includes e.g. emoticons, pictograms).
    /// A _MUTF-8_-encoded string containing these code points cannot be successfully
    /// decoded as UTF-8. The behavior in this case depends on the target language -
    /// usually an exception is thrown, or the bytes that are not valid UTF-8
    /// are replaced or ignored.
    /// 
    /// **Sample files:**
    /// 
    ///   * &lt;https://wiki.vg/NBT#Download&gt;
    ///   * &lt;https://github.com/twoolie/NBT/blob/f9e892e/tests/world_test/data/scoreboard.dat&gt;
    ///   * &lt;https://github.com/chmod222/cNBT/tree/3f74b69/testdata&gt;
    ///   * &lt;https://github.com/PistonDevelopers/hematite_nbt/tree/0b85f89/tests&gt;
    /// </summary>
    /// <remarks>
    /// Reference: <a href="https://wiki.vg/NBT">Source</a>
    /// </remarks>
    /// <remarks>
    /// Reference: <a href="https://web.archive.org/web/20110723210920/https://www.minecraft.net/docs/NBT.txt">Source</a>
    /// </remarks>
    /// <remarks>
    /// Reference: <a href="https://minecraft.gamepedia.com/NBT_format">Source</a>
    /// </remarks>
    public partial class MinecraftNbt : KaitaiStruct
    {
        public static MinecraftNbt FromFile(string fileName)
        {
            return new MinecraftNbt(new KaitaiStream(fileName));
        }


        public enum Tag
        {
            End = 0,
            Byte = 1,
            Short = 2,
            Int = 3,
            Long = 4,
            Float = 5,
            Double = 6,
            ByteArray = 7,
            String = 8,
            List = 9,
            Compound = 10,
            IntArray = 11,
            LongArray = 12,
        }
        public MinecraftNbt(KaitaiStream p__io, KaitaiStruct p__parent = null, MinecraftNbt p__root = null) : base(p__io)
        {
            m_parent = p__parent;
            m_root = p__root ?? this;
            f_rootType = false;
            _read();
        }
        private void _read()
        {
            if ( ((RootType == Tag.End) && (false)) ) {
                _rootCheck = m_io.ReadBytes(0);
            }
            _root = new NamedTag(m_io, this, m_root);
        }
        public partial class TagLongArray : KaitaiStruct
        {
            public static TagLongArray FromFile(string fileName)
            {
                return new TagLongArray(new KaitaiStream(fileName));
            }

            public TagLongArray(KaitaiStream p__io, KaitaiStruct p__parent = null, MinecraftNbt p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                f_tagsType = false;
                _read();
            }
            private void _read()
            {
                _numTags = m_io.ReadS4be();
                _tags = new List<long>((int) (NumTags));
                for (var i = 0; i < NumTags; i++)
                {
                    _tags.Add(m_io.ReadS8be());
                }
            }
            private bool f_tagsType;
            private Tag _tagsType;
            public Tag TagsType
            {
                get
                {
                    if (f_tagsType)
                        return _tagsType;
                    _tagsType = (Tag) (MinecraftNbt.Tag.Long);
                    f_tagsType = true;
                    return _tagsType;
                }
            }
            private int _numTags;
            private List<long> _tags;
            private MinecraftNbt m_root;
            private KaitaiStruct m_parent;
            public int NumTags { get { return _numTags; } }
            public List<long> Tags { get { return _tags; } }
            public MinecraftNbt M_Root { get { return m_root; } }
            public KaitaiStruct M_Parent { get { return m_parent; } }
        }
        public partial class TagByteArray : KaitaiStruct
        {
            public static TagByteArray FromFile(string fileName)
            {
                return new TagByteArray(new KaitaiStream(fileName));
            }

            public TagByteArray(KaitaiStream p__io, KaitaiStruct p__parent = null, MinecraftNbt p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                _read();
            }
            private void _read()
            {
                _lenData = m_io.ReadS4be();
                _data = m_io.ReadBytes(LenData);
            }
            private int _lenData;
            private byte[] _data;
            private MinecraftNbt m_root;
            private KaitaiStruct m_parent;
            public int LenData { get { return _lenData; } }
            public byte[] Data { get { return _data; } }
            public MinecraftNbt M_Root { get { return m_root; } }
            public KaitaiStruct M_Parent { get { return m_parent; } }
        }
        public partial class TagIntArray : KaitaiStruct
        {
            public static TagIntArray FromFile(string fileName)
            {
                return new TagIntArray(new KaitaiStream(fileName));
            }

            public TagIntArray(KaitaiStream p__io, KaitaiStruct p__parent = null, MinecraftNbt p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                f_tagsType = false;
                _read();
            }
            private void _read()
            {
                _numTags = m_io.ReadS4be();
                _tags = new List<int>((int) (NumTags));
                for (var i = 0; i < NumTags; i++)
                {
                    _tags.Add(m_io.ReadS4be());
                }
            }
            private bool f_tagsType;
            private Tag _tagsType;
            public Tag TagsType
            {
                get
                {
                    if (f_tagsType)
                        return _tagsType;
                    _tagsType = (Tag) (MinecraftNbt.Tag.Int);
                    f_tagsType = true;
                    return _tagsType;
                }
            }
            private int _numTags;
            private List<int> _tags;
            private MinecraftNbt m_root;
            private KaitaiStruct m_parent;
            public int NumTags { get { return _numTags; } }
            public List<int> Tags { get { return _tags; } }
            public MinecraftNbt M_Root { get { return m_root; } }
            public KaitaiStruct M_Parent { get { return m_parent; } }
        }
        public partial class TagList : KaitaiStruct
        {
            public static TagList FromFile(string fileName)
            {
                return new TagList(new KaitaiStream(fileName));
            }

            public TagList(KaitaiStream p__io, KaitaiStruct p__parent = null, MinecraftNbt p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                _read();
            }
            private void _read()
            {
                _tagsType = ((MinecraftNbt.Tag) m_io.ReadU1());
                _numTags = m_io.ReadS4be();
                _tags = new List<object>((int) (NumTags));
                for (var i = 0; i < NumTags; i++)
                {
                    switch (TagsType) {
                    case MinecraftNbt.Tag.LongArray: {
                        _tags.Add(new TagLongArray(m_io, this, m_root));
                        break;
                    }
                    case MinecraftNbt.Tag.Compound: {
                        _tags.Add(new TagCompound(m_io, this, m_root));
                        break;
                    }
                    case MinecraftNbt.Tag.Double: {
                        _tags.Add(m_io.ReadF8be());
                        break;
                    }
                    case MinecraftNbt.Tag.List: {
                        _tags.Add(new TagList(m_io, this, m_root));
                        break;
                    }
                    case MinecraftNbt.Tag.Float: {
                        _tags.Add(m_io.ReadF4be());
                        break;
                    }
                    case MinecraftNbt.Tag.Short: {
                        _tags.Add(m_io.ReadS2be());
                        break;
                    }
                    case MinecraftNbt.Tag.Int: {
                        _tags.Add(m_io.ReadS4be());
                        break;
                    }
                    case MinecraftNbt.Tag.ByteArray: {
                        _tags.Add(new TagByteArray(m_io, this, m_root));
                        break;
                    }
                    case MinecraftNbt.Tag.Byte: {
                        _tags.Add(m_io.ReadS1());
                        break;
                    }
                    case MinecraftNbt.Tag.IntArray: {
                        _tags.Add(new TagIntArray(m_io, this, m_root));
                        break;
                    }
                    case MinecraftNbt.Tag.String: {
                        _tags.Add(new TagString(m_io, this, m_root));
                        break;
                    }
                    case MinecraftNbt.Tag.Long: {
                        _tags.Add(m_io.ReadS8be());
                        break;
                    }
                    }
                }
            }
            private Tag _tagsType;
            private int _numTags;
            private List<object> _tags;
            private MinecraftNbt m_root;
            private KaitaiStruct m_parent;
            public Tag TagsType { get { return _tagsType; } }
            public int NumTags { get { return _numTags; } }
            public List<object> Tags { get { return _tags; } }
            public MinecraftNbt M_Root { get { return m_root; } }
            public KaitaiStruct M_Parent { get { return m_parent; } }
        }
        public partial class TagString : KaitaiStruct
        {
            public static TagString FromFile(string fileName)
            {
                return new TagString(new KaitaiStream(fileName));
            }

            public TagString(KaitaiStream p__io, KaitaiStruct p__parent = null, MinecraftNbt p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                _read();
            }
            private void _read()
            {
                _lenData = m_io.ReadU2be();
                _data = System.Text.Encoding.GetEncoding("utf-8").GetString(m_io.ReadBytes(LenData));
            }
            private ushort _lenData;
            private string _data;
            private MinecraftNbt m_root;
            private KaitaiStruct m_parent;

            /// <summary>
            /// unsigned according to https://wiki.vg/NBT#Specification
            /// </summary>
            public ushort LenData { get { return _lenData; } }
            public string Data { get { return _data; } }
            public MinecraftNbt M_Root { get { return m_root; } }
            public KaitaiStruct M_Parent { get { return m_parent; } }
        }
        public partial class TagCompound : KaitaiStruct
        {
            public static TagCompound FromFile(string fileName)
            {
                return new TagCompound(new KaitaiStream(fileName));
            }

            public TagCompound(KaitaiStream p__io, KaitaiStruct p__parent = null, MinecraftNbt p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                f_dumpNumTags = false;
                _read();
            }
            private void _read()
            {
                _tags = new List<NamedTag>();
                {
                    var i = 0;
                    NamedTag M_;
                    do {
                        M_ = new NamedTag(m_io, this, m_root);
                        _tags.Add(M_);
                        i++;
                    } while (!(M_.IsTagEnd));
                }
            }
            private bool f_dumpNumTags;
            private int _dumpNumTags;
            public int DumpNumTags
            {
                get
                {
                    if (f_dumpNumTags)
                        return _dumpNumTags;
                    _dumpNumTags = (int) ((Tags.Count - ( ((Tags.Count >= 1) && (Tags[Tags.Count - 1].IsTagEnd))  ? 1 : 0)));
                    f_dumpNumTags = true;
                    return _dumpNumTags;
                }
            }
            private List<NamedTag> _tags;
            private MinecraftNbt m_root;
            private KaitaiStruct m_parent;
            public List<NamedTag> Tags { get { return _tags; } }
            public MinecraftNbt M_Root { get { return m_root; } }
            public KaitaiStruct M_Parent { get { return m_parent; } }
        }
        public partial class NamedTag : KaitaiStruct
        {
            public static NamedTag FromFile(string fileName)
            {
                return new NamedTag(new KaitaiStream(fileName));
            }

            public NamedTag(KaitaiStream p__io, KaitaiStruct p__parent = null, MinecraftNbt p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                f_isTagEnd = false;
                _read();
            }
            private void _read()
            {
                _type = ((MinecraftNbt.Tag) m_io.ReadU1());
                if (!(IsTagEnd)) {
                    _name = new TagString(m_io, this, m_root);
                }
                if (!(IsTagEnd)) {
                    switch (Type) {
                    case MinecraftNbt.Tag.LongArray: {
                        _payload = new TagLongArray(m_io, this, m_root);
                        break;
                    }
                    case MinecraftNbt.Tag.Compound: {
                        _payload = new TagCompound(m_io, this, m_root);
                        break;
                    }
                    case MinecraftNbt.Tag.Double: {
                        _payload = m_io.ReadF8be();
                        break;
                    }
                    case MinecraftNbt.Tag.List: {
                        _payload = new TagList(m_io, this, m_root);
                        break;
                    }
                    case MinecraftNbt.Tag.Float: {
                        _payload = m_io.ReadF4be();
                        break;
                    }
                    case MinecraftNbt.Tag.Short: {
                        _payload = m_io.ReadS2be();
                        break;
                    }
                    case MinecraftNbt.Tag.Int: {
                        _payload = m_io.ReadS4be();
                        break;
                    }
                    case MinecraftNbt.Tag.ByteArray: {
                        _payload = new TagByteArray(m_io, this, m_root);
                        break;
                    }
                    case MinecraftNbt.Tag.Byte: {
                        _payload = m_io.ReadS1();
                        break;
                    }
                    case MinecraftNbt.Tag.IntArray: {
                        _payload = new TagIntArray(m_io, this, m_root);
                        break;
                    }
                    case MinecraftNbt.Tag.String: {
                        _payload = new TagString(m_io, this, m_root);
                        break;
                    }
                    case MinecraftNbt.Tag.Long: {
                        _payload = m_io.ReadS8be();
                        break;
                    }
                    }
                }
            }
            private bool f_isTagEnd;
            private bool _isTagEnd;
            public bool IsTagEnd
            {
                get
                {
                    if (f_isTagEnd)
                        return _isTagEnd;
                    _isTagEnd = (bool) (Type == MinecraftNbt.Tag.End);
                    f_isTagEnd = true;
                    return _isTagEnd;
                }
            }
            private Tag _type;
            private TagString _name;
            private object _payload;
            private MinecraftNbt m_root;
            private KaitaiStruct m_parent;
            public Tag Type { get { return _type; } }
            public TagString Name { get { return _name; } }
            public object Payload { get { return _payload; } }
            public MinecraftNbt M_Root { get { return m_root; } }
            public KaitaiStruct M_Parent { get { return m_parent; } }
        }
        private bool f_rootType;
        private Tag _rootType;
        public Tag RootType
        {
            get
            {
                if (f_rootType)
                    return _rootType;
                long _pos = m_io.Pos;
                m_io.Seek(0);
                _rootType = ((Tag) m_io.ReadU1());
                m_io.Seek(_pos);
                f_rootType = true;
                if (!(RootType == Tag.Compound))
                {
                    throw new ValidationNotEqualError(Tag.Compound, RootType, M_Io, "/instances/root_type");
                }
                return _rootType;
            }
        }
        private byte[] _rootCheck;
        private NamedTag _root;
        private MinecraftNbt m_root;
        private KaitaiStruct m_parent;
        public byte[] RootCheck { get { return _rootCheck; } }
        public NamedTag Root { get { return _root; } }
        public MinecraftNbt M_Root { get { return m_root; } }
        public KaitaiStruct M_Parent { get { return m_parent; } }
    }
}