NBT 格式参考

支持 Minecraft 基岩版和 Java 版,也支持两者所有常见或少见的服务端核心:Vanilla、Paper、Spigot、Purpur、Folia、Fabric、Forge、NeoForge、Mohist、Sponge、Bedrock Dedicated Server、PocketMine-MP、Nukkit、PowerNukkitX、Cloudburst、Dragonfly、Endstone、LeviLamina 等。完整列表。

NBT(Named Binary Tag)是 Minecraft 使用的二进制树状格式:一个标签由一字节类型、带长度的名称和负载组成,复合标签不断嵌套,直到 TAG_End 字节将其关闭。目前 Java 和基岩版共使用 13 种标签类型及五种编码。本页记录全部格式;还可用上方编辑器打开实际文件核对。

13 种标签类型大端序小端序VarintgzipzlibSNBT

将任意要检查的 NBT 文件拖放到这里

level.dat · .nbt · .mcstructure · .schem · .schematic · .dat · 区块转储数据

13 种标签类型

Id标签负载SNBT
0TAG_End无,用于关闭复合标签—
1TAG_Byte1 字节,有符号 −128…1271b
2TAG_Short2 字节,有符号1s
3TAG_Int4 字节,有符号1
4TAG_Long8 字节,有符号1L
5TAG_Float4 字节,IEEE 7541.0f
6TAG_Double8 字节,IEEE 7541.0d
7TAG_Byte_Arrayint 长度,随后为相应数量的字节[B;1b,2b]
8TAG_Stringunsigned short 长度,随后为 UTF-8 字节"text"
9TAG_List元素类型字节、int 长度,再接无名称的负载[1,2]
10TAG_Compound重复类型 + 名称 + 负载,直到 TAG_End{a:1}
11TAG_Int_Arrayint 长度,随后为相应数量的 4 字节 int[I;1,2]
12TAG_Long_Arrayint 长度,随后为相应数量的 8 字节 long[L;1L,2L]

一个文件只包含一个根 TAG_Compound:先是一字节类型 0x0A 和名称,然后是复合标签的内容。具名标签只会出现在复合标签内;列表元素仅包含负载,因此列表必须由同一种类型的元素组成。

实际使用的五种编码

编码整数字符串长度根标签使用位置
Java(经典)固定宽度大端序2 字节大端序具名Java 世界、结构和原理图
Java 网络格式(1.20.2+)固定宽度大端序2 字节大端序无名称Java 协议
基岩版固定宽度小端序2 字节小端序具名基岩版世界、.mcstructure、LevelDB 值
基岩版 level.dat固定宽度小端序2 字节小端序具名,位于 8 字节文件头之后基岩版世界元数据
基岩版网络格式int 和 long 使用 zigzag varint无符号 varint具名基岩版协议

在 varint 编码中,TAG_Short、TAG_Float 和 TAG_Double 仍是固定宽度小端序;只有 TAG_Int 和 TAG_Long 变为 zigzag varint,数组和列表长度也采用 TAG_Int 对应的编码。字符串长度使用无符号 varint,因此不再受固定宽度编码的 65535 字节上限约束。

字符串:modified UTF-8 与 UTF-8

Java 使用 DataOutputStream.writeUTF 序列化字符串,即 修订版 UTF-8:NUL 字符写为 C0 80 而不是 00,基本多文种平面以外的字符写为两个三字节代理项(CESU-8),而不是一个四字节序列。基岩版使用标准 UTF-8。如果工具对两者都假定同一种编码,保存时会损坏表情符号和部分中日韩文字。本编辑器按格式选择编码,因此含表情符号的世界名称往返保存后仍保持不变。

压缩

NBT 本身不压缩,是否压缩由容器决定。Java 文件通常使用 gzip(魔数 1F 8B),区域文件内的区块负载使用 zlib(魔数 78 01、78 9C 或 78 DA),基岩版则以未压缩形式保存 level.dat 和结构文件。格式通过魔数字节检测,因此同一解析器可处理这三种情况。使用错误压缩方式保存文件,是手动编辑后世界无法加载的最常见原因。

逐字节分析最小文件

标准示例 hello_world.nbt:名为 hello world 的根复合标签,其中包含一个名为 name、值为 Bananrama 的字符串标签。采用 Java 大端序编码时:

0A                          TAG_Compound
00 0B 68 65 6C 6C 6F 20 77 6F 72 6C 64   name length 11, "hello world"
   08                       TAG_String
   00 04 6E 61 6D 65        name length 4, "name"
   00 09 42 61 6E 61 6E 72 61 6D 61      value length 9, "Bananrama"
00                          TAG_End

同一文档使用基岩版小端序编码时,仅两个长度字段的字节顺序不同:使用 0B 00 而不是 00 0B。在基岩版网络 NBT 中,长度会变成单字节 varint,即 0B 和 04,完全没有填充。将三种文件中的任意一个拖入上方编辑器,它会报告收到的格式。

SNBT

SNBT 是 NBT 的文本写法,也是命令使用的格式:/data merge entity @s {Invulnerable:1b}。类型后缀使其能够无损表示数据:b 表示 byte,s 表示 short,L 表示 long,f 表示 float,d 表示 double,无后缀表示 int;带类型的数组字面量写为 [B;…]、[I;…]、[L;…]。编辑器可从任何已加载文件导出 SNBT,使两个世界版本能够用普通文本差异工具比较。

常见问题

有官方 NBT 规范吗?

Mojang 没有发布正式规范。Notch 在 2010 年记录了该格式,此后一直由社区维护;本页列出的标签 id 和布局与游戏当前读写的内容一致。

为什么基岩版使用小端序?

基岩版引擎使用 C++ 并以小端序硬件为目标,因此按原生顺序存储值。Java 的序列化使用大端序,因为 Java DataOutput 约定如此规定。

什么是 zigzag varint 编码?

它能用尽可能少的字节写入整数,同时让负数保持较短:符号被折叠到最低位,因此 −1 编码为 1,1 编码为 2。基岩版协议用它表示 int 和 long。

TAG_List 可以包含混合类型吗?

不能。列表会声明一种元素类型,每个项目都必须匹配。混合数据需要使用复合标签列表。

字符串的最大长度是多少?

固定宽度编码的上限为 65535 字节,因为长度字段是 unsigned short。varint 网络编码没有实际限制。

相关工具