跳到主要内容

第 2 章:文件结构

规范状态: Draft
本章范围: 本章定义 VFP 1.3 的二进制容器布局、Header、Footer、Directory、Section 索引和文件级一致性规则。METAPAL0CHIXVOX0 及其他区段的 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、METAPAL0CHIX、一个或多个 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

OffsetSizeTypeFieldDescription
04char[4]magic固定 ASCII 字节 VFPK
42uint16majorVersion主版本;VFP 1.3 为 1
62uint16minorVersion次版本;VFP 1.3 为 3
84uint32headerSizeHeader 总长度;必须为 64
124uint32featureFlags文件级特性位;VFP 1.3 必须为 0
1616uint8[16]assetUuid资产 UUID;全零表示未指定
3232uint8[32]reserved保留;写入器必须写入全零

2.2.3 字段规则

  • magic 必须逐字节等于 56 46 50 4B,即 ASCII VFPK
  • 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.1 作用

Footer 位于文件结尾,固定长度为 64 字节。它是定位当前有效 Directory 的唯一权威入口。

VFP 支持追加式保存:写入器可以保留旧数据区段和旧 Directory,在文件末尾追加新或替换后的 Payload、新 Directory 和新 Footer。读取器只使用最后 64 字节中的 Footer;此前出现的 Footer 不具有当前文件语义。

2.3.2 Binary Layout

OffsetSizeTypeFieldDescription
04char[4]magic固定 ASCII 字节 VFPF
42uint16majorVersion当前 Directory 所属主版本;必须为 1
62uint16minorVersion当前 Directory 所属次版本;必须为 3
84uint32footerSizeFooter 总长度;必须为 64
128uint64directoryOffset当前 DIR0 的起始字节偏移
208uint64directoryLength当前 DIR0 的总长度,含 Directory Header 与所有 Entry
284uint32directoryCrc32当前 DIR0 全部原始字节的 CRC32
324uint32footerFlagsFooter 标志;VFP 1.3 必须为 0
368uint64fileLength文件总长度,必须等于实际长度
4420uint8[20]reserved保留;写入器必须写入全零

读取器必须验证:

  1. 文件长度不少于 128 字节;
  2. 文件最后 64 字节的 magic 等于 VFPF
  3. footerSize == 64
  4. Footer 版本与 Header 主版本兼容;
  5. fileLength 等于实际文件长度;
  6. directoryOffset 为 8 字节对齐;
  7. directoryOffset 不小于 Header 长度;
  8. directoryOffset + directoryLength 不发生溢出,且不大于 Footer 起始偏移;
  9. footerFlags == 0
  10. 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:METAPAL0CHIX 与至少一条 VOX0

2.4.2 Directory Header Binary Layout

OffsetSizeTypeFieldDescription
04char[4]magic固定 ASCII 字节 DIR0
42uint16directoryVersionDirectory 版本;VFP 1.3 必须为 1
62uint16headerSizeDirectory Header 长度;必须为 32
84uint32entryCountDirectory Entry 数量
124uint32entrySize单个 Entry 长度;必须为 48
164uint32directoryFlagsDirectory 标志;VFP 1.3 必须为 0
2012uint8[12]reserved保留;必须为全零

Directory 总长度必须精确满足:

directoryLength = 32 + entryCount × 48

其中 directoryLength 取自 Footer。读取器必须以 64 位无符号算术验证此表达式,防止 entryCount × 48 溢出。

2.4.3 Directory Entry Binary Layout

OffsetSizeTypeFieldDescription
04char[4]tag区段类型,如 METAVOX0
44uint32codecPayload 编码方式
84int32chunkId所属 Chunk;全局区段固定为 -1
124uint32flags区段属性标志
168uint64offsetPayload 起始偏移
248uint64length存储后 Payload 长度
328uint64rawLength解码后的原始 Payload 长度
404uint32crc32存储后 Payload 的 CRC32
444uint32reserved保留;必须为 0

