完整版BedrockWiki镜像!

This commit is contained in:
boybook
2025-03-20 11:52:46 +08:00
parent 1994c41f01
commit bf9aa4b056
214 changed files with 9042 additions and 8867 deletions

View File

@@ -1,64 +1,74 @@
---
title: About NBT (Named Binary Tag)
category: NBT in Depth
title: NBT深度解析
category: NBT深度解析
mentions:
- ConsoleTerm
- SmokeyStack
- ThomasOrs
tags:
- expert
- 专家
---
NBT (Named Binary Tag) is a name for data encoding format at the binary level, you certainly know format JSON which is based on the text level. Therefore, we will be able to use the JSON format for some examples, you may also notice that minecraft itself uses JSON to represent NBT in commands such as java commands or simplified bedrock commands ( `/give`, `/replaceitem`). See [NBT Commands](/commands/nbt-commands) . In this article, we will show NBT in much more detail than you will ever expect, because what you could see in the commands is far from NBT, and we will show you how NBT works and how to read it, also how `Minecraft BE` itself uses it as well.
# NBT深度解析
## NBT Tags and Data Types
NBT, just like JSON, has given types and knows how to read them, for example JSON knows that a compound object starts with the symbol `{` and ends with `}`, it also knows that when it has to read a string, the string always starts with the symbol ", this means that we want to learn to read and understand NBT so you need to know when a composite object starts, and how to read individual types.
Now let's look at the table of NBT tags for NBT types and how they are marked in NBT.
As it was said, NBT works on a binary level, so you need to know that the smallest data type is a byte, which is 8 bits in size. And individual types can contain multiple bytes, but they can never be 1/2 byte extra or less, not possible! : )
We also cannot say how the tags should be named, because everyone can call NBT tags differently, but they must always have the same binary base (`id`), id is represented by one byte.
<!--@include: @/wiki/bedrock-wiki-mirror.md-->
| Name | Binary ID | Binary Size | Description |
| :----------------: | --------: | :---------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Byte | 0x01 | 1 byte (8-bits) | One byte size type |
| Int16 (short) | 0x02 | 2 bytes (16-bits) | A two-byte type |
| Int32 (intiger) | 0x03 | 4 bytes (32-bits) | A four-byte type |
| Int64 (long) | 0x04 | 8 bytes (64-bits) | An eight-byte type |
| Float | 0x05 | 4 bytes (32-bits) | A four-byte type with regular decimal precision |
| Double | 0x06 | 8 bytes (64-bits) | An eight-byte type with higher decimal precision |
| String | 0x08 | Predefined | A string type that has a predefined size. Text uses UTF-8 encoding |
| List | 0x09 | Predefined | A list type with a predefined size and defining type for the elements in the list |
| Compoud | 0x0A (10) | Undefined | Type compound, the compound does not have a predefined size, so it is necessary to read the keys and values until we do not encounter the tag for ending the compound. |
| End of the Compoud | 0x00 | 0 bytes | This tag is not a type but only a tag and can only be used depending on the compound. It marks the end of a compound |
| Byte List | 0x07 | Predefined | List type of Byte and predefined size, not commonly used by Minecraft BE |
| Int List | 0x0B (11) | Predefined | List type of Int and predefined size, not commonly used by Minecraft BE |
| Long List | 0x0C (12) | Predefined | List type of Long and predefined size, not commonly used by Minecraft BE |
NBT命名二进制标签是一种二进制层级的编码格式正如你熟悉的基于文本层级的JSON格式。因此我们可以用JSON格式举例说明你可能也注意到Minecraft本身在命令中使用JSON来表示NBT如Java版命令或简化的基岩版命令`/give``/replaceitem`)。详见[NBT命令](/wiki/commands/nbt-commands)。本文将深入解析NBT的运作原理、读取方式以及`Minecraft BE`如何应用它其详细程度远超你在命令中接触的NBT表象。
You may notice that there is no boolean value like in JSON and that means we will express true false values as 1 and 0 using Byte.
## NBT标签与数据类型
与JSON类似NBT具备类型识别系统。例如JSON通过`{}`符号识别复合对象,通过`"`识别字符串。要理解NBT需要掌握复合对象的起始标记和各类型的读取方式。
## How to read/write NBT tags
The same reading method applies to all numbers, read as many bytes as the number tag type is large, such as: Int16 (short) is 2 bytes in size, so I will read 2 bytes, but you need to know that Minecraft BE uses the [little-endian](#little-endian), unlike Java, it uses big-endian. [Little-endian](#little-endian) is a way to write or read bytes of numbers.
NBT基于二进制操作最小单位为8位字节。各类型占用固定字节数不存在半字节情况。注意类型名称可能有不同叫法但二进制标识符ID始终由1字节表示。
### Reading Types
Type is always one byte in size, so we read the type and find out what to read next for the tag.
| 名称 | 二进制ID | 二进制大小 | 描述 |
| :----------------: | -------: | :--------------- | :------------------------------------------------------------------------------------------------------------------------------- |
| 字节 | 0x01 | 1字节8位 | 单字节类型 |
| Int16短整型 | 0x02 | 2字节16位 | 双字节类型 |
| Int32整型 | 0x03 | 4字节32位 | 四字节类型 |
| Int64长整型 | 0x04 | 8字节64位 | 八字节类型 |
| 浮点 | 0x05 | 4字节32位 | 常规精度浮点数 |
| 双精度 | 0x06 | 8字节64位 | 高精度浮点数 |
| 字符串 | 0x08 | 预定义长度 | UTF-8编码文本前接2字节长度标识 |
| 列表 | 0x09 | 预定义长度 | 元素类型统一前接4字节元素数量 |
| 复合标签 | 0x0A (10)| 未定义长度 | 通过遍历键值对读取,直到遇到结束标记 |
| 复合标签结束标记 | 0x00 | 0字节 | 仅作为复合标签结束标识 |
| 字节列表 | 0x07 | 预定义长度 | 基岩版不常用 |
| 整型列表 | 0x0B (11)| 预定义长度 | 基岩版不常用 |
| 长整型列表 | 0x0C (12)| 预定义长度 | 基岩版不常用 |
### Reading Numbers
When reading a number, it is necessary to know what type of number we are reading, we can find out by reading the type *([Reading types](#reading-types))*. Then when we know what type of number we have to read, we read it, for example, if we know that we want type `3`, then we look in the table and we know that type 3 is a number of 4-bytes size, so we read 4 bytes. All numbers ***BE*** reads/writes with [little-endian](#little-endian) method.
注意NBT没有布尔类型需用字节类型的1/0表示true/false。
### Reading Strings
When reading a string, you need to know its length in bytes, this string length is always written with Int16 (short) `2 bytes` ([how to read numbers](#reading-numbers)) before the string, i.e. first we read the number, then we read the number of bytes of the number we read before, after we know the bytes we can stuff them through UTF-8 encoding and we get text from them.
## NBT标签读写方法
所有数值类型按字节长度读取,基岩版采用[小端序](#小端序)编码与Java版的大端序相反。
### Reading Lists
When reading a list, we must first read the list ([type](#reading-types)), whether this list contains numbers or other lists or strings, etc. So first we read the type of this list, then we read the [number](#reading-numbers) of elements which is written as an Int32 (int) number, so we read 4 bytes, now we know the type of our elements and their count, so we read this type as many times as we know from the readed number before. Reading the size of a list is not the same as reading the size of a string! Should read Int32 no Int16! This solution does not apply to `Byte-List, Int-List, Long-List`!
### 类型读取
始终读取1字节确定后续数据类型。
### Reading Compouds
Compound has all properties named so when reading an property it is always necessary to read its name as well. The procedure for reading Compoud is rather simple. First, we read the type, the type can be anything, but if it is equal to an empty byte, then it is the end of the compound and then we jut stop reading, but if the type is not equal to the Compoud Ending tag, then the significant type of the property that we will read. The read property is always followed by the name (key), which needs to be read as a [string](#reading-strings), and after the string is read, then we can read value.
### 数值读取
根据类型确定字节数如类型3读取4字节。所有数值使用[小端序](#小端序)处理。
## Minecraft BE NBT files
When reading Minecraft NBT files, it is always important to be careful if there is no Bedrock Header at the beginning of the file, see [Bedrock NBT Header](#bedrock-nbt-file-header), but not all MCBE NBT files contain this header, for example `.mcstructure` also does not contain a Bedrock NBT header, unlike `level.dat`.
You also need to pay attention to the root element in the file, i.e. the list or compoud,
The root element also looks like a property, so you need to read the name of this root property, although Bedrock does not use these names, so these names are empty, but they are there.
Here is how .mcstructure looks like where JSON represents NBT.
```json
### 字符串读取
先读取2字节长度标识Int16再按对应字节数读取UTF-8编码内容。
### 列表读取
1. 读取列表元素类型1字节
2. 读取元素数量Int324字节
3. 按数量循环读取元素
注意:字节列表/整型列表/长整型列表的读取方式不同
### 复合标签读取
1. 读取类型标记:
- 0x00结束读取
- 其他类型:继续读取键值对
2. 读取键名字符串
3. 读取对应类型值
## 基岩版NBT文件解析
注意文件开头的[基岩版NBT文件头](#基岩版nbt文件头),如`.mcstructure`文件无此头而`level.dat`有。根元素需作为匿名属性处理(即使键名为空)。
::: code-group
```json [.mcstructure示例]
"":{
"format_version":1,
"size":[],//...
@@ -66,23 +76,27 @@ Here is how .mcstructure looks like where JSON represents NBT.
"structure_world_origin":[]//..
}
```
:::warning
This example shows that it is also necessary to read the name of the basic element, although it is usually unused and empty.
:::
## Writing NBT
There is no certain procedure for writing, because it is the same mothods as when reading, but backwards. That's why we recommend first understanding NBT and learning to read it correctly, then it won't be difficult to write NBT.
:::warning
此示例表明即使根元素键名通常为空,仍需进行读取操作。
:::
## Bedrock NBT File header
The NBT bedrock Header is indicated by two 4-byte numbers, the first is always 8 and the second indicates the size of the nbt structure in bytes. E.g.
- `08 00 00 00` - `bf 00 00 00`
- < always 8 > < always the size of the NBT structure - exclude headers 8 bytes>
## NBT写入规范
写入流程与读取逆向操作,需先掌握读取原理。
## Little-Endian
Little-Endian is the common method of writing numbers in bytes to streams or files.
It's not a science and it's easy to understand. So if Int16 `(short)` of value `0x5a72` then we convert it to bytes [`0x5a`, `0x72`] and then reverse their order that means [`0x72`, `0x5a`] and write d file: `72 5a`. It may seem illogical, but little-endian is almost always used when writing and reading from files. A single `byte` is the same in both methods because it is one byte in size. For example:
- Int64 (long) `0x11223344aabbccdd`
- Split to 8 bytes `0x11 0x22 0x33 0x44 0xAA 0xBB 0xCC 0xDD`
- Reverse `0xDD 0xCC 0xBB 0xAA 0x44 0x33 0x22 0x11`
- Write `dd cc bb aa 44 33 22 11`
- Done (when reading the number just go backwards this example.)
## 基岩版NBT文件头
包含两个4字节数值
1. 固定值80x08000000
2. NBT结构体字节数不含头部的8字节
示例:
- `08 00 00 00` - `bf 00 00 00`
- <固定值8> <NBT结构体大小>
## 小端序
数字按字节逆序存储的编码方式:
- Int16 `0x5a72` → 字节数组 `[0x72, 0x5a]`
- Int64 `0x11223344aabbccdd` → 存储为 `dd cc bb aa 44 33 22 11`
读取时反向转换即可还原数值。单字节类型不受影响。