GTA .chk(ChkFile)Format Specification + CHK Tool Development Reference
版本:v2.3(开发级规范,2026-09-04。v2.1:并入附录 E「全语料普查」49/49 实证与证据 E024–E033,原编号与原文内容不变;v2.2:文风修整 + 本机绝对路径改语料内相对路径,事实与结论零变动;v2.3:工程审计增补——多字节/单字节端序表述精确化、【NORMATIVE】标记约定、§6.1
0x28/0x2C勘误升格为规范裁定(实现一律以 §8.2meta_base+0x10为准)、§32 结论表 PSP 推断链口径注、伪代码切片显式端点消歧义、新增 §29.1 最小测试向量三组(原始字节可复现);既有结论零变动)。基于crates/txd-format/src/chk.rs的实现 + 逆向样例 + 全语料(49 个.chk)逐字节核对。.chk没有公开官方规范,本文区分 ✅ 已确证 / ⚠️ 推断 / ❓ 待验证,不把推测写成事实。 目标:开发者仅凭本文即可彼此独立实现出一致结果的 Parser / Decoder / Encoder / Serializer / Validator。 旧版(格式研究文档,chk-format.md.bak-pre-spec)的全部信息已逐字保留并归入新章节(新旧章节映射见附录 D);新增技术结论一律标注证据 ID(§31 证据表 E001…)。
目录
- 概述 Overview
- 术语 Terminology
- 支持的家族 Supported Families
- 二进制布局 Binary Layout
- 通用头 Common Header
- 头字段表 Header Field Table(A/B 逐字段)
- 偏移与尺寸规则 Offset / Size Rules
- 重定位与指针系统 Relocation / Pointer System
- Family A 实现规范
- Family A 像素格式
- Family A 调色板
- Family A 元数据块(Meta 92B)
- Family B 实现规范
- Family B PAL8(8bit 块状)
- Family B PAL4(4bit 块状)
- 块布局 Block Layout
- 半字节布局 Nibble Layout(4bit)
- 调色板规范 Palette Specification
- 纹理解码 Texture Decode(CHK→索引→RGBA)
- 纹理编码 Texture Encode(RGBA→索引→CHK)
- 编码管线 PNG→CHK
- 解码管线 CHK→PNG
- 序列化与字段变更策略 Serialization / Mutation Policy
- 精确保留模式 Exact Preservation Mode
- 重建模式 Rebuild Mode
- 校验器规范 Validator Specification
- 修复规范 Repair Specification
- 错误分类 Error Classification
- 测试语料 Test Corpus(SHA-256)
- 往返测试 Round-trip Tests
- 证据与置信度体系 Evidence / Confidence
- 已知未知 Known Unknowns
- 参考伪代码 Reference Pseudocode
- 实现规则(禁止行为清单)Implementation Rules
- 开发闭环架构图
- 与 TXD 的区别
- 附录 A:真实样例头部十六进制 dump
- 附录 B:通道映射测试法
- 附录 C:已知不一致记录(只记录,不改判定)
- 附录 D:新旧章节映射
- 附记(原文逐字保留)
- 附录 E:全语料普查(Census)
1. 概述 Overview
.chk是一张 调色板索引 纹理(非真彩、非 TXD 字典):每个像素存的是调色板下标。- 魔数:
b"xet\0"(0x78 0x65 0x74 0x00),注意是"xet"反序(对应"tex")。 - 一个
.chk文件 = 单张贴图(宽 × 高 + 调色板 + 索引像素),与 RenderWare.txd(可含多贴图)不同。 - 平台:✅ Family A = PS2,⚠️ 推断 Family B = PSP(依据:用户给的
LOADSC0/LOADSC3确认为 PS2 (E011);PSP 专用工具GTAStoriesTex只认 Family B (E010))。 - 字节序(✅):所有字段(魔数、尺寸、偏移、地址表、
log2几何、位深类型)均为 little-endian。截至当前样例未发现 big-endian 版本;若未来样本呈高位端序,需按像素做字节交换,此处留作警示。- (v2.3 精确化)端序描述适用于多字节整数字段(u32 偏移/尺寸/地址表等,按 LE 读写的由
le32/put_le32承担);单字节字段(魔数逐字节、B 头0x41–0x44、名字字节等)是原始字节值,不涉及端序概念。
- (v2.3 精确化)端序描述适用于多字节整数字段(u32 偏移/尺寸/地址表等,按 LE 读写的由
- 不含 mipmap / 无级联(✅):
.chk是单一级的位图,解析未发现任何多级/mip 链,只有一个像素区 + 一个调色板。它不是像.txd那样可含多贴图/多 mip 的字典。 - 两家族容器头结构完全相同(✅),不能用 header 前部区分平台,只能靠
0x3C标志 + 像素布局区分(见 §3、§5.1)。 - 实现参考(公式的最终裁判):
crates/txd-format/src/chk.rs。本文所有公式均与该实现逐行核对。 - (v2.3)规范性标记约定:✅/⚠️/❓ 为置信标记;正文中【NORMATIVE】表示"实现必须遵守、不得自行变体"的规范要求(检测顺序、指针表、判定①等);未标注的公式与伪代码为规范性实现指引;E 证据、普查过程性描述为参考信息(informative)。
2. 术语 Terminology
| 术语 | 含义 |
|---|---|
| Family A | 0x40 头 + 方形 8bit 行主序像素 + 256 色 RGBA 调色板 + 92B meta + tail 的布局(PS2,见 §9) |
| Family B | 0xA0 头 + 128 字节块状像素 + 256/16 色 RGBA 调色板 + tail 的布局(PSP,见 §13) |
| kind | chk.rs 中的布局枚举:Psp8Bit256(A)/ PspBlock8Bit / PspBlock4Bit(B 两种位深) |
| PAL8 / PAL4 | 8bit 索引(256 色调色板)/ 4bit 索引(16 色调色板) |
| pixel region | 像素区:header 之后、palette 之前的索引数据区 |
| block | Family B 的 128 字节存取单元:8 行 × 16 字节/行(8bit=8×16 像素,4bit=8×32 像素) |
| nibble | 4bit 半字节;PAL4 一个字节装 2 个像素索引 |
| data_end | header 0x0C/0x10 字段 = 文件大小 − tail(0x1C)【✅】 |
| meta | Family A 独有的 92 字节元数据块(名字 + 指针,见 §12) |
| tail | 文件末尾 0x1C=28 字节的 7×u32 偏移/地址表(见 §8) |
| swap34 | Family A 索引 bit3↔bit4 交换变换(自反,见 §10.2) |
| row-major | 行主序:逻辑像素按行从左到右、从上到下排列 |
| block-major | 块主序:先按块(左→右、上→下),块内再按行 |
| byte-exact roundtrip | parse→serialize 输出与原文件逐字节相等(最强证据形式) |
| preserve / recalc / regenerate | 字段保存策略:原样保留 / 按公式重算 / 从文档模型重建(见 §23) |
| unknown_xx | 语义未知的字段命名约定(如 unknown_04),工具必须原样保留 |
| Tool Policy | 工具(编码器)自由选择的策略(如量化算法),不是 CHK 格式规则(见 §20.3) |
3. 支持的家族 Supported Families
| Family A | Family B(8bit) | Family B(4bit) | |
|---|---|---|---|
| kind(chk.rs) | Psp8Bit256 | PspBlock8Bit | PspBlock4Bit |
| 平台【✅ A / ⚠️ B】 | PS2 (E011) | PSP(推断,E010) | PSP(推断,E010) |
| header 大小 | 0x40(64B) | 0xA0(160B) | 0xA0(160B) |
| 位深 | 8bit 索引 | 8bit 索引 | 4bit 索引 |
| 调色板 | 256×RGBA | 256×RGBA | 16×RGBA |
| 像素布局 | 方形(W==H),行主序 | 块状 8×16 像素/块 | 块状 8×32 像素/块 |
| 像素区大小 | W×H 字节(完全平方数) | block_rows×block_cols×128 | 同左 |
| 索引交换 | swap34(bit3↔bit4) | 无 | 无 |
| meta 块 | 92B(名字在 meta[0x18]) | 无 | 无 |
| 名字位置 | meta[0x18](8B,NUL 截断) | header 0x58(8B,NUL 截断) | 同左 |
| tail | 0x1C(7×u32,meta 基址序列) | 0x1C(7×u32,header 内偏移常量表) | 同左 |
家族自动检测(ChkFile::parse,chk.rs L101–L143)(✅),检测顺序是规范的一部分【NORMATIVE——任何语言实现必须保持同一顺序,不得调换 A/B 试探次序或增删步骤】:
- 校验魔数
xet\0、文件 ≥ 0x40+0x1C、0x08==实际长度、0x0C==长度−0x1C; - 先做 Family A 几何试探:按 A 公式算
palette_off8 = len − 0x1C − 92 − 1024,pixel_bytes8 = palette_off8 − 0x40;若pixel_bytes8是完全平方数(isqrt判定)→ 按 Family A 解析; - 否则若
le32(0x3C) == 0xA0→ 按 Family B 解析; - 都不满足 → 报错
unrecognized .chk pixel layout。
⚠️ 注意:A 的检测不依赖
0x3C(A 文件 0x3C=0),是纯几何判定;B 检测依赖0x3C==0xA0。理论上存在"4bit B 文件像素区恰好通过 A 方形试探"的构造可能,当前 49 样本中未出现(E017),检测顺序照抄实现即可。
4. 二进制布局 Binary Layout
4.1 容器(通用结构)【✅ 原文保留】
[ header ] [ pixel region ] [ palette ] [ (Family A: meta block) ] [ tail 0x1C ]
tail:0x1C= 28 字节(7 个 u32),两家族共用。header关键字段(little-endian u32,偏移自文件头):0x08= 文件总大小(解析时校验 == 实际长度)。0x0c= data_end(= 文件大小 − tail)。0x10= data_end。0x3c=0xA0→ Family B 标志(用于自动识别家族)。
- 容器头结构在 Family A / Family B 完全相同(✅):不能用 header 分 PSP/PS2,只能靠像素布局分。
4.1.1 布局图(v2.3 补充,informative;数值取自 §29.1 向量实测)
Family A(512×512,总长 0x404B8 = LOADSC0.CHK) Family B(512×1024 4bit,总长 0x400FC = FONTSJP.CHK)
0x00000 ┌ header 0x40 0x00000 ┌ header 0xA0
0x00040 ├ pixels W·H = 0x40000(行主序+swap34) 0x000A0 ├ pixels br·bc·128B 块区
0x40040 ├ palette 256×4B = 0x400 │ (4bit: bc=W/32, br=H/8;
0x40440 ├ meta 92B(meta_base,名字@0x18) │ 8bit: bc=W/16, br=H/8)
0x4049C └ tail 7×u32 = 0x1C 0x400A0 ├ palette nb×4B(8bit 0x400 / 4bit 0x40)
0x400E0 └ tail 7×u32 = 0x1C
4.2 Family A 全文件结构表(W==H,参考 512×512)
列说明:Read = 何时读(Parse=解析、Validate=校验);Write = 保存策略(preserve=原样保留 / recalc=按公式重算 / regenerate=从文档模型重建,详见 §23);Confidence = 置信度。
| Offset | Size | Type | Field | Description | Read | Write | Confidence |
|---|---|---|---|---|---|---|---|
| 0x00 | 4 | u8[4] | magic | "xet\0" = 78 65 74 00 | Parse (V01) | regenerate(恒定值) | ✅ |
| 0x04 | 4 | u32 | unknown_04 | 观测恒 0;语义未知,NEVER 修改 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x08 | 4 | u32 | file_size | 文件总字节数,== 实际长度 | Parse (V03) | recalc | ✅ |
| 0x0C | 4 | u32 | data_end | = file_size − 0x1C | Parse (V04) | recalc | ✅ |
| 0x10 | 4 | u32 | data_end2 | data_end 复本,== 0x0C 值 | Parse (V05) | recalc | ✅ |
| 0x14 | 4 | u32 | unknown_14 | 观测恒 7;语义未知 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x18 | 4 | u32 | unknown_18 | 观测恒 0 | Parse | preserve | ✅ |
| 0x1C | 4 | u32 | unknown_1C | 观测恒 0 | Parse | preserve | ✅ |
| 0x20 | 4 | u32 | unknown_20 | 观测恒 6;语义未知 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x24 | 4 | u32 | unknown_24 | 观测恒 0 | Parse | preserve | ✅ |
| 0x28 | 4 | u32 | ptr_28 | 观测 = meta_base+0x10(如 0x40450);见 §8、附录 C-1 | Parse/Validate (V19) | recalc | ✅ 字节观测 / ⚠️ 语义 |
| 0x2C | 4 | u32 | ptr_2C | == 0x28 的值 | Parse/Validate (V19) | recalc | ✅ |
| 0x30 | 4 | u32 | unknown_30 | 家族特有值,逐样本可变(A=0x042C4100);NEVER 自动修改 | Parse | preserve | ⚠️ |
| 0x34 | 4 | u32 | unknown_34 | 同 0x30(A=0x056DC9F8) | Parse | preserve | ⚠️ |
| 0x38 | 4 | u32 | unknown_38 | 观测恒 0 | Parse | preserve | ✅ |
| 0x3C | 4 | u32 | family_flag | Family A 观测 0(B 为 0xA0) | Parse | preserve | ✅ |
| 0x40 | W×H | u8[W*H] | pixels | 索引像素,行主序,每字节经 swap34(§10.2) | Parse | regenerate | ✅ |
| palette_offset | 1024 | RGBA×256 | palette | 256×[R,G,B,A],紧邻像素区(§11) | Parse | regenerate/preserve | ✅ |
| palette_offset+1024 | 92 | meta | meta | 元数据块(§12) | Parse | preserve+patch | ✅ |
| file_size−0x1C | 0x1C | u32×7 | tail | 偏移/地址表(§8.3 A 表) | Parse/Validate (V18) | 首两项 preserve+后五项 recalc | ✅ |
pixel_offset = 0x40(✅);pixel_region_size = W*H(✅;W==H,像素字节数为完全平方数,isqrt检测)。palette_offset = file_size − 0x1C − 92 − 256*4(✅);palette_size = 1024(✅)。meta_offset = palette_offset + 1024(✅);meta_size = 92(✅);tail_offset = file_size − 0x1C(✅)。file_size = 0x40 + W*H + 1024 + 92 + 0x1C = W² + 1208(✅ 逐项相加核对成立)。W=512 → 262144 + 1208 = 263352 = 0x404B8,与 LOADSC0/LOADSC3 实际字节数一致 ✔(263352 − 0x40 − 1024 − 92 − 0x1C = 262144 = 512² ✔)。
4.3 Family B 全文件结构表(PAL8 / PAL4)
| Offset | Size | Type | Field | Description | Read | Write | Confidence |
|---|---|---|---|---|---|---|---|
| 0x00 | 4 | u8[4] | magic | "xet\0" | Parse (V01) | regenerate(恒定值) | ✅ |
| 0x04 | 4 | u32 | unknown_04 | 观测恒 0 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x08 | 4 | u32 | file_size | 文件总字节数 | Parse (V03) | recalc | ✅ |
| 0x0C | 4 | u32 | data_end | = file_size − 0x1C | Parse (V04) | recalc | ✅ |
| 0x10 | 4 | u32 | data_end2 | data_end 复本 | Parse (V05) | recalc | ✅ |
| 0x14 | 4 | u32 | unknown_14 | 观测恒 7 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x18 | 4 | u32 | unknown_18 | 观测恒 0 | Parse | preserve | ✅ |
| 0x1C | 4 | u32 | unknown_1C | 观测恒 0 | Parse | preserve | ✅ |
| 0x20 | 4 | u32 | unknown_20 | 观测恒 6 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x24 | 4 | u32 | unknown_24 | 观测恒 0 | Parse | preserve | ✅ |
| 0x28 | 4 | u32 | const_28 | 观测恒 0x50(跨内容/尺寸恒定) | Parse | preserve | ✅ |
| 0x2C | 4 | u32 | const_2C | 观测恒 0x50 | Parse | preserve | ✅ |
| 0x30 | 4 | u32 | unknown_30 | 逐样本可变(46 文件 0x0AAE1BA0;FONTSJP 0x0AAE33A0);NEVER 自动修改 | Parse | preserve | ⚠️ |
| 0x34 | 4 | u32 | unknown_34 | 同 0x30(0x08BF08CC / 0x08BEF28C) | Parse | preserve | ⚠️ |
| 0x38 | 4 | u32 | unknown_38 | 观测恒 0 | Parse | preserve | ✅ |
| 0x3C | 4 | u32 | family_flag | = 0xA0 → Family B 标志 | Parse (V06) | preserve(重建时 regenerate=0xA0) | ✅ |
| 0x40 | 1 | u8 | type | 观测恒 0x00 | Parse | preserve(重建 regenerate=0x00) | ✅ |
| 0x41 | 1 | u8 | bits_div4 | 8bit=0x02,4bit=0x01(位深冗余判定②) | Parse | 重建时 recalc | ✅ |
| 0x42 | 1 | u8 | log2_w | W=2^该值(读取时 & 0x0F) | Parse (V07) | 重建时 recalc | ✅ |
| 0x43 | 1 | u8 | log2_h | H=2^该值(读取时 & 0x0F) | Parse (V07) | 重建时 recalc | ✅ |
| 0x44 | 1 | u8 | depth_type | 0x08=8bit,0x04=4bit(位深判定①:byte&0x08) | Parse | 重建时 recalc | ✅ |
| 0x45 | 1 | u8 | unknown_45 | 观测恒 0x01 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x46 | 1 | u8 | unknown_46 | 观测恒 0x04 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x47 | 1 | u8 | unknown_47 | 8bit=0x25,4bit=0x45(随位深联动) | Parse | 重建时 recalc | ✅ 值 / ❓ 语义 |
| 0x48 | 4 | u32 | unknown_48 | 观测恒 56 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x4C | 4 | u32 | unknown_4C | 观测恒 32 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x50 | 4 | u32 | unknown_50 | 观测恒 40 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x54 | 4 | u32 | unknown_54 | 观测恒 40 | Parse | preserve | ✅ 值 / ❓ 语义 |
| 0x58 | 8 | u8[8] | name | 贴图名,NUL 截断(如 "capcom");可溢出到 0x60+(SCEJLEGALJ 为 10 字节名,溢出 2 字节,E029) | Parse | patch(name_to_8) | ✅ |
| 0x60 | 56 | u8[56] | unknown_60 | 观测全 0,除 0x98(见下行);普查字节级确认 0x60..0x9F 除 0x98 外唯一非零 = SCEJLEGALJ.CHK 0x60–0x61=4C 4A(名字溢出,C-10/E029) | Parse | preserve | ✅ 观测 / ❓ 语义 |
| 0x98 | 1 | u8 | unknown_98 | 46/47 文件=0x01;FONTSJP1=0x00 (E015);语义未知;普查复核 47/47 一致(E029) | Parse | preserve | ⚠️ |
| 0x99 | 7 | u8[7] | unknown_99 | 观测全 0 | Parse | preserve | ✅ 观测 |
| 0xA0 | 变 | 块区 | pixels | 128B 块阵列,block-major(§16),无 swap34 | Parse | regenerate | ✅ |
| palette_offset | nb×4 | RGBA×nb | palette | nb=256(8bit)/16(4bit) | Parse | regenerate/preserve | ✅ |
| file_size−0x1C | 0x1C | u32×7 | tail | B 固定值表(§8.3 B 表) | Parse/Validate (V18) | preserve(重建 regenerate) | ✅ |
pixel_offset = 0xA0(✅);block_bytes = 128、block_rows_per_block = 8、block_row_bytes = 16(✅)。palette_offset = file_size − 0x1C − nb*4(✅);palette_size = nb*4(8bit=1024 / 4bit=64,✅);无 meta 块(✅)。file_size公式与样本核对见 §7.2。
5. 通用头 Common Header
5.1 两家族共有字段(0x00–0x3F)【✅ 原文保留并扩充】
| 偏移 | 含义 | 观测 | 置信 |
|---|---|---|---|
0x00 | 魔数 "xet\0"(= 0x00746578) | 恒定 | ✅ |
0x08 | 文件总大小 | = 文件实际字节数 | ✅ |
0x0c | data_end | = 文件大小 − tail(0x1C) | ✅ |
0x10 | data_end(复本) | = data_end | ✅ |
0x14 | 常量 | 7(两家族恒为 7) | ✅ 家族共用常量,语义未明 |
0x18 | 常量 | 0 | ✅ |
0x1c | 常量 | 0 | ✅ |
0x20 | 常量 | 6(两家族恒为 6) | ✅ 家族共用常量,语义未明 |
0x24 | 常量 | 0 | ✅ |
0x30 | 家族特有值(非恒定) | A=0x042C4100;B:0x0AAE1BA0(FONTSJP1/INTRO1) / 0x0AAE33A0(FONTSJP) | ⚠️ 同家族+同名样本间仍可变,非内容哈希,非全局常量;语义未知 |
0x34 | 家族特有值(非恒定) | A=0x056DC9F8;B:0x08BF08CC(FONTSJP1/INTRO1) / 0x08BEF28C(FONTSJP) | ⚠️ 同 0x30,随样本变化;语义未知 |
0x38 | 常量 | 0 | ✅ |
0x3c | Family B 标志 | B:0xA0;A:0 | ✅ |
全语料补充(E013):0x30/0x34 在 49 样本中恰为 3 组,即 Family A 两样本一组(
0x042C4100/0x056DC9F8);Family B 中 46 个文件一组(0x0AAE1BA0/0x08BF08CC,含 FONTSJP1/INTRO1/全部 TXD 样本),FONTSJP 单独一组(0x0AAE33A0/0x08BEF28C)。工具策略:原样保留,永不自动生成/修改。普查补充(2026-09-04,E025/E028):上表恒定行(0x00/0x04/0x14/0x18/0x1C/0x20/0x24/0x38)经独立普查脚本对 49 个原生样本复核:两家族逐字段恒定 49/49,无例外。0x30/0x34 的排除性证据增强:内容哈希(CRC32/adler32/sum8/xor32/fnv1a × whole_file/header/pixel_region/palette/pixel+palette/name±NUL/filename 等 10 类对象)与 14 项布局指针/大小对候选在全部样本上 0 命中(附录 E.2):"非内容哈希、非布局指针"由假设升级为排除性实证;语义仍未知(❓ 不升级)。
5.2 解析期强校验(parse 阶段即失败项)【✅ 来自 chk.rs L101–L124】
| 校验 | 条件 | 失败错误(原文风格) |
|---|---|---|
| 魔数 | bytes[0..4] == b"xet\0" | bad magic (expected b"xet\0") |
| 最小长度 | len ≥ 0x40 + 0x1C | file too small (N bytes) |
| file_size | le32(0x08) == 实际长度 | header file size N != actual M |
| data_end | le32(0x0C) == len − 0x1C | header data end N != M |
6. 头字段表(Header Field Table,A/B 逐字段)
偏移 + 观测值来自对全部样例(
LOADSC0/LOADSC3Family A 与TXD\*.chkFamily B)的逐字段 dump。u32均为 little-endian。数值栏给出"观测常量"或"随内容变化的样例"。 标注:✅ 确证 / ⚠️ 推断 / ❓ 语义未知。
6.1 Family A 特有(0x40 头 + meta)
| 偏移 | 含义 | 观测 | 置信 |
|---|---|---|---|
0x28,0x2c | meta 起始地址 | = data_end − 92 | ✅ |
meta[0x00] | 常量 | 0x40 | ✅ |
meta[0x08] | meta 基址 | = meta 起始 | ✅ |
meta[0x18] | 贴图名(8 字节,NUL 截断) | 如 "loadsc0" | ✅ |
meta[其余] | 大多为 0 | 0 | ❓ 少量字段语义未确认 |
❌ 规范裁定(E019;v2.3 升格为正式结论):上表
0x28/0x2C = data_end − 92一行已被字节实测证伪,禁止按该式实现。规范值:0x28/0x2C = meta_base + 0x10(§8.2 表【NORMATIVE】,与chk.rs L488–489写入一致,byte-exact 通过)。核对数据:LOADSC0/LOADSC3 实测0x00040450,而data_end − 92 = 0x00040440(恰为 meta_base),两者差恒为 0x10。原文行按"逐字保留"原则未改动;实现者一律以 §8.2 与附录 C-1 为准。
6.2 Family B 特有(0xA0 头)
| 偏移 | 含义 | 观测 | 置信 |
|---|---|---|---|
0x28,0x2c | 常量 | 0x50,0x50 | ✅ 跨内容/尺寸恒定 |
0x40 | 类型 | 0x00 | ✅ |
0x41 | bits / 4 | 8bit=0x02,4bit=0x01 | ✅ |
0x42 | log2(W) | 如 512→0x09 | ✅ |
0x43 | log2(H) | 512→0x09,256→0x08 | ✅ |
0x44 | 位深类型 | 8bit=0x08,4bit=0x04 | ✅ |
0x45 | 常量 | 0x01 | ✅ |
0x46 | 常量 | 0x04 | ✅ |
0x47 | 常量 | 8bit=0x25,4bit=0x45 | ✅ |
0x48 | 常量 | 0x38(56) | ✅ 跨 8bit/4bit 相同 |
0x4c | 常量 | 0x20(32) | ✅ 同上 |
0x50 | 常量 | 0x28(40) | ✅ 同上 |
0x54 | 常量 | 0x28(40) | ✅ 同上 |
0x58 | 贴图名(8 字节,NUL 截断) | 如 "capcom" | ✅ |
全语料补充(E015):
0x60..0x9F在全部 47 个 Family B 样本中除0x98外全为 0;header[0x98]46 个文件为0x01、FONTSJP1 为0x00。unknown_98语义未知,工具原样保留。
6.3 位深判定的两条途径(必须交叉核对)
- 判定①(解析用,parse_block L333):
bits = (header[0x44] & 0x08) != 0 ? 8 : 4(✅)。 - 判定②(冗余校验用):
header[0x41] == 0x02即 8bit,== 0x01即 4bit(✅)。两条途径在全部样本中一致;若不一致,Validator 报CHK_ERR_UNSUPPORTED_BIT_DEPTH(V 检查见 §26)。
7. 偏移与尺寸规则(Offset / Size Rules)
7.1 Family A 公式【✅ 全部经 49 样本核对(E017)】
header_size = 0x40
pixel_offset = 0x40
W == H # 方形约束(编码强制;解析按 isqrt 检测完全平方数)
pixel_region_size = W * H # 每像素 1 字节;必须为完全平方数(isqrt 判定)
palette_offset = file_size − 0x1C − 92 − 256*4
palette_size = 256 * 4 = 1024
meta_offset = palette_offset + 1024
meta_size = 92
tail_offset = file_size − 0x1C
file_size = 0x40 + W*H + 1024 + 92 + 0x1C
样本核对:
LOADSC0.CHK:263352 = 0x40 + 262144 + 1024 + 92 + 28 ✔(263352 = 0x404B8;palette_offset = 263352−28−92−1024 = 262208 = 0x40040;meta_offset = 263232 = 0x40440;tail_offset = 263324 = 0x4049C)LOADSC3.CHK:同上(同长度 263352 = 0x404B8)✔
7.2 Family B 公式【✅ 全部经 49 样本核对(E017)】
header_size = 0xA0
W = 2 ^ header[0x42] # 读取时按字节取:1 << (b[0x42] & 0x0F)
H = 2 ^ header[0x43] # 1 << (b[0x43] & 0x0F)
bits = (b[0x44] & 0x08) != 0 ? 8 : 4 # 判定①
# 冗余判定②:b[0x41]==2 → 8bit;==1 → 4bit(须与①一致)
block_w = 16 (8bit) | 32 (4bit)
block_h = 8
block_bytes = 128 # 恒定
block_cols = W / block_w
block_rows = H / block_h
pixel_region_size = block_rows * block_cols * 128 # = file_size − 0xA0 − nb*4
palette_offset = file_size − 0x1C − nb*4 # nb = 256 (8bit) | 16 (4bit)
palette_size = nb * 4 # 1024 | 64
tail_offset = file_size − 0x1C
file_size = 0xA0 + block_rows*block_cols*128 + nb*4 + 0x1C
样本核对(四种文件长度全部被公式精确解释):
FONTSJP.CHK:262396 = 0x400FC = 0xA0 + (64×8×128=262144) + 16×4 + 28 ✔(512×1024 4bit;palette_offset = 262396−28−64 = 262304 = 0x400A0)CAPCOM.CHK(8bit 512×512):263356 = 160 + 262144 + 1024 + 28 ✔TXD\LOADSC0.CHK(8bit 512×256):132284 = 160 + 131072 + 1024 + 28 ✔TXD\INTRO1.CHK(4bit 512×512):131324 = 160 + 131072 + 64 + 28 ✔TXD\MEMCARD.CHK(4bit 512×256):65788 = 160 + 65536 + 64 + 28 ✔- 全量普查(E026,附录 E.7):上述公式(含 Family A 侧
file_size = 0x40 + W·H + 1024 + 92 + 0x1C与容器 0x08/0x0C/0x10 公式)对全部 49 个原生样本逐文件核验,49/49 OK、误差为 0;尺寸类分布:A 512²×8 ×2;B 512×256×8 ×18、512×512×4 ×23、512×512×8 ×4、512×1024×4 ×1(FONTSJP)、512×256×4 ×1(MEMCARD)。
7.3 尺寸约束(Family B,引 validate_block_dims chk.rs L265–278)【✅】
- W、H 必须为 2 的幂(因为 header 存的是 log2);
- 8bit:W ≥ 16;4bit:W ≥ 32;H ≥ 8(块对齐:
W % block_w == 0,H % 8 == 0); - 违反报错:
chk 尺寸需为 2 的幂(8bit 宽≥16、4bit 宽≥32、高≥8);请求 {w}x{h}/chk 尺寸过小:宽需≥{min_w}、高需≥8;请求 {w}x{h}。 - Family A 尺寸约束:W == H(方形,✅ 见 §9)。
7.4 像素区布局总公式【✅ 原文保留】
pixel_region_size = palette_offset - header_size,且 =
(Family A: W*H) (8bit 方形,每像素 1 字节)
(Family B: block_rows * block_cols * block_bytes 块布局)
8. 重定位与指针系统(Relocation / Pointer System)
8.1 已确证结论总览
哪些字段存 offset、绝对还是相对:
| 字段 | 内容 | 绝对/相对 | 置信 |
|---|---|---|---|
header 0x08 | 文件总大小(自指) | 绝对(自文件头 0) | ✅ |
header 0x0C/0x10 | data_end = file_size − 0x1C | 绝对 | ✅ |
header 0x28/0x2C | Family A:meta 基址序列成员(实测 = meta_base+0x10,E019;原文表记 data_end−92,冲突见附录 C-1);Family B:常量 0x50 | A 绝对 / B 常量 | ✅ 字节 / ⚠️ A 语义;【NORMATIVE】实现以本行为准 |
header 0x30/0x34 | 家族特有值,逐样本可变,语义未知 | Unknown | ⚠️ |
| meta[0x08] | meta 基址(A 的 meta 内自指针) | 绝对 | ✅ |
| tail 全部 7 项 | 偏移/地址表(见 8.3) | A 后 5 项绝对 / B 后 5 项为 header 内偏移("相对") | ✅ |
Family A 指针公式(chk.rs serialize_square L482–500 逐行):
meta_base = 0x40 + W*H + 1024 # = palette_offset + 1024 = meta_offset
header[0x08] = meta_base + 92 + 0x1C # = file_size
header[0x0C] = header[0x10] = meta_base + 92
header[0x28] = header[0x2C] = meta_base + 0x10
meta[0x00] = 0x40 # 恒定(观测 + default_meta)
meta[0x08] = meta_base
tail[0x08..0x18] = meta_base + {0, 8, 0xC, 0x10, 0x14}
⚠️ meta[0x0C]、meta[0x10]、meta[0x14] 在样本中观测为 32/40/40(E012),
chk.rs解析后原样保留、不重算。meta[0x04..0x08] 观测0x49 0x82 0x10 0x00(= u320x00108249,E012),语义未知,原样保留。
Family B 指针公式(chk.rs serialize_block L544–553 + default_font_header/default_font_tail):
header[0x08] = 0xA0 + packed_len + palette_bytes_len + 0x1C # = file_size
header[0x0C] = header[0x10] = 0xA0 + packed_len + palette_bytes_len
header[0x28] = header[0x2C] = 0x50 # 常量,不随内容变
tail = {0x28, 0x2C, 0x3C, 0x48, 0x4C, 0x50, 0x54} # 全常量
改布局后要更新哪些(Mutation 视角,详见 §23):
- A:改像素区尺寸 → 重算
0x08/0x0C/0x10/0x28/0x2C、meta[0x08]、tail 后 5 项;0x30/0x34/0x14/0x20等 unknown 一律原样保留。 - B:改像素区尺寸/位深 → 重算
0x08/0x0C/0x10+0x41/0x42/0x43/0x44/0x47;0x28/0x2C=0x50、tail 全表、0x30/0x34、0x45/0x46/0x48/0x4C/0x50/0x54/0x98原样保留(重建时按固定表重新生成)。
如何"重新生成":除上述 recalc 项外,Family B 的 0x28/0x2C/tail/常量字段、Family A 的 0x14/0x20/meta[0x00] 均有实测常量,可按 §6/§8 的固定值表重新生成(rebuilt_header_matches_real_family_b 已对 ≥40 个真文件验证重建头逐字段一致,E005)。0x30/0x34 没有可用的生成规则:精确保留模式下必须从原文件拷贝,重建模式下 chk.rs 写入家族代表值(A=0x042C4100/0x056DC9F8,B=0x0AAE1BA0/0x08BF08CC),此时不再是逐字节等价重建(⚠️ 如实标注)。
8.2 Family A 指针/常量一览(实现用)
| 位置 | 观测值(LOADSC0/LOADSC3) | 生成规则 | 置信 |
|---|---|---|---|
| hdr 0x28 / 0x2C | 0x00040450 | meta_base + 0x10(E019;附录 C-1 记录与原文表差异) | ✅ 字节 / ⚠️ 语义 |
| meta[0x00] | 0x40 | 恒定 | ✅ |
| meta[0x04..0x08] | 49 82 10 00(u32 0x00108249) | 无规则,原样保留 | ⚠️(E012) |
| meta[0x08] | 0x00040440 | = meta_base | ✅ |
| meta[0x0C] | 32 | 无规则,原样保留 | ⚠️(E012) |
| meta[0x10] | 40 | 无规则,原样保留 | ⚠️(E012) |
| meta[0x14] | 40 | 无规则,原样保留 | ⚠️(E012) |
| meta[0x18..0x20] | "loadsc0\0\0" | name_to_8(name) | ✅ |
| meta[0x58] | 0x01 | 无规则,原样保留 | ⚠️(E012) |
| meta 其余 | 0 | 原样保留 | ✅ 观测 |
8.3 tail(0x1C = 28 字节,两家族共用结构)—— 子结构偏移/地址表【✅ 原文保留】
tail 是 7 个 u32 的偏移/地址表:前 2 项是指向内部子结构的固定偏移,后 5 项因家族而异。与生成器 default_tail/default_font_tail 逐项一致(已用原生 CAPCOM/LOADSC0 验证 byte-exact)。
Family A(PS2 方形):绝对地址(随文件大小变)
| tail 偏移 | 值 | 置信 |
|---|---|---|
0x00 | 0x28 | ✅ |
0x04 | 0x2c | ✅ |
0x08 | meta_base(如 0x00040440) | ✅ |
0x0c | meta_base + 0x08 | ✅ |
0x10 | meta_base + 0x0c | ✅ |
0x14 | meta_base + 0x10 | ✅ |
0x18 | meta_base + 0x14 | ✅ |
Family B(PSP 块状):header 内偏移(恒定)
| tail 偏移 | 值 | 置信 |
|---|---|---|
0x00 | 0x28 | ✅ |
0x04 | 0x2c | ✅ |
0x08 | 0x3c | ✅ |
0x0c | 0x48 | ✅ |
0x10 | 0x4c | ✅ |
0x14 | 0x50 | ✅ |
0x18 | 0x54 | ✅ |
Family A/B 的 tail[0..1] 同为
0x28/0x2C;Family B 后 5 项是 header 内偏移(0x3C, 0x48, 0x4C, 0x50, 0x54),Family A 后 5 项是 meta 基址序列。结构表语义已确证,但其消费方(是否每个都必须有效、固定)仍需游戏/工具侧验证。E014 全语料交叉。49 个样本的 tail 值集恰为上述两组:46+2=48 个文件(全部 Family B + Family A 两样本)分别逐字节匹配 B 表与 A 表(A 表 meta_base=0x40440)。
serialize_square对 tail[0x00]/[0x04] 不重写(从原 tail 保留),default_tail/default_font_tail的固定表与观测一致,精确保留与重建两条路都闭合。普查刷新(2026-09-04,E027,附录 E.3)。独立普查脚本全量核验:A 2/2 + B 47/47 = 49/49 逐项精确匹配、无例外(补齐 E014 当时未计入的 FONTSJP,"46+2=48" 口径就此关闭)。
_chk_verify11 个工具生成文件的 tail 亦全部匹配 B 表(A 重建件匹配 A 表)。
9. Family A 实现规范
header大小:0x40(64 字节)。✅ 原文保留- 几何要求:宽 == 高(正方形)。参考尺寸 512×512。
- 布局:
[ header 0x40 ] [ pixel region (square) ] [ palette 256×4 ] [ meta 92 ] [ tail 0x1C ]palette_offset = len − tail(0x1C) − meta(92) − 256×4- pixel region =
header[0x40 .. palette_offset],像素字节数为完全平方数(用isqrt检测)。
- 索引:每像素 1 字节的 8bit 调色板下标,行主序;但解码/编码时做
chk_swap34交换(交换 index 的 bit3 与 bit4)。 - 调色板:256 个
[R,G,B,A](紧邻像素区之后)。 - meta 块:92 字节;
0x18处存 8 字节贴图名(NUL 截断),0x08处为偏移字段(回写用)。
实现要点(与 chk.rs 对齐):
- Header:解析时整体保存
bytes[0..0x40]原始字节(header: Vec<u8>,byte-exact 的关键);序列化时 clone 原始头,仅 patch0x08/0x0C/0x10/0x28/0x2C(serialize_square L478–489)。 - Pixel Offset:恒
0x40。 - Pixel Size:
palette_offset − 0x40字节;W = H = isqrt(pixel_bytes),且必须W*W == pixel_bytes(否则不是 Family A)。 - W·H 推导:除 isqrt 外没有显式的 W/H 字段,Family A 头部不存尺寸,尺寸完全由文件大小反推(✅,实现推论:parse_square 参数 w=h=side)。
- Palette Offset / Size:见 §7.1;Entry 布局 [R,G,B,A](§18)。
- Meta:92B,整体保存原始字节;序列化时 patch meta[0x08]=meta_base 与 meta[0x18..0x20]=name_to_8(name)(L490、L476),其余字节原样。
- Tail:整体保存原始字节;序列化时 patch 后 5 项(L496–500);首两项
0x28/0x2C从原 tail 保留(与default_tail值一致)。 - 索引纹理无损性:decode(encode(x)) == x 对索引层恒成立(swap34 是自反对合;palette 查表后若索引唯一则 RGBA 亦无损,✅)。
10. Family A 像素格式
10.1 行主序索引
- 每像素 1 字节 = 调色板下标 0..255,逻辑顺序 row-major:
pixel_index(x, y) = y * W + x(✅)。存储值是逻辑索引经 swap34 之后的值(见 10.2)。
10.2 索引交换(仅 Family A)【✅ 原文保留 + 公式】
chk_swap34(v):交换一个 8bit 索引的 bit3 与 bit4。仅 Family A 使用;Family B 直接存下标。
-- 位运算公式(chk.rs L566–568)--
swapped = (v & 0xE7) | ((v & 0x08) << 1) | ((v & 0x10) >> 1)
# 0xE7 = 1110 0111b:清掉 bit3/bit4,其余位原样
# bit3(0x08) 左移 1 位 → bit4;bit4(0x10) 右移 1 位 → bit3
-- swap34 是自反(involution):swap34(swap34(v)) == v,编码/解码同一个函数(L1299–1311 测试覆盖 0..255 全域)--
11. Family A 调色板
- 256 个条目,每条目 4 字节
[R,G,B,A],紧邻像素区之后(offset = file_size − 0x1C − 92 − 1024,✅)。 - 通道序 RGBA(✅ 决定性证据 E003:LOADSC0 解码与参考 PNG diff=0);Alpha 全范围行为见 §18。
- 解析:
for i in 0..256: entry[i] = bytes[palette_offset + i*4 : palette_offset + i*4 + 4](逐字节拷贝,不做通道交换;v2.3 显式端点)。
12. Family A 元数据块(Meta 92B)
- 位置:
palette_offset + 1024;大小 92 字节;解析时整体保存原始字节(✅)。
| meta 偏移 | 含义 | 观测(LOADSC0/LOADSC3) | 置信 |
|---|---|---|---|
0x00 | 常量 | 0x40 | ✅ |
0x04..0x08 | 未知 | 0x00108249(49 82 10 00,两样本相同) | ❓ 语义未知(E012) |
0x08 | meta 基址 | = meta 起始(如 0x00040440) | ✅ |
0x0C | 未知 | 32 | ❓ 语义未知(E012) |
0x10 | 未知 | 40 | ❓(E012) |
0x14 | 未知 | 40 | ❓(E012) |
0x18 | 贴图名(8 字节,NUL 截断) | 如 "loadsc0" | ✅ |
0x58 | 未知 | 0x01(两样本相同) | ❓ 语义未知(E012) |
| 其余 | 观测为 0 | 0 | ✅ 观测 |
- 保存策略:除
0x08(recalc)与0x18(随名字 patch)外全部 preserve / NEVER 自动修改(§23);语义未知字段没有证据不得改写。 - 序列化实现:meta 长度 ≠92 时 resize 补 0(L472–474),这是重建路径的宽容行为,精确保留路径要求原始 92 字节原样。
13. Family B 实现规范
header大小:0xA0(160 字节);贴图名在0x58(8 字节,NUL 截断)。✅ 原文保留- 位深:
byte[0x44] & 0x08非零则 8bit,否则 4bit。 - 几何:
byte[0x42] = log2(W),byte[0x43] = log2(H)(因此 W/H 必须为 2 的幂)。 - 布局:
[ header 0xA0 ] [ block pixel region ] [ palette 256或16×4 ] [ tail 0x1C ]palette_offset = len − tail(0x1C) − nb×4(nb = 256 或 16)。- pixel region =
header[0xA0 .. palette_offset],按 128 字节块 排列。
- 块布局:每个块 = 8 行 × 16 字节/行(
CHK_BLOCK_BYTES=128,CHK_BLOCK_ROWS=8,CHK_BLOCK_ROW_BYTES=16)。- 8bit:每块 8×16 像素(1 字节/texel)。
- 4bit:每块 8×32 像素(2 texel/字节,低 4bit 在前,高 4bit 在后)。
- 块栅格:
block_cols = W/16(8bit)或 W/32(4bit),block_rows = H/8。
- 尺寸约束(✅):2 的幂;8bit 宽 ≥16、4bit 宽 ≥32;高 ≥8;块对齐(
W % block_w == 0,H % 8 == 0)。 - 索引:不做 bit3/4 交换。
- 调色板:8bit=256 色,4bit=16 色。
实现要点(与 chk.rs 对齐):
- parse_block(L332–384)流程:判位深(0x44&0x08)→ 定 nb/palette_offset →
texels = pixel_bytes * (8/bits)→ 读w = 1 << (b[0x42] & 0x0F)、h = 1 << (b[0x43] & 0x0F)→ 交叉核对w*h == texels(不一致报block .chk geometry inconsistent)→ 块对齐检查(不一致报block .chk size not block-aligned)→ 读调色板 → 读名字(0x58,8B,NUL 截断,from_utf8_lossy)→unpack_block展开 → 保存bytes[0..0xA0]原始头与原样 tail。 - serialize_block(L512–561):
pack_block重打包 → patch 头0x08/0x0C/0x10+0x58..0x60名字 → tail 原样 → 顺序拼接。 - palette 交叉核对:kind() 由
header.len()==0xA0 && palette.len()恢复(L157–167);位深判定①与调色板大小必须一致,否则 Validator 报错(V09)。 - 尺寸约束引
validate_block_dims(§7.3)。
13.1 完整坐标映射公式【✅ 与 unpack_block/pack_block(L388/L416)一致】
-- 输入:逻辑坐标 (x, y),0 <= x < W,0 <= y < H;输出:pixel_offset(文件内字节偏移)--
block_x = x / block_w # block_w = 16(8bit) | 32(4bit)
block_y = y / block_h # block_h = 8
local_x = x % block_w
local_y = y % block_h
block_index = block_y * block_cols + block_x # block-major:先按块(左→右、上→下)
block_offset = block_index * 128
8bit: byte_off = local_y * 16 + local_x # 每 texel 1 字节
4bit: byte_off = local_y * 16 + local_x / 2 # 每 2 texel 1 字节
nibble = local_x % 2 # 0=低 4bit,1=高 4bit
pixel_offset = header_size + block_offset + byte_off # header_size = 0xA0
-- 反向(文件 → 逻辑索引,unpack_block)--
idx8 = data[pixel_offset] # 8bit 直接
raw = data[pixel_offset]; idx4 = (nibble==0) ? raw & 0x0F : (raw >> 4) & 0x0F
Family B 不做 swap34:索引直接寻址调色板(✅)。块内顺序为行序(
local_y*16步进 16 字节),块与块之间按 block-major 连续存放。
13.2 尺寸/位深/名字字段在重建时的写入(patch_block_header L286–298)
header[0x40] = 0x00
header[0x41] = bits / 4
header[0x42] = log2(W) # trailing_zeros,min(0x0F)
header[0x43] = log2(H) # trailing_zeros,min(0x0F)
header[0x44] = 0x08 (8bit) | 0x04 (4bit)
header[0x45] = 0x01
header[0x46] = 0x04
header[0x47] = 0x25 (8bit) | 0x45 (4bit)
rebuilt_header_matches_real_family_b(L1053–1111)对 ≥40 个真文件验证:用零图重建的 header 在0x28/0x2C/0x30/0x34/0x3C/0x40/0x44/0x48/0x4C/0x50/0x54字段上与原文件逐字段一致(E005)。注意该测试比较的 u32 0x44 槽位同时覆盖 0x44..0x48(即 0x44/0x45/0x46/0x47 四字节)。
14. Family B PAL8(8bit 块状)
- 位深 8bit:
header[0x44]=0x08、header[0x41]=0x02、header[0x47]=0x25(✅)。 - 块 = 8×16 像素 = 128 字节(1 字节/texel);
block_w=16;W≥16 且为 2 的幂;H≥8 且为 2 的幂。 - 调色板 256×RGBA(1024 字节)位于
file_size − 0x1C − 1024。 - 索引直接寻址调色板,无 swap34、无 nibble。
- 样本:CAPCOM/WARNING/SCEJLEGALJ/FONTSJP1(512×512)、TXD\LOADSC0–3/MPLOAD0–7/NEWS*/SPLASH1–3(512×256)。
15. Family B PAL4(4bit 块状)
- 位深 4bit:
header[0x44]=0x04、header[0x41]=0x01、header[0x47]=0x45(✅)。 - 块 = 8×32 像素 = 128 字节(2 texel/字节);
block_w=32;W≥32 且为 2 的幂;H≥8 且为 2 的幂。 - 调色板 16×RGBA(64 字节)位于
file_size − 0x1C − 64。 - Nibble 打包见 §17;低 4bit 在前(先到的像素放低半字节)(✅)。
- 4bit 调色板是真 16 色 RGBA(不限灰度):源文件恰好是灰/alpha 字型,但彩色编辑图保留色相(
from_rgba_font_keeps_color测试,L978–1008,✅)。 - 样本:FONTSJP(512×1024)、INTRO1–4 / LOADSC4–22(512×512)、MEMCARD(512×256)。
16. 块布局(Block Layout)【✅】
- 像素区被切分为 128 字节块:每块 8 行 × 16 字节/行。
- 块栅格:
block_cols = W / block_w(8bit: block_w=16;4bit: block_w=32),block_rows = H / 8。 - 块顺序:block-major:
block_index = block_y * block_cols + block_x,从左到右、从上到下连续存放;块内按行序(k = 0..8),行内按字节序(a = 0..16)。 unpack_block(L388–412):对每个块、每行、每列把packed[base + k*16 + a](或 nibble 拆分)写入out[(bi*8+k)*w + bj*px_per_row + a]。pack_block(L416–444):严格的逆变换;4bit 写入时低半字节初始化为 0 再|=高半字节,保证两个 nibble 都正确落位。block_bytes = block_h * block_w / (8 / bits),两种位深均 = 128:
4.1 节原文:块栅格与 4bit 打包/解包【✅ 原文保留】
-- 位深(family B header 判定)--
bits = (header[0x41] * 4) # header[0x41]=2->8bit, =1->4bit
-- 块几何(像素单位)--
block_w = 16 if bits==8 else 32 # 每块宽(像素)
block_h = 8 # 每块高(像素,恒定)
block_cols = ceil( W / block_w )
block_rows = ceil( H / block_h )
block_bytes = block_h * block_w / (8 / bits) # 恒为 128 字节
# 8bit: 8 行 × 16 字节 = 128
# 4bit: 8 行 × 32texel × 0.5 字节 = 128(每行仍 16 字节)
-- 4bit 半字节打包:低 4bit 在前,高 4bit 在后 --
def pack4(lo, hi): return (hi << 4) | lo # lo=低4bit(先画), hi=高4bit
def unpack4(byte): return (byte & 0x0F), ((byte >> 4) & 0x0F)
-- 像素区布局 --
pixel_region_size = block_rows * block_cols * block_bytes
# 块按"从左到右、从上到下"的顺序连续存放;块内按行序,行内按字节序。
# 由 pixel_region_size = palette_offset - header_size,且 =
# (Family A: W*H) (8bit 方形,每像素 1 字节)
# (Family B: 见上式块布局)
说明:
block_bytes两种位深都是 128,因此块大小恒定;差异只在 每行换算的像素数(8bit=16 像素/行,4bit=32 像素/行,均为 16 字节/行)。 实现注:有效样本的 W/H 均为 block_w/8 的整数倍,ceil(...)在实际公式中退化为整除(parse_block 使用整除并显式校验块对齐);header[0x41]*4与判定①(0x44&0x08)在全部样本中一致。
17. 半字节布局(Nibble Layout,4bit)【✅】
- 读:
byte = data[offset];local_x % 2 == 0(偶数位像素,先到)取byte & 0x0F;local_x % 2 == 1(奇数位像素,后到)取(byte >> 4) & 0x0F。 - 写:先到的像素(偶数 local_x)放低 4bit,后到的像素(奇数 local_x)放高 4bit:
byte = (hi << 4) | lo。 - 1 byte = 2 像素;一行 32 像素 = 16 字节;与既有"低 4bit 在前"结论一致(✅:unpack_block/pack_block L402–405/L431–437 逐行一致,byte-exact 证据覆盖全部 4bit 样本,E002/E004)。
- 序列化注意:写高半字节时用
out[byte_idx] |= idx4 << 4且输出缓冲区已清零,不能直接赋值覆盖低半字节。
18. 调色板规范(Palette Specification)
18.1 条目布局
| 位深 | 条目数 | 条目大小 | 总大小 | 家族 |
|---|---|---|---|---|
| 8bit | 256 色(PAL8) | 4B | 1024B | Family A(PS2,方形)、Family B(PSP,块状) |
| 4bit | 16 色(PAL4) | 4B | 64B | 仅 Family B(PSP,块状) |
- 调色板条目每项 4 字节
[R,G,B,A](✅ 原文保留)。字节序:byte0=R、byte1=G、byte2=B、byte3=A。- ✅ 决定性证据(E003):解析
LOADSC0.CHK的 RGBA 与参考loadsc0.png逐字节 diff=0(512×512),通道序若有任何偏差(如 BGRA)diff 不可能为 0。
- ✅ 决定性证据(E003):解析
- count 判定:Family A 恒 256;Family B 由位深判定①决定(8bit 时 256,4bit 时 16),并须与判定②(0x41)及调色板大小公式(palette_size = nb*4)交叉一致。
- 实测首项示例(E012,仅供观察,非规范):FONTSJP 调色板首 8 字节
FD FD FD F5 FF FF FF 67(entry0=[253,253,253,245] 亮灰近不透明、entry1=[255,255,255,103] 白色半透明,与"字型灰/alpha"用途一致);LOADSC0 entry0=[246,212,107,255]。
18.2 Alpha 行为(⚠️/❓ 如实标注)
- Alpha 字节取值 0..255 均出现在真实样本中(FONTSJP 即含 0xF5/0x67 等中间值,E012;✅ 存在性)。
- 游戏渲染端对全范围 alpha 的插值/阶梯行为未证实(❓),需要运行时证据。工具侧策略:原样保留 alpha 字节,不做任何折算。
- 普查收窄(2026-09-04,E030,附录 E.6):全语料 6544 个调色板条目中 alpha≠255 共 297 条,去重值集 49 种(0..254;其余 206 个中间值语料内未见);含 alpha==0 条目的文件 25 个。仅 FONTSJP 的 alpha≠255 条目被像素数据实际引用(≥12/16 条,且解码与 fontjp.png diff=0);其余 24 个含 alpha 条目文件的 alpha 条目全部未被索引引用。上句"0..255 均出现"应读作存在性成立;全范围覆盖语料内未证实(矛盾登记 C-13),渲染端语义仍 ❓ 不升级。
18.3 通道映射测试法(附录 B 摘要,详见 §38)
构造纯红/纯绿/纯蓝/纯黑/纯白/半透明测试图,编码后解码比对通道:若红绿互换说明通道序错误;若 alpha 全丢失说明第 4 字节不是 A。判定标准 = 解码 RGBA 与源 PNG diff=0。
19. 纹理解码(Texture Decode:CHK → 索引 → RGBA)
Family A 解码管线:
CHK → read header[0..0x40] → parse_square:
pixel region bytes = file[0x40 .. palette_offset]
logical index i = swap34(raw_byte) # raw_byte = file[0x40 + (y*W + x)]
RGBA(x,y) = palette[i] # 256 项查表
Family B 解码管线:
CHK → read header[0..0xA0] → parse_block:
bits = (b[0x44]&0x08)?8:4; W=1<<(b[0x42]&0x0F); H=1<<(b[0x43]&0x0F)
对每个 (x,y) 按 §13.1 公式定位 → raw index(4bit 按 nibble 拆)
logical index = raw index(无 swap34)
RGBA(x,y) = palette[index] # 256/16 项查表
- 越界索引处理(✅ 实现定义):
palette.get(e).unwrap_or([0,0,0,255]),索引 ≥ palette 长度时输出不透明白(to_rgba L178)。Validator 应把"索引 ≥ nb"列为错误(V14)。 - 解码输出:
height * width * 4紧密 RGBA 缓冲。 - Decoder 只读不改原始数据:解析结果除逻辑索引缓冲(unpack 后)外,header/meta/tail 原始字节必须原样保存(ChkFile 的 header/meta/tail 字段语义,L91–96)。
20. 纹理编码(Texture Encode:RGBA → 索引 → CHK)
20.1 索引生成(Format-enforced 部分)
- Family A:
logical_index = quantized_index,存储时stored = swap34(logical_index)(from_rgba L198–200);编码前校验 W==H。 - Family B 8bit:直接存 0..255 索引(from_rgba_block8)。
- Family B 4bit:索引 &0x0F(from_rgba_font);16 色调色板。
20.2 调色板生成(Tool Policy,见 20.3)
chk.rs 参考实现 quantize_rgba(L591–689)的做法(作为参考策略描述,非格式规则):
- 全透明像素(A==0)不参与颜色聚类,固定占 index 0(
[0,0,0,0]); - 其余颜色经 median-cut 聚类到 k−1(或 k)个箱,箱代表 = 频率加权平均(box_avg);
- 最近邻分配用
dist2 = dr²+dg²+db²+(da/2)²,即 Alpha 降权(颜色主导,L586); - 调色板不足 k 项时用最后一项补齐。
20.3 Native Format Rules vs Tool Encoding Policy【必须分离】
| 条目 | 类别 | 说明 |
|---|---|---|
| CHK 只规定 Palette + Index 两层 | 格式规则 | 任何合法量化算法产生的 palette+index 都是合法 CHK |
| 索引必须落在 0..nb−1 | 格式规则 | 违反即非法文件 |
| Family A 存储索引经 swap34 / B 不交换 | 格式规则 | 见 §10/§13 |
| 透明像素占 index 0 不参与聚类 | Tool Policy(chk.rs quantize_rgba) | 格式不强制 index 0 必须透明; encoder 自由 |
| median-cut | Tool Policy | 换成 k-means、octree 等同样合法 |
| Alpha 降权(dist2 中 da/2) | Tool Policy(L586) | 距离度量是 encoder 内部选择 |
| 4bit "真 16 色 RGBA 不限灰度" | 格式允许(样本恰为灰度) | encoder 可用彩色,保留色相已被测试固化 |
| 量化算法选择 | Encoder 自由 | 但量化有损,必须通过 Round-trip Test C 如实报告 |
21. 编码管线(PNG→CHK)
每步标注【F】=格式强制 /【T】=工具策略:
| # | 步骤 | 输入 | 输出 | 类别 |
|---|---|---|---|---|
| 1 | 加载 PNG → RGBA8888 | PNG 文件 | w×h×4 RGBA | 【T】 |
| 2 | Alpha Analysis | RGBA | 透明像素统计 | 【T】 |
| 3 | Quantization(median-cut 等) | RGBA | ≤k 代表色 | 【T】算法自由 |
| 4 | Palette Generation | 代表色 | palette[0..k] | 【T】;index0 透明是策略 |
| 5 | Index Assignment | RGBA+palette | 索引缓冲 | 【T】最近邻自由 |
| 6 | 索引合法性 | 索引 | 0..k−1 校验 | 【F】 |
| 7 | 4/8bit Index | 索引 | 索引字节流 | 【F】4bit &0x0F |
| 8 | Nibble / Block 转换 | 字节流 | on-disk 像素区 | 【F】(A: swap34+行主序;B: pack_block) |
| 9 | Header 生成 | 家族/尺寸/位深/名字 | 0x40/0xA0 头 | 【F】字段布局+【T】unknown 取值策略 |
| 10 | Offset/Size 计算 | 各区大小 | 0x08/0x0C/0x10 等 | 【F】公式见 §7 |
| 11 | Pointer/Tail 更新 | meta_base 等 | 0x28/0x2C/meta[0x08]/tail | 【F】A 公式 / B 常量 |
| 12 | Validator | 完整字节流 | PASS/错误码 | 【F】 |
| 13 | 输出 CHK | — | .chk 文件 | — |
- 家族/位深选择(步骤 9 前):目标 Family A 必须 W==H;Family B 必须 2 的幂 + 块对齐(§7.3),不满足则拒绝(rejects_non_square / rejects_non_pow2_block_dims 测试)。
- 参考编码入口:
from_rgba(A)、from_rgba_block8(B 8bit)、from_rgba_font(B 4bit)→serialize()。
22. 解码管线(CHK→PNG)
CHK bytes
→ Validate Header(§5.2 强校验;失败即 CHK_ERR_*)
→ Detect Family(§3 检测顺序:A 几何试探 → B 0x3C==0xA0)
→ Bit Depth(B: 0x44&0x08;A 恒 8bit)
→ Dimensions(A: isqrt 反推;B: 2^0x42 × 2^0x43)
→ Locate Pixel Region(palette_offset 公式反推)
→ Block 展开 unpack_block(B)/ 行主序+swap34(A)
→ Nibble 拆分(仅 B 4bit)
→ Index 缓冲(row-major,1B/texel)
→ Palette 查表(§18)
→ RGBA 缓冲
→ PNG 编码
- Decoder 只读:不修改文件任何字节;原始 header/meta/tail 原样保存在文档模型中。
- 参考解码路径:
ChkFile::parse→to_rgba()→ PNG writer(本仓库 preview 管线对全部样本验证尺寸一致,E006)。
23. 序列化与字段变更策略(Serialization / Field Mutation Policy)
23.1 序列化总规则(与 chk.rs 逐行对齐)
serialize()按 kind 分派:A→serialize_square,B8→serialize_block(8),B4→serialize_block(4)。- 序列化前自检:palette 长度必须 ==256/16(否则
expected N palette colors, got M);pixels 长度必须 ==W*H(否则expected N pixels, got M)。 - 输出顺序恒为:header → pixel region → palette → (meta) → tail;总长 == header[0x08](byte-exact 的前提)。
- 名字写入:
name_to_8(L788–794),UTF-8 字节截断到 8,不足补 0;A 写 meta[0x18..0x20],B 写 header[0x58..0x60]。
23.2 Mutation 表(每字段 Read/Preserve/Recalculate/Regenerate)
术语:preserve=原样保留输入文件的字节;recalc=按公式重算;regenerate=从文档模型重建(重建模式下的固定表/恒定值)。unknown 字段一律 preserve,NEVER 自动修改。括号内为典型触发场景。
| 字段 | Family A | Family B | 依据/证据 |
|---|---|---|---|
| magic (0x00) | preserve | preserve | byte-exact(E002) |
| unknown_04/18/1C/24/38 | preserve(NEVER 修改) | preserve(NEVER 修改) | 观测恒 0(E013) |
| 0x08 file_size | recalc(任何保存) | recalc | serialize L485/L546 |
| 0x0C / 0x10 data_end | recalc | recalc | L486–487/L547–548 |
| unknown_14=7 / unknown_20=6 | preserve | preserve | 观测恒定(E013);重建时 regenerate=7/6 |
| 0x28 / 0x2C | recalc(=meta_base+0x10) | preserve(重建 regenerate=0x50) | L488–489(A);B 观测恒 0x50(E013) |
| unknown_30 / unknown_34 | preserve(NEVER 修改;无生成规则) | preserve(同左) | §8.1;⚠️ 重建时 chk.rs 写家族代表值(E005 重建头逐字段=原文件即以此验证) |
| 0x3C family_flag | preserve(观测 0) | preserve(重建 regenerate=0xA0) | E013 |
| B 0x40 type | n/a | preserve(重建 regenerate=0x00) | patch_block_header |
| B 0x41 bits/4 | n/a | recalc(尺寸/位深变化时) | patch_block_header L291 |
| B 0x42 log2W / 0x43 log2H | n/a | recalc(尺寸变化时) | L292–293 |
| B 0x44 depth_type | n/a | recalc(位深变化时) | L294 |
| B 0x45=0x01 / 0x46=0x04 | n/a | preserve(重建 regenerate) | patch_block_header;观测恒定 |
| B 0x47 | n/a | recalc(随位深联动 0x25/0x45) | L297 |
| B 0x48/4C/50/54(56/32/40/40) | n/a | preserve(重建 regenerate) | 观测恒定(E013) |
| B 0x58 name | n/a | patch(name_to_8) | L536–537 |
| B 0x60..0x9F(除 0x98) | n/a | preserve | E015 观测全 0 |
| B unknown_98 | n/a | preserve | E015(46/47=0x01) |
| A 0x28/0x2C 同上、meta[0x00]=0x40 | preserve(重建 regenerate=0x40) | n/a | default_meta |
| A meta[0x08] | recalc(=meta_base) | n/a | L490 |
| A meta[0x18] 名字 | patch(name_to_8) | n/a | L476 |
| A meta 其余(0x04、0x0C、0x10、0x14、0x58…) | preserve(NEVER 修改) | n/a | E012 |
| pixel region | regenerate(索引内容来自文档模型) | regenerate | pack_block/直拷 |
| palette | regenerate 或 preserve(只改色即 preserve 其余项) | regenerate 或 preserve | palette_edit 测试(E009) |
| tail[0x00]/[0x04](=0x28/0x2C) | preserve(从原 tail;重建 regenerate=0x28/0x2C) | preserve(重建 regenerate) | 恒定,两家族 tail[0..1]=0x28/0x2C 不变 |
| A tail[0x08..0x18] | recalc(meta_base+{0,8,0xC,0x10,0x14}) | n/a | L496–500 |
| B tail 全表 | n/a | preserve(重建 regenerate=固定值表) | default_font_tail |
三类典型场景:
- 只改调色板:pixel/tail/头全 preserve,仅写调色板 4B×改动项(§24 精确保留模式;E009:索引不被重量化)。
- 改尺寸/位深(Family B):recalc 0x08/0x0C/0x10/0x41/0x42/0x43/0x44/0x47,regenerate 像素区/调色板/tail;其余 preserve 或按固定表 regenerate(§25 重建模式;E008 位深转换往返测试)。
- 从零生成(新文件):无 preserve 来源,unknown 字段按家族代表值生成(A 头
default_header:0x14=7、0x20=6、0x30=0x042C4100、0x34=0x056DC9F8;B 头default_font_header:0x28/0x2C=0x50、0x30=0x0AAE1BA0、0x34=0x08BF08CC、0x3C=0xA0、0x40..0x54 按 patch_block_header+常量表、0x48..0x54=56/32/40/40;A metadefault_meta=0x40;tail 按家族表)。生成结果对 unknown 字段不保证与"原版工具"一致(0x30/0x34 语义未知),须在输出标注。字节级锚点:default_header/default_meta/default_tail的常量字节以 §29.1 三向量的实测 dump 为准(变量槽位按本表 recalc 规则回填)。
24. 精确保留模式(Exact Preservation Mode)
定义:打开 → 解析 → 只改某字段 → 只写必要字节 → 未知字段原样 → 保存。文件其余部分逐字节不变。
流程:
- 读入全部字节;Parser 解析并保存 raw_sections(header/meta/tail 原始字节);
- 编辑器只修改 ChkDocument 的逻辑字段(如 palette[17]);
- 保存时对目标字段就地 patch:只写被修改的字节区间,其余字节从原文件拷贝;
- Validator 重跑(§26)确认一致。
理想结果示例(只改 Palette[17],Family A):
- 改动字节:
file[palette_offset + 17*4 : palette_offset + 17*4 + 4]共 4 字节(新 RGBA);文件其余全部字节(263352−4 字节)逐字节不变,包括 header 全部、0x30/0x34、meta、tail、像素区全部索引;文件长度不变,无需重算任何指针(palette 大小不变,布局不变)。
实现要点:
- 保存 raw header / raw meta / raw tail,序列化时整体拷贝、仅 patch 已知可变字段(chk.rs serialize 即此模式:头只 patch 0x08/0x0C/0x10/0x28/0x2C,meta 只 patch 0x08 与名字,tail 只 patch A 的后 5 项);
- 禁止在精确保留模式下触发重量化/重新编码像素(E009:改调色板不得改变索引);
- 未知字段(unknown_30/34、meta[0x04]、0x98 等)原样透传,不做任何"规范化";
- 若只改了不影响布局的字段,输出长度必须 == 原长度(parse_all 系列断言的长度不变性即此检查)。
25. 重建模式(Rebuild Mode)
定义:不基于原文件字节,而是从 ChkDocument 重新生成整份文件。适用:改尺寸 / 改位深 / 转家族 / 重新量化。
流程:
ChkDocument
→ Generate Pixel Region(pack_block / swap34 行主序)
→ Generate Palette(量化产物或文档模型中的调色板)
→ Generate Metadata(A: default_meta + 名字;B: 无)
→ Calculate Offsets/Sizes(§7 公式)
→ Update Pointers(§8 公式)
→ Generate Tail(A: 0x28/0x2C + meta_base 序列;B: 固定值表)
→ Serialize(header → pixels → palette → meta → tail)
→ Validate Output(§26)
→ CHK bytes
- 入口:
from_rgba/from_rgba_block8/from_rgba_font(重建即"RGBA→量化→重建布局")。 - 与精确保留的边界:重建后的文件不承诺与原文件逐字节一致:unknown 字段取家族代表值、Family A 的 meta 非零观测字段(E012)在
default_meta中为 0、0x30/0x34 无生成规则。重建正确性的判据是:Validator 通过 + parse 回读后逻辑内容(尺寸/位深/kind/调色板/索引)一致(E008/E009 测试模式)。 实现能力分级(Implementation Scope,v2.3 增补)——明示各能力层级在当前证据下的可靠度,防止"能生成文件"被误读为"与原生等价":
| Level | 能力 | 可靠度 | 依据 |
|---|---|---|---|
| L1 | Parser(CHK→ChkDocument) | ✅ 可直接实现 | 本文即为规范;48/49 byte-exact |
| L2 | Decoder(CHK→RGBA) | ✅ 可直接实现 | E003/E031 像素级 diff=0 |
| L3 | Validator | ✅ 可直接实现 | §26 检查项 + §30 往返测试 |
| L4 | Exact Preservation 编辑器 | ✅ 可直接实现 | §24;E009 索引不变性 |
| L5 | Family B Encoder(RGBA→B8/B4) | ✅ 证据充分 | E008 位深转换往返;语料 byte-exact |
| L6 | Family A Encoder(RGBA→A) | ⚠️ Rebuild/兼容模式 | 0x30/0x34 与 meta 非零字段无生成规则(E012/E028);产物过 Validator 但不承诺与"原版工具"逐字节一致 |
| L7 | 改尺寸后"原生等价"重建 | 🔴 不支持 | unknown 字段平台语义未证实;作此声明将超出当前证据 |
| L8 | 跨家族转换(A↔B) | ⚠️ 仅结构层可行 | 像素层可转,语义字段按 L6 处理;宣称需实机验证 |
后续验证待办(v2.3 登记):①独立实现交叉测试——仅凭本文用第二语言实现 Parser,对 49 样本与 chk.rs 输出逐字节比对(即开头目标句的终审);②FONTSJP1 byte-exact 缺口补齐(§29 表②)。
- 模式选择规则表:
| 用户操作 | 模式 | 原因 |
|---|---|---|
| 改 1..N 个调色板色 | Exact Preservation | 布局不变;索引必须不动(E009) |
| 改名字 | Exact Preservation | 仅 patch meta[0x18](A)/ 0x58(B) |
| 改像素内容(同尺寸同位深) | Exact Preservation(仅重写像素区)或 Rebuild | 同尺寸下布局不变 |
| 改尺寸 | Rebuild | 偏移/tail/0x42/0x43 全部变化 |
| 改位深 | Rebuild | palette 大小、0x41/0x44/0x47、块几何变化(E008) |
| Family A ↔ B 转换 | Rebuild | 头大小/布局完全不同 |
| 重新量化(换调色板) | Rebuild | 索引与调色板同时重建 |
26. 校验器规范(Validator Specification)
输出格式(样例):
CHK VALID
Family: A (PS2 square, 8-bit)
Bit Depth: 8
Resolution: 512 x 512
File Size: 263352 bytes (0x000404B8)
Magic: PASS (V01)
Header FileSize: PASS (V03)
Data End: PASS (V04, V05)
Family Flag: PASS (V06)
Geometry: PASS (V07, V08, V09)
Pixel Region: PASS (V11)
Palette: PASS (V12)
Block/Nibble: N/A (Family A)
Metadata: PASS (V21)
Tail: PASS (V18)
Pointers: PASS (V19)
Constants: PASS (V20)
Validation: 24/24 PASS
失败时每项输出 FAIL (Vxx, CHK_ERR_*)。
检查项(24 项):
| # | 名称 | 判定规则 | 错误码 | 级别 |
|---|---|---|---|---|
| V01 | Magic | 前 4 字节 == 78 65 74 00 | CHK_ERR_INVALID_MAGIC | ERROR |
| V02 | Actual Size | 长度 ≥ 0x40+0x1C | CHK_ERR_TRUNCATED | ERROR |
| V03 | Header FileSize | le32(0x08) == 实际长度 | CHK_ERR_INVALID_SIZE | ERROR |
| V04 | Data End | le32(0x0C) == 长度−0x1C | CHK_ERR_INVALID_SIZE | ERROR |
| V05 | Data End 复本 | le32(0x10) == le32(0x0C) | CHK_ERR_INVALID_SIZE | WARNING |
| V06 | Family | 0x3C==0(A,且方形试探通过)或 ==0xA0(B) | CHK_ERR_UNSUPPORTED_FAMILY | ERROR |
| V07 | W/H > 0 | A: isqrt 反推 >0;B: 2^(0x42&0x0F)×2^(0x43&0x0F) >0 且 == texels | CHK_ERR_INVALID_GEOMETRY | ERROR |
| V08 | Pixel Size | A: (palette_offset−0x40)==WH 且为完全平方数;B: 像素区字节数==block_rowsblock_cols*128 | CHK_ERR_INVALID_GEOMETRY | ERROR |
| V09 | Palette Size | A: 1024;B: 1024(8bit)/64(4bit),且与 0x41/0x44 判定一致 | CHK_ERR_INVALID_PALETTE | ERROR |
| V10 | Palette Offset | palette_offset == 长度−0x1C−(meta?)+公式值,且 > header_size | CHK_ERR_INVALID_OFFSET | ERROR |
| V11 | Pixel Offset | ==header_size(A:0x40 / B:0xA0) | CHK_ERR_INVALID_OFFSET | ERROR |
| V12 | Block Alignment | B: W%block_w==0 且 H%8==0(chk.rs L348 报 block .chk size not block-aligned) | CHK_ERR_ALIGNMENT | ERROR |
| V13 | Nibble Alignment | B 4bit: 每行 32 texel == 16 字节,无奇数截断 | CHK_ERR_ALIGNMENT | ERROR |
| V14 | Index Bounds | 所有索引 < nb(to_rgba 的 fallback 只是宽容行为,不是格式保证) | CHK_ERR_INVALID_OFFSET | WARNING |
| V15 | Pow2 Dims | B: W/H 为 2 的幂(validate_block_dims L267) | CHK_ERR_INVALID_GEOMETRY | ERROR(编码侧) |
| V16 | Min Dims | B: 8bit W≥16 / 4bit W≥32 / H≥8(L272) | CHK_ERR_INVALID_GEOMETRY | ERROR(编码侧) |
| V17 | Tail Bounds | tail_offset == 长度−0x1C ≥ palette 末尾 | CHK_ERR_INVALID_OFFSET | ERROR |
| V18 | Tail Table | B: 7 项 == {0x28,0x2C,0x3C,0x48,0x4C,0x50,0x54};A: 后 5 项 == meta_base+{0,8,0xC,0x10,0x14} 且首两项 == 0x28/0x2C | CHK_ERR_POINTER_MISMATCH | WARNING |
| V19 | Pointer Bounds | A: 0x28/0x2C ∈ 文件范围且 == meta_base+0x10(E019);meta[0x08]==meta_base;B: 0x28/0x2C==0x50 | CHK_ERR_POINTER_MISMATCH | WARNING |
| V20 | Constant Fields | 0x14==7、0x20==6;B: 0x40==0、0x45==0x01、0x46==0x04、0x47∈{0x25,0x45}、0x48==56、0x4C==32、0x50==40、0x54==40 | CHK_ERR_CONST_FIELD_MISMATCH | WARNING |
| V21 | Metadata Bounds | A: meta 区 [palette_offset+1024, +92) 恰在 tail 之前;meta[0x00]==0x40 | CHK_ERR_INVALID_OFFSET | ERROR |
| V22 | No OOB | palette_offset + palette_size (+meta+tail) == file_size,任何派生区不越界 | CHK_ERR_TRUNCATED | ERROR |
| V23 | Name Encoding | 名字区 8 字节可安全按 NUL 截断(≥1 个 NUL 或恰 8 字节) | CHK_ERR_NAME_ENCODING | INFO |
| V24 | Bit Depth 一致性 | B: 判定①(0x44&0x08)与判定②(0x41)与调色板大小三者一致;0x44∈{0x04,0x08} | CHK_ERR_UNSUPPORTED_BIT_DEPTH | ERROR |
对照说明:chk.rs 现有实现只做 §5.2 的 4 项强校验 + 几何/对齐校验;V05/V14/V18/V19/V20/V23/V24 是本规范对 Validator 的要求,读取端可以宽容、写入端必须执行(与
TxdError::Chk错误信息风格保持一致)。
27. 修复规范(Repair Specification)
Strict 模式:任何 ERROR 即拒绝打开/保存(默认)。 Repair 模式:只修复有明确规则的项目,其余原样并警告。
| 类别 | 项目 | 处理 |
|---|---|---|
| ✅ 可自动修复 | header file_size (0x08) 与实际不符 | recalc 后修复(有公式) |
| ✅ 可自动修复 | data_end (0x0C/0x10) 错误/不一致 | recalc |
| ✅ 可自动修复 | Family A tail 后 5 项、meta[0x08](meta_base 序列可由长度公式重推) | recalc |
| ⚠️ 只警告不修 | offset 越界(0x28/0x2C 指向文件外) | 不许盲修——可能指向未知子结构;必须警告人工确认 |
| ⚠️ 只警告不修 | tail 常量表不匹配(B) | 按证据可重写为固定表(default_font_tail),但先警告:可能是有意变体 |
| ⚠️ 只警告不修 | 0x14/0x20 等常量字段不匹配 | 警告 + preserve(语义未知,可能承载版本信息) |
| ❌ 拒绝 | palette 长度不足(< nb*4) | ERROR:CHK_ERR_INVALID_PALETTE,不修 |
| ❌ 拒绝 | magic 错误 | ERROR:CHK_ERR_INVALID_MAGIC,不修 |
| ❌ 拒绝 | 像素区字节数 ≠ W*H / texels | ERROR:CHK_ERR_INVALID_GEOMETRY,不修 |
| ❌ 拒绝 | unknown 字段 | NEVER 自动修改(无语义,修了等于制造新文件) |
28. 错误分类(Error Classification)
级别定义:
- ERROR:文件不可能按规范解析/写入,必须中止。
- WARNING:偏离观测规律但仍可处理,输出警告继续。
- INFO:纯提示(如名字截断)。
错误码表(与 chk.rs 现有错误信息风格对照,TxdError::Chk(String)):
| 错误码 | 触发 | chk.rs 现有对应信息 |
|---|---|---|
| CHK_ERR_INVALID_MAGIC | 魔数错 | bad magic (expected b"xet\0") |
| CHK_ERR_TRUNCATED | 文件过小/像素区不完整 | file too small (N bytes) |
| CHK_ERR_INVALID_SIZE | 0x08/0x0C/0x10 与实际不符;B 几何与 texels 不符 | header file size N != actual M / header data end N != M / block .chk geometry inconsistent: header WxH, texels T |
| CHK_ERR_INVALID_OFFSET | palette/pixel/meta/tail/指针越界 | (Validator 层新增) |
| CHK_ERR_INVALID_PALETTE | 调色板长度不符 | expected N palette colors, got M |
| CHK_ERR_INVALID_GEOMETRY | 非方形(A 编码)/ 非块对齐 / 尺寸非法 | chk geometry must be square for the 8-bit format; got WxH / block .chk size not block-aligned (WxH, block Bx8) / chk 尺寸需为 2 的幂(…);请求 WxH / chk 尺寸过小:… |
| CHK_ERR_UNSUPPORTED_FAMILY | 0x3C 非法且非方形 | unrecognized .chk pixel layout (pixel region N bytes) |
| CHK_ERR_UNSUPPORTED_BIT_DEPTH | 0x44/0x41/调色板大小互相矛盾 | (Validator 层新增;parse 现按 0x44&0x08 宽容二分) |
| CHK_ERR_ALIGNMENT | 块/半字节对齐失败 | block .chk size not block-aligned (WxH, block Bx8) |
| CHK_ERR_POINTER_MISMATCH | tail/0x28/0x2C/meta[0x08] 与公式不符 | (Validator 层新增) |
| CHK_ERR_CONST_FIELD_MISMATCH | 0x14/0x20/B 常量字段不匹配 | (Validator 层新增) |
| CHK_ERR_NAME_ENCODING | 名字区异常 | (Validator 层新增;INFO 级) |
| CHK_ERR_BUFFER_TOO_SMALL | 输入 RGBA 不足 wh4 | rgba buffer too small |
| CHK_ERR_PIXEL_COUNT_MISMATCH | pixels 长度 ≠ w*h | expected N pixels, got M |
29. 测试语料(Test Corpus,SHA-256)
SHA-256 计算方式:PowerShell Get-FileHash -Algorithm SHA256,2026-09-04 对全部 49 个 .chk(根目录 4 + TXD\ 45)+ 参考图实测,禁止编造。
PixelSize/PaletteSize/MetaSize/TailSize 列的推导过程(非猜测):
- Family A:
PixelSize = file_size − 0x40 − 1024 − 92 − 28;PaletteSize=1024;MetaSize=92;TailSize=28。核验:263352−64−1024−92−28 = 262144 = 512² ✔。 - Family B:先由 header 0x44/0x42/0x43 读出 bits/W/H,得
nb(8bit=256/4bit=16);PaletteSize = nb*4;MetaSize = 0;TailSize = 28;PixelSize = file_size − 0xA0 − nb*4 − 28;并交叉核验PixelSize × (8/bits) == W×H(texels)。脚本化全量核验结果FORMULA_CHECK_ALL_OK=True(49/49 通过,E017)。
| Filename(语料内相对路径) | SHA-256 | Platform | Family | Width | Height | BitDepth | PixelSize | PaletteSize | MetaSize | TailSize | Decode Status | Encode Status | Roundtrip Status |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| FONTSJP.CHK | 926a9d5d1a3d50c1c13d5eb80b476cd3003a28a15fcfc0ac4f13a022d5727cce | PSP(推断) | B4 | 512 | 1024 | 4 | 262144(texels=524288) | 64 | 0 | 28 | OK | OK(serialize) | OK(byte-exact) |
| FONTSJP1.CHK | 07b7363812006756d6a223259dc9947ba79fc78e3e78a5ab8bd89fa481c1ace9 | PSP(推断) | B8 | 512 | 512 | 8 | 262144(texels=262144) | 1024 | 0 | 28 | OK(结构核验①) | 未逐样本验证② | 未逐样本验证② |
| LOADSC0.CHK | c93ce323647c96d47bf29e9b0cd17f641134984d149665a0b06f42cc4260d324 | PS2 | A | 512 | 512 | 8 | 262144 | 1024 | 92 | 28 | OK | OK(serialize) | OK(byte-exact) |
| LOADSC3.CHK | 3f1badbaaf3dd0d9ed94d6d2af980a29c7f25ffb2b3262d38a675f8361542f12 | PS2 | A | 512 | 512 | 8 | 262144 | 1024 | 92 | 28 | OK | OK(serialize) | OK(byte-exact) |
| TXD\CAPCOM.CHK | e476dac66e7ee02ba010f5c96b6b3114e81e139c1d75472b26ed7bb4cb62ad83 | PSP(推断) | B8 | 512 | 512 | 8 | 262144(texels=262144) | 1024 | 0 | 28 | OK | OK(serialize) | OK(byte-exact) |
| TXD\INTRO1.CHK | 6d241c0c7a3b7862b6c3f7961cfd9483799c8ca4f9d6192b660bc72e659b837b | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | OK(serialize) | OK(byte-exact) |
| TXD\INTRO2.CHK | 45eb9bf128eddfd0e05c154c5d1a5217a5cdd2e819191ea58170ecffc91ac6e6 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\INTRO3.CHK | aa49d72d65b2cf48a17bc41c20c27b3899b726094227a350a2524e644eae9c54 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\INTRO4.CHK | 3196dfdbfafb90033a2d593ca3ab9636d9265882c7219863859619687e9241a8 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC0.CHK | 959e6ad2d9f1fb3b59b3d739df8547529a1fa46409b4ffa34875bcb20c6cc4fa | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC1.CHK | f8369a8e9fdabb95841cf3b7d16d03fa8485c8d9e074da7f9137eee469da832c | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC10.CHK | ff7158b8acdf7387bd641c87af642f749354071b111647cfa2abeb1721be9ff5 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC11.CHK | 16522252d0c12784dd9c5c8ce894af0d1e3aacfb4f9c897605b2fc6a9e1ec05a | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC12.CHK | 0bca0b1b3200dd0b805efaae4218ad311820194a191adcb3873c06c601d9eed4 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC13.CHK | cf43e50cecebc6ca37148e5ba2708e9604f7fa5a5c949a54b8912e907dbdb249 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC14.CHK | 6c8dce3a0be54c68186f51d08519c06333ebf1add115237042e8ac5a08c97984 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC15.CHK | c266492d3fb746bd32ecaa3c02499644c1af6a943a9f2762c15aa0c81ed4b6ff | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC16.CHK | 09017cd5460f5203e6d23f7362a168bfc3dc45e8651a95b682bf18ce8fc039ab | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC17.CHK | ff383c7f28c70430395819dc15a3c7c18428a8da5478ea3b7b63d5d376dbeeda | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC18.CHK | 8de1608bf74c95cefd5bddd4d9593bbe40abf314811328829e46aa3ef2a30666 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC19.CHK | fbcd3aed8d90c383eb63426d3444cad951e7a2f6258e82a8ee46a3db00c203ec | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC2.CHK | 73cae31bdcd82fe5e36b67bc410cf9a2b8985e5e0f533e1cff8f09a1723a45ba | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC20.CHK | 4a5fa7e361b26417f5ca5ec7beb525742f383c77424c0af0215e1761b369c980 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC21.CHK | 2ef16f6c777d35ac0358888bf099e0f38061e1ee14be111869da9d9a1ff11b63 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC22.CHK | 677ed2cf5c8cd775ba11f7d8d4ded7bbbfd32700732594bc182083696559236e | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC3.CHK | 753f792acd772b563d7e31a7b6a0debc9046de2549fef44a227789d538e970e8 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC4.CHK | 4da87275215ab230432f468eb7ab26ee69c8e424c43c7bb6b07b6b9da0e74687 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC5.CHK | 77f2750035e3fd463c32d984347117fc5e19093d9787035201d4a32e0b521f60 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC6.CHK | 8ab0515da0730d1964437b8d151a38d363ef4038732bd887a40b32724144e484 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC7.CHK | 20d730fe857e9ae24ce6c426690fc59d0aaf07fece81ee74436e4c06dbea736f | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC8.CHK | 915beadbf5743ea9631ebac64173d4419ad5c4717c7588ae5a741564ec71e254 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\LOADSC9.CHK | ef9cd5a1388808f6cb47daa9c2ffc37a48fa3dc36797a6ca77b48d9e65b12584 | PSP(推断) | B4 | 512 | 512 | 4 | 131072(texels=262144) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\MEMCARD.CHK | 4f80fad01cdd1b32f504767720a5c6baf4c33cc34932fc3484eb12baed1cd0e6 | PSP(推断) | B4 | 512 | 256 | 4 | 65536(texels=131072) | 64 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\MPLOAD0.CHK | f1b929024c790f893ba09473481a51adf068ad39d80e8d537265049479664cf5 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\MPLOAD1.CHK | d6132a0251c9aab508a91f1bbcf22126739e5870ddc5658e85d3d2e75d13ece1 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\MPLOAD2.CHK | c9c3a2e1d254a3420d2a29fe05ba04e42a6f2ae4560834a8b7e33fcb722bf32b | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\MPLOAD3.CHK | d8d3e1633185b13179d2e8bc750a60f87c831440f4f58d6b87fb55511b5de1c8 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\MPLOAD4.CHK | 26047370ba73d9d0575d035d498bdef98e854fc12e4088ff2680f48b7c8f22a7 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\MPLOAD5.CHK | 7e159d87e14c14c40408980aa7251a88b8495e414dcf85641e0c902b87e39899 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\MPLOAD6.CHK | a805d9f581e45a7543d98cd9d26b89c10dab2d4c6fad1c972b866a431dc269ef | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\MPLOAD7.CHK | e00ea256b9e3ae429ebe3b19bc302745a71558cdcb51c9b3c1af040be6e0bb7b | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\NEWS.CHK | 473606dd49e4826cb2865c4ba649fe30a22694552614d585f124d5125082afd7 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\NEWS2.CHK | e446521f64e64c7231cdd61d88228521da367029d9e2a6f3157b2180e277e71c | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\NEWS3.CHK | e9f5b61b984747e2fd17ce41ad7b8da4fc85ea50007b16740d3c0ab00d27c4e9 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\SCEJLEGALJ.CHK | 8d3cf1ed559c975615d332d50a40943da8c52cfb1fc9b665a0ee1391d23564fb | PSP(推断) | B8 | 512 | 512 | 8 | 262144(texels=262144) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\SPLASH1.CHK | 4666dba37ec2584a6e07a5e243911d845e872388de96a6eb532b20a032bf3d64 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\SPLASH2.CHK | 7705165420d349ea65651cd3ca77608b88759dfcceec8b45e06eff861148114a | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\SPLASH3.CHK | c93f0044356d08490685c2e9c1ac89a0ddbcb7efbafe0319c226fa8f56555e89 | PSP(推断) | B8 | 512 | 256 | 8 | 131072(texels=131072) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
| TXD\WARNING.CHK | dfe6547b0f7f739fde4eac65e71c75f84e672702391cd78820b2b55d2897249b | PSP(推断) | B8 | 512 | 512 | 8 | 262144(texels=262144) | 1024 | 0 | 28 | OK | 未逐样本验证② | 未逐样本验证② |
参考图(SHA-256 实测):
| 文件 | 大小 | SHA-256 | 用途 |
|---|---|---|---|
| loadsc0.png | 95472 | bfb3ea4900f0907046042bf0bc2b763b396aa111a5ec46b975c5581e15e36406 | LOADSC0.CHK 解码 diff=0 的参考(E003) |
| fontjp.png | 135439 | aa81cb65e8f59057072fd1650375667c565a4f58e3eea56e0f5e3445651dc3dc | FONTSJP.CHK 参考图 |
| decode_loadsc0.png | 308869 | 13d40b14a27a0a59cea1fad779519e41a74acaa5c85b1175dda322dfba883187 | 解码过程产物 |
注①:FONTSJP1.CHK 未被 Rust 测试套件覆盖(parse_all_family_b_in_dir 只扫 TXD 目录);其家族/尺寸/位深/名字(B8 512×512 "fontjp")由本次整理对 header 字段(0x3C/0x41/0x42/0x43/0x44/0x58)的结构核验得出,且文件长度被 §7.2 公式精确解释(E017)。
注②:"未逐样本验证"指未为该样本单独运行/记录往返测试。补充事实:批量测试 parse_all_family_b_in_dir(chk.rs L877–879)的断言体实际对 TXD 目录每个 .chk 断言 serialize() 输出逐字节等于原文件且长度不变,即该测试每次运行即覆盖目录内全部样本的 byte-exact(附录 C-5 记录该口径差异)。既有文档的正式口径为"ok=4 bad=0(LOADSC0/LOADSC3/CAPCOM/INTRO1)"(E001)。
注②-bis(2026-09-04 复测后覆盖口径核算,E032):原生 49 个中 48 个已有 byte-exact 实测:TXD 45 个 B(parse_all_family_b_in_dir 批量断言)+ 根目录 LOADSC0/LOADSC3(E001 诊断 + LOADSC0 专项测试)+ FONTSJP(roundtrip_fontjp_is_byte_exact);唯一无 byte-exact 实测的是 FONTSJP1(根目录、B8 512×512),其结构/几何/调色板/0x98 已由普查核验(E024–E030)。另:reverse_diag::byte_exact_roundtrip 对 TXD 目录的枚举使用大小写敏感扩展名过滤(== Some("chk"),实际文件为 .CHK 大写),故该诊断测试结构性只覆盖 4 个显式路径(本次实测 ok=4 bad=0,E032)。
注③:⚠️ 同名不同家族:根目录 LOADSC0.CHK(263352 B,Family A/PS2)≠ TXD\LOADSC0.CHK(132284 B,Family B/PSP 推断);LOADSC3 同理(E023)。引用样本时必须写明所在目录(根目录或 TXD\)。
注④:样本计数现状:TXD\ 实测 45 个 .chk;旧文档写"47 个 Family B",47 = 全语料 Family B 总数(TXD 45 + 根目录 FONTSJP/FONTSJP1),两数并存不矛盾,但"TXD 目录 47 个"的旧表述与现状不符(E022,附录 C-2)。
注⑤(普查复核,2026-09-04,E024–E030,附录 E):独立普查脚本对本表全部 49 行的 Width/Height/BitDepth/PixelSize/PaletteSize 逐项复核,49/49 与本表一致(几何/布局公式零误差,E026);Family 判定与 §3 规则一致(A=2、B=47)。测试状态刷新:B 各行 Decode Status 的"OK"由"结构核验"升级为"结构核验 + 全量几何公式核验(E026)";FONTSJP1 的 Encode/Roundtrip 仍未逐样本 byte-exact(覆盖口径见注②-bis),其余 48/49 均有 byte-exact 实测。语料完整性:全树另见 _chkprobe\ 下 FONTSJP/LOADSC0 两份副本,SHA-256 与原件一致,不计入样本(E024)。
29.1 最小测试向量(v2.3 新增;informative,供独立实现自检)
三个最小向量直接摘自语料原生样本(SHA-256 与 §29 表逐一致;排除 _chkprobe 副本与 _chk_verify 重建件)。任何语言的实现应能:①解析三文件得到相同的家族/几何/位深判定;②重新序列化得到逐字节相同输出。提取脚本:docs\tools\chk_test_vectors.py(可复现)。
向量 1 — Family A · 512×512 · 8bit(根目录 LOADSC0.CHK,263352 B,SHA-256 c93ce323647c96d47bf29e9b0cd17f641134984d149665a0b06f42cc4260d324)
0000: 78 65 74 00 00 00 00 00 B8 04 04 00 9C 04 04 00
0010: 9C 04 04 00 07 00 00 00 00 00 00 00 00 00 00 00
0020: 06 00 00 00 00 00 00 00 50 04 04 00 50 04 04 00
0030: 00 41 2C 04 F8 C9 6D 05 00 00 00 00 00 00 00 00
palette first 32B @ 0x40040: F6 D4 6B FF FA D6 6A FF FA DA 6E FF FA D6 66 FF FA DA 6A FF FA D2 66 FF FA D2 62 FF FA D6 6E FF
tail 28B @ 0x4049C: 28 00 00 00 2C 00 00 00 40 04 04 00 48 04 04 00 4C 04 04 00 50 04 04 00 54 04 04 00
期望:family=A、W=H=512、bits=8、0x28/0x2C=0x00040450=meta_base+0x10、0x30=0x042C4100、0x34=0x056DC9F8;解码 RGBA 与 loadsc0.png diff=0(E003)。
向量 2 — Family B 8bit · 512×256(TXD\LOADSC0.CHK,132284 B,SHA-256 959e6ad2d9f1fb3b59b3d739df8547529a1fa46409b4ffa34875bcb20c6cc4fa)
0000: 78 65 74 00 00 00 00 00 BC 04 02 00 A0 04 02 00
0010: A0 04 02 00 07 00 00 00 00 00 00 00 00 00 00 00
0020: 06 00 00 00 00 00 00 00 50 00 00 00 50 00 00 00
0030: A0 1B AE 0A CC 08 BF 08 00 00 00 00 A0 00 00 00
0040: 00 02 09 08 08 01 04 25 38 00 00 00 20 00 00 00
0050: 28 00 00 00 28 00 00 00 6C 6F 61 64 73 63 30 00
0090: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
palette first 32B @ 0x200A0: 00 00 00 FF 01 01 00 FF 04 01 00 FF 03 03 03 FF 08 01 01 FF 05 04 02 FF 0B 01 02 FF 05 04 05 FF
tail 28B @ 0x204A0: 28 00 00 00 2C 00 00 00 3C 00 00 00 48 00 00 00 4C 00 00 00 50 00 00 00 54 00 00 00
期望:family=B、W=512、H=256、bits=8、name=loadsc0、0x30=0x0AAE1BA0、0x34=0x08BF08CC、0x41=0x02(判定②)↔ 0x44=0x08(判定①)、0x42=0x09=log2(512)、0x43=0x08=log2(256)、0x47=0x25、0x48..0x54=56/32/40/40、0x98=0x01(原生观测;C-11:重建件为 0)、0x60..0x97=0。
向量 3 — Family B 4bit · 512×1024(FONTSJP.CHK,262396 B,SHA-256 926a9d5d1a3d50c1c13d5eb80b476cd3003a28a15fcfc0ac4f13a022d5727cce)
0000: 78 65 74 00 00 00 00 00 FC 00 04 00 E0 00 04 00
0010: E0 00 04 00 07 00 00 00 00 00 00 00 00 00 00 00
0020: 06 00 00 00 00 00 00 00 50 00 00 00 50 00 00 00
0030: A0 33 AE 0A 8C F2 BE 08 00 00 00 00 A0 00 00 00
0040: 00 01 09 0A 04 01 04 45 38 00 00 00 20 00 00 00
0050: 28 00 00 00 28 00 00 00 66 6F 6E 74 6A 70 00 00
0090: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
palette first 32B @ 0x400A0: FD FD FD F5 FF FF FF 67 00 00 00 00 FF FF FF BC 6D 6D 6D F9 FF FF FF 00 FF FF FF 8B FF FF FF E9
tail 28B @ 0x400E0: 28 00 00 00 2C 00 00 00 3C 00 00 00 48 00 00 00 4C 00 00 00 50 00 00 00 54 00 00 00
期望:family=B、W=512、H=1024、bits=4、name=fontjp、0x30=0x0AAE33A0、0x34=0x08BEF28C(FONTSJP 单独值组,E028)、0x41=0x01 ↔ 0x44=0x04、0x42=0x09、0x43=0x0A=log2(1024)、0x47=0x45;解码与 fontjp.png diff=0(E031)。
备注:B 头
0x40..0x5F在同类(几何×位深)内逐字节恒定(E029);0x45=0x01、0x46=0x04恒定;0x47随位深(本语料 8bit=0x25 / 4bit=0x45);A 头0x14=7、0x20=6恒定,0x08/0x0C/0x10/0x28/0x2C随文件重算(§23)。
30. 往返测试(Round-trip Tests)
| 测试 | 路径 | 无损性 | 通过判据 | 既有证据 |
|---|---|---|---|---|
| 测试 A:序列化往返 | CHK → Parse → Serialize → Binary Compare | 完全无损(未编辑文件) | 输出与输入逐字节相等(含长度) | E001/E002:LOADSC0/LOADSC3/CAPCOM/INTRO1 正式记录 ok=4 bad=0;FONTSJP 专项测试;TXD 目录批量断言(注②);2026-09-04 复测通过(E032,覆盖口径 48/49 见 §29 注②-bis) |
| 测试 B:解码往返 | CHK → Decode(RGBA) → Encode → Decode | 索引层无损;RGBA 层无损(当索引→颜色映射唯一时) | 两次解码 RGBA diff=0 | E003:LOADSC0 与 loadsc0.png diff=0(512×512);Family B 侧 to_rgba→pack/unpack 由测试 B 覆盖(from_rgba_roundtrip 等合成测试) |
| 测试 C:编码往返(有损) | PNG → Encode → Decode → PNG | 量化有损 | 记录 Pixel Diff / Max Channel Error / Different Pixel Count;透明像素(A==0)必须精确往返(index 0 策略) | 尚无正式记录的逐样本报告表——作为 Validator 附属输出(TBD:需要工具实现输出报告) |
- 测试 A 是格式规范正确性的最终判据("重新写回来和原文件逐字节一致",见附记);测试 B 检验 decode/encode 互逆(swap34 自反、pack/unpack_block 互逆,E007/E002);测试 C 的有损指标只描述量化质量,不影响格式合法性。
- 2026-09-04 普查轮实测(E032):
cargo test -p txd-format -- --include-ignored(workdir 为本仓库根目录)→ 75 tests:74 passed / 1 failed。唯一失败为txd::fonts_diag::dump_fontgif(读 FONTS.TXD 时 panic:txd.rs:248 index out of bounds: len 1049029, index 16804789),属 TXD 侧诊断测试自身缺陷,与 CHK 无关;全部 CHK 测试通过(含 parse_all_family_b_in_dir、rebuilt_header_matches_real_family_b、preview_dims_match_for_all_family_b、from_rgba/palette/depth 往返等)。byte-exact 诊断复测ok=4 bad=0(LOADSC0/LOADSC3/CAPCOM/INTRO1)。附带发现:重建 B 文件(_chk_verify)的 0x60..0x9F 区域为 0(0x98=0),而原生 46/47 为0x98=0x01,rebuilt_header_matches_real_family_b的槽位比较不含该区域(附录 C-11)。
31. 证据与置信度体系(Evidence / Confidence)
9 节原文:置信度【✅ 原文保留】
| 项目 | 置信度 | 依据 |
|---|---|---|
| 魔数、容器头 0x08/0x0c/tail | ✅ 确证 | 解析实现 + LOADSC0/LOADSC3/TXD/*.chk 样例字节 |
| Family A 方形 8bit、bit3/4 交换 | ✅ 确证 | 实现 + LOADSC0 正常显示 |
| Family B 块状、log2 几何、0x58 名 | ✅ 确证 | 实现 + 47 个样例 |
| byte-exact roundtrip(全家族全 kind) | ✅ 强证据 | parse→serialize 对 LOADSC0/LOADSC3/CAPCOM/INTRO1 逐字节还原,ok=4 bad=0;2026-09-04 复测同结果,覆盖口径 48/49(E032) |
| 调色板字节序(RGBA)+ 索引交换还原原图 | ✅ 决定性 | 解析 LOADSC0.CHK 的 RGBA 与参考 loadsc0.png 逐字节 diff=0(512×512) |
| Family A 方形 / Family B 块 的像素布局+索引交换 | ✅ 强证据 | byte-exact 还原证实(非仅"看起来对");普查公式 49/49 零误差(E026)+ FONTSJP 全图 diff=0(E031) |
| Family A=PS2、Family B=PSP | ✅ 确证 | 用户确认 LOADSC0/3=PS2;PSP 专用工具 GTAStoriesTex 仅认 Family B;且 byte-exact 证实两家族均为索引纹理(注:其中 Family B=PSP 为强推断链 E010,非直接观测;此处"确证"指该证据链在语料内成立) |
| PS2 vs PSP 的字节序/通道交换差异 | ⚠️ 样例内未发现 | 两家族各自解码颜色正确 + byte-exact;但无 PSP/PS2 成对图无法最终定论 |
| Family A / B 头、meta、tail 各偏移字段 | ✅(原第 10 节逐字段表) | 用 LOADSC0/LOADSC3 + TXD*.chk 逐字节比对 |
家族特有值(0x30/0x34) | ⚠️ 逐样本可变 | 见 §5.1:A 两样本恒定,B 在两同名样本间不同(FONTSJP vs FONTSJP1);族内不恒定,语义未知 |
tail 偏移/地址表 | ✅ | 原生 CAPCOM/INTRO1/LOADSC0 逐项 byte-exact;普查 49/49 逐项精确匹配(E027) |
| 布局/几何公式(§7/§8:尺寸、指针、调色板定位) | ✅ 确证(49/49,零误差) | 普查逐文件核验(E026/E027)——由 2 样本手工核对升级为全语料实证 |
| B 位深双判定 + 调色板大小三方一致 | ✅ 确证(47/47) | 普查交叉验证(E033);C-4 关闭 |
| FONTSJP(B4 512×1024)解码正确性 | ✅ 强证据 | fontjp.png 逐像素 diff=0(E031,Python 独立复现 chk.rs to_rgba) |
0x30/0x34 排除面 | ⚠️ 排除性实证:非内容哈希、非布局指针/大小;语义仍 ❓ | E028(哈希与指针对假设全语料 0 命中) |
证据 ID 表(正文引用 (E0xx) 的定义)
| ID | 类型 | 内容 | 支持的结论 |
|---|---|---|---|
| E001 | Round-trip/样本验证 | byte_exact_roundtrip 诊断:LOADSC0/LOADSC3/TXD\CAPCOM/TXD\INTRO1 parse→serialize 逐字节相等,ok=4 bad=0 | 容器/指针/布局公式整体正确 |
| E002 | Round-trip/代码实现 | roundtrip_real_loadsc0_is_byte_exact、roundtrip_fontjp_is_byte_exact(chk.rs L886/L898) | A/B 两条 serialize 路径 byte-exact |
| E003 | 样本验证(决定性) | LOADSC0.CHK 解码 RGBA 与 loadsc0.png diff=0(512×512) | 调色板 RGBA 通道序、A 布局、swap34 正确 |
| E004 | 多样本交叉 | parse_all_family_b_in_dir(L843–883):TXD 目录每个 .chk parse+断言(含 serialize 逐字节与长度不变,count≥40) | B 布局普适 |
| E005 | 多样本交叉/代码实现 | rebuilt_header_matches_real_family_b(L1053–1111):重建 header 在 0x28/0x2C/0x30/0x34/0x3C/0x40/0x44/0x48/0x4C/0x50/0x54 逐字段==真文件(≥40 样本) | B 常量表与重建公式正确 |
| E006 | 多样本交叉 | preview_dims_match_for_all_family_b(L1011–1050):全部样本 to_rgba→raster→decode 尺寸一致 | 解码管线 |
| E007 | 代码实现/单元测试 | swap34_swaps_bits_3_and_4(L1299–1311):8 个向量 + 0..255 全域 involution | swap34 定义 |
| E008 | Round-trip | chk_depth_conversion_roundtrip(L1268–1290):CAPCOM 8bit→4bit→8bit 保持几何/kind | 位深转换(Rebuild 模式) |
| E009 | Round-trip | palette_edit_roundtrip_preserves_indices(L1250–1265):改调色板不重量化索引 | 精确保留模式(§24) |
| E010 | 外部工具行为 | GTAStoriesTex.exe(PSP 专用)只认 Family B | Family B=PSP(⚠️ 推断链) |
| E011 | 用户证言 | 用户确认 LOADSC0/LOADSC3 为 PS2 | Family A=PS2(✅) |
| E012 | Hex/实测(本次整理) | Family A meta 非零字节(0x04..0x07=0x00108249、0x0C=32、0x10=40、0x14=40、0x58=0x01);FONTSJP 调色板首 8 字节 FD FD FD F5 FF FF FF 67;LOADSC0 调色板首 16 字节 F6 D4 6B FF FA D6 6A FF FA DA 6E FF FA D6 66 FF | §12/§18 观测;alpha 中间值存在 |
| E013 | 多样本交叉/实测(49 文件) | 0x30/0x34 恰 3 组(A:0x042C4100/0x056DC9F8;B 常见:0x0AAE1BA0/0x08BF08CC ×46;FONTSJP:0x0AAE33A0/0x08BEF28C);0x14=7、0x20=6、B 0x28/0x2C=0x50 全语料恒定 | §5.1/§8;unknown 字段策略 |
| E014 | 多样本交叉/实测(49 文件) | tail 值集恰两组:B 47 文件={0x28,0x2C,0x3C,0x48,0x4C,0x50,0x54};A 2 文件={0x28,0x2C,0x40440,0x40448,0x4044C,0x40450,0x40454}(=meta_base 序列) | §8.3 两表 |
| E015 | Hex/实测(47 B 文件) | header 0x60..0x9F 除 0x98 外全 0;0x98=0x01(46 文件)/0x00(FONTSJP1) | §6.2 补充;unknown_98 |
| E016 | Hex(原文档) | 附录 A:LOADSC0(A)与 FONTSJP(B)头部 dump,逐字段核对 | 头部全部字段 |
| E017 | 公式推导+实测(49 文件) | §7 公式解释全部文件长度(263352/263356/132284/131324/65788/262396),脚本核验 True | §7 全部公式 |
| E018 | 代码实现 | chk.rs 行号对照(parse L101、serialize L148、unpack/pack L388/L416、quantize L591、default_* L728–778 等) | 本文与实现一致 |
| E019 | 字节实测(本次整理) | A 的 header 0x28/0x2C = 0x00040450 = meta_base+0x10(LOADSC0/LOADSC3),与 chk.rs L488–489 写入一致;与原文表 "= data_end − 92"(=0x40440)不一致,差 0x10 | §8.2;附录 C-1(只记录,未改判定) |
| E020 | 代码实现/负面测试 | rejects_bad_magic、rejects_non_square、rejects_non_pow2_block_dims(L925/L1148/L1293) | §26 校验项、§27 拒绝类 |
| E021 | 外部参考代码 | png2chk.utf8.cpp(旧写入器)的 0x30/0x34 写入有误,以 chk.rs 为准 | §11 原文;unknown_30/34 策略 |
| E022 | 实测计数(本次整理) | TXD\ 实测 45 个 .chk;全语料 Family B = 47(45+根目录 2);旧文档"TXD*.chk(47 个)"与现状不符 | 附录 C-2 |
| E023 | 多样本交叉/实测 | 同名不同家族:根目录 LOADSC0/LOADSC3(A, 263352B)≠ TXD\LOADSC0/LOADSC3(B, 132284B);FONTSJP(B4 512×1024)≠ FONTSJP1(B8 512×512) | §29 注③;路径敏感性 |
| E024 | 语料清点(普查脚本 2026-09-04) | 普查语料 = 根目录 4 + TXD\ 45 = 49 原生;另有 _chk_verify\ 11 个工具生成件(非原生,单独标注)与 _chkprobe\ 下 FONTSJP/LOADSC0 两份副本(SHA-256 与原件一致,不计入样本) | §29 表语料完整性;附录 E.0 |
| E025 | 字段恒定性普查(49 文件) | 通用头恒定字段 0x00/0x04/0x14=7/0x18/0x1C/0x20=6/0x24/0x38 49/49 无例外;0x3C A=0/B=0xA0 49/49;0x28/0x2C 家族内恒定(A=0x40450×2、B=0x50×47) | §5.1/§6.1/§6.2 恒定列 |
| E026 | 几何公式全量核验(49 文件) | §7 全部公式(容器 0x08/0x0C/0x10;A file_size=0x40+W·H+1024+92+0x1C、0x28/0x2C=meta_base+0x10、meta[0x08]=meta_base;B file_size=0xA0+block_rows·block_cols·128+nb*4+0x1C、pixel_region=br·bc·128、texels=W·H、0x28/0x2C=0x50)逐文件核验 49/49 OK、误差 0 | §7.1/§7.2/§8;§29 W/H/BitDepth 列 |
| E027 | tail 全量核验(49 文件) | A 2/2 匹配 meta_base 序列表、B 47/47 匹配固定表,逐项精确、无例外(刷新 E014 "46+2=48" 口径) | §8.3 |
| E028 | 排除性实测(0x30/0x34) | 5 算法(crc32/adler32/sum8/xor32/fnv1a)×10 类对象(whole_file/header/pixel_region/palette/pixel+palette/name±NUL/filename 等)哈希与 14 项布局指针/大小对候选在全部 49 文件上 0 命中;v34−v30:A=0x014188F8(2/2)、B=0xFE10ED2C(46)/0xFE10BEEC(1) | §5.1;unknown_30/34 排除面(语义仍未知) |
| E029 | 字节级实测(47 B 文件,0x60..0x9F) | 除 0x98 外唯一非零:TXD\SCEJLEGALJ.CHK 0x60–0x61=4C 4A(10 字节名 SCEJLEGALJ 溢出 0x58 名字区 2 字节);0x98=0x01×46、FONTSJP1=0x00×1;0x40..0x5F 逐字节恒定:0x40=0x00、0x42=0x09、0x45=0x01、0x46=0x04、0x48=0x38、0x4C=0x20、0x50=0x28、0x54=0x28 | §6.2;附录 C-10;unknown_98 复核 |
| E030 | 调色板统计(49 文件,6544 条目) | alpha≠255 共 297 条;含 alpha==0 条目的文件 25 个(FONTSJP + TXD\LOADSC4-22 + NEWS2/SCEJLEGALJ/SPLASH1-3);index0 共 11 种((0,0,0,255)×39 为主,LOADSC0(A)=(246,212,107,255)、FONTSJP=(253,253,253,245) 等);alpha≠255 去重值集 49 种(0..254,206 个中间值语料内未见) | §18.2;unknown_alpha 表述收窄 |
| E031 | 图像级验证(Python 复刻 to_rgba) | LOADSC0(A)↔loadsc0.png diff=0(262144 像素);FONTSJP(B4)↔fontjp.png diff=0(524288 像素);FONTSJP1↔fontJAP_1.png 尺寸不一致(512×512 vs 2048×1024)无法逐像素比对 | E003 的 Family B 侧独立复现;§19 解码管线 |
| E032 | 测试运行记录(2026-09-04) | cargo test -p txd-format -- --include-ignored:75 tests,74 passed / 1 failed——唯一失败 txd::fonts_diag::dump_fontgif(txd.rs:248 panic,TXD 侧诊断测试,与 CHK 无关);CHK 全部测试通过;byte-exact 诊断 ok=4 bad=0;byte-exact 覆盖 48/49(唯一缺口 FONTSJP1,见 §29 注②-bis) | §30;附录 C-12 |
| E033 | 位深交叉验证(47 B 文件) | 判定①(0x44&0x08)== 判定②(b[0x41]∈{2,1})== 调色板大小 47/47 三方一致;0x47 联动:0x25(8bit)×22、0x45(4bit)×25 | §6.3;C-4 全语料版关闭 |
32. 已知未知(Known Unknowns)
10.5 节原文:待确认点 / 下一步【✅ 原文保留】
- header
0x14=7、0x20=6、Family A/B 的0x30/0x34、Family B0x45/0x46/0x47/0x48..0x54等恒定常量:数值已确证,但确切语义(版本号?格式标记?)仍需对照更多已知家族/版本才能锁定。❓ - tail 表在游戏/工具中的消费方式(是否逐项校验):未验证。❓
- 家族间字节序/通道交换是否真有差异:目前两家族均按各自规则解码且颜色正确,样例范围内未发现需额外交换。⚠️
普查补充(2026-09-04,E024–E033,附录 E),上列各项在 49/49 全语料下的新状态:
- 恒定常量类(0x14/0x20/0x3C/B 0x40..0x5F 逐字节恒定部分/tail 表):数值恒定性全部升级为 49/49 或 47/47 实证(E025/E027/E029/E033);但语义类未知一律不升级(仍是 ❓)。
- 0x30/0x34:排除面实证增强,非内容哈希(5 算法×10 对象 0 命中)、非布局指针/大小(14 项候选 0 命中)(E028);语义仍 ❓。
unknown_98:47/47 复核维持(46×0x01 + FONTSJP1×0x00,E029);语义 ❓ 不变;另发现重建件 0x98=0 与原生 0x01 的系统性差异(C-11)。- 0x60..0x9F 零区:唯一例外 SCEJLEGALJ 0x60–0x61 为名字溢出(E029/C-10),"0x60..0x9F 除 0x98 外全 0"表述精确化。
- Alpha:语料内实测 alpha≠255 条目 297/6544、去重值 49 种(0..254),"字节直出"在语料内一致成立,但只能记「语料内未见反例」,不能升 ✅(渲染端证据仍缺)。
- tail 消费方式 / 平台通道交换 / PSP 归属:普查无新证据,维持原状(❓/⚠️,不升级)。
12 节原文:已完全掌握 vs 需外部资料【✅ 原文保留】
✅ 已完全掌握(能忠实编解码 + 还原)
- 容器结构:魔数、
0x08大小、0x0cdata_end、tail偏移/地址表(header/meta/tail 逐字段表见原第 10 节,现 §5/§6/§8)。 - 家族与 kind:PS2 Family A(方形 8bit · 索引 bit3/4 交换)、PSP Family B(块状 8bit/4bit · 8×16 / 8×32)。
- 调色板RGBA 字节序、索引读取、块打包、4bit 半字节打包(低 4bit 在前)。
- 硬证据:
parse → serialize对LOADSC0/LOADSC3/CAPCOM/INTRO1byte-exact(ok=4 bad=0)。- 解析
LOADSC0.CHK的 RGBA 与参考loadsc0.png逐字节 diff=0(512×512)。
- 平台归属:Family A=PS2、Family B=PSP(用户确认 + PSP 工具只认 Family B + byte-exact)。
❓ 需外部资料才能进一步确证(非现有样例/资源可解,属于"格式之外"的解释性知识)
- 常量字段
0x14=7、0x20=6、0x30/0x34、0x45..0x54的确切语义(版本号?格式标记?),需要跨版本家族对照或官方/社区 spec。 tail偏移表在游戏/引擎中的消费方式(是否逐项校验),需要运行时/strace 逆向。- PSP/PS2 通道交换差异的最终确认,需要同一张图在两平台的成对
.chk(样例范围内两家族 byte-exact 且颜色正确,未发现差异)。
结论:".chk 格式"本身(结构、布局、字节序、平台、调色板)已完全掌握并被 byte-exact + diff=0 证明。以上 ❓ 项是元数据语义 / 消费方层面的知识,不影响正确读写
.chk,需要不同来源的样本或运行时才能继续锁定。
v2.2 收敛表述(不改上文,仅限定口径):上段结论的准确范围是当前 49 样本语料。严谨说法:在当前样本范围内,
.chk的可读写二进制结构、纹理布局与调色板编码已足以实现可靠的编解码,并已通过 byte-exact(48/49)与像素级 diff=0 验证;0x30/0x34语义、tail 消费方式、部分常量语义及 Family B 平台归属仍为 ❓/⚠️(§32)。未来出现新样本时以新证据为准。
未知项登记表(每项:观察/假设/证据/所需样本/工具处理)
| 未知项 | 当前观察 | 当前假设(仅假设) | 证据 | 需要什么样本 | 当前工具处理 |
|---|---|---|---|---|---|
unknown_04(header 0x04..0x07) | 两家族观测恒 0 | 保留字段/版本 | E016 | 任何非 0 样本 | preserve,NEVER 修改 |
unknown_14=7、unknown_20=6 | 两家族恒为 7/6 | 版本号或格式标记 | E016、E013 | 跨游戏/跨版本家族样本 | preserve;重建按 default 写 7/6 |
unknown_30/unknown_34 | A 一组;B 46 文件一组;FONTSJP 单独一组;同家族同名样本间仍可变 | 非内容哈希、非全局常量、非尺寸函数 | E013、E021、E005;普查排除面(E028:5 算法×10 对象哈希 + 14 项指针/大小对候选,49 文件 0 命中) | 同图跨版本对;官方生成器;更大语料 | preserve,NEVER 自动修改;从零生成时写家族代表值并标注 |
Family B 0x45..0x54 常量(01/04/25|45/56/32/40/40) | 跨内容/尺寸恒定;0x47 随位深联动 | 格式标记/块几何提示 | E016、E005、E013 | 跨版本样本 | preserve;重建按固定表 regenerate |
| Family A meta 非零字段(0x04=u32 0x00108249、0x0C=32、0x10=40、0x14=40、0x58=1) | 两个 A 样本完全一致;tail 后 5 项恰好指向 meta[0x00]/[0x08]/[0x0C]/[0x10]/[0x14] | 与 tail 指针序列对应的子结构(无更细证据不下结论) | E012、E014 | 更多 Family A 样本(非 512²) | preserve,NEVER 自动修改 |
unknown_98(B header 0x98) | 47/47 复核:46 文件=0x01;FONTSJP1=0x00(唯一离群,E029);重建件全部=0x00(C-11) | 无 | E015、E029 | 更多 B 样本(尤其非 TXD 来源) | preserve |
| tail 表消费方式 | 结构确证(E014);游戏是否逐项校验未知 | 可能逐项用于定位子结构 | E014 | 运行时逆向/strace | 按公式重建(A)/固定表(B) |
| 平台通道交换差异 | 样例内未发现需要交换的证据 | 无差异 | E003、E013 | 同一张图双平台成对 .chk | 不做任何通道交换 |
| Alpha 全范围行为 | 语料内 6544 条目中 alpha≠255 共 297 条、去重 49 值(0..254);未发现与"字节直出"矛盾的样本 | 字节直出 | E012、E030(普查;206 个中间值语料内未见,只能记「语料内未见反例」,不升 ✅) | 游戏渲染端证据 | 原样保留 alpha 字节 |
| Family A 非 512² 支持 | 仅有 512² 证据 | isqrt 自适应应可支持其他方形尺寸 | E017、E018 | 非 512 的方形 A 样本 | isqrt 自适应(不做硬编码假设) |
fontJAP_1.png(2048×1024)与 FONTSJP1(B8 512×512)的对应关系 | 尺寸不一致,无法逐像素比对(普查 E.9);fontJAP_1 更可能是 FONTSJP(B4 512×1024)整图/拼接图或无关参考 | 无 | E031 | 该 PNG 的来源说明或对应版本 FONTSJP1 | 不参与判定 |
33. 参考伪代码(Reference Pseudocode)
语言无关;以 chk.rs 实际逻辑为蓝本(E018)。
# ---------- 解析 ----------
function parse_chk(file: byte[]) -> ChkDocument:
if len(file) < 4 or file[0..4] != b"xet\0": error CHK_ERR_INVALID_MAGIC
if len(file) < 0x40 + 0x1C: error CHK_ERR_TRUNCATED
file_size = le32(file, 0x08); data_end = le32(file, 0x0C)
if file_size != len(file): error CHK_ERR_INVALID_SIZE
if data_end != len(file) - 0x1C: error CHK_ERR_INVALID_SIZE
# Family A 几何试探(先于 0x3C 判定)
pal_a = len(file) - 0x1C - 92 - 256*4
pix_a = pal_a - 0x40
side = isqrt(pix_a)
if side * side == pix_a:
return parse_family_a(file, pal_a, side) # 方形 A
if le32(file, 0x3C) == 0xA0:
return parse_family_b(file) # 块状 B
error CHK_ERR_UNSUPPORTED_FAMILY
function parse_family_a(file, palette_offset, side) -> ChkDocument:
W = H = side
palette = [ file[palette_offset + i*4 : palette_offset + i*4 + 4] for i in 0..256 ] # RGBA 原样拷贝(显式端点)
meta_offset = palette_offset + 1024
meta = file[meta_offset : meta_offset + 92] # 原始 92B(v2.3 消歧义:显式端点)
name = nul_trim(meta[0x18 .. 0x20])
pixels = file[0x40 .. palette_offset] # 存储序(含 swap34)
header = file[0 .. 0x40]; tail = file[len-0x1C ..]
return {family: A, width: W, height: H, name, palette,
pixels, raw_header: header, raw_meta: meta, raw_tail: tail}
function parse_family_b(file) -> ChkDocument:
bits = (file[0x44] & 0x08) != 0 ? 8 : 4
nb = bits==8 ? 256 : 16
palette_offset = len(file) - 0x1C - nb*4
texels = (palette_offset - 0xA0) * (8 / bits)
W = 1 << (file[0x42] & 0x0F); H = 1 << (file[0x43] & 0x0F)
if W*H != texels: error CHK_ERR_INVALID_SIZE # geometry inconsistent
block_w = bits==8 ? 16 : 32
if W % block_w != 0 or H % 8 != 0: error CHK_ERR_ALIGNMENT # not block-aligned
palette = [ file[palette_offset + i*4 : palette_offset + i*4 + 4] for i in 0..nb ] # 显式端点
name = nul_trim(file[0x58 .. 0x60])
pixels = unpack_block(file[0xA0 .. palette_offset], W, H, bits)
header = file[0 .. 0xA0]; tail = file[len-0x1C ..]
return {family: B, bit_depth: bits, width: W, height: H, name, palette,
pixels, raw_header: header, raw_meta: [], raw_tail: tail}
# ---------- 编码 ----------
function encode_chk(doc) -> byte[]:
validate_document(doc) # §26 编码侧子集
nb = doc.family==A ? 256 : (doc.bit_depth==8 ? 256 : 16)
if doc.palette.len != nb: error CHK_ERR_INVALID_PALETTE
if doc.pixels.len != doc.width * doc.height: error CHK_ERR_PIXEL_COUNT_MISMATCH
packed = encode_pixels(doc) # A: 存储值=swap34(逻辑值) 行主序;B: pack_block
header = doc.raw_header ?? default_header(doc.family)
if doc.rebuild and doc.family==B: patch_block_header(header, W, H, bits) # 0x40..0x48
data_end = (doc.family==A ? 0x40 : 0xA0) + len(packed) + nb*4 (+ doc.family==A ? 92 : 0)
put_le32(header, 0x08, data_end + 0x1C)
put_le32(header, 0x0C, data_end); put_le32(header, 0x10, data_end)
if doc.family == A:
meta = doc.raw_meta ?? default_meta() # 92B,0x00=0x40
meta_base = 0x40 + len(packed) + nb*4
put_le32(header, 0x28, meta_base + 0x10); put_le32(header, 0x2C, meta_base + 0x10)
put_le32(meta, 0x08, meta_base)
meta[0x18..0x20] = name_to_8(doc.name)
tail = doc.raw_tail ?? default_tail() # 0x00=0x28, 0x04=0x2C
put_le32(tail, 0x08, meta_base); put_le32(tail, 0x0C, meta_base+0x08)
put_le32(tail, 0x10, meta_base+0x0C); put_le32(tail, 0x14, meta_base+0x10)
put_le32(tail, 0x18, meta_base+0x14)
return header + packed + palette_bytes + meta + tail
else:
header[0x58..0x60] = name_to_8(doc.name)
tail = doc.raw_tail ?? default_font_tail() # 固定 7 项
return header + packed + palette_bytes + tail
# 末尾统一:validate_output(bytes) → §26 全量校验
# ---------- 像素解码(单像素) ----------
function decode_pixel(doc, x, y) -> RGBA:
if doc.family == A:
raw = file[0x40 + y*W + x]
idx = chk_swap34(raw) # A 唯一需要 swap
else: # Family B,§13.1
block_x = x / block_w; block_y = y / block_h
local_x = x % block_w; local_y = y % block_h
base = header_size + (block_y*block_cols + block_x)*128
if bits == 8:
idx = file[base + local_y*16 + local_x]
else:
b = file[base + local_y*16 + local_x/2]
idx = (local_x % 2 == 0) ? (b & 0x0F) : ((b >> 4) & 0x0F)
return doc.palette[idx] # 越界时实现回退 [0,0,0,255](to_rgba L178)
# ---------- 基础变换 ----------
function chk_swap34(v: u8) -> u8:
return (v & 0xE7) | ((v & 0x08) << 1) | ((v & 0x10) >> 1) # 自反:swap34(swap34(v))==v
function pack4(lo: u8, hi: u8) -> u8: return (hi << 4) | lo # lo=先到像素(低 4bit)
function unpack4(b: u8) -> (u8, u8): return (b & 0x0F, (b >> 4) & 0x0F)
34. 实现规则(禁止行为清单,Implementation Rules)
- 不许猜测读未知字段:unknown_xx 字段的语义未知,读取时只保存原始字节,不解释、不错位。
- 不许 Encoder 改未知字段:0x30/0x34、meta[0x04]、0x98 等一律 preserve;无证据的"规范化"等于制造新文件。
- 不许 GUI 直接操作二进制:界面只操作 ChkDocument;字节级读写只在 Parser/Serializer 层。
- 不许混用 A/B 规则:swap34 仅 Family A;块布局仅 Family B;meta 仅 Family A。
- 不许把 PAL4 当 PAL8:16 色 vs 256 色、64B vs 1024B、0x41=1 vs 2、0x44=0x04 vs 0x08。
- 不许忽略 Block Layout:Family B 严禁行主序直写;必须走 pack/unpack_block(§16)。
- 不许假设正方形:方形证据仅覆盖 Family A 语料(512²,E017);通用 .chk 不得假设方形,Family B 明确支持 512×256/512×1024 等非方形。
- 不许假设平台严格对应 Family:Family A=PS2 为用户确认(E011);Family B=PSP 是推断(E010,⚠️);不得反向用"平台"决定解析分支。
- 不许因能显示就认为正确:显示正确 ≠ 布局正确;必须以 byte-exact/diff=0 为判据(附记方法论)。
- 必须过 Validator:任何写入(patch/rebuild)输出前必须通过 §26。
- 必须尽量过 Round-trip:未编辑文件 serialize 必须逐字节还原(测试 A);编辑文件必须能被自己的 parser 读回且逻辑一致。
- 未知字段必须保留:preserve 是默认策略;recalc/regenerate 仅限本文列出公式的字段(§23.2)。
- 推断必须标注:一切 ⚠️/❓ 不得写成 ✅;新结论必须带证据 ID。
- 新结论必须有证据:样本/Hex/代码/Round-trip 之一;"看起来对"不是证据。
35. 开发闭环架构图
┌────────────────────────────────────────────────────────────┐
│ CHK File (bytes) │
└───────┬────────────────────────────────────────────┬───────┘
│ parse(§33 伪代码) ▲ serialize(§23)
▼ │
┌───────────────┐ family detect (0x3C / isqrt) ┌──┴────────────┐
│ Detector │──────────┬──────────────────────►│ Serializer │
└───────┬───────┘ │ └──┬────────────┘
▼ ▼ │
┌──────────────────┐ ┌──────────────────┐ │
│ Family A Parser │ │ Family B Parser │ │
│ (square, swap34) │ │ (blocks, nibble) │ │
└────────┬─────────┘ └────────┬─────────┘ │
└──────────┬──────────┘ │
▼ │
┌────────────────────────────────┐ rebuild ┌────────▼────────┐
│ ChkDocument │────────────►│ Encoder │
│ format / family / platform │ │ (quantize, │
│ width / height / bit_depth │ │ pack, swap34) │
│ pixels (logical indices) │ └────────┬────────┘
│ palette / metadata / name │ │
│ unknown_fields (raw bytes) │ │
│ relocation / raw_sections │ │
└────────────────┬───────────────┘ │
│ edit(GUI 不接触二进制) │
▼ │
┌────────────────────────────────┐ │
│ Editor / Validator │──────────────────────┘
│ (§24 Exact Preservation / │ Validator(§26)
│ §25 Rebuild / §26 24 项) │
└────────────────┬───────────────┘
▼
Valid CHK File
链路:CHK Binary → Parser → ChkDocument → Editor → Encoder → Serializer → Validator → CHK Binary。
36. 与 TXD 的区别【✅ 原文保留】
.txd(RenderWare) | .chk | |
|---|---|---|
| 结构 | 字典,可含多贴图 | 单贴图 |
| 格式 | 真彩 8888 / ABGR、16bit 4444/5551/565、PAL8、DXT | 调色板索引(4/8bit) |
| 平台 | PC(D3D8/D3D9)、PS2、XBOX | PSP / PS2 自制纹 |
| 解码 | 直接像素内容 | 调色板查表 |
37. 附录 A:真实样例头部十六进制 dump【✅ 原文保留】
字节来自
LOADSC0.CHK(Family A)与FONTSJP.CHK(Family B / 4bit),已逐字段核对。 偏移列 = 文件内字节偏移(十六进制)。
Family A · PS2 · LOADSC0.CHK(263352 字节)—— 前 0x40(64 字节)头
0000 78 65 74 00 00 00 00 00 B8 04 04 00 9C 04 04 00
0010 9C 04 04 00 07 00 00 00 00 00 00 00 00 00 00 00
0020 06 00 00 00 00 00 00 00 50 04 04 00 50 04 04 00
0030 00 41 2C 04 F8 C9 6D 05 00 00 00 00 00 00 00 00
| 偏移 | 字节 | 含义 |
|---|---|---|
0x00 | 78 65 74 00 | 魔数 "xet\0"(= 0x00746578) |
0x08 | B8 04 04 00 | 文件总大小 = 0x000404B8 = 263352 |
0x0C | 9C 04 04 00 | data_end = 0x0004049C = 263324 = 大小 − 0x1C |
0x10 | 同上 | data_end 复本 |
0x14 | 07 00 00 00 | 家族共用常量 7(语义未明) |
0x20 | 06 00 00 00 | 家族共用常量 6(语义未明) |
0x28 | 50 04 04 00 | meta 基址相关(0x00040450,见 §8.2/§8.3) |
0x2C | 同上 | 同上(+0x14) |
0x30 | 00 41 2C 04 | 0x042C4100(家族特有值,非恒定) |
0x34 | F8 C9 6D 05 | 0x056DC9F8(同上,非恒定) |
0x3C | 00 00 00 00 | = 0 → Family A 标志 |
0x40 | … | 像素区(行主序 512×512,每像素 1 字节) |
Family B · PSP · FONTSJP.CHK(262396 字节)—— 前 0x60(含名字区)
0000 78 65 74 00 00 00 00 00 FC 00 04 00 E0 00 04 00
0010 E0 00 04 00 07 00 00 00 00 00 00 00 00 00 00 00
0020 06 00 00 00 00 00 00 00 50 00 00 00 50 00 00 00
0030 A0 33 AE 0A 8C F2 BE 08 00 00 00 00 A0 00 00 00
0040 00 01 09 0A 04 01 04 45 38 00 00 00 20 00 00 00
0050 28 00 00 00 28 00 00 00 66 6F 6E 74 6A 70 00 00
(0x60..0x9F 全为 0;0xA0 起为 128 字节块像素区)
| 偏移 | 字节 | 含义 |
|---|---|---|
0x00 | 78 65 74 00 | 魔数 |
0x08 | FC 00 04 00 | 大小 = 0x000400FC = 262396 |
0x0C / 0x10 | E0 00 04 00 | data_end = 0x000400E0 = 262368 |
0x14 / 0x20 | 07 / 06 | 家族共用常量 |
0x28 / 0x2C | 50 00 00 00 | 常量 0x50 |
0x30 | A0 33 AE 0A | 0x0AAE33A0(此处 ≠ FONTSJP1/INTRO1 的 0x0AAE1BA0;非恒定) |
0x34 | 8C F2 BE 08 | 0x08BEF28C(同上,非恒定) |
0x3C | A0 00 00 00 | = 0xA0 → Family B 标志 |
0x41 | 01 | bits/4 = 1 → 4bit |
0x42 | 09 | log2(W) = 512 |
0x43 | 0A | log2(H) = 1024 |
0x44 | 04 | 位深类型 = 0x04(4bit) |
0x45 / 0x46 | 01 / 04 | 常量 |
0x48 | 38 | 常量 56 |
0x4C / 0x50 / 0x54 | 20 / 28 / 28 | 常量 32 / 40 / 40 |
0x58 | 66 6F 6E 74 6A 70 00 00 | 贴图名 "fontjp"(NUL 截断) |
0xA0 | … | 块像素区起点 |
⚠️ 注意:
0x30/0x34在本例(FONTSJP)与 §5.1 表、以及 FONTSJP1/INTRO1 均不同,是逐样本可变的值,已据此把置信度改为 ⚠️。Family A/B 头主体结构一致,只能靠0x3C标志 + 像素布局区分家族(这正是 §4.1 的结论)。E015 补充(本次实测):"
0x60..0x9F 全为 0"在本例 dump 时成立到字节级指认之外;实测 0x98=0x01(46/47 个 B 文件,FONTSJP 亦为 0x01;FONTSJP1=0x00)。原句按 dump 注释保留;精确零区边界见 §6.2。
38. 附录 B:通道映射测试法
目的:验证调色板条目的字节序确为 [R,G,B,A] 且 Alpha 在第 4 字节。
步骤:
- 构造测试 PNG(如 64×64):8 个纵向色带,即纯黑(0,0,0,255)、纯白(255,255,255,255)、纯红(255,0,0,255)、纯绿(0,255,0,255)、纯蓝(0,0,255,255)、透明(0,0,0,0)、半透明白(255,255,255,128)、中间灰(128,128,128,255)。8 ≤ 16 ≤ 256,PAL4/PAL8 都放得下。
- PNG → Encode(按 §21,目标家族任选,两种都跑)→ CHK。
- CHK → Decode(§22)→ RGBA 缓冲。
- 逐像素比对解码 RGBA 与源 PNG。
判定标准:
- 全部 8 色 diff=0,则通道序 [R,G,B,A] 与 alpha 直通成立(✅ 若通过)。
- 红绿互换(红带变蓝/绿带变红等)说明通道序错误(如 BGRA)。
- 半透明白变为全透/全不透说明 alpha 未直通(第 4 字节不是 A)。
- 透明带必须恰好映射到 index 0(当前工具策略,§20.3)。
- Family A 上必须重复一次(swap34 只影响索引不影响通道序,理论上与 B 同判)。
当前证据状态:LOADSC0 已通过等价验证(E003 diff=0);本测试法作为新工具的自检程序(❓ 其余家族×位深组合的 diff=0 报告暂无正式记录)。
39. 附录 C:已知不一致记录(只记录,不改判定)
| # | 不一致 | 详情 | 处理 |
|---|---|---|---|
| C-1 | 原文 §10.2 表(§6.1)"0x28/0x2C = data_end − 92" vs 字节实测/代码 | LOADSC0/LOADSC3 实测 0x28/0x2C = 0x00040450;data_end−92 = 0x00040440(= meta_base);0x40450 = meta_base + 0x10,与 chk.rs serialize_square L488–489 写入一致(byte-exact 测试通过)。两者差恒为 0x10。§13 dump 注(0x40450)本身与代码一致,与 §10.2 公式矛盾 | 原文表逐字保留(§6.1);规范采用 meta_base+0x10(§8.2,E019);正式判定留给维护者复核 |
| C-2 | 原文"TXD*.chk(47 个 Family B)" vs 实测 45 | TXD\ 现有 45 个 .chk(2026-09-04 清点,E022)。47 = 全语料 Family B 总数(45 + 根目录 FONTSJP/FONTSJP1),疑为口径混用 | 两口径并存记录(§29 注④),不硬凑 |
| C-3 | §16 原文 block_cols = ceil(W/block_w) vs parse_block 整除 | 代码先校验块对齐(W%block_w==0)再整除;ceil 在合法文件上等价 | 原文保留;实现注已说明(§16) |
| C-4 | §16 原文位深判定 bits = header[0x41]*4 vs 代码判定① 0x44&0x08 | 两者在全部样本一致;parse 实际用 0x44(L333),0x41 为冗余校验 | §6.3 双判定并列,原文保留 |
| C-5 | "ok=4 bad=0"口径 vs parse_all_family_b_in_dir 断言体 | 诊断测试正式记录 4 个指定样本 ok=4 bad=0(E001);parse_all_family_b_in_dir(L877–879)实际对 TXD 目录每个文件断言 serialize 逐字节相等(每次运行覆盖全部 45 个) | §29 注② 两个口径并存,按原文口径填表、批量事实加注 |
| C-6 | 原文"FONTSJP 0x60..0x9F 全为 0" vs 实测 0x98=0x01 | FONTSJP 自身 0x98=0x01(本次实测,E015);原文 dump 注释在 0x98 这一个字节上不精确 | 原文保留 + §37 补充注 |
| C-7 | FONTSJP1 的 0x98=0x00 离群 | 47 个 B 文件中唯一(E015) | 无判定,登记 §32 |
| C-8 | ChkFile docstring 称 pixels 存"逻辑索引" vs from_rgba 实际先 swap34 再存 | Family A 的 pixels 在内存中是存储序(swap34 后)值;to_rgba 对 A 再做一次 swap34 得逻辑索引。对 Family B 两者相同。对使用者透明(encode/decode 互逆,E007/E002),但按字面理解 docstring 会写错 A 的直接序列化 | §19/§20 按实际行为描述;实现者注意 |
| C-9 | 原文 §13 dump 注"0x2C 同上(+0x14)" 表述含糊 | 0x2C 与 0x28 字节相同(均为 0x40450);"+0x14"疑为对 tail 槽位 0x14(其值=meta_base+0x10=0x40450)的注记 | 原文保留;规范值见 §8.2 |
| C-10 | §6.2/E015 "0x60..0x9F 除 0x98 外全 0" vs SCEJLEGALJ.CHK 0x60–0x61 非零(普查新发现) | 普查(E029)字节级确认 0x60..0x9F 除 0x98 外唯一非零:TXD\SCEJLEGALJ.CHK 0x60–0x61=4C 4A——系 10 字节名 SCEJLEGALJ 从 0x58 名字区溢出 2 字节,非独立字段;"名字 8 字节"应精确为"名义 8B、可溢出"(47 B 文件中仅此 1 例) | 原文保留;§6.2 已加注;普查记录于附录 E.5 |
| C-11 | chk.rs 从零重建的 B 文件 0x60..0x9F=0(含 0x98=0) vs 原生 46/47=0x98=0x01 | 普查实测 11 个 _chk_verify 重建件 0x98 全为 0(E024/E029);rebuilt_header_matches_real_family_b 的槽位比较(0x28..0x54)不覆盖 0x60..0x9F,故"重建头逐字段相等"与"该区域系统性差异"并存;parse→serialize 的 byte-exact 往返不受影响(保留原字节) | 登记;重建策略(preserve)不变;待维护者决定是否让 default_header 写 0x98=0x01 |
| C-12 | "测试套件全绿"表述 vs 2026-09-04 实测 74/75 | cargo test -p txd-format -- --include-ignored 75 tests:74 passed / 1 failed——唯一失败 txd::fonts_diag::dump_fontgif(txd.rs:248 panic index out of bounds: len 1049029, index 16804789,读 FONTS.TXD 的 TXD 侧诊断测试自身缺陷,与 CHK 格式无关);全部 CHK 测试通过(E032) | §30/§31 已按实测记录;dump_fontgif 修复留待 TXD 侧维护 |
| C-13 | §18.2 "Alpha 字节取值 0..255 均出现在真实样本中" vs 普查实测 49 种离散值 | 普查(E030):6544 条目中 alpha≠255 共 297 条、去重 49 值(0..254),206 个中间值语料内未见——"全范围均出现"在字面上不成立,成立的是存在性(中间值大量出现) | 原文保留;§18.2 已加收窄注;渲染端语义仍 ❓ |
40. 附录 D:新旧章节映射
| 旧版章节 | 新位置 |
|---|---|
| 1 概述 | §1 |
| 2 容器(通用结构) | §4.1 |
| 3 Family A | §9(原文)+ §4.2/§6.1/§7.1/§10/§11/§12 |
| 4 Family B / 4.1 块栅格与 4bit 打包 | §13(原文)+ §16/§17;4.1 代码块原文在 §16 |
| 5 位深 / 调色板 | §18.1 |
| 6 索引交换(仅 Family A) | §10.2 |
| 7 从 RGBA 生成(量化) | §20 |
| 8 与 TXD 的区别 | §36 |
| 9 置信度 | §31(原文表) |
| 10 字段级映射 10.1–10.5 | §5.1(10.1)、§6.1(10.2)、§6.2(10.3)、§8.3(10.4)、§32(10.5) |
| 11 参考 / 样例 | §41 附记(原文)+ §29 语料 |
| 12 已完全掌握 vs 需外部资料 | §32(原文照录) |
| 13 附录 A Hex dump | §37 |
| 附记(致谢与许可) | §41 |
| (新增) | §2 术语、§3 家族判定、§4.2–4.3/§6 字段表、§7 公式、§8 指针、§14/§15、§19–§28、§29–§35、§38–§40 |
41. 附记(致谢与许可)【✅ 原文保留】
这个 .chk 格式没有公开官方规范,本文靠的是字节级对照 + parse→serialize 的 byte-exact 验证,而不是猜测。核心方法论很简单,也最能避免翻车:
结构、字节序、平台、调色板——都要能通过「重新写回来和原文件逐字节一致」才算证明,而不是"看起来对"。
- 参考:写入器
png2chk.utf8.cpp(注意它写0x30/0x34有误,以本文为准);PSP 专用工具GTAStoriesTex.exe(只认 Family B)。 - 许可:本文可自由转载/改写,请保留"区分【✅/⚠️/❓】的置信度标注"——那一列比字段表本身更值钱,它告诉你哪些结论可以被放心依赖、哪些还要打问号。
- 别把可变值当常量:
0x30/0x34、0x14/0x20等字段是逐样本可变的,硬编码进自己的工具会让它只在少数文件上碰巧能用。
欢迎指正与补充样本,尤其这几类最有助于把 ❓ 变 ✅:
- 同一张图在两平台的成对
.chk(锁定 PSP/PS2 的通道交换差异); - 跨游戏/跨版本的 Family 常量样本(锁定
0x30/0x34、0x14/0x20的真实语义); - 任何结构化异常样本(非 2 的幂、非方形、极端尺寸、0x1C tail 被改的个案)。
关于「家族常量」:如果你拿去加载一个官方没做过的自定义
.chk,尽量让工具按家族几何+调色板自适应,而不是依赖那几个未明语义的常量——这样最稳。
(完)
42. 附录 E:全语料普查(Census)【新增,2026-09-04】
本附录是 v2.1 整理轮新增:用独立 Python 脚本对全部 49 个原生
.chk(+11 个工具生成件,单独标注)做一次实证普查,把正文各节的"观测"从 2 样本手工核对升级为全语料逐文件核验。原始逐文件数据(399 行)完整保存于docs\tools\chk_census_results.txt,本附录只收录结论与关键数字。所有新增证据 ID 为 E024–E033(§31),新增矛盾登记 C-10/C-11/C-13(§39)。
E.0 工具、语料与可复现性【E024】
- 脚本:
docs\tools\chk_census.py(Python 3.14,无第三方依赖)。复现命令:python docs\tools\chk_census.py(自动写chk_census_results.txt)。 - 语料:根目录 4(LOADSC0/LOADSC3/FONTSJP/FONTSJP1)+
TXD\45 = 49 原生;另含_chk_verify\11 个工具生成件(非原生,所有统计单独标注)。 - 家族判定与 chk.rs
parse完全一致:A=完全平方几何试探先行;B=le32(0x3C)==0xA0。结果 A=2、B=47,与 §3 一致。 - 语料完整性核对:全树 walk 实得 62 个
.chk= 49 原生 + 11 生成 +_chkprobe\下 FONTSJP/LOADSC0 两份副本(SHA-256 与原件逐字节一致:926a9d5d…/c93ce323…,不计入样本),普查语料无遗漏。
E.1 字段恒定性(普查 a 节)【E025】
- 两家族恒定同一值(49/49 无例外):
0x00=0x00746578(魔数)、0x04=0、0x14=7、0x18=0、0x1C=0、0x20=6、0x24=0、0x38=0。 - 家族内恒定、家族间不同(49/49):
0x28/0x2C:A=0x00040450(=meta_base+0x10,两样本);B=0x00000050(47/47)。0x3C:A=0;B=0xA0(家族判据)。 - 家族内可变但公式导出:
0x08/0x0C/0x10在 B 侧恰 5 组值,与 5 个尺寸/位深类一一对应(512×512×4→0x000200FC/0x000200E0;512×256×8→0x000204BC/0x000204A0;512×512×8→0x000404BC/0x000404A0;512×1024×4→0x000400FC/0x000400E0;512×256×4→0x000100FC/0x000100E0);A 侧单组(0x000404B8/0x0004049C)。全部被 §7 公式解释(E026)。 - B 头 0x40..0x5F 逐字节恒定(47/47):
0x40=0x00、0x42=0x09、0x45=0x01、0x46=0x04、0x48=0x38、0x49..0x4B=0、0x4C=0x20、0x4D..0x4F=0、0x50=0x28、0x51..0x53=0、0x54=0x28、0x55..0x57=0;随位深/尺寸联动:0x41(0x01×25 / 0x02×22)、0x43(0x08×19 / 0x09×27 / 0x0A×1)、0x44(0x04×25 / 0x08×22)、0x47(0x25×22 / 0x45×25);0x58..0x5F为名字区(46 种取值,逐文件与 §29 文件名对应)。 - B 头 0x60..0x9C(u32 视角):
0x64..0x94全 0(47/47);0x9C=0(47/47);仅0x60与0x98有非零例外(见 E.5)。
E.2 0x30/0x34 语义破解(普查 b 节)【E028】
- 全语料观测恰 3 组 (0x30,0x34):A=
(0x042C4100, 0x056DC9F8)×2;B 常见=(0x0AAE1BA0, 0x08BF08CC)×46(全部 TXD + FONTSJP1);FONTSJP 单独=(0x0AAE33A0, 0x08BEF28C)×1。11 个工具生成件全部写 B 常见组(与 chk.rs default/E005 重建头比较一致)。 - 哈希暴力破解:全部 0 命中。算法 {CRC32, adler32, sum8, xor32(按 u32 累积), FNV-1a} × 对象 {whole_file, header, pixel_region, palette, pixel+palette, name8 含 NUL, name 不含 NUL, filename(含扩展名), filename(无扩展名), meta 92B},没有任何 (算法×对象) 组合在任何文件上命中 0x30 或 0x34。
- 指针/大小对假设:全部排除。14 项候选(file_size、data_end、pixel_region_size、palette_offset、palette_size、meta_offset、W·H、W·H·4、texels、tail_offset、meta_base+0x10、0x08−0x1C、pixel_offset、file_size×4)hit30=hit34=0(逐文件比较)。
- 相对关系:v34−v30 = A
0x014188F8(2/2)、B0xFE10ED2C(46)、FONTSJP0xFE10BEEC(1),组内一致、组间不同,无布局含义。 - 结论(不升级语义):0x30/0x34 不是语料内任何内容/文件名/元数据的常见校验和,也不是任何布局指针或尺寸量;"逐来源组恒定、跨组不同"的观测面扩大到 49/49。语义仍 ❓(§32)。
E.3 tail 全量核验(普查 c 节)【E027】
- A:2/2 匹配
{0x28, 0x2C, meta_base, meta_base+8, +0xC, +0x10, +0x14}(meta_base=0x40440);B:47/47 匹配{0x28, 0x2C, 0x3C, 0x48, 0x4C, 0x50, 0x54},无例外(刷新 E014 的 "46+2=48" 口径 → 49/49)。 - 11 个生成件 tail 亦全部匹配(A 重建件匹配 A 表、B 重建件匹配 B 表)。
E.4 Family A meta 92B 全 dump(普查 d 节)【E012/E014 复核】
- 两个 A 样本(LOADSC0/LOADSC3)meta 逐字节对比:除名字字节(
loadsc0\0vsloadsc3\0@ meta[0x18..0x20])外完全一致,非零字段集(18 字节):meta[0x00..0x04]=u320x00000040;meta[0x04..0x08]=u320x00108249;meta[0x08..0x0C]=u320x00040440(=meta_base);meta[0x0C]=32;meta[0x10]=40;meta[0x14]=40;meta[0x58]=1;其余 74 字节全 0。
- 与 §12/E012 既有记录一致(确证,非新字段);tail 后 5 项=meta_base 序列恰指向 meta[0x00]/[0x08]/[0x0C]/[0x10]/[0x14](§8.2/E014)。
E.5 B 头 0x60..0x9F(普查 e 节)【E029 → C-10/C-11】
- 字节级普查:0x60..0x9F 全 0 的文件 1/47;非零字节仅两类:
0x98:46 文件=0x01,FONTSJP1=0x00(唯一原生离群,复核 E015 成立);TXD\SCEJLEGALJ.CHK的0x60=0x4C('L')、0x61=0x4A('J'),系名字SCEJLEGALJ(10 字节)从 0x58 名字区溢出 2 字节,47 文件中唯一一例;0x60..0x9F 不存在其他独立字段。
- C-11(重建件差异):11 个
_chk_verify重建件 0x60..0x9F 全 0(0x98=0),rebuilt_header_matches_real_family_b的槽位比较(0x28..0x54)不覆盖该区域;parse→serialize byte-exact 不受影响(原字节保留)。
E.6 调色板与 Alpha(普查 f 节)【E030 → C-13】
- 原生 49 文件调色板条目总数 6544(A 512 + B 6032);alpha≠255 条目 297 条;含 alpha==0 条目的文件 25 个(FONTSJP、TXD\LOADSC4-22 共 19 个、NEWS2、SCEJLEGALJ、SPLASH1-3)。
- alpha≠255 去重值集 49 种(0..254);206 个中间值语料内未见,故"0..255 均出现"按存在性理解(C-13),全范围覆盖未证实。
- 引用关系(新发现):仅 FONTSJP 的 alpha≠255 条目被像素数据实际引用(≥12/16 条;其解码与 fontjp.png diff=0 佐证这些 alpha 参与成像);其余 24 个含 alpha 条目文件的 alpha≠255 条目全部未被像素索引引用(如 TXD\LOADSC4-22 每文件 16 条中 14 条未用、且未用的恰为全部 alpha≠255 条目)。
- index0 分布 11 种:
(0,0,0,255)×39 为主;彩色 idx0:LOADSC0(A)=(246,212,107,255)、LOADSC3(A)=(186,90,46,255)、NEWS=(112,161,187,255)、NEWS2=(26,103,161,255)、NEWS3=(39,113,132,255)、SPLASH1-3、MEMCARD=(4,4,4,255)、FONTSJP=(253,253,253,245)。 - 重复色条目:CAPCOM 29、SPLASH1 13、SPLASH2 11、SPLASH3 5、FONTSJP1 1、SCEJLEGALJ 1,其余 0,palette_edit/depth-conversion 类测试需注意(§23/§26 的索引层无损不受影响)。
E.7 几何/布局公式全量核验(普查 g 节)【E026】
49/49 OK,误差 0。核验项:容器 0x08/0x0C/0x10;A:file_size=0x40+W·H+1024+92+0x1C、0x28/0x2C=meta_base+0x10、meta[0x08]=meta_base;B:块对齐、file_size=0xA0+block_rows·block_cols·128+nb*4+0x1C、pixel_region=br·bc·128、texels=W·H、0x28/0x2C=0x50。尺寸类分组(原生):
| 类(W×H×bits) | 文件数 | file_size | 代表 |
|---|---|---|---|
| A 512×512×8 | 2 | 263352 | 根目录 LOADSC0/LOADSC3 |
| B 512×256×8 | 18 | 132284 | TXD\LOADSC0-3、MPLOAD0-7、NEWS×3、SPLASH×3 |
| B 512×512×4 | 23 | 131324 | TXD\LOADSC4-22、INTRO1-4 |
| B 512×512×8 | 4 | 263356 | CAPCOM、SCEJLEGALJ、WARNING、FONTSJP1 |
| B 512×1024×4 | 1 | 262396 | FONTSJP |
| B 512×256×4 | 1 | 65788 | MEMCARD |
11 个生成件(含 1024×1024、2048×512、256×512 等非语料尺寸)几何亦全部满足公式。
E.8 位深双判定交叉(普查 h 节)【E033 → C-4 全语料版关闭】
- 判定①(
b[0x44]&0x08)== 判定②(b[0x41]∈{2,1})== 调色板大小(nb*4):47/47 三方一致,无不一致样本。 0x47与位深联动:0x25(8bit)×22、0x45(4bit)×25。
E.9 解码 vs 参考 PNG 逐像素 diff(普查 i 节)【E031】
LOADSC0.CHK(A 512×512)↔loadsc0.png:diff=0 / 262144(复现 E003)。FONTSJP.CHK(B4 512×1024)↔fontjp.png:diff=0 / 524288(新增决定性证据:Family B 块序+nibble 布局+调色板 RGBA 首次获得图像级全图验证)。FONTSJP1.CHK(B8 512×512)↔fontJAP_1.png(2048×1024):尺寸不一致,无法逐像素比对;fontJAP_1 与 FONTSJP1 的对应关系未定(新 ❓,登记 §32 补充)。
E.10 对置信度体系的影响(汇总)
- 升级(字段恒定性/布局公式类,仅此两类):§5.1 通用头恒定字段、§6.2 B 头 0x40..0x5F 恒定字节、§7 全部公式、§8.3 tail 表、§12 meta 布局,由 2 样本核对升级为 49/49(E025–E027、E033)。
- 不升级(语义类,维持原判):
0x30/0x34语义(排除面扩大但语义仍 ❓,E028)、0x98语义(E029)、0x14/0x20语义、tail 消费方式、alpha 渲染语义(E030 只收窄表述)、通道交换差异、Family B=PSP 归属(E010 推断链不变)。 - 新登记矛盾:C-10(名字溢出 vs "0x60..0x9F 全 0")、C-11(重建件 0x98=0 vs 原生 0x01)、C-13("0..255 均出现" vs 49 种离散值实测)。
- cargo test 刷新:E032(75 tests,74 passed / 1 failed,唯一失败为 TXD 侧 dump_fontgif;CHK 全过;byte-exact 覆盖 48/49)。













