第 2 章:文件结构
规范状态: Draft
本章范围: 本章定义 VFP 1.3 的二进制容器布局、Header、Footer、Directory、Section 索引和文件级一致性规则。META、PAL0、CHIX、VOX0 及其他区段的 Payload 字段由后续章节定义。
VFP 是一个由 Directory 驱动的区段化二进制容器。文件中的区段不依赖固定物理顺序;读取器必须从文件末尾的 Footer 定位当前有效 Directory,再通过 Directory Entry 定位所需数据。
2.1 文件布局
2.1.1 总体结构
一个 VFP 文件由固定 Header、零个或多个数据区段、一个当前有效的 Directory 和固定 Footer 组成:
Offset 0
┌──────────────────────────────────────────┐
│ Header │ 64 bytes
├──────────────────────────────────────────┤
│ Data Sections │ variable length
│ META / PAL0 / SCNE / CHIX / VOX0 / ... │
│ (区段顺序任意,区段之间可有对齐填充) │
├──────────────────────────────────────────┤
│ DIR0 Directory │ variable length
├──────────────────────────────────────────┤
│ Footer │ 64 bytes
└──────────────────────────────────────────┘
EOF
最小合法文件必须至少包含 Header、META、PAL0、CHIX、一个或多个 VOX0、Directory 和 Footer。可选缓存区段可以不存在。
2.1.2 物理顺序与逻辑顺序
区段的物理排列不表达资产语义。写入器可以按生成顺序、区段类型、Chunk 顺序或追加更新策略排列 Payload;读取器不得假设 META 紧跟 Header,也不得假设 VOX0 连续排列。
文件的逻辑顺序由当前有效 Directory 中的 Entry 决定。对每个 Entry,offset 指向该区段 Payload 的第一个字节,length 表示其在文件中占用的存储长度。
2.1.3 对齐与填充
VFP 1.3 采用 8 字节对齐:
- Header 的起始偏移必须为
0; - 每个 Data Section 的
offset必须是 8 的整数倍; - Directory 的
directoryOffset必须是 8 的整数倍; - Footer 必须位于文件最后 64 字节;
- 区段之间允许存在填充字节;写入器应使用
0x00填充; - 填充字节不属于任一 Section Payload,不计入 Directory Entry 的
length或 CRC32。
读取器不得依据填充内容推断区段类型或长度。验证器只需确认已登记区段的范围合法且不交叠。
2.1.4 文件长度与资源边界
Footer 中的 fileLength 必须等于实际文件长度。读取器在计算下列值时必须采用不会溢出的算术:
directoryOffset + directoryLength
entry.offset + entry.length
32 + entryCount × entrySize
任何溢出、负数解释、超出文件范围、区段交叠或落入 Header、Directory、Footer 范围的情况均为结构错误。实现可以配置更严格的文件大小、目录数量和解码后大小上限,但不得在没有报错的情况下截断数据。
2.2 Header
2.2.1 作用
Header 位于文件偏移 0,固定长度为 64 字节。它用于确认文件类型、确定基础版本和提供稳定资产标识。Header 不保存任何区段 Offset,也不用于确定当前 Directory。
2.2.2 Binary Layout
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 4 | char[4] | magic | 固定 ASCII 字节 VFPK |
| 4 | 2 | uint16 | majorVersion | 主版本;VFP 1.3 为 1 |
| 6 | 2 | uint16 | minorVersion | 次版本;VFP 1.3 为 3 |
| 8 | 4 | uint32 | headerSize | Header 总长度;必须为 64 |
| 12 | 4 | uint32 | featureFlags | 文件级特性位;VFP 1.3 必须为 0 |
| 16 | 16 | uint8[16] | assetUuid | 资产 UUID;全零表示未指定 |
| 32 | 32 | uint8[32] | reserved | 保留;写入器必须写入全零 |
2.2.3 字段规则
magic必须逐字节等于56 46 50 4B,即 ASCIIVFPK;majorVersion大于读取器支持的主版本时,读取器必须拒绝文件;headerSize != 64时,VFP 1.3 读取器必须拒绝文件;featureFlags的所有位在 VFP 1.3 均未定义,因此写入器必须写0;读取器遇到非零值必须拒绝文件;assetUuid仅用于资产身份识别,不参与权威体素内容、CRC32 或 Source Hash;reserved必须为全零。严格验证器遇到非零字节必须报错;宽容读取器可给出兼容性警告,但不得据此赋予额外语义。
2.2.4 Header 读取顺序
读取器首先读取 64 字节 Header,完成 Magic、版本和固定长度校验后,仍必须继续读取 Footer。Header 合法不代表文件完整,也不代表 Directory 或权威数据可用。
2.3 Footer
2.3.1 作用
Footer 位于文件结尾,固定长度为 64 字节。它是定位当前有效 Directory 的唯一权威入口。
VFP 支持追加式保存:写入器可以保留旧数据区段和旧 Directory,在文件末尾追加新或替换后的 Payload、新 Directory 和新 Footer。读取器只使用最后 64 字节中的 Footer;此前出现的 Footer 不具有当前文件语义。
2.3.2 Binary Layout
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 4 | char[4] | magic | 固定 ASCII 字节 VFPF |
| 4 | 2 | uint16 | majorVersion | 当前 Directory 所属主版本;必须为 1 |
| 6 | 2 | uint16 | minorVersion | 当前 Directory 所属次版本;必须为 3 |
| 8 | 4 | uint32 | footerSize | Footer 总长度;必须为 64 |
| 12 | 8 | uint64 | directoryOffset | 当前 DIR0 的起始字节偏移 |
| 20 | 8 | uint64 | directoryLength | 当前 DIR0 的总长度,含 Directory Header 与所有 Entry |
| 28 | 4 | uint32 | directoryCrc32 | 当前 DIR0 全部原始字节的 CRC32 |
| 32 | 4 | uint32 | footerFlags | Footer 标志;VFP 1.3 必须为 0 |
| 36 | 8 | uint64 | fileLength | 文件总长度,必须等于实际长度 |
| 44 | 20 | uint8[20] | reserved | 保留;写入器必须写入全零 |
2.3.3 Footer 验证规则
读取器必须验证:
- 文件长度不少于
128字节; - 文件最后 64 字节的
magic等于VFPF; footerSize == 64;- Footer 版本与 Header 主版本兼容;
fileLength等于实际文件长度;directoryOffset为 8 字节对齐;directoryOffset不小于 Header 长度;directoryOffset + directoryLength不发生溢出,且不大于 Footer 起始偏移;footerFlags == 0;reserved全为0。
如任一条件不成立,读取器必须拒绝文件,不得通过扫描文件内容猜测 Directory 位置。
2.4 Directory
2.4.1 作用
Directory 以 Tag DIR0 标识,是 VFP 的唯一 Section 索引。它不保存实际资产 Payload,只保存每个区段的类型、编码方式、所属 Chunk、位置、长度、校验和标志。
Directory 由一个固定 32 字节的 Directory Header 和零个或多个固定 48 字节的 Directory Entry 组成。有效 VFP 至少应有四条 Entry:META、PAL0、CHIX 与至少一条 VOX0。
2.4.2 Directory Header Binary Layout
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 4 | char[4] | magic | 固定 ASCII 字节 DIR0 |
| 4 | 2 | uint16 | directoryVersion | Directory 版本;VFP 1.3 必须为 1 |
| 6 | 2 | uint16 | headerSize | Directory Header 长度;必须为 32 |
| 8 | 4 | uint32 | entryCount | Directory Entry 数量 |
| 12 | 4 | uint32 | entrySize | 单个 Entry 长度;必须为 48 |
| 16 | 4 | uint32 | directoryFlags | Directory 标志;VFP 1.3 必须为 0 |
| 20 | 12 | uint8[12] | reserved | 保留;必须为全零 |
Directory 总长度必须精确满足:
directoryLength = 32 + entryCount × 48
其中 directoryLength 取自 Footer。读取器必须以 64 位无符号算术验证此表达式,防止 entryCount × 48 溢出。
2.4.3 Directory Entry Binary Layout
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 4 | char[4] | tag | 区段类型,如 META 或 VOX0 |
| 4 | 4 | uint32 | codec | Payload 编码方式 |
| 8 | 4 | int32 | chunkId | 所属 Chunk;全局区段固定为 -1 |
| 12 | 4 | uint32 | flags | 区段属性标志 |
| 16 | 8 | uint64 | offset | Payload 起始偏移 |
| 24 | 8 | uint64 | length | 存储后 Payload 长度 |
| 32 | 8 | uint64 | rawLength | 解码后的原始 Payload 长度 |
| 40 | 4 | uint32 | crc32 | 存储后 Payload 的 CRC32 |
| 44 | 4 | uint32 | reserved | 保留;必须为 0 |
crc32 校验 offset 至 offset + length 范围的存储字节。若 codec 为 RAW8,length 必须等于 rawLength。如果 Payload 经过压缩,length 是压缩后字节数,rawLength 是解码结果字节数。
2.4.4 Directory Entry 关系规则
- 全局区段必须使用
chunkId = -1; META、PAL0、SCNE、CHIX、RND0、ANM0、BORD、PRVW是全局区段;VOX0、MSH0、VBUF、PMSH可以是 Chunk 级区段;- 一个有效 Directory 中,同一
(tag, chunkId)组合不得出现两次; META、PAL0与CHIX必须各存在一次且仅存在一次;- 每个
CHIX所声明的 Chunk 必须有且仅有一条对应VOX0Entry; - 对 Chunk 级缓存,
chunkId必须指向CHIX中存在的 Chunk; DIR0不得作为普通 Directory Entry 出现。
2.4.5 Directory 读取顺序
读取器应先验证 Directory Header 和完整 Directory CRC32,再建立 (tag, chunkId) → Entry 查找表。仅在需要实际数据时读取目标 Entry 的 Payload。这样可支持按需加载、流式读取和大文件资源控制。
2.5 Section 系统
2.5.1 Section 基本模型
VFP 中的每一段实际数据均为一个 Section Payload。Section 不含通用的外层区段头;其元信息统一存放在 Directory Entry 中。
每个 Section 由以下属性定义:
| 属性 | 来源 | 含义 |
|---|---|---|
| Tag | DirectoryEntry.tag | Payload 的数据类型 |
| Codec | DirectoryEntry.codec | 读取 Payload 前需使用的编码方式 |
| Chunk ID | DirectoryEntry.chunkId | 区段作用于全局资产还是具体 Chunk |
| Flags | DirectoryEntry.flags | 必需性、可重建性和表现属性 |
| Offset / Length | DirectoryEntry | Payload 的文件范围 |
| Raw Length | DirectoryEntry.rawLength | 解码后的预期长度 |
| CRC32 | DirectoryEntry.crc32 | 存储数据完整性校验 |
2.5.2 标准 Tag
| Tag | 名称 | 类别 | 是否可重建 |
|---|---|---|---|
META | Metadata | 权威全局数据 | 否 |
PAL0 | Palette | 权威全局数据 | 否 |
SCNE | Scene | 展示数据 | 是/可忽略 |
CHIX | Chunk Index | 权威全局数据 | 否 |
VOX0 | Voxel Source | 权威 Chunk 数据 | 否 |
MSH0 | Mesh Edit Cache | 编辑缓存 | 是 |
VBUF | Vertex Buffer | 表现缓存 | 是 |
PMSH | Polygon Mesh | 表现缓存 | 是 |
RND0 | Render Data | 表现缓存 | 是 |
ANM0 | Animation | 表现附加数据 | 是/可忽略 |
BORD | Border Data | 表现缓存 | 是 |
PRVW | Preview | 兼容预览 | 是/可忽略 |
未知 Tag 不自动表示文件无效。读取器应根据 Section Flags 判断该区段能否安全忽略;但未知 Tag 不得被当作已知权威区段或缓存解释。
2.5.3 Codec
VFP 1.3 定义下列 Codec:
| 值 | 名称 | 定义 |
|---|---|---|
| 0 | RAW8 | Payload 不压缩,按对应区段章节直接解释 |
| 1 | RLE8 | 8 位游程编码;仅可用于后续章节明确允许的区段 |
所有 VFP 1.3 读取器必须支持 RAW8。对必需区段,如果 Codec 未知或未被实现,读取器必须拒绝该资产;对可选且可忽略区段,读取器可以跳过该区段并记录兼容性警告。
2.5.4 Section Flags
Directory Entry 的 flags 使用下列位定义:
| Bit | 名称 | 语义 |
|---|---|---|
| 0 | REQUIRED | 该区段对完整资产解释不可缺失 |
| 1 | REBUILDABLE | 可由权威数据重新生成 |
| 2 | PRESENTATION_ONLY | 只影响展示,不改变资产权威语义 |
| 3–31 | Reserved | VFP 1.3 必须为 0 |
写入器必须为 META、PAL0、CHIX 和所有 VOX0 设置 REQUIRED。MSH0、VBUF、PMSH、RND0、BORD 和 PRVW 应设置 REBUILDABLE。呈现专用数据可设置 PRESENTATION_ONLY。
若未知 Flag 位被设置,读取器不得猜测其含义:对必需区段必须拒绝文件;对可选区段可以整体跳过。
2.5.5 Payload 校验顺序
读取某个 Section 时,读取器必须按如下顺序处理:
- 验证 Entry 的范围、对齐、长度和资源上限;
- 读取长度为
length的存储字节; - 对存储字节计算 CRC32 并与
crc32比较; - 依据
codec解码; - 验证解码结果长度等于
rawLength; - 按 Tag 对应章节解析 Payload;
- 执行该 Tag 的语义验证和跨区段验证。
CRC32 失败时,读取器不得继续使用该 Payload。对于可重建缓存可以丢弃并回退;对于必需权威数据必须拒绝资产。
2.6 数据一致性
2.6.1 结构一致性
结构一致性确保文件可以被安全定位和读取。验证器必须检查:
- Header、Footer、Directory Magic、版本与固定长度;
- Footer 所述文件长度和实际长度;
- Directory 长度、Entry 数量和 Entry Size;
- 所有 Offset 与 Length 的算术安全性;
- 所有 Payload 范围的边界、对齐和不交叠性;
- 所有保留字段和未定义标志位;
- Directory CRC32 与 Section CRC32。
2.6.2 权威数据一致性
权威数据一致性确保资产可以被唯一解释。验证器必须检查:
META、PAL0、CHIX存在且唯一;- 每个
CHIXChunk 有且仅有一个对应VOX0; VOX0的 Chunk ID、尺寸、原始长度与CHIX声明一致;- 所有非零 Palette Index 在
PAL0中有定义; - Chunk 坐标与尺寸不超出
META所声明的逻辑网格; - 所有权威区段可使用受支持 Codec 读取;
- Source Hash 与权威区段一致,具体算法见第 16 章。
权威数据不一致时,读取器必须拒绝资产,不能选择其中“看起来较新”或“显示正常”的数据作为替代。
2.6.3 缓存一致性
缓存数据必须与生成它时的权威 Source Hash 绑定。读取器使用缓存前应检查:
- 缓存区段的 Payload 自身通过 CRC32;
- 缓存格式版本受支持;
- 缓存关联的 Chunk ID 存在;
- 缓存所记录或引用的 Source Hash 与当前权威 Source Hash 完全相等;
- 缓存内部索引不越界。
缓存无效不应导致权威资产失效。读取器应删除、忽略或重建无效缓存,并以 VOX0 为基础继续工作。
2.6.4 追加式保存一致性
追加式保存时,写入器可以在文件末尾依次追加新的 Payload、新 Directory 和新 Footer。写入器必须确保新 Footer 写完后才将文件作为成功保存结果公开。
如果写入中断导致文件尾不存在完整有效 Footer,则文件不应被视为新的有效版本;读取器可以按文件末尾 Footer 验证失败而拒绝文件。恢复旧版本、临时写入和原子替换属于实现策略,不改变“最后一个 Footer 决定当前 Directory”的规范规则。
本章总结
VFP 1.3 使用“Footer 定位 Directory、Directory 定位 Section、权威数据驱动缓存”的容器设计。Header 用于识别文件,Footer 用于找到当前有效目录,Directory 负责随机访问和完整性校验,Section Payload 承载实际资产数据。
下一章定义所有区段共同使用的字节序、数值类型、浮点规则、字符串和 Hash 编码。