跳到主要内容

附录 B:Python struct 定义

以下定义使用 Python 标准库 struct,前缀 < 表示 Little-endian、标准尺寸和无隐式对齐。所有 unpack 结果仍必须接受范围、CRC、Hash、跨区段关系和资源上限验证。

from __future__ import annotations

import struct

# Container
VFP_HEADER = struct.Struct("<4sHHII16s32s")
VFP_FOOTER = struct.Struct("<4sHHIQQIIQ20s")
DIR0_HEADER = struct.Struct("<4sHHIII12s")
DIR0_ENTRY = struct.Struct("<4sIiIQQQII")

# Authoritative sections
META_V1 = struct.Struct("<HHIBBBBBBHBBBBIIIffffffII32s16s16s")
PAL0_HEADER = struct.Struct("<HHHHII")
PAL0_ENTRY = struct.Struct("<8B")
CHIX_HEADER = struct.Struct("<HHIII16s")
CHIX_ENTRY = struct.Struct("<i8BIII8s")
VOX0_HEADER = struct.Struct("<HHi4BIIIII")

# Optional sections
MSH0_HEADER = struct.Struct("<HHiII32s")
FACE_RECORD = struct.Struct("<8B8s")
VBUF_HEADER = struct.Struct("<HHiIIIIII32s")
VBUF_VERTEX = struct.Struct("<fffII")
PMSH_HEADER = MSH0_HEADER
RND0_HEADER = struct.Struct("<HHIIII3ff28s")
RND0_BATCH = struct.Struct("<HHIIIII")
ANM0_HEADER = struct.Struct("<HHIII16s")
ANM0_CLIP = struct.Struct("<IIIII12s")
ANM0_KEYFRAME = struct.Struct("<I3f4f3fI")
BORD_HEADER = struct.Struct("<HHIII32s")
BORD_EDGE = struct.Struct("<6BH")
PRVW_HEADER = struct.Struct("<HHIIIIIQ32s")

assert VFP_HEADER.size == 64
assert VFP_FOOTER.size == 64
assert DIR0_HEADER.size == 32
assert DIR0_ENTRY.size == 48
assert META_V1.size == 128
assert PAL0_HEADER.size == 16
assert PAL0_ENTRY.size == 8
assert CHIX_HEADER.size == 32
assert CHIX_ENTRY.size == 32
assert VOX0_HEADER.size == 32
assert MSH0_HEADER.size == 48
assert FACE_RECORD.size == 16
assert VBUF_HEADER.size == 64
assert VBUF_VERTEX.size == 20
assert RND0_BATCH.size == 24
assert ANM0_CLIP.size == 32
assert ANM0_KEYFRAME.size == 48
assert BORD_EDGE.size == 8
assert PRVW_HEADER.size == 64

B.1 常用读取辅助函数

import binascii

def require_range(file_size: int, offset: int, length: int) -> None:
if offset < 0 or length < 0 or offset > file_size or length > file_size - offset:
raise ValueError("VFP_DIRECTORY_RANGE")

def require_crc32(data: bytes, expected: int) -> None:
actual = binascii.crc32(data) & 0xFFFFFFFF
if actual != expected:
raise ValueError("VFP_CRC_MISMATCH")

def unpack_exact(layout: struct.Struct, data: bytes, offset: int = 0):
if offset < 0 or len(data) - offset < layout.size:
raise ValueError("VFP_TRUNCATED")
return layout.unpack_from(data, offset)

B.2 RLE8 解码参考

def decode_rle8(data: bytes, capacity: int) -> bytes:
if len(data) % 2:
raise ValueError("VOX0_RLE_ODD_LENGTH")
output = bytearray(capacity)
cursor = 0
for i in range(0, len(data), 2):
run_length = data[i]
palette_index = data[i + 1]
if run_length == 0:
raise ValueError("VOX0_RLE_ZERO_RUN")
if run_length > capacity - cursor:
raise ValueError("VOX0_RLE_OVERFLOW")
output[cursor:cursor + run_length] = bytes([palette_index]) * run_length
cursor += run_length
if cursor != capacity:
raise ValueError("VOX0_RLE_UNDERFLOW")
return bytes(output)

上述代码是布局和校验辅助,不应被视为完整读取器。完整实现还必须解析 META、PAL0、CHIX,验证 Source Hash,并根据 Directory Flags 做兼容性决策。