文件信息
- 名称:
華は短し、踊れよ乙女/Chip4/ST02A_S.pna - 大小: 2,861,570
- 端序: 小端
| 偏移 | 十六进制 | 字段含义 | 十进制/ASCII |
|---|---|---|---|
| 0x00~0x03 | 50 4E 41 50 | Magic | PNAP |
| 0x04~0x07 | 10 06 00 00 | 版本号? | 1552 |
| 0x08~0x0B | FD 03 00 00 | 画布宽度 | 1021 |
| 0x0C~0x0F | EF 04 00 00 | 画布高度 | 1263 |
| 0x10~0x13 | 23 00 00 00 | 条目数 | 35 |
注:版本号字段在不同文件中会变化,详见「跨文件对比」。
PNA 文件的核心是一个树形结构,目录条目通过 Type 字段构建嵌套层级:
| Type | 含义 | 说明 |
|---|---|---|
| 1 | Push(入栈) | 创建一个新的分组容器,后续的 Type=0 条目归属于该分组 |
| 0 | Leaf(叶子) | 普通图层,包含图片数据,归属于当前最内层容器 |
| 2 | Pop(出栈) | 关闭当前容器,回到上一层容器 |
解析方式:维护一个栈,遍历条目时:
Type=1→ 创建新容器并入栈Type=0→ 将图层添加到栈顶容器Type=2→ 出栈(回到父容器)
以 ST02A_S.pna 为例:
搜索 89 50 4E 47 (PNG 签名)
实际 PNG 数 = 29
| 数据块 | 地址 | 宽度 | 高度 |
|---|---|---|---|
| P1 | 0x058C | 145 | 101 |
| P2 | 0x36ED | 131 | 103 |
EntrySize = (P1 - HeaderSize) / Count
P1 = 0x058C = 1420HeaderSize = 0x14 = 20Count = 35
(1420 - 20) / 35 = 40 字节
结论:每个目录条目固定 40 字节(0x28)。
每个条目 40 字节,字段分布如下:
| 相对偏移 | 长度 | 字段 | 说明 |
|---|---|---|---|
+0x00 |
4 | Type | 0=图层, 1=Push, 2=Pop |
+0x04 |
4 | ID | 图层编号(标记条目为 -1) |
+0x08 |
4 | BaseX | 绘制锚点 X |
+0x0C |
4 | BaseY | 绘制锚点 Y |
+0x10 |
4 | Width | 图像宽度 |
+0x14 |
4 | Height | 图像高度 |
+0x18 |
4 | 保留 | 固定为 0 |
+0x1C |
4 | 保留 | 固定为 0 |
+0x20 |
4 | 缩放系数 | 固定浮点数 1.875(普通图层) |
+0x24 |
4 | CompSize | 压缩后数据大小 |
01 00 00 00 FF FF FF FF 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
| 偏移 | 十六进制 | 字段 | 值 |
|---|---|---|---|
| 0x3C~0x3F | 00 00 00 00 | Type | 0 |
| 0x40~0x43 | 21 00 00 00 | ID/Zorder | 33 |
| 0x44~0x47 | 9E 01 00 00 | BaseX | 414 |
| 0x48~0x4B | 14 01 00 00 | BaseY | 276 |
| 0x4C~0x4F | 91 00 00 00 | Width | 145 |
| 0x50~0x53 | 65 00 00 00 | Height | 101 |
| 0x54~0x57 | 00 00 00 00 | 保留 | 0 |
| 0x58~0x5B | 00 00 00 00 | 保留 | 0 |
| 0x5C~0x5F | 00 00 F0 3F | 缩放? | 1.875 |
| 0x60~0x63 | 61 31 00 00 | CompSize | 12641 |
| 偏移 | 十六进制 | 字段 | 值 |
|---|---|---|---|
| 0x64~0x67 | 00 00 00 00 | Type | 0 |
| 0x68~0x6B | 20 00 00 00 | ID | 32 |
| 0x6C~0x6F | A3 01 00 00 | BaseX | 419 |
| 0x70~0x73 | 12 01 00 00 | BaseY | 274 |
| 0x74~0x77 | 83 00 00 00 | Width | 131 |
| 0x78~0x7B | 67 00 00 00 | Height | 103 |
| 0x7C~0x7F | 00 00 00 00 | 保留 | 0 |
| 0x80~0x83 | 00 00 00 00 | 保留 | 0 |
| 0x84~0x87 | 00 00 F0 3F | 缩放? | 1.875 |
| 0x88~0x8B | B0 17 00 00 | CompSize | 6080 |
| 索引 | Type | ID | 分组 |
|---|---|---|---|
| 0 | 1 | -1 | 根组开始 |
| 1~22 | 0 | 33~12 | 表情组(22 个图层) |
| 23 | 2 | -1 | 表情组结束 |
| 24 | 1 | -1 | 颊组开始 |
| 25~26 | 0 | 9~8 | 颊组(2 个图层) |
| 27 | 2 | -1 | 颊组结束 |
| 28 | 1 | -1 | 身体组开始 |
| 29~33 | 0 | 5~1 | 身体组(5 个图层) |
| 34 | 2 | -1 | 身体组结束 |
规律:
- 根节点
Type=1(索引 0)和最后的Type=2不一定成对出现 - 每个分组由
Type=1开始,Type=2结束 - 有效图层数 =
Count - (Type=1/2 标记数)
标题文件展示了比立绘更复杂的树形结构:
- 版本号:3708
- 画布:1280 × 720
- 条目数:84
- 有效图层:48
- 分组数:16(嵌套多层)
关键发现:
- 树形结构可以任意深度嵌套
- 分组数量不固定(立绘文件 3 组,标题文件 16 组)
Type=1/2是栈操作符,实现了完整的树形描述能力
PNA 文件不存储每个条目的数据偏移量(Offset)。引擎通过顺序累加 CompSize 定位下一张图:
- 第一个 PNG 数据块位于
HeaderSize + Count × EntrySize - 读取当前条目的
CompSize后,指针后移CompSize字节 - 指针位置即为下一个 PNG 数据块的起始地址
验证:P1(0x058C) + CompSize条目1(0x3161) = 0x36ED = P2 ✅
结论在《華は短し、踊れよ乙女》及另一作《宿りし乙女の誓いと魔法》中验证成立。
- 固定结构:
HeaderSize = 20,EntrySize = 40 - 字段
[+0x20]固定为1.875(普通图层)或0.0(标记条目) - 树形结构通用,分组数量不固定
- 版本号字段不固定,可用于区分不同游戏/版本
| 版本号 | 十六进制 | 包含的文件组 |
|---|---|---|
| 3708 | 0x0E7C | SYS_Title.pna(标题文件) |
| 1552 | 0x0610 | ST01A、ST02A(L/M/S)、ST02B(L/M/S) |
| 1508 | 0x05E4 | ST01B、ST03A、ST03B、ST04A、ST05A、ST07A |
| 1464 | 0x05B8 | ST04B、ST05B |
| 1420 | 0x058C | ST06A |
| 936 | 0x03A8 | ST08A |
| 804 | 0x0324 | ST09A |
- HeaderSize = 20(0x14),固定不变
- EntrySize = 40(0x28),固定不变
- 版本号不影响解析逻辑,仅作元信息参考
- 树形结构是通用特性,不要假设固定分组数
- 数据块连续堆叠,无偏移表