GTA .chk 纹理格式(ChkFile)Wiki
版本:基于
crates/txd-format/src/chk.rs的实现 + 逆向样例。.chk没有公开官方规范, 本文区分【✅ 已确证】与【⚠️ 推断】与【❓ 待验证】,不把推测写成事实。
目录
- 概述
- 容器(通用结构)
- Family A(PS2 · 8bit 方形 · 256 色)
- Family B(PSP · 块状 · 8bit 256 / 4bit 16)
- 位深 / 调色板
- 索引交换(仅 Family A)
- 从 RGBA 生成(量化)
- 与 TXD 的区别
- 置信度
- 字段级映射(header / meta / tail 各偏移)
- 参考 / 样例
- 已完全掌握 vs 需外部资料
- 附录 A:真实样例头部十六进制 dump
1. 概述
.chk是一张 调色板索引 纹理(非真彩、非 TXD 字典):每个像素存的是调色板下标。- 魔数:
b"xet\0"(0x78 0x65 0x74 0x00)——注意是"xet"反序(对应"tex")。 - 一个
.chk文件 = 单张贴图(宽 × 高 + 调色板 + 索引像素),与 RenderWare.txd(可含多贴图)不同。 - 平台:【✅】Family A = PS2,【⚠️ 推断】Family B = PSP(依据:你给的
LOADSC0/LOADSC3确认为 PS2;PSP 专用工具GTAStoriesTex只认 Family B)。 - 字节序【✅】:所有字段(魔数、尺寸、偏移、地址表、
log2几何、位深类型)均为 little-endian。截至当前样例未发现 big-endian 版本;若未来样本呈高位端序,需按像素做字节交换,此处留作警示。 - 不含 mipmap / 无级联【✅】:
.chk是单一级的位图,解析未发现任何多级/mip 链——只有一个像素区 + 一个调色板。它不是像.txd那样可含多贴图/多 mip 的字典。
2. 容器(通用结构)
[ 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,只能靠像素布局分。
3. Family A(PS2 · 8bit 方形 · 256 色)【✅ 已确证】
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处为偏移字段(回写用)。
4. Family B(PSP · 块状 · 8bit 256 / 4bit 16)【✅ 已确证】
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 色。
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 字节/行)。
5. 位深 / 调色板
| 位深 | 调色板 | 家族 |
|---|---|---|
| 8bit | 256 色(PAL8) | Family A(PS2,方形)、Family B(PSP,块状) |
| 4bit | 16 色(PAL4) | 仅 Family B(PSP,块状) |
- 调色板条目每项 4 字节
[R,G,B,A]。
6. 索引交换(仅 Family A)
chk_swap34(v):交换一个 8bit 索引的 bit3 与 bit4。仅 Family A 使用;Family B 直接存下标。
7. 从 RGBA 生成(量化)【✅ 已确证】
写入时用 median-cut 把 RGBA 量化到 256/16 色:
- 全透明像素(
A==0)不参与颜色聚类,固定占一个透明项(index 0),不挤占真实色槽。 - 颜色距离
dist2中 Alpha 降权(颜色主导)。 - 8bit:
from_rgba(方形 A)/from_rgba_block8(块 B);4bit:from_rgba_font。
8. 与 TXD 的区别
.txd(RenderWare) | .chk | |
|---|---|---|
| 结构 | 字典,可含多贴图 | 单贴图 |
| 格式 | 真彩 8888 / ABGR、16bit 4444/5551/565、PAL8、DXT | 调色板索引(4/8bit) |
| 平台 | PC(D3D8/D3D9)、PS2、XBOX | PSP / PS2 自制纹 |
| 解码 | 直接像素内容 | 调色板查表 |
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 |
| 调色板字节序(RGBA)+ 索引交换还原原图 | ✅ 决定性 | 解析 LOADSC0.CHK 的 RGBA 与参考 loadsc0.png 逐字节 diff=0(512×512) |
| Family A 方形 / Family B 块 的像素布局+索引交换 | ✅ 强证据 | byte-exact 还原证实(非仅"看起来对") |
| Family A=PS2、Family B=PSP | ✅ 确证 | 用户确认 LOADSC0/3=PS2;PSP 专用工具 GTAStoriesTex 仅认 Family B;且 byte-exact 证实两家族均为索引纹理 |
| PS2 vs PSP 的字节序/通道交换差异 | ⚠️ 样例内未发现 | 两家族各自解码颜色正确 + byte-exact;但无 PSP/PS2 成对图无法最终定论 |
| Family A / B 头、meta、tail 各偏移字段 | ✅(第 10 节逐字段表) | 用 LOADSC0/LOADSC3 + TXD*.chk 逐字节比对 |
家族特有值(0x30/0x34) | ⚠️ 逐样本可变 | 见 §10.1:A 两样本恒定,B 在两同名样本间不同(FONTSJP vs FONTSJP1);族内不恒定,语义未知 |
tail 偏移/地址表 | ✅ | 原生 CAPCOM/INTRO1/LOADSC0 逐项 byte-exact |
10. 字段级映射(header / meta / tail 各偏移)
偏移 + 观测值来自对全部样例(
LOADSC0/LOADSC3Family A 与TXD\*.chkFamily B)的逐字段 dump。u32均为 little-endian。数值栏给出"观测常量"或"随内容变化的样例"。 标注:【✅】确证 /【⚠️】推断 /【❓】语义未知。
10.1 通用 header(两家族共有)
| 偏移 | 含义 | 观测 | 置信 |
|---|---|---|---|
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 | ✅ |
10.2 Family A(方形 8bit,PS2)特有
| 偏移 | 含义 | 观测 | 置信 |
|---|---|---|---|
0x28,0x2c | meta 起始地址 | = data_end − 92 | ✅ |
meta[0x00] | 常量 | 0x40 | ✅ |
meta[0x08] | meta 基址 | = meta 起始 | ✅ |
meta[0x18] | 贴图名(8 字节,NUL 截断) | 如 "loadsc0" | ✅ |
meta[其余] | 大多为 0 | 0 | ❓ 少量字段语义未确认 |
10.3 Family B(块状,PSP)特有
| 偏移 | 含义 | 观测 | 置信 |
|---|---|---|---|
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" | ✅ |
10.4 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 基址序列。结构表语义已确证,但其消费方(是否每个都必须有效、固定)仍需游戏/工具侧验证。
10.5 待确认点 / 下一步
- header
0x14=7、0x20=6、Family A/B 的0x30/0x34、Family B0x45/0x46/0x47/0x48..0x54等恒定常量:数值已确证,但确切语义(版本号?格式标记?)仍需对照更多已知家族/版本才能锁定。❓ - tail 表在游戏/工具中的消费方式(是否逐项校验):未验证。❓
- 家族间字节序/通道交换是否真有差异:目前两家族均按各自规则解码且颜色正确,样例范围内未发现需额外交换。⚠️
11. 参考 / 样例
png2chk.utf8.cpp:参考写入器(注:其0x30/0x34写入有误,以本实现为准)。GTAStoriesTex.exe:PSP 专用工具,只认 Family B。- 样例:
LOADSC0.CHK、LOADSC3.CHK(PS2 · Family A,263352 字节);TXD\*.chk(47 个 Family B)。
12. 已完全掌握 vs 需外部资料
✅ 已完全掌握(能忠实编解码 + 还原)
- 容器结构:魔数、
0x08大小、0x0cdata_end、tail偏移/地址表(header/meta/tail 逐字段表见第 10 节)。 - 家族与 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,需要不同来源的样本或运行时才能继续锁定。
13. 附录 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,见 §10.2/10.4) |
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)与 §10.1 表、以及 FONTSJP1/INTRO1 均不同,是逐样本可变的值,已据此把置信度改为 ⚠️。Family A/B 头主体结构一致,只能靠0x3C标志 + 像素布局区分家族(这正是 §2 的结论)。
附记(致谢与许可)
这个 .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,尽量让工具按家族几何+调色板自适应,而不是依赖那几个未明语义的常量——这样最稳。
(完)






