Zh:NBT: Difference between revisions
imported>Kaniol m (→当前用途) |
imported>Kaniol (→规范) |
||
| (5 intermediate revisions by the same user not shown) | |||
| Line 61: | Line 61: | ||
| TAG_End | | TAG_End | ||
| 0 | | 0 | ||
| | | 表明TAG_Compound的结尾。它只会在TAG_Compound内部使用,而且即使在TAG_Compound中也不会被指名。 | ||
|- | |- | ||
| 1 | | 1 | ||
| TAG_Byte | | TAG_Byte | ||
| 1 | | 1 | ||
| | | 单个有符号字节 | ||
|- | |- | ||
| 2 | | 2 | ||
| TAG_Short | | TAG_Short | ||
| 2 | | 2 | ||
| | | 单个有符号的大字节序16位整型 | ||
|- | |- | ||
| 3 | | 3 | ||
| TAG_Int | | TAG_Int | ||
| 4 | | 4 | ||
| | | 单个有符号的大字节序32位整型 | ||
|- | |- | ||
| 4 | | 4 | ||
| TAG_Long | | TAG_Long | ||
| 8 | | 8 | ||
| | | 单个有符号的大字节序64位整型 | ||
|- | |- | ||
| 5 | | 5 | ||
| TAG_Float | | TAG_Float | ||
| 4 | | 4 | ||
| | | 单个大字节序的[[wikipedia:zh:IEEE 754-2008|IEEE-754]]单精度浮点数(可能为[[wikipedia:zh:NaN|NaN]]) | ||
|- | |- | ||
| 6 | | 6 | ||
| TAG_Double | | TAG_Double | ||
| 8 | | 8 | ||
| | | 单个大字节序的[[wikipedia:zh:IEEE 754-2008|IEEE-754]]双精度浮点数(可能为NaN) | ||
|- | |- | ||
| 7 | | 7 | ||
| TAG_Byte_Array | | TAG_Byte_Array | ||
| ... | | ... | ||
| | | 一个包含长度前缀的'''有符号'''字节数组。前缀是一个'''有符号'''整型(即4字节) | ||
|- | |- | ||
| 8 | | 8 | ||
| TAG_String | | TAG_String | ||
| ... | | ... | ||
| | | 一个包含长度前缀的[https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 modified UTF-8]字符串。前缀是一个'''无符号'''短整型(即2字节)来表明字符串以字节为单位的长度。 | ||
|- | |- | ||
| 9 | | 9 | ||
| TAG_List | | TAG_List | ||
| ... | | ... | ||
| | | 一个相同类型'''无名'''标签的列表。列表的前缀是它包含项目的<code>类型ID</code>(即1字节)列表的长度就是一个'''有符号'''整型(即4字节)。如果列表的长度为0或为负,则类型可能是0(TAG_End),但反之它必须是其他的什么类型。(Notch式的实现在这种情况下使用了TAG_End,但在另一个Mojang参考实现中使用了1,总之解析器应该在长度<=0时接受任何类型) | ||
|- | |- | ||
| 10 | | 10 | ||
| TAG_Compound | | TAG_Compound | ||
| ... | | ... | ||
| | | 有效地列出'''named'''标签。不能保证顺序。 | ||
|- | |- | ||
| 11 | | 11 | ||
| TAG_Int_Array | | TAG_Int_Array | ||
| ... | | ... | ||
| | | 一个带长度前缀的'''有符号'''整型数组。前缀是一个'''有符号'''整型(即4字节),表示4字节整型的数量。 | ||
|- | |- | ||
| 12 | | 12 | ||
| TAG_Long_Array | | TAG_Long_Array | ||
| ... | | ... | ||
| | | 一个带长度前缀的'''有符号'''整型数组。前缀是一个'''有符号'''整型(即4字节),表示8字节整型的数量。 | ||
|} | |} | ||
| Line 133: | Line 133: | ||
|- | |- | ||
| | | | ||
! | ! 类型ID | ||
! | ! 名称长度 | ||
! | ! 名称 | ||
! | ! 负载 | ||
|- | |- | ||
! | ! 被编码的 | ||
| 2 | | 2 | ||
| 9 | | 9 | ||
| Line 144: | Line 144: | ||
| <code>32767</code> | | <code>32767</code> | ||
|- | |- | ||
! | ! 磁盘中(以十六进制) | ||
| <code>02</code> | | <code>02</code> | ||
| <code>00 09</code> | | <code>00 09</code> | ||
| Line 169: | Line 169: | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
| | | (整个内容隐含在一个复合中) | ||
! | ! 类型ID(隐含复合中的第一个元素) | ||
! | ! 根复合的名称长度 | ||
! | ! 根复合的名称 | ||
! | ! 根复合中第一个元素的类型ID | ||
! | ! 根中第一个元素的名称长度 | ||
! | ! 第一个元素的名称 | ||
! | ! 字符串长度 | ||
! | ! 字符串 | ||
! | ! 标签末尾(根复合的) | ||
|- | |- | ||
! | ! 被编码的 | ||
| Compound | | Compound | ||
| 11 | | 11 | ||
| Line 191: | Line 191: | ||
| | | | ||
|- | |- | ||
! | ! 磁盘中(以十六进制) | ||
| <code>0a</code> | | <code>0a</code> | ||
| <code>00 0b</code> | | <code>00 0b</code> | ||
Latest revision as of 09:43, 7 October 2019
二进制命名标签(Named Binary Tag,NBT)文件格式是一种非常简单、尽管有些烦人(我们真的需要另一个格式吗?)[见讨论]的结构化二进制格式,Minecraft游戏将它用于多种用途。因此,一些第三方实用工具也利用了这种格式。你可以在本文底部找到示例文件。
Mojang还发布了一个参考实现和他们的Anvil转换工具,在此处可用:https://mojang.com/2012/02/new-minecraft-map-format-anvil/
当前用途
NBT格式目前在多个地方使用,主要是:
- 在协议中作为槽数据的一部分。
- 多人游戏中保存的服务器列表(
servers.dat)。 - 玩家数据(单人游戏和多人游戏,每个玩家一个文件),包含如物品栏和位置等内容。
- 已保存的世界(单人游戏和多人游戏)
- 包含常规信息(出生点、一天中的时间等)的世界索引文件
- 区块数据(见区域文件)
不幸的是,作为开发者遇到的NBT文件会以三种不同的方式存储,这就有些有趣了。
库
有很多很多用于操纵NBT的库,它们用各种语言写成,且每个语言都有好几种,例如:
- C
- C#
- D
- Go (New)
- Go (Old, without TagLongArray)
- Java (Old, without TagLongArray)
- Java
- Javascript
- PHP
- Python
- Ruby
- Rust
- Scala
- Kotlin(非常简易,200行)
- Kotlin Multiplatform
- 你的想法…
除非你有特定的目标或许可要求,否则强烈建议使用其中一种现有库。
实用工具
几乎每个第三方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 | 表明TAG_Compound的结尾。它只会在TAG_Compound内部使用,而且即使在TAG_Compound中也不会被指名。 |
| 1 | TAG_Byte | 1 | 单个有符号字节 |
| 2 | TAG_Short | 2 | 单个有符号的大字节序16位整型 |
| 3 | TAG_Int | 4 | 单个有符号的大字节序32位整型 |
| 4 | TAG_Long | 8 | 单个有符号的大字节序64位整型 |
| 5 | TAG_Float | 4 | 单个大字节序的IEEE-754单精度浮点数(可能为NaN) |
| 6 | TAG_Double | 8 | 单个大字节序的IEEE-754双精度浮点数(可能为NaN) |
| 7 | TAG_Byte_Array | ... | 一个包含长度前缀的有符号字节数组。前缀是一个有符号整型(即4字节) |
| 8 | TAG_String | ... | 一个包含长度前缀的modified UTF-8字符串。前缀是一个无符号短整型(即2字节)来表明字符串以字节为单位的长度。 |
| 9 | TAG_List | ... | 一个相同类型无名标签的列表。列表的前缀是它包含项目的类型ID(即1字节)列表的长度就是一个有符号整型(即4字节)。如果列表的长度为0或为负,则类型可能是0(TAG_End),但反之它必须是其他的什么类型。(Notch式的实现在这种情况下使用了TAG_End,但在另一个Mojang参考实现中使用了1,总之解析器应该在长度<=0时接受任何类型)
|
| 10 | TAG_Compound | ... | 有效地列出named标签。不能保证顺序。 |
| 11 | TAG_Int_Array | ... | 一个带长度前缀的有符号整型数组。前缀是一个有符号整型(即4字节),表示4字节整型的数量。 |
| 12 | TAG_Long_Array | ... | 一个带长度前缀的有符号整型数组。前缀是一个有符号整型(即4字节),表示8字节整型的数量。 |
这几件简单的事情需要记住:
- 表示数字的数据类型在Java版中为大字节序,而携带版为小字节序。除非使用Java,否则你很有可能必须将其交换为小字节序。见维基百科上有关字节序的文章。
- 每个NBT文件将总是隐式地包含在复合标签中,并且也以TAG_Compound开头。
- NBT文件的结构由TAG_List和TAG_Compound类型定义,因为这样的标签本身仅包含负载,但是这取决于标签中包含的内容,它也可能包含其他头。即,如果位于复合标签内部,则每个标签都将以TAG_id开头,然后是字符串(标签名),最后是负载。在列表中时,它仅是负载,因为没有名称,并且标签类型在列表的开头给出。
例如,这是磁盘上的TAG_Short的示例布局:
| 类型ID | 名称长度 | 名称 | 负载 | |
|---|---|---|---|---|
| 被编码的 | 2 | 9 | shortTest
|
32767
|
| 磁盘中(以十六进制) | 02
|
00 09
|
73 68 6F 72 74 54 65 73 74
|
7F FF
|
如果此TAG_Short曾经在TAG_List中,则它仅是负载,因为类型是隐式的且列表第一级中的标签是没有名称的。
示例
Markus最初提供了两个实际上的示例文件(test.nbt & bigtest.nbt)用于测试你的实现。下面提供的示例输出是使用PyNBT的debug-nbt工具生成的。
test.nbt
第一个示例是一个未压缩的“Hello World”NBT示例。如果你正确地解析了它,你会得到与下列类似的结构:
TAG_Compound('hello world'): 1 entry
{
TAG_String('name'): 'Bananrama'
}
这里是示例的说明:
| (整个内容隐含在一个复合中) | 类型ID(隐含复合中的第一个元素) | 根复合的名称长度 | 根复合的名称 | 根复合中第一个元素的类型ID | 根中第一个元素的名称长度 | 第一个元素的名称 | 字符串长度 | 字符串 | 标签末尾(根复合的) |
|---|---|---|---|---|---|---|---|---|---|
| 被编码的 | Compound | 11 | hello world | String | 4 | name | 9 | Bananrama | |
| 磁盘中(以十六进制) | 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'
}
}
下载
- test.nbt/hello_world.nbt(未压缩)
- bigtest.nbt(gzip压缩)
- NaN-value-double.dat(已压缩,原始版本未知)