Minecraft NBT (Named Binary Tag): Java (read-write) 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.wiki/w/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.

Java (read-write) source code to parse Minecraft NBT (Named Binary Tag)

MinecraftNbt.java

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

import io.kaitai.struct.ByteBufferKaitaiStream;
import io.kaitai.struct.KaitaiStruct;
import io.kaitai.struct.KaitaiStream;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import io.kaitai.struct.ConsistencyError;
import java.util.Objects;
import java.util.ArrayList;
import java.util.List;
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;


/**
 * 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`](https://minecraft.wiki/w/Player.dat_format); contains
 * e.g. player's inventory and location), saved worlds
 * ([`level.dat`](
 *   https://minecraft.wiki/w/Java_Edition_level_format#level.dat_format
 * ) and [Chunk format](https://minecraft.wiki/w/Chunk_format#NBT_structure)),
 * list of saved multiplayer servers
 * ([`servers.dat`](https://minecraft.wiki/w/Servers.dat_format)) and so on -
 * see <https://minecraft.wiki/w/NBT_format#Uses>.
 * 
 * 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 > 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](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:**
 * 
 *   * <https://wiki.vg/NBT#Download>
 *   * <https://github.com/twoolie/NBT/blob/f9e892e/tests/world_test/data/scoreboard.dat>
 *   * <https://github.com/chmod222/cNBT/tree/3f74b69/testdata>
 *   * <https://github.com/PistonDevelopers/hematite_nbt/tree/0b85f89/tests>
 * @see <a href="https://wiki.vg/NBT">Source</a>
 * @see <a href="https://web.archive.org/web/20110723210920/https://www.minecraft.net/docs/NBT.txt">Source</a>
 * @see <a href="https://minecraft.wiki/w/NBT_format">Source</a>
 */
public class MinecraftNbt extends KaitaiStruct.ReadWrite {
    public static MinecraftNbt fromFile(String fileName) throws IOException {
        return new MinecraftNbt(new ByteBufferKaitaiStream(fileName));
    }

    public enum Tag {
        END(0),
        BYTE(1),
        SHORT(2),
        INT(3),
        LONG(4),
        FLOAT(5),
        DOUBLE(6),
        BYTE_ARRAY(7),
        STRING(8),
        LIST(9),
        COMPOUND(10),
        INT_ARRAY(11),
        LONG_ARRAY(12);

        private final long id;
        Tag(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, Tag> byId = new HashMap<Long, Tag>(13);
        static {
            for (Tag e : Tag.values())
                byId.put(e.id(), e);
        }
        public static Tag byId(long id) { return byId.get(id); }
    }
    public MinecraftNbt() {
        this(null, null, null);
    }

    public MinecraftNbt(KaitaiStream _io) {
        this(_io, null, null);
    }

    public MinecraftNbt(KaitaiStream _io, KaitaiStruct.ReadWrite _parent) {
        this(_io, _parent, null);
    }

    public MinecraftNbt(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, MinecraftNbt _root) {
        super(_io);
        this._parent = _parent;
        this._root = _root == null ? this : _root;
    }
    public void _read() {
        if ( ((rootType() == Tag.END) && (false)) ) {
            this.rootCheck = this._io.readBytes(0);
        }
        this.root = new NamedTag(this._io, this, _root);
        this.root._read();
        _dirty = false;
    }

    public void _fetchInstances() {
        if ( ((rootType() == Tag.END) && (false)) ) {
        }
        this.root._fetchInstances();
        rootType();
        if (this.rootType != null) {
        }
    }

    public void _write_Seq() {
        _assertNotDirty();
        _shouldWriteRootType = _enabledRootType;
        if ( ((rootType() == Tag.END) && (false)) ) {
            if (this.rootCheck.length != 0)
                throw new ConsistencyError("root_check", 0, this.rootCheck.length);
            this._io.writeBytes(this.rootCheck);
        }
        this.root._write_Seq(this._io);
    }

    public void _check() {
        if (!Objects.equals(this.root._root(), _root()))
            throw new ConsistencyError("root", _root(), this.root._root());
        if (!Objects.equals(this.root._parent(), this))
            throw new ConsistencyError("root", this, this.root._parent());
        if (_enabledRootType) {
            if (!(this.rootType == Tag.COMPOUND)) {
                throw new KaitaiStream.ValidationNotEqualError(Tag.COMPOUND, this.rootType, null, "/instances/root_type");
            }
        }
        _dirty = false;
    }
    public static class NamedTag extends KaitaiStruct.ReadWrite {
        public static NamedTag fromFile(String fileName) throws IOException {
            return new NamedTag(new ByteBufferKaitaiStream(fileName));
        }
        public NamedTag() {
            this(null, null, null);
        }

        public NamedTag(KaitaiStream _io) {
            this(_io, null, null);
        }

        public NamedTag(KaitaiStream _io, KaitaiStruct.ReadWrite _parent) {
            this(_io, _parent, null);
        }

        public NamedTag(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, MinecraftNbt _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
        }
        public void _read() {
            this.type = MinecraftNbt.Tag.byId(this._io.readU1());
            if (!(isTagEnd())) {
                this.name = new TagString(this._io, this, _root);
                this.name._read();
            }
            if (!(isTagEnd())) {
                {
                    Tag on = type();
                    if (on != null) {
                        switch (type()) {
                        case BYTE: {
                            this.payload = ((Object) (this._io.readS1()));
                            break;
                        }
                        case BYTE_ARRAY: {
                            this.payload = new TagByteArray(this._io, this, _root);
                            ((TagByteArray) (this.payload))._read();
                            break;
                        }
                        case COMPOUND: {
                            this.payload = new TagCompound(this._io, this, _root);
                            ((TagCompound) (this.payload))._read();
                            break;
                        }
                        case DOUBLE: {
                            this.payload = ((Object) (this._io.readF8be()));
                            break;
                        }
                        case FLOAT: {
                            this.payload = ((Object) (this._io.readF4be()));
                            break;
                        }
                        case INT: {
                            this.payload = ((Object) (this._io.readS4be()));
                            break;
                        }
                        case INT_ARRAY: {
                            this.payload = new TagIntArray(this._io, this, _root);
                            ((TagIntArray) (this.payload))._read();
                            break;
                        }
                        case LIST: {
                            this.payload = new TagList(this._io, this, _root);
                            ((TagList) (this.payload))._read();
                            break;
                        }
                        case LONG: {
                            this.payload = ((Object) (this._io.readS8be()));
                            break;
                        }
                        case LONG_ARRAY: {
                            this.payload = new TagLongArray(this._io, this, _root);
                            ((TagLongArray) (this.payload))._read();
                            break;
                        }
                        case SHORT: {
                            this.payload = ((Object) (this._io.readS2be()));
                            break;
                        }
                        case STRING: {
                            this.payload = new TagString(this._io, this, _root);
                            ((TagString) (this.payload))._read();
                            break;
                        }
                        }
                    }
                }
            }
            _dirty = false;
        }

        public void _fetchInstances() {
            if (!(isTagEnd())) {
                this.name._fetchInstances();
            }
            if (!(isTagEnd())) {
                {
                    Tag on = type();
                    if (on != null) {
                        switch (type()) {
                        case BYTE: {
                            break;
                        }
                        case BYTE_ARRAY: {
                            ((TagByteArray) (this.payload))._fetchInstances();
                            break;
                        }
                        case COMPOUND: {
                            ((TagCompound) (this.payload))._fetchInstances();
                            break;
                        }
                        case DOUBLE: {
                            break;
                        }
                        case FLOAT: {
                            break;
                        }
                        case INT: {
                            break;
                        }
                        case INT_ARRAY: {
                            ((TagIntArray) (this.payload))._fetchInstances();
                            break;
                        }
                        case LIST: {
                            ((TagList) (this.payload))._fetchInstances();
                            break;
                        }
                        case LONG: {
                            break;
                        }
                        case LONG_ARRAY: {
                            ((TagLongArray) (this.payload))._fetchInstances();
                            break;
                        }
                        case SHORT: {
                            break;
                        }
                        case STRING: {
                            ((TagString) (this.payload))._fetchInstances();
                            break;
                        }
                        }
                    }
                }
            }
        }

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeU1(((Number) (this.type.id())).intValue());
            if (!(isTagEnd())) {
                this.name._write_Seq(this._io);
            }
            if (!(isTagEnd())) {
                {
                    Tag on = type();
                    if (on != null) {
                        switch (type()) {
                        case BYTE: {
                            this._io.writeS1(((Number) (this.payload)).byteValue());
                            break;
                        }
                        case BYTE_ARRAY: {
                            ((TagByteArray) (this.payload))._write_Seq(this._io);
                            break;
                        }
                        case COMPOUND: {
                            ((TagCompound) (this.payload))._write_Seq(this._io);
                            break;
                        }
                        case DOUBLE: {
                            this._io.writeF8be(((Number) (this.payload)).doubleValue());
                            break;
                        }
                        case FLOAT: {
                            this._io.writeF4be(((Number) (this.payload)).floatValue());
                            break;
                        }
                        case INT: {
                            this._io.writeS4be(((Number) (this.payload)).intValue());
                            break;
                        }
                        case INT_ARRAY: {
                            ((TagIntArray) (this.payload))._write_Seq(this._io);
                            break;
                        }
                        case LIST: {
                            ((TagList) (this.payload))._write_Seq(this._io);
                            break;
                        }
                        case LONG: {
                            this._io.writeS8be(((Number) (this.payload)).longValue());
                            break;
                        }
                        case LONG_ARRAY: {
                            ((TagLongArray) (this.payload))._write_Seq(this._io);
                            break;
                        }
                        case SHORT: {
                            this._io.writeS2be(((Number) (this.payload)).shortValue());
                            break;
                        }
                        case STRING: {
                            ((TagString) (this.payload))._write_Seq(this._io);
                            break;
                        }
                        }
                    }
                }
            }
        }

        public void _check() {
            if (!(isTagEnd())) {
                if (!Objects.equals(this.name._root(), _root()))
                    throw new ConsistencyError("name", _root(), this.name._root());
                if (!Objects.equals(this.name._parent(), this))
                    throw new ConsistencyError("name", this, this.name._parent());
            }
            if (!(isTagEnd())) {
                {
                    Tag on = type();
                    if (on != null) {
                        switch (type()) {
                        case BYTE: {
                            break;
                        }
                        case BYTE_ARRAY: {
                            if (!Objects.equals(((MinecraftNbt.TagByteArray) (this.payload))._root(), _root()))
                                throw new ConsistencyError("payload", _root(), ((MinecraftNbt.TagByteArray) (this.payload))._root());
                            if (!Objects.equals(((MinecraftNbt.TagByteArray) (this.payload))._parent(), this))
                                throw new ConsistencyError("payload", this, ((MinecraftNbt.TagByteArray) (this.payload))._parent());
                            break;
                        }
                        case COMPOUND: {
                            if (!Objects.equals(((MinecraftNbt.TagCompound) (this.payload))._root(), _root()))
                                throw new ConsistencyError("payload", _root(), ((MinecraftNbt.TagCompound) (this.payload))._root());
                            if (!Objects.equals(((MinecraftNbt.TagCompound) (this.payload))._parent(), this))
                                throw new ConsistencyError("payload", this, ((MinecraftNbt.TagCompound) (this.payload))._parent());
                            break;
                        }
                        case DOUBLE: {
                            break;
                        }
                        case FLOAT: {
                            break;
                        }
                        case INT: {
                            break;
                        }
                        case INT_ARRAY: {
                            if (!Objects.equals(((MinecraftNbt.TagIntArray) (this.payload))._root(), _root()))
                                throw new ConsistencyError("payload", _root(), ((MinecraftNbt.TagIntArray) (this.payload))._root());
                            if (!Objects.equals(((MinecraftNbt.TagIntArray) (this.payload))._parent(), this))
                                throw new ConsistencyError("payload", this, ((MinecraftNbt.TagIntArray) (this.payload))._parent());
                            break;
                        }
                        case LIST: {
                            if (!Objects.equals(((MinecraftNbt.TagList) (this.payload))._root(), _root()))
                                throw new ConsistencyError("payload", _root(), ((MinecraftNbt.TagList) (this.payload))._root());
                            if (!Objects.equals(((MinecraftNbt.TagList) (this.payload))._parent(), this))
                                throw new ConsistencyError("payload", this, ((MinecraftNbt.TagList) (this.payload))._parent());
                            break;
                        }
                        case LONG: {
                            break;
                        }
                        case LONG_ARRAY: {
                            if (!Objects.equals(((MinecraftNbt.TagLongArray) (this.payload))._root(), _root()))
                                throw new ConsistencyError("payload", _root(), ((MinecraftNbt.TagLongArray) (this.payload))._root());
                            if (!Objects.equals(((MinecraftNbt.TagLongArray) (this.payload))._parent(), this))
                                throw new ConsistencyError("payload", this, ((MinecraftNbt.TagLongArray) (this.payload))._parent());
                            break;
                        }
                        case SHORT: {
                            break;
                        }
                        case STRING: {
                            if (!Objects.equals(((MinecraftNbt.TagString) (this.payload))._root(), _root()))
                                throw new ConsistencyError("payload", _root(), ((MinecraftNbt.TagString) (this.payload))._root());
                            if (!Objects.equals(((MinecraftNbt.TagString) (this.payload))._parent(), this))
                                throw new ConsistencyError("payload", this, ((MinecraftNbt.TagString) (this.payload))._parent());
                            break;
                        }
                        }
                    }
                }
            }
            _dirty = false;
        }
        private Boolean isTagEnd;
        public Boolean isTagEnd() {
            if (this.isTagEnd != null)
                return this.isTagEnd;
            this.isTagEnd = type() == MinecraftNbt.Tag.END;
            return this.isTagEnd;
        }
        public void _invalidateIsTagEnd() { this.isTagEnd = null; }
        private Tag type;
        private TagString name;
        private Object payload;
        private MinecraftNbt _root;
        private KaitaiStruct.ReadWrite _parent;
        public Tag type() { return type; }
        public void setType(Tag _v) { _dirty = true; type = _v; }
        public TagString name() { return name; }
        public void setName(TagString _v) { _dirty = true; name = _v; }
        public Object payload() { return payload; }
        public void setPayload(Object _v) { _dirty = true; payload = _v; }
        public MinecraftNbt _root() { return _root; }
        public void set_root(MinecraftNbt _v) { _dirty = true; _root = _v; }
        public KaitaiStruct.ReadWrite _parent() { return _parent; }
        public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
    }
    public static class TagByteArray extends KaitaiStruct.ReadWrite {
        public static TagByteArray fromFile(String fileName) throws IOException {
            return new TagByteArray(new ByteBufferKaitaiStream(fileName));
        }
        public TagByteArray() {
            this(null, null, null);
        }

        public TagByteArray(KaitaiStream _io) {
            this(_io, null, null);
        }

        public TagByteArray(KaitaiStream _io, KaitaiStruct.ReadWrite _parent) {
            this(_io, _parent, null);
        }

        public TagByteArray(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, MinecraftNbt _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
        }
        public void _read() {
            this.lenData = this._io.readS4be();
            this.data = this._io.readBytes(lenData());
            _dirty = false;
        }

        public void _fetchInstances() {
        }

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeS4be(this.lenData);
            this._io.writeBytes(this.data);
        }

        public void _check() {
            if (this.data.length != lenData())
                throw new ConsistencyError("data", lenData(), this.data.length);
            _dirty = false;
        }
        private int lenData;
        private byte[] data;
        private MinecraftNbt _root;
        private KaitaiStruct.ReadWrite _parent;
        public int lenData() { return lenData; }
        public void setLenData(int _v) { _dirty = true; lenData = _v; }
        public byte[] data() { return data; }
        public void setData(byte[] _v) { _dirty = true; data = _v; }
        public MinecraftNbt _root() { return _root; }
        public void set_root(MinecraftNbt _v) { _dirty = true; _root = _v; }
        public KaitaiStruct.ReadWrite _parent() { return _parent; }
        public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
    }
    public static class TagCompound extends KaitaiStruct.ReadWrite {
        public static TagCompound fromFile(String fileName) throws IOException {
            return new TagCompound(new ByteBufferKaitaiStream(fileName));
        }
        public TagCompound() {
            this(null, null, null);
        }

        public TagCompound(KaitaiStream _io) {
            this(_io, null, null);
        }

        public TagCompound(KaitaiStream _io, KaitaiStruct.ReadWrite _parent) {
            this(_io, _parent, null);
        }

        public TagCompound(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, MinecraftNbt _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
        }
        public void _read() {
            this.tags = new ArrayList<NamedTag>();
            {
                NamedTag _it;
                int i = 0;
                do {
                    NamedTag _t_tags = new NamedTag(this._io, this, _root);
                    try {
                        _t_tags._read();
                    } finally {
                        _it = _t_tags;
                        this.tags.add(_it);
                    }
                    i++;
                } while (!(_it.isTagEnd()));
            }
            _dirty = false;
        }

        public void _fetchInstances() {
            for (int i = 0; i < this.tags.size(); i++) {
                this.tags.get(((Number) (i)).intValue())._fetchInstances();
            }
        }

        public void _write_Seq() {
            _assertNotDirty();
            for (int i = 0; i < this.tags.size(); i++) {
                this.tags.get(((Number) (i)).intValue())._write_Seq(this._io);
            }
        }

        public void _check() {
            if (this.tags.size() == 0)
                throw new ConsistencyError("tags", 0, this.tags.size());
            for (int i = 0; i < this.tags.size(); i++) {
                if (!Objects.equals(this.tags.get(((Number) (i)).intValue())._root(), _root()))
                    throw new ConsistencyError("tags", _root(), this.tags.get(((Number) (i)).intValue())._root());
                if (!Objects.equals(this.tags.get(((Number) (i)).intValue())._parent(), this))
                    throw new ConsistencyError("tags", this, this.tags.get(((Number) (i)).intValue())._parent());
                {
                    NamedTag _it = this.tags.get(((Number) (i)).intValue());
                    if (_it.isTagEnd() != (i == this.tags.size() - 1))
                        throw new ConsistencyError("tags", i == this.tags.size() - 1, _it.isTagEnd());
                }
            }
            _dirty = false;
        }
        private Integer dumpNumTags;
        public Integer dumpNumTags() {
            if (this.dumpNumTags != null)
                return this.dumpNumTags;
            this.dumpNumTags = ((Number) (tags().size() - ( ((tags().size() >= 1) && (tags().get(tags().size() - 1).isTagEnd()))  ? 1 : 0))).intValue();
            return this.dumpNumTags;
        }
        public void _invalidateDumpNumTags() { this.dumpNumTags = null; }
        private List<NamedTag> tags;
        private MinecraftNbt _root;
        private KaitaiStruct.ReadWrite _parent;
        public List<NamedTag> tags() { return tags; }
        public void setTags(List<NamedTag> _v) { _dirty = true; tags = _v; }
        public MinecraftNbt _root() { return _root; }
        public void set_root(MinecraftNbt _v) { _dirty = true; _root = _v; }
        public KaitaiStruct.ReadWrite _parent() { return _parent; }
        public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
    }
    public static class TagIntArray extends KaitaiStruct.ReadWrite {
        public static TagIntArray fromFile(String fileName) throws IOException {
            return new TagIntArray(new ByteBufferKaitaiStream(fileName));
        }
        public TagIntArray() {
            this(null, null, null);
        }

        public TagIntArray(KaitaiStream _io) {
            this(_io, null, null);
        }

        public TagIntArray(KaitaiStream _io, KaitaiStruct.ReadWrite _parent) {
            this(_io, _parent, null);
        }

        public TagIntArray(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, MinecraftNbt _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
        }
        public void _read() {
            this.numTags = this._io.readS4be();
            this.tags = new ArrayList<Integer>();
            for (int i = 0; i < numTags(); i++) {
                this.tags.add(this._io.readS4be());
            }
            _dirty = false;
        }

        public void _fetchInstances() {
            for (int i = 0; i < this.tags.size(); i++) {
            }
        }

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeS4be(this.numTags);
            for (int i = 0; i < this.tags.size(); i++) {
                this._io.writeS4be(this.tags.get(((Number) (i)).intValue()));
            }
        }

        public void _check() {
            if (this.tags.size() != numTags())
                throw new ConsistencyError("tags", numTags(), this.tags.size());
            for (int i = 0; i < this.tags.size(); i++) {
            }
            _dirty = false;
        }
        private Tag tagsType;
        public Tag tagsType() {
            if (this.tagsType != null)
                return this.tagsType;
            this.tagsType = MinecraftNbt.Tag.INT;
            return this.tagsType;
        }
        public void _invalidateTagsType() { this.tagsType = null; }
        private int numTags;
        private List<Integer> tags;
        private MinecraftNbt _root;
        private KaitaiStruct.ReadWrite _parent;
        public int numTags() { return numTags; }
        public void setNumTags(int _v) { _dirty = true; numTags = _v; }
        public List<Integer> tags() { return tags; }
        public void setTags(List<Integer> _v) { _dirty = true; tags = _v; }
        public MinecraftNbt _root() { return _root; }
        public void set_root(MinecraftNbt _v) { _dirty = true; _root = _v; }
        public KaitaiStruct.ReadWrite _parent() { return _parent; }
        public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
    }
    public static class TagList extends KaitaiStruct.ReadWrite {
        public static TagList fromFile(String fileName) throws IOException {
            return new TagList(new ByteBufferKaitaiStream(fileName));
        }
        public TagList() {
            this(null, null, null);
        }

        public TagList(KaitaiStream _io) {
            this(_io, null, null);
        }

        public TagList(KaitaiStream _io, KaitaiStruct.ReadWrite _parent) {
            this(_io, _parent, null);
        }

        public TagList(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, MinecraftNbt _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
        }
        public void _read() {
            this.tagsType = MinecraftNbt.Tag.byId(this._io.readU1());
            this.numTags = this._io.readS4be();
            this.tags = new ArrayList<Object>();
            for (int i = 0; i < numTags(); i++) {
                {
                    Tag on = tagsType();
                    if (on != null) {
                        switch (tagsType()) {
                        case BYTE: {
                            this.tags.add(((Object) (this._io.readS1())));
                            break;
                        }
                        case BYTE_ARRAY: {
                            TagByteArray _t_tags = new TagByteArray(this._io, this, _root);
                            try {
                                ((TagByteArray) (_t_tags))._read();
                            } finally {
                                this.tags.add(_t_tags);
                            }
                            break;
                        }
                        case COMPOUND: {
                            TagCompound _t_tags = new TagCompound(this._io, this, _root);
                            try {
                                ((TagCompound) (_t_tags))._read();
                            } finally {
                                this.tags.add(_t_tags);
                            }
                            break;
                        }
                        case DOUBLE: {
                            this.tags.add(((Object) (this._io.readF8be())));
                            break;
                        }
                        case FLOAT: {
                            this.tags.add(((Object) (this._io.readF4be())));
                            break;
                        }
                        case INT: {
                            this.tags.add(((Object) (this._io.readS4be())));
                            break;
                        }
                        case INT_ARRAY: {
                            TagIntArray _t_tags = new TagIntArray(this._io, this, _root);
                            try {
                                ((TagIntArray) (_t_tags))._read();
                            } finally {
                                this.tags.add(_t_tags);
                            }
                            break;
                        }
                        case LIST: {
                            TagList _t_tags = new TagList(this._io, this, _root);
                            try {
                                ((TagList) (_t_tags))._read();
                            } finally {
                                this.tags.add(_t_tags);
                            }
                            break;
                        }
                        case LONG: {
                            this.tags.add(((Object) (this._io.readS8be())));
                            break;
                        }
                        case LONG_ARRAY: {
                            TagLongArray _t_tags = new TagLongArray(this._io, this, _root);
                            try {
                                ((TagLongArray) (_t_tags))._read();
                            } finally {
                                this.tags.add(_t_tags);
                            }
                            break;
                        }
                        case SHORT: {
                            this.tags.add(((Object) (this._io.readS2be())));
                            break;
                        }
                        case STRING: {
                            TagString _t_tags = new TagString(this._io, this, _root);
                            try {
                                ((TagString) (_t_tags))._read();
                            } finally {
                                this.tags.add(_t_tags);
                            }
                            break;
                        }
                        }
                    }
                }
            }
            _dirty = false;
        }

        public void _fetchInstances() {
            for (int i = 0; i < this.tags.size(); i++) {
                {
                    Tag on = tagsType();
                    if (on != null) {
                        switch (tagsType()) {
                        case BYTE: {
                            break;
                        }
                        case BYTE_ARRAY: {
                            ((TagByteArray) (this.tags.get(((Number) (i)).intValue())))._fetchInstances();
                            break;
                        }
                        case COMPOUND: {
                            ((TagCompound) (this.tags.get(((Number) (i)).intValue())))._fetchInstances();
                            break;
                        }
                        case DOUBLE: {
                            break;
                        }
                        case FLOAT: {
                            break;
                        }
                        case INT: {
                            break;
                        }
                        case INT_ARRAY: {
                            ((TagIntArray) (this.tags.get(((Number) (i)).intValue())))._fetchInstances();
                            break;
                        }
                        case LIST: {
                            ((TagList) (this.tags.get(((Number) (i)).intValue())))._fetchInstances();
                            break;
                        }
                        case LONG: {
                            break;
                        }
                        case LONG_ARRAY: {
                            ((TagLongArray) (this.tags.get(((Number) (i)).intValue())))._fetchInstances();
                            break;
                        }
                        case SHORT: {
                            break;
                        }
                        case STRING: {
                            ((TagString) (this.tags.get(((Number) (i)).intValue())))._fetchInstances();
                            break;
                        }
                        }
                    }
                }
            }
        }

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeU1(((Number) (this.tagsType.id())).intValue());
            this._io.writeS4be(this.numTags);
            for (int i = 0; i < this.tags.size(); i++) {
                {
                    Tag on = tagsType();
                    if (on != null) {
                        switch (tagsType()) {
                        case BYTE: {
                            this._io.writeS1(((Number) (this.tags.get(((Number) (i)).intValue()))).byteValue());
                            break;
                        }
                        case BYTE_ARRAY: {
                            ((TagByteArray) (this.tags.get(((Number) (i)).intValue())))._write_Seq(this._io);
                            break;
                        }
                        case COMPOUND: {
                            ((TagCompound) (this.tags.get(((Number) (i)).intValue())))._write_Seq(this._io);
                            break;
                        }
                        case DOUBLE: {
                            this._io.writeF8be(((Number) (this.tags.get(((Number) (i)).intValue()))).doubleValue());
                            break;
                        }
                        case FLOAT: {
                            this._io.writeF4be(((Number) (this.tags.get(((Number) (i)).intValue()))).floatValue());
                            break;
                        }
                        case INT: {
                            this._io.writeS4be(((Number) (this.tags.get(((Number) (i)).intValue()))).intValue());
                            break;
                        }
                        case INT_ARRAY: {
                            ((TagIntArray) (this.tags.get(((Number) (i)).intValue())))._write_Seq(this._io);
                            break;
                        }
                        case LIST: {
                            ((TagList) (this.tags.get(((Number) (i)).intValue())))._write_Seq(this._io);
                            break;
                        }
                        case LONG: {
                            this._io.writeS8be(((Number) (this.tags.get(((Number) (i)).intValue()))).longValue());
                            break;
                        }
                        case LONG_ARRAY: {
                            ((TagLongArray) (this.tags.get(((Number) (i)).intValue())))._write_Seq(this._io);
                            break;
                        }
                        case SHORT: {
                            this._io.writeS2be(((Number) (this.tags.get(((Number) (i)).intValue()))).shortValue());
                            break;
                        }
                        case STRING: {
                            ((TagString) (this.tags.get(((Number) (i)).intValue())))._write_Seq(this._io);
                            break;
                        }
                        }
                    }
                }
            }
        }

        public void _check() {
            if (this.tags.size() != numTags())
                throw new ConsistencyError("tags", numTags(), this.tags.size());
            for (int i = 0; i < this.tags.size(); i++) {
                {
                    Tag on = tagsType();
                    if (on != null) {
                        switch (tagsType()) {
                        case BYTE: {
                            break;
                        }
                        case BYTE_ARRAY: {
                            if (!Objects.equals(((MinecraftNbt.TagByteArray) (this.tags.get(((Number) (i)).intValue())))._root(), _root()))
                                throw new ConsistencyError("tags", _root(), ((MinecraftNbt.TagByteArray) (this.tags.get(((Number) (i)).intValue())))._root());
                            if (!Objects.equals(((MinecraftNbt.TagByteArray) (this.tags.get(((Number) (i)).intValue())))._parent(), this))
                                throw new ConsistencyError("tags", this, ((MinecraftNbt.TagByteArray) (this.tags.get(((Number) (i)).intValue())))._parent());
                            break;
                        }
                        case COMPOUND: {
                            if (!Objects.equals(((MinecraftNbt.TagCompound) (this.tags.get(((Number) (i)).intValue())))._root(), _root()))
                                throw new ConsistencyError("tags", _root(), ((MinecraftNbt.TagCompound) (this.tags.get(((Number) (i)).intValue())))._root());
                            if (!Objects.equals(((MinecraftNbt.TagCompound) (this.tags.get(((Number) (i)).intValue())))._parent(), this))
                                throw new ConsistencyError("tags", this, ((MinecraftNbt.TagCompound) (this.tags.get(((Number) (i)).intValue())))._parent());
                            break;
                        }
                        case DOUBLE: {
                            break;
                        }
                        case FLOAT: {
                            break;
                        }
                        case INT: {
                            break;
                        }
                        case INT_ARRAY: {
                            if (!Objects.equals(((MinecraftNbt.TagIntArray) (this.tags.get(((Number) (i)).intValue())))._root(), _root()))
                                throw new ConsistencyError("tags", _root(), ((MinecraftNbt.TagIntArray) (this.tags.get(((Number) (i)).intValue())))._root());
                            if (!Objects.equals(((MinecraftNbt.TagIntArray) (this.tags.get(((Number) (i)).intValue())))._parent(), this))
                                throw new ConsistencyError("tags", this, ((MinecraftNbt.TagIntArray) (this.tags.get(((Number) (i)).intValue())))._parent());
                            break;
                        }
                        case LIST: {
                            if (!Objects.equals(((MinecraftNbt.TagList) (this.tags.get(((Number) (i)).intValue())))._root(), _root()))
                                throw new ConsistencyError("tags", _root(), ((MinecraftNbt.TagList) (this.tags.get(((Number) (i)).intValue())))._root());
                            if (!Objects.equals(((MinecraftNbt.TagList) (this.tags.get(((Number) (i)).intValue())))._parent(), this))
                                throw new ConsistencyError("tags", this, ((MinecraftNbt.TagList) (this.tags.get(((Number) (i)).intValue())))._parent());
                            break;
                        }
                        case LONG: {
                            break;
                        }
                        case LONG_ARRAY: {
                            if (!Objects.equals(((MinecraftNbt.TagLongArray) (this.tags.get(((Number) (i)).intValue())))._root(), _root()))
                                throw new ConsistencyError("tags", _root(), ((MinecraftNbt.TagLongArray) (this.tags.get(((Number) (i)).intValue())))._root());
                            if (!Objects.equals(((MinecraftNbt.TagLongArray) (this.tags.get(((Number) (i)).intValue())))._parent(), this))
                                throw new ConsistencyError("tags", this, ((MinecraftNbt.TagLongArray) (this.tags.get(((Number) (i)).intValue())))._parent());
                            break;
                        }
                        case SHORT: {
                            break;
                        }
                        case STRING: {
                            if (!Objects.equals(((MinecraftNbt.TagString) (this.tags.get(((Number) (i)).intValue())))._root(), _root()))
                                throw new ConsistencyError("tags", _root(), ((MinecraftNbt.TagString) (this.tags.get(((Number) (i)).intValue())))._root());
                            if (!Objects.equals(((MinecraftNbt.TagString) (this.tags.get(((Number) (i)).intValue())))._parent(), this))
                                throw new ConsistencyError("tags", this, ((MinecraftNbt.TagString) (this.tags.get(((Number) (i)).intValue())))._parent());
                            break;
                        }
                        }
                    }
                }
            }
            _dirty = false;
        }
        private Tag tagsType;
        private int numTags;
        private List<Object> tags;
        private MinecraftNbt _root;
        private KaitaiStruct.ReadWrite _parent;
        public Tag tagsType() { return tagsType; }
        public void setTagsType(Tag _v) { _dirty = true; tagsType = _v; }
        public int numTags() { return numTags; }
        public void setNumTags(int _v) { _dirty = true; numTags = _v; }
        public List<Object> tags() { return tags; }
        public void setTags(List<Object> _v) { _dirty = true; tags = _v; }
        public MinecraftNbt _root() { return _root; }
        public void set_root(MinecraftNbt _v) { _dirty = true; _root = _v; }
        public KaitaiStruct.ReadWrite _parent() { return _parent; }
        public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
    }
    public static class TagLongArray extends KaitaiStruct.ReadWrite {
        public static TagLongArray fromFile(String fileName) throws IOException {
            return new TagLongArray(new ByteBufferKaitaiStream(fileName));
        }
        public TagLongArray() {
            this(null, null, null);
        }

        public TagLongArray(KaitaiStream _io) {
            this(_io, null, null);
        }

        public TagLongArray(KaitaiStream _io, KaitaiStruct.ReadWrite _parent) {
            this(_io, _parent, null);
        }

        public TagLongArray(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, MinecraftNbt _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
        }
        public void _read() {
            this.numTags = this._io.readS4be();
            this.tags = new ArrayList<Long>();
            for (int i = 0; i < numTags(); i++) {
                this.tags.add(this._io.readS8be());
            }
            _dirty = false;
        }

        public void _fetchInstances() {
            for (int i = 0; i < this.tags.size(); i++) {
            }
        }

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeS4be(this.numTags);
            for (int i = 0; i < this.tags.size(); i++) {
                this._io.writeS8be(this.tags.get(((Number) (i)).intValue()));
            }
        }

        public void _check() {
            if (this.tags.size() != numTags())
                throw new ConsistencyError("tags", numTags(), this.tags.size());
            for (int i = 0; i < this.tags.size(); i++) {
            }
            _dirty = false;
        }
        private Tag tagsType;
        public Tag tagsType() {
            if (this.tagsType != null)
                return this.tagsType;
            this.tagsType = MinecraftNbt.Tag.LONG;
            return this.tagsType;
        }
        public void _invalidateTagsType() { this.tagsType = null; }
        private int numTags;
        private List<Long> tags;
        private MinecraftNbt _root;
        private KaitaiStruct.ReadWrite _parent;
        public int numTags() { return numTags; }
        public void setNumTags(int _v) { _dirty = true; numTags = _v; }
        public List<Long> tags() { return tags; }
        public void setTags(List<Long> _v) { _dirty = true; tags = _v; }
        public MinecraftNbt _root() { return _root; }
        public void set_root(MinecraftNbt _v) { _dirty = true; _root = _v; }
        public KaitaiStruct.ReadWrite _parent() { return _parent; }
        public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
    }
    public static class TagString extends KaitaiStruct.ReadWrite {
        public static TagString fromFile(String fileName) throws IOException {
            return new TagString(new ByteBufferKaitaiStream(fileName));
        }
        public TagString() {
            this(null, null, null);
        }

        public TagString(KaitaiStream _io) {
            this(_io, null, null);
        }

        public TagString(KaitaiStream _io, KaitaiStruct.ReadWrite _parent) {
            this(_io, _parent, null);
        }

        public TagString(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, MinecraftNbt _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
        }
        public void _read() {
            this.lenData = this._io.readU2be();
            this.data = new String(this._io.readBytes(lenData()), StandardCharsets.UTF_8);
            _dirty = false;
        }

        public void _fetchInstances() {
        }

        public void _write_Seq() {
            _assertNotDirty();
            this._io.writeU2be(this.lenData);
            this._io.writeBytes((this.data).getBytes(Charset.forName("UTF-8")));
        }

        public void _check() {
            if ((this.data).getBytes(Charset.forName("UTF-8")).length != lenData())
                throw new ConsistencyError("data", lenData(), (this.data).getBytes(Charset.forName("UTF-8")).length);
            _dirty = false;
        }
        private int lenData;
        private String data;
        private MinecraftNbt _root;
        private KaitaiStruct.ReadWrite _parent;

        /**
         * unsigned according to <https://wiki.vg/NBT#Specification>
         */
        public int lenData() { return lenData; }
        public void setLenData(int _v) { _dirty = true; lenData = _v; }
        public String data() { return data; }
        public void setData(String _v) { _dirty = true; data = _v; }
        public MinecraftNbt _root() { return _root; }
        public void set_root(MinecraftNbt _v) { _dirty = true; _root = _v; }
        public KaitaiStruct.ReadWrite _parent() { return _parent; }
        public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
    }
    private Tag rootType;
    private boolean _shouldWriteRootType = false;
    private boolean _enabledRootType = true;
    public Tag rootType() {
        if (_shouldWriteRootType)
            _writeRootType();
        if (this.rootType != null)
            return this.rootType;
        if (!_enabledRootType)
            return null;
        long _pos = this._io.pos();
        this._io.seek(0);
        this.rootType = Tag.byId(this._io.readU1());
        if (!(this.rootType == Tag.COMPOUND)) {
            throw new KaitaiStream.ValidationNotEqualError(Tag.COMPOUND, this.rootType, this._io, "/instances/root_type");
        }
        this._io.seek(_pos);
        return this.rootType;
    }
    public void setRootType(Tag _v) { _dirty = true; rootType = _v; }
    public void setRootType_Enabled(boolean _v) { _dirty = true; _enabledRootType = _v; }

    private void _writeRootType() {
        _shouldWriteRootType = false;
        long _pos = this._io.pos();
        this._io.seek(0);
        this._io.writeU1(((Number) (this.rootType.id())).intValue());
        this._io.seek(_pos);
    }
    private byte[] rootCheck;
    private NamedTag root;
    private MinecraftNbt _root;
    private KaitaiStruct.ReadWrite _parent;
    public byte[] rootCheck() { return rootCheck; }
    public void setRootCheck(byte[] _v) { _dirty = true; rootCheck = _v; }
    public NamedTag root() { return root; }
    public void setRoot(NamedTag _v) { _dirty = true; root = _v; }
    public MinecraftNbt _root() { return _root; }
    public void set_root(MinecraftNbt _v) { _dirty = true; _root = _v; }
    public KaitaiStruct.ReadWrite _parent() { return _parent; }
    public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
}