crc32 校验 offsetoffset + length 范围的存储字节。若 codecRAW8length 必须等于 rawLength。如果 Payload 经过压缩,length 是压缩后字节数,rawLength 是解码结果字节数。

2.4.4 Directory Entry 关系规则

  • 全局区段必须使用 chunkId = -1
  • METAPAL0SCNECHIXRND0ANM0BORDPRVW 是全局区段;
  • VOX0MSH0VBUFPMSH 可以是 Chunk 级区段;
  • 一个有效 Directory 中,同一 (tag, chunkId) 组合不得出现两次;
  • METAPAL0CHIX 必须各存在一次且仅存在一次;
  • 每个 CHIX 所声明的 Chunk 必须有且仅有一条对应 VOX0 Entry;
  • 对 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 由以下属性定义:

属性来源含义
TagDirectoryEntry.tagPayload 的数据类型
CodecDirectoryEntry.codec读取 Payload 前需使用的编码方式
Chunk IDDirectoryEntry.chunkId区段作用于全局资产还是具体 Chunk
FlagsDirectoryEntry.flags必需性、可重建性和表现属性
Offset / LengthDirectoryEntryPayload 的文件范围
Raw LengthDirectoryEntry.rawLength解码后的预期长度
CRC32DirectoryEntry.crc32存储数据完整性校验

2.5.2 标准 Tag

Tag名称类别是否可重建
METAMetadata权威全局数据
PAL0Palette权威全局数据
SCNEScene展示数据是/可忽略
CHIXChunk Index权威全局数据
VOX0Voxel Source权威 Chunk 数据
MSH0Mesh Edit Cache编辑缓存
VBUFVertex Buffer表现缓存
PMSHPolygon Mesh表现缓存
RND0Render Data表现缓存
ANM0Animation表现附加数据是/可忽略
BORDBorder Data表现缓存
PRVWPreview兼容预览是/可忽略

未知 Tag 不自动表示文件无效。读取器应根据 Section Flags 判断该区段能否安全忽略;但未知 Tag 不得被当作已知权威区段或缓存解释。

2.5.3 Codec

VFP 1.3 定义下列 Codec:

名称定义
0RAW8Payload 不压缩,按对应区段章节直接解释
1RLE88 位游程编码;仅可用于后续章节明确允许的区段

所有 VFP 1.3 读取器必须支持 RAW8。对必需区段,如果 Codec 未知或未被实现,读取器必须拒绝该资产;对可选且可忽略区段,读取器可以跳过该区段并记录兼容性警告。

2.5.4 Section Flags

Directory Entry 的 flags 使用下列位定义:

Bit名称语义
0REQUIRED该区段对完整资产解释不可缺失
1REBUILDABLE可由权威数据重新生成
2PRESENTATION_ONLY只影响展示,不改变资产权威语义
3–31ReservedVFP 1.3 必须为 0

写入器必须为 METAPAL0CHIX 和所有 VOX0 设置 REQUIREDMSH0VBUFPMSHRND0BORDPRVW 应设置 REBUILDABLE。呈现专用数据可设置 PRESENTATION_ONLY

若未知 Flag 位被设置,读取器不得猜测其含义:对必需区段必须拒绝文件;对可选区段可以整体跳过。

2.5.5 Payload 校验顺序

读取某个 Section 时,读取器必须按如下顺序处理:

  1. 验证 Entry 的范围、对齐、长度和资源上限;
  2. 读取长度为 length 的存储字节;
  3. 对存储字节计算 CRC32 并与 crc32 比较;
  4. 依据 codec 解码;
  5. 验证解码结果长度等于 rawLength
  6. 按 Tag 对应章节解析 Payload;
  7. 执行该 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 权威数据一致性

权威数据一致性确保资产可以被唯一解释。验证器必须检查:

  • METAPAL0CHIX 存在且唯一;
  • 每个 CHIX Chunk 有且仅有一个对应 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 编码。