Difference between revisions of "NBT"

From wiki.vg
Jump to navigation Jump to search
(add Mojang's reference implementation link)
(16 intermediate revisions by 6 users not shown)
Line 1: Line 1:
The Named Binary Tag (NBT) file format is an extremely simple, albiet annoying (did we really need yet ''another'' format?)<sup>[See Discussion]</sup> structured binary format used by the [http://www.minecraft.net Minecraft] game for a variety of things. Due to this, several third-party utilities now also utilize the format. You may find example files at: http://wiki.vg/nbt/
+
The Named Binary Tag (NBT) file format is an extremely simple, albeit annoying (did we really need yet ''another'' format?)<sup>[See Discussion]</sup> structured binary format used by the [http://www.minecraft.net Minecraft] game for a variety of things. Due to this, several third-party utilities now also utilize the format. You may find example files at: http://wiki.vg/NBT
 +
 
 +
Mojang has also released a reference implementation along with their Anvil conversion tool, available from https://mojang.com/2012/02/new-minecraft-map-format-anvil/
  
 
== Current Uses ==
 
== Current Uses ==
Line 16: Line 18:
  
 
=== Libraries ===
 
=== Libraries ===
 +
 
There are many, many libraries for manipulating NBT, written in several languages, and often several per language. For example,
 
There are many, many libraries for manipulating NBT, written in several languages, and often several per language. For example,
  
Line 22: Line 25:
 
* [https://github.com/Dav1dde/nbd D],
 
* [https://github.com/Dav1dde/nbd D],
 
* [https://github.com/toqueteos/minero/tree/master/proto/nbt Go],
 
* [https://github.com/toqueteos/minero/tree/master/proto/nbt Go],
* [https://github.com/Evil-Co/NBT-Lib Java],
+
* [https://github.com/flow/nbt Java],
 
* [https://github.com/sjmulder/nbt-js Javascript],  
 
* [https://github.com/sjmulder/nbt-js Javascript],  
 
* [https://github.com/TheFrozenFire/PHP-NBT-Decoder-Encoder PHP],
 
* [https://github.com/TheFrozenFire/PHP-NBT-Decoder-Encoder PHP],
 
* [https://github.com/twoolie/NBT Python],
 
* [https://github.com/twoolie/NBT Python],
 
* [https://github.com/mental/nbtfile Ruby],
 
* [https://github.com/mental/nbtfile Ruby],
 +
* [https://github.com/PistonDevelopers/hematite_nbt Rust],
 
* [https://github.com/drXor/ScalaNBT Scala],  
 
* [https://github.com/drXor/ScalaNBT Scala],  
* You get the idea...
+
* You get the idea…
  
 
Unless you have specific goals or licence requirements, it is ''extremely recommended'' to go with one of the existing libraries.
 
Unless you have specific goals or licence requirements, it is ''extremely recommended'' to go with one of the existing libraries.
Line 37: Line 41:
 
* [http://gerritg.de/?p=152 NEINedit] (Obj-C), an OS X specific editor.
 
* [http://gerritg.de/?p=152 NEINedit] (Obj-C), an OS X specific editor.
 
* [https://bitbucket.org/zzzeek/nbt2yaml nbt2yaml] (Python), provides command-line editing of NBT via the YAML format, as well as a fast and minimalist NBT parsing/rendering API.
 
* [https://bitbucket.org/zzzeek/nbt2yaml nbt2yaml] (Python), provides command-line editing of NBT via the YAML format, as well as a fast and minimalist NBT parsing/rendering API.
 +
* [https://github.com/C4K3/nbted nbted] (Rust), provides command-line editing of NBT files via your $EDITOR
  
 
== Specification ==
 
== Specification ==
Line 52: Line 57:
 
| TAG_End   
 
| TAG_End   
 
| 0   
 
| 0   
| This tag serves no purpose but to signify the end of an open TAG_Compound. In most libraries, this type is abstracted away and never seen. TAG_End is not named.
+
| Signifies the end of a TAG_Compound. It is only ever used inside a TAG_Compound, and is not named despite being in a TAG_Compound
 
|-
 
|-
 
| 1
 
| 1
Line 92: Line 97:
 
| TAG_String
 
| TAG_String
 
| ...
 
| ...
| A length-prefixed [[wikipedia:UTF-8|UTF-8]] string. The prefix is an '''unsigned''' short (thus 2 bytes)
+
| A length-prefixed [[wikipedia:UTF-8|UTF-8]] string. The prefix is an '''unsigned''' short (thus 2 bytes) signifying the length of the string in bytes
 
|-
 
|-
 
| 9
 
| 9
 
| TAG_List
 
| TAG_List
 
| ...
 
| ...
| A list of '''nameless''' tags, all of the same type. The list is prefixed with the <code>Type ID</code> of the items it contains (thus 1 byte), and the length of the list as a '''signed''' integer (a further 4 bytes).
+
| A list of '''nameless''' tags, all of the same type. The list is prefixed with the <code>Type ID</code> of the items it contains (thus 1 byte), and the length of the list as a '''signed''' integer (a further 4 bytes).  If the length of the list is 0 or negative, the type may be 0 (TAG_End) but otherwise it must be any other type.  (The notchian implementation uses TAG_End in that situation, but another reference implementation by Mojang uses 1 instead; parsers should accept any type if the length is <= 0).
 
|-
 
|-
 
| 10
 
| 10
Line 110: Line 115:
 
|}
 
|}
  
 
+
There are a couple of simple things to remember:
There are five simple things to remember:
+
* The datatypes representing numbers are in big-endian in the PC version, but Pocket Version is in little-endian. Unless you're using Java, you will most likely have to swap it to little-endian. See [[wikipedia:Endianness|the Wikipedia article on Endianness]].
* '''Everything''' is in big-endian in the PC version, but Pocket Version is in little-endian. Unless you're using Java, you will most likely have to swap it to little-endian. See [[wikipedia:Endianness|the Wikipedia article on Endianness]].
+
* Every NBT file will '''always''' implicitly be inside a tag compound, and also begin with a TAG_Compound
* Every NBT file will '''always''' begin with a TAG_Compound. No exceptions.
+
* The structure of a NBT file is defined by the TAG_List and TAG_Compound types, as such a tag itself will only contain the payload, but depending on what the tag is contained within may contain additional headers. I.e. if it's inside a Compound, then each tag will begin with the TAG_id, and then a string (the tag's name), and finally the payload. While in a list it will be only the payload, as there is no name and the tag type is given in the beginning of the list.
* Every tag begins with a single byte which is is the <code>Type ID</code> of the following tag.
 
* Every tag, except TAG_End, begins with a TAG_String, minus the <code>Type ID</code>. This is the name given to whatever is stored in the tag.
 
* The preceding two rules do '''not apply''' to the first level of tags within a <code>TAG_List</code>.
 
  
 
For example, here's the example layout of a <code>TAG_Short</code> on disk:
 
For example, here's the example layout of a <code>TAG_Short</code> on disk:
Line 149: Line 151:
 
This first example is an uncompressed [[wikipedia:Hello world program|"Hello World"]] NBT example. Should you parse it correctly, you will get a structure similar to the following:
 
This first example is an uncompressed [[wikipedia:Hello world program|"Hello World"]] NBT example. Should you parse it correctly, you will get a structure similar to the following:
  
<code>
+
<pre>
 
   TAG_Compound('hello world'): 1 entry
 
   TAG_Compound('hello world'): 1 entry
 
   {
 
   {
 
     TAG_String('name'): 'Bananrama'
 
     TAG_String('name'): 'Bananrama'
 
   }
 
   }
</code>
+
</pre>
 +
 
 +
Here is the example explained:
 +
{| class="wikitable"
 +
|-
 +
| (The entire thing is implicitly inside a compound)
 +
! Type ID (first element in the implicit compound)
 +
! Length of name of the root compound
 +
! Name of the root compound
 +
! Type ID of first element in root compound
 +
! Length of name of first element in root
 +
! Name of first element
 +
! Length of string
 +
! String
 +
! Tag end (of root compound)
 +
|-
 +
! Decoded
 +
| Compound
 +
| 11
 +
| ''hello world''
 +
| String
 +
| 4
 +
| ''name''
 +
| 9
 +
| ''Bananrama''
 +
|
 +
|-
 +
! On Disk (in hex)
 +
| <code>0a</code>
 +
| <code>00 0b</code>
 +
| <code>68 65 6c 6c 6f 20 77 6f 72 6c 64</code>
 +
| <code>08</code>
 +
| <code>00 04</code>
 +
| <code>6e 6a 6d 65</code>
 +
| <code>00 09</code>
 +
| <code>42 61 6e 61 6e 72 61 6d 61</code>
 +
| <code>00</code>
 +
|}
  
 
==== bigtest.nbt ====
 
==== bigtest.nbt ====
 
This second example is a gzip compressed test of every available tag. If your program can successfully parse this file, then you've done well. Note that the tags under ''TAG_List'' do not have a name, as mentioned above.  
 
This second example is a gzip compressed test of every available tag. If your program can successfully parse this file, then you've done well. Note that the tags under ''TAG_List'' do not have a name, as mentioned above.  
<code>
+
<pre>
 
   TAG_Compound('Level'): 11 entries
 
   TAG_Compound('Level'): 11 entries
 
   {
 
   {
Line 204: Line 243:
 
     TAG_Short('shortTest'): 32767
 
     TAG_Short('shortTest'): 32767
 
   }
 
   }
</code>
+
</pre>
  
 
==== servers.dat ====
 
==== servers.dat ====
 
The ''servers.dat'' file contains a list of multiplayer servers you've added to the game. To mix things up a bit, this file will always be uncompressed. Below is an example of the structure seen in ''servers.dat''.
 
The ''servers.dat'' file contains a list of multiplayer servers you've added to the game. To mix things up a bit, this file will always be uncompressed. Below is an example of the structure seen in ''servers.dat''.
<code>
+
<pre>
 
   TAG_Compound(<nowiki>''</nowiki>): 1 entry
 
   TAG_Compound(<nowiki>''</nowiki>): 1 entry
 
   {
 
   {
Line 229: Line 268:
 
     }
 
     }
 
   }
 
   }
</code>
+
</pre>
  
 
==== level.dat ====
 
==== level.dat ====
 
This final example is of a single player ''level.dat'', which is compressed using gzip. Notice the player's inventory and general world details such as spawn position, world name, and the game seed.
 
This final example is of a single player ''level.dat'', which is compressed using gzip. Notice the player's inventory and general world details such as spawn position, world name, and the game seed.
<code>
+
<pre>
 
   TAG_Compound(<nowiki>''</nowiki>): 1 entry
 
   TAG_Compound(<nowiki>''</nowiki>): 1 entry
 
   {
 
   {
Line 396: Line 435:
 
     }
 
     }
 
   }
 
   }
</code>
+
</pre>
  
 
==== Download ====
 
==== Download ====
 
* [https://raw.github.com/Dav1dde/nbd/master/test/hello_world.nbt test.nbt/hello_world.nbt] (uncompressed),
 
* [https://raw.github.com/Dav1dde/nbd/master/test/hello_world.nbt test.nbt/hello_world.nbt] (uncompressed),
 
* [https://raw.github.com/Dav1dde/nbd/master/test/bigtest.nbt bigtest.nbt] (gzip compressed)
 
* [https://raw.github.com/Dav1dde/nbd/master/test/bigtest.nbt bigtest.nbt] (gzip compressed)

Revision as of 10:09, 2 April 2017

The Named Binary Tag (NBT) file format is an extremely simple, albeit annoying (did we really need yet another format?)[See Discussion] structured binary format used by the Minecraft game for a variety of things. Due to this, several third-party utilities now also utilize the format. You may find example files at: http://wiki.vg/NBT

Mojang has also released a reference implementation along with their Anvil conversion tool, available from https://mojang.com/2012/02/new-minecraft-map-format-anvil/

Current Uses

The NBT format is currently used in several places, chiefly:

  • In the Protocol as part of Slot Data
  • Multiplayer saved server list (servers.dat).
  • Player data (both single player and multiplayer, one file per player). This includes such things as inventory and location.
  • Saved worlds (both single player and multiplayer).
    • World index file (level.dat) that contains general information (spawn point, time of day, etc...)
    • Chunk data (see Region Files)

Unfortunately, the NBT files you can encounter as a developer will be stored in three different ways, just to make things interesting.

  • Uncompressed,
  • gzip'd,
  • zlib'd (aka DEFLATE with a few bytes extra)

Libraries

There are many, many libraries for manipulating NBT, written in several languages, and often several per language. For example,

Unless you have specific goals or licence requirements, it is extremely recommended to go with one of the existing libraries.

Utilities

Almost every 3rd-party Minecraft application uses NBT on some level. There also exist several dedicated NBT editors, which will likely be useful to you if you are developing an NBT library of your own. These include:

  • NBTEdit (C#, Mono-capable), one of the very first NBT editors.
  • NEINedit (Obj-C), an OS X specific editor.
  • nbt2yaml (Python), provides command-line editing of NBT via the YAML format, as well as a fast and minimalist NBT parsing/rendering API.
  • nbted (Rust), provides command-line editing of NBT files via your $EDITOR

Specification

The NBT file format is extremely simple, and writing a library capable of reading/writing it is a simple affair. There are 11 datatypes supported by this format, and one type used to close compound tags. It is strongly advised to read this entire section or you may run into issues.


Type ID Type Name Payload Size (Bytes) Description
0 TAG_End 0 Signifies the end of a TAG_Compound. It is only ever used inside a TAG_Compound, and is not named despite being in a TAG_Compound
1 TAG_Byte 1 A single signed byte
2 TAG_Short 2 A single signed, big endian 16 bit integer
3 TAG_Int 4 A single signed, big endian 32 bit integer
4 TAG_Long 8 A single signed, big endian 64 bit integer
5 TAG_Float 4 A single, big endian IEEE-754 single-precision floating point number
6 TAG_Double 8 A single, big endian IEEE-754 double-precision floating point number
7 TAG_Byte_Array ... A length-prefixed array of signed bytes. The prefix is a signed integer (thus 4 bytes)
8 TAG_String ... A length-prefixed UTF-8 string. The prefix is an unsigned short (thus 2 bytes) signifying the length of the string in bytes
9 TAG_List ... A list of nameless tags, all of the same type. The list is prefixed with the Type ID of the items it contains (thus 1 byte), and the length of the list as a signed integer (a further 4 bytes). If the length of the list is 0 or negative, the type may be 0 (TAG_End) but otherwise it must be any other type. (The notchian implementation uses TAG_End in that situation, but another reference implementation by Mojang uses 1 instead; parsers should accept any type if the length is <= 0).
10 TAG_Compound ... Effectively a list of a named tags. Order is not guaranteed.
11 TAG_Int_Array ... A length-prefixed array of signed integers. The prefix is a signed integer (thus 4 bytes) and indicates the number of 4 byte integers.

There are a couple of simple things to remember:

  • The datatypes representing numbers are in big-endian in the PC version, but Pocket Version is in little-endian. Unless you're using Java, you will most likely have to swap it to little-endian. See the Wikipedia article on Endianness.
  • Every NBT file will always implicitly be inside a tag compound, and also begin with a TAG_Compound
  • The structure of a NBT file is defined by the TAG_List and TAG_Compound types, as such a tag itself will only contain the payload, but depending on what the tag is contained within may contain additional headers. I.e. if it's inside a Compound, then each tag will begin with the TAG_id, and then a string (the tag's name), and finally the payload. While in a list it will be only the payload, as there is no name and the tag type is given in the beginning of the list.

For example, here's the example layout of a TAG_Short on disk:

Type ID Length of Name Name Payload
Decoded 2 9 shortTest 32767
On Disk (in hex) 02 00 09 73 68 6F 72 74 54 65 73 74 7F FF

If this TAG_Short had been in a TAG_List, it would have been nothing more than the payload, since the type is implied and tags within the first level of a list are nameless.

Examples

There are two defacto example files used for testing your implementation (test.nbt & bigtest.nbt), originally provided by Markus. The example output provided below was generated using PyNBT's debug-nbt tool.

test.nbt

This first example is an uncompressed "Hello World" NBT example. Should you parse it correctly, you will get a structure similar to the following:

  TAG_Compound('hello world'): 1 entry
  {
    TAG_String('name'): 'Bananrama'
  }

Here is the example explained:

(The entire thing is implicitly inside a compound) Type ID (first element in the implicit compound) Length of name of the root compound Name of the root compound Type ID of first element in root compound Length of name of first element in root Name of first element Length of string String Tag end (of root compound)
Decoded Compound 11 hello world String 4 name 9 Bananrama
On Disk (in hex) 0a 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 08 00 04 6e 6a 6d 65 00 09 42 61 6e 61 6e 72 61 6d 61 00

bigtest.nbt

This second example is a gzip compressed test of every available tag. If your program can successfully parse this file, then you've done well. Note that the tags under TAG_List do not have a name, as mentioned above.

  TAG_Compound('Level'): 11 entries
  {
    TAG_Compound('nested compound test'): 2 entries
    {
      TAG_Compound('egg'): 2 entries
      {
        TAG_String('name'): 'Eggbert'
        TAG_Float('value'): 0.5
      }
      TAG_Compound('ham'): 2 entries
      {
        TAG_String('name'): 'Hampus'
        TAG_Float('value'): 0.75
      }
    }
    TAG_Int('intTest'): 2147483647
    TAG_Byte('byteTest'): 127
    TAG_String('stringTest'): 'HELLO WORLD THIS IS A TEST STRING \xc3\x85\xc3\x84\xc3\x96!'
    TAG_List('listTest (long)'): 5 entries
    {
      TAG_Long(None): 11
      TAG_Long(None): 12
      TAG_Long(None): 13
      TAG_Long(None): 14
      TAG_Long(None): 15
    }
    TAG_Double('doubleTest'): 0.49312871321823148
    TAG_Float('floatTest'): 0.49823147058486938
    TAG_Long('longTest'): 9223372036854775807L
    TAG_List('listTest (compound)'): 2 entries
    {
      TAG_Compound(None): 2 entries
      {
        TAG_Long('created-on'): 1264099775885L
        TAG_String('name'): 'Compound tag #0'
      }
      TAG_Compound(None): 2 entries
      {
        TAG_Long('created-on'): 1264099775885L
        TAG_String('name'): 'Compound tag #1'
      }
    }
    TAG_Byte_Array('byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))'): [1000 bytes]
    TAG_Short('shortTest'): 32767
  }

servers.dat

The servers.dat file contains a list of multiplayer servers you've added to the game. To mix things up a bit, this file will always be uncompressed. Below is an example of the structure seen in servers.dat.

  TAG_Compound(''): 1 entry
  {
    TAG_List('servers'): 2 entries
    {
      TAG_Compound(None): 3 entries
      {
        TAG_Byte('hideAddress'): 0 (Don't hide the IP address)
        TAG_String('name'): 'Dainz1 - Creative'
        TAG_String('ip'): '199.167.132.229:25620'
        
      }
      TAG_Compound(None): 3 entries
      {
        TAG_Byte('hideAddress'): 1
        TAG_String('name'): 'minstarmin4'
        TAG_String('ip'): '76.127.122.65:25565'
        
      }
    }
  }

level.dat

This final example is of a single player level.dat, which is compressed using gzip. Notice the player's inventory and general world details such as spawn position, world name, and the game seed.

  TAG_Compound(''): 1 entry
  {
    TAG_Compound('Data'): 17 entries
    {
      TAG_Byte('raining'): 0
      TAG_Long('RandomSeed'): 3142388825013346304L
      TAG_Int('SpawnX'): 0
      TAG_Int('SpawnZ'): 0
      TAG_Long('LastPlayed'): 1323133681772L
      TAG_Int('GameType'): 1
      TAG_Int('SpawnY'): 63
      TAG_Byte('MapFeatures'): 1
      TAG_Compound('Player'): 24 entries
      {
        TAG_Int('XpTotal'): 0
        TAG_Compound('abilities'): 4 entries
        {
          TAG_Byte('instabuild'): 1
          TAG_Byte('flying'): 1
          TAG_Byte('mayfly'): 1
          TAG_Byte('invulnerable'): 1
        }
        TAG_Int('XpLevel'): 0
        TAG_Int('Score'): 0
        TAG_Short('Health'): 20
        TAG_List('Inventory'): 13 entries
        {
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 1
            TAG_Byte('Slot'): 0
            TAG_Short('id'): 24
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 1
            TAG_Byte('Slot'): 1
            TAG_Short('id'): 25
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 1
            TAG_Byte('Slot'): 2
            TAG_Short('id'): 326
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 1
            TAG_Byte('Slot'): 3
            TAG_Short('id'): 29
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 10
            TAG_Byte('Slot'): 4
            TAG_Short('id'): 69
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 3
            TAG_Byte('Slot'): 5
            TAG_Short('id'): 33
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 43
            TAG_Byte('Slot'): 6
            TAG_Short('id'): 356
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 64
            TAG_Byte('Slot'): 7
            TAG_Short('id'): 331
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 20
            TAG_Byte('Slot'): 8
            TAG_Short('id'): 76
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 64
            TAG_Byte('Slot'): 9
            TAG_Short('id'): 331
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 1
            TAG_Byte('Slot'): 10
            TAG_Short('id'): 323
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 16
            TAG_Byte('Slot'): 11
            TAG_Short('id'): 331
            TAG_Short('Damage'): 0
          }
          TAG_Compound(None): 4 entries
          {
            TAG_Byte('Count'): 1
            TAG_Byte('Slot'): 12
            TAG_Short('id'): 110
            TAG_Short('Damage'): 0
          }
        }
        TAG_Short('HurtTime'): 0
        TAG_Short('Fire'): -20
        TAG_Float('foodExhaustionLevel'): 0.0
        TAG_Float('foodSaturationLevel'): 5.0
        TAG_Int('foodTickTimer'): 0
        TAG_Short('SleepTimer'): 0
        TAG_Short('DeathTime'): 0
        TAG_List('Rotation'): 2 entries
        {
          TAG_Float(None): 1151.9342041015625
          TAG_Float(None): 32.249679565429688
        }
        TAG_Float('XpP'): 0.0
        TAG_Float('FallDistance'): 0.0
        TAG_Short('Air'): 300
        TAG_List('Motion'): 3 entries
        {
          TAG_Double(None): -2.9778325794951344e-11
          TAG_Double(None): -0.078400001525878907
          TAG_Double(None): 1.1763942772801152e-11
        }
        TAG_Int('Dimension'): 0
        TAG_Byte('OnGround'): 1
        TAG_List('Pos'): 3 entries
        {
          TAG_Double(None): 256.87499499518492
          TAG_Double(None): 112.62000000476837
          TAG_Double(None): -34.578128612797634
        }
        TAG_Byte('Sleeping'): 0
        TAG_Short('AttackTime'): 0
        TAG_Int('foodLevel'): 20
      }
      TAG_Int('thunderTime'): 2724
      TAG_Int('version'): 19132
      TAG_Int('rainTime'): 5476
      TAG_Long('Time'): 128763
      TAG_Byte('thundering'): 1
      TAG_Byte('hardcore'): 0
      TAG_Long('SizeOnDisk'): 0
      TAG_String('LevelName'): 'Sandstone Test World'
    }
  }

Download