Difference between revisions of "ZH:NBT"

From wiki.vg
Jump to: navigation, search
(Created page with "二进制命名标签(Named Binary Tag,NBT)文件格式是一种非常简单、尽管有些烦人(我们真的需要''另一个''格式吗?)<sup>[见讨论]</sup>...")
 
m (当前用途)
Line 10: Line 10:
 
* 已保存的世界(单人游戏和多人游戏)
 
* 已保存的世界(单人游戏和多人游戏)
 
** 包含常规信息(出生点、一天中的时间等)的世界索引文件
 
** 包含常规信息(出生点、一天中的时间等)的世界索引文件
** 区块数据(见[[ZH:Region Files:区域文件]])
+
** 区块数据(见[[ZH:Region Files|区域文件]])
  
 
不幸的是,作为开发者遇到的NBT文件会以三种不同的方式存储,这就有些有趣了。
 
不幸的是,作为开发者遇到的NBT文件会以三种不同的方式存储,这就有些有趣了。

Revision as of 23:02, 6 October 2019

二进制命名标签(Named Binary Tag,NBT)文件格式是一种非常简单、尽管有些烦人(我们真的需要另一个格式吗?)[见讨论]的结构化二进制格式,Minecraft游戏将它用于多种用途。因此,一些第三方实用工具也利用了这种格式。你可以在本文底部找到示例文件。

Mojang还发布了一个参考实现和他们的Anvil转换工具,在此处可用:https://mojang.com/2012/02/new-minecraft-map-format-anvil/

当前用途

NBT格式目前在多个地方使用,主要是:

  • 协议中作为槽数据的一部分。
  • 多人游戏中保存的服务器列表(servers.dat)。
  • 玩家数据(单人游戏和多人游戏,每个玩家一个文件),包含如物品栏和位置等内容。
  • 已保存的世界(单人游戏和多人游戏)
    • 包含常规信息(出生点、一天中的时间等)的世界索引文件
    • 区块数据(见区域文件

不幸的是,作为开发者遇到的NBT文件会以三种不同的方式存储,这就有些有趣了。

有很多很多用于操纵NBT的库,它们用各种语言写成,且每个语言都有好几种,例如:

除非你有特定的目标或许可要求,否则强烈建议使用其中一种现有库。

实用工具

几乎每个第三方Minecraft应用程序都在某种程度上使用了NBT。还有一些专用的NBT编辑器,如果您要开发自己的NBT库,这可能会有帮助,包括:

  • NBTEdit(C#,单一功能),最早的NBT编辑器之一。
  • NEINedit(Obj-C),一个OS X限定的编辑器。
  • nbt2yaml(Python),提供了通过YAML格式使用命令行编辑NBT,以及快速而极简的NBT解析/渲染API的功能。
  • nbted(Rust;CC0),提供了通过你的$EDITOR使用命令行编辑NBT文件的功能。
  • nbt2json(Golang;MIT)NBT至JSON/YAML双向转换的命令行实用工具。支持MCPE-NBT。可用以库的形式使用。

规范

NBT文件格式非常简单,编写读写它的库也很简单。此格式支持13种数据类型,其中一种用于闭合复合标签。强烈建议你阅读整个部分,否则可能会遇到问题。

类型ID 类型名称 负载大小(字节) 描述
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 (NaN possible)
6 TAG_Double 8 A single, big endian IEEE-754 double-precision floating point number (NaN possible)
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 modified 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.
12 TAG_Long_Array ... A length-prefixed array of signed longs. The prefix is a signed integer (thus 4 bytes) and indicates the number of 8 byte longs.

这几件简单的事情需要记住:

  • 表示数字的数据类型在Java版中为大字节序,而携带版为小字节序。除非使用Java,否则你很有可能必须将其交换为小字节序。见维基百科上有关字节序的文章
  • 每个NBT文件将总是隐式地包含在复合标签中,并且也以TAG_Compound开头。
  • NBT文件的结构由TAG_List和TAG_Compound类型定义,因为这样的标签本身仅包含负载,但是这取决于标签中包含的内容,它也可能包含其他头。即,如果位于复合标签内部,则每个标签都将以TAG_id开头,然后是字符串(标签名),最后是负载。在列表中时,它仅是负载,因为没有名称,并且标签类型在列表的开头给出。

例如,这是磁盘上的TAG_Short的示例布局:

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

如果此TAG_Short曾经在TAG_List中,则它仅是负载,因为类型是隐式的且列表第一级中的标签是没有名称的。

示例

Markus最初提供了两个实际上的示例文件(test.nbt & bigtest.nbt)用于测试你的实现。下面提供的示例输出是使用PyNBTdebug-nbt工具生成的。

test.nbt

第一个示例是一个未压缩的“Hello World”NBT示例。如果你正确地解析了它,你会得到与下列类似的结构:

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

这里是示例的说明:

(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 61 6d 65 00 09 42 61 6e 61 6e 72 61 6d 61 00

bigtest.nbt

第二个示例是每个可用标签的gzip压缩的测试。如果你的程序可以成功解析此文件,那么你做得很好。请注意,如上所述,TAG_List下的标签没有名称。

  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

servers.dat文件包含你已添加到游戏中的多人服务器的列表。这有点混乱,此文件将总是未压缩的。以下是在servers.dat中看到的结构示例。

  TAG_Compound(''): 1 entry
  {
    TAG_List('servers'): 2 entries
    {
      TAG_Compound(None): 3 entries
      {
        TAG_Byte('acceptTextures'): 1 (Automatically accept resourcepacks from this server)
        TAG_String('ip'): '199.167.132.229:25620'
        TAG_String('name'): 'Dainz1 - Creative'
        
      }
      TAG_Compound(None): 3 entries
      {
        TAG_String('icon'): 'iVBORw0KGgoAAAANUhEUgAAAEAAAABACA...' (The base64-encoded server icon. Trimmed here for the example's sake)
        TAG_String('ip'): '76.127.122.65:25565'
        TAG_String('name'): 'minstarmin4'
        
      }
    }
  }

level.dat

最后一个示例是单人游戏的level.dat,它使用gzip压缩。注意玩家的物品栏和常规世界细节如出生位置、世界名称和游戏种子。

  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'
    }
  }

下载