# 像素档案库：六 App 资源解析与动画恢复技术笔记

> 最近核对：2026-09-02。本文记录当前工作区已经验证的技术路径；“已确认”来自原始元数据、二进制边界、原生库调用链或批量回归，“当前实现”是站点的统一展示策略，“运行时边界”表示仅靠离线资源无法证明。

## 1. 项目目标

项目不是一个普通图片查看器，而是一条面向 APK、CDN 与私有资源格式的证据化恢复链：

```text
APK / CDN / 原始数据
  → magic 与资源清单
  → 容器、压缩、加密和字节序
  → Atlas / Frame / Clip / 数据表
  → ID 与对象引用链
  → 原生实例或匹配版本运行时
  → 可追溯 PNG、动画 metadata、物品 catalog
  → 本地图库、Sprite Studio、物品与角色页面
  → 结构校验、专项回归和浏览器视觉抽查
```

核心原则是：**原始结构优先于像素猜测，原生运行时优先于静态推导，无法证明的关系进入 diagnostics，不能伪造成正确结果。**

当前图库索引包含 37,447 张图片：艾诺迪亚3 433、艾诺迪亚4 762、大海贼探险物语 1,906、家园7 794、英雄守卫2（内购）1,002、最强祖师 32,550；其中 6,511 张标记为包含 Frame，7,354 张标记为包含 Clip。

## 2. 证据优先级与永久约束

证据优先级固定为：

1. 游戏原生构造、格式化与渲染输出；
2. 原始动画、Atlas、数据库和对象引用 metadata；
3. 原生库中的加载、更新与渲染实现；
4. 能由边界、数量、引用闭合和批量回归证明的二进制结构；
5. 文件名、规则网格、透明孤岛和外观相似度等启发式。

因此禁止以下退化：

- 有 atlas、animation、SEB、SNA 或 skeleton 时重新按透明孤岛切图；
- 把 Item ID、Costume Part、Variant 和 Frame index 当成同一编号；
- 随机选择武器、Skin、季节主题或动态贴图；
- 在未证明前跨版本复用 table 编号、record size、byte offset 或时间单位；
- 忽略 Pivot、Trim、Rotation、逻辑纹理尺寸、Alpha/PMA 和图层顺序；
- 把整张 Spine atlas 伪造成一个 Frame，或把错误但“会动”的组合称为动画；
- 用测试通过否定页面中可见的错帧、黑边、抖动或 UI 错位。

## 3. 统一 Sprite 资源模型

统一模型不是替换原格式，而是让提取器、图库和播放器共享概念：

```text
SpriteDocument
├─ Source：游戏、原文件、格式、提取器和诊断
├─ TexturePage[]：实际像素、逻辑尺寸、scale、alphaMode
├─ Region[]：矩形或 polygon 采样区域、旋转状态
├─ Frame[]：sourceSize、trimOffset、pivot、region 引用
├─ Clip[]：sequence / composite / skeleton
│  └─ Track[] → Keyframe[]：时间、图层、变换、Alpha、Frame/Variant
├─ VariantSet[]：Skin、装备、主题、方向和动态绑定
└─ RenderProfile：坐标系、联合边界、预览隐藏项和取景规则
```

必须区分四层：

- **TexturePage / Region** 回答像素存在哪里；
- **Frame** 回答裁剪前尺寸、Trim 和 Pivot；
- **Keyframe / Clip** 回答什么时间、什么图层、用什么变换播放；
- **Variant / Runtime binding** 回答当前装备、Skin、季节或角色实际选择哪个资源。

时间真值使用毫秒和逐 Key 时长，FPS 只用于播放采样与 UI。复合动画使用整段可见联合边界避免画布抖动；Spine 保留 skeleton、atlas 和 texture page，由匹配版本 runtime 解释骨骼、Slot、Attachment、Mesh、Skin 与 Deform。

## 4. 六 App Adapter 矩阵

| App | 资源入口 | Frame / Clip 真值 | 关键解析 | 属性与展示边界 |
|---|---|---|---|---|
| 艾诺迪亚3 | `i_*.dat.jpg`、`game.dat.jpg`、`libgame.so` | SNA Sprite → Costume → Frame → Animation | 外层/记录级 LZMA、24 位 LE offset、高位压缩标志、索引色/RGB565 | Item table 12，17-byte record；属性以原生实例和 `DESC_MakeItem` 为最高事实层 |
| 艾诺迪亚4 | 同族 SNA 与 `game.dat.jpg` | SNA 复合动画，Key 含 duration | 7 组 Bank；Sprite 宽高 16 位；Frame 11 bytes；Animation Key 8 bytes | Item table 13，23-byte record；使用 `UIDesc_MakeItem`，不能套用 I3 偏移 |
| 英雄守卫2 | PNG + legacy `.sprite` v1.0.02、`unitdata.dat`、本地化、`libgame.so` | `.sprite` 多 Part 轨道、Anchor、Key 时间 | Cocos Y-up 到 Canvas Y-down；rotation 不反转；按 order/blend 合成 | 卡牌 UI 需复用原生背景、图标与 `status_number.png` 位图数字，不用 CSS 字体近似 |
| 家园7 | `assets/images-1x`、TexturePacker XML/JSON、`.animation`、Spine atlas | Atlas Region/Trim/Pivot/Polygon + animation 状态/时长 | 旋转块先按存储矩形裁剪再逆旋转；polygon 用三角形 UV 重建；季节按来源路径绑定 | economy CSV、l10n 与 `libmain.so` 提供资源、生产链、建筑和规则；动态价格与地图实例单列 |
| 大海贼探险物语 | Unity AssetBundle 导出的 `.bytes`，内部 Kairo archive | `seb.inf` + `.seb` 多层关键帧；`img.inf`/OPT 提供贴图 | archive BE；明文优先，失败后用 44-byte 循环 XOR；逻辑/物理尺寸与 density 必须分开 | Kairo TSV/字符串序列、IL2CPP DataLoad 类型；Job 默认武器经 `sampleWeapon → EquipData → weapon image`，不能随机 |
| 最强祖师 | APK 基础资源 + CDN AssetBundle | Unity SpriteAnimation 或 Spine 3.8 skeleton | 清单断点下载 + size/CRC32；Unity `PPtr` 以 `(assets file, pathID)` 标识；跨 CAB 建索引 | Lua 配置、Tips 组件链、Unity SpriteAtlas；玩家随机词条和服务器状态属于运行时边界 |

## 5. 各格式最关键的已证明语义

### 艾诺迪亚3 / 4

伪 `.jpg` 实际是 LZMA/ResourceBank。Bank 目录为 little-endian，offset 使用 24 位并把 `0x800000` 作为记录级 LZMA 标志。两代只共享 reader 骨架，不共享已证明常量：I3 有 6 组、I4 有 7 组；I3 Costume part 在 Item record `+13`，I4 在 signed `+16`；主副手槽号也不同。

正确装备链是：

```text
装备槽 → Costume Group 的 class mask → Item class
      → Item Costume part → Costume variant → Sprite / Frame
```

### 英雄守卫2

`.sprite` 文件保存 Frame 表、Clip、Part、Part anchor、order、blend 及 Key 的 bounds、position、rotation、scale、time 和 alpha。多个 Part 是同一时刻叠加的图层，不是多帧图片。原生库确认 position 与 SpriteFrame offset 是 Cocos Y-up；转换到 Canvas 时只处理 Y 与 anchorY 方向，不反转 rotation。

### 家园7

TexturePacker metadata 已提供 `x/y/w/h`、`oX/oY/oW/oH`、pivot、rotation 和 polygon 顶点/UV/triangles。`.animation` 的 section 是 Clip 名，`frame:` 顺序与时长是时间轴真值。透明孤岛只能用于没有 metadata 的人工回退。

### 大海贼

Unity 只是外层，实际动画是 Kairo SEB。Archive 先严格尝试明文；结构校验失败后才从 byte 0 使用 44-byte key 循环 XOR。SEB/INF/OPT 均以 big-endian 解释；SEB 保存稀疏多层 Key、UV、尺寸、位移、flip、blend 和 color alpha。动态人物与怪物贴图依 `checkimg.inf`、`img.inf` 和原始对象字段绑定。

### 最强祖师

大量资源不在 APK，而由 CDN 清单提供。下载输出先写 `.part`，断点请求必须得到 206，否则从零重下；正式替换前校验 fileSize 与 CRC32。Unity 对象引用不能只看 pathID，必须连同所属 assets file。Spine 预览必须使用 3.8 兼容链路；atlas 未声明 `pma: true` 时按 straight alpha，避免亮色块或黑边。

## 6. 物品、人物和建筑数据恢复

当前 catalog 规模：

| 数据集 | 数量 | 内容 |
|---|---:|---|
| 艾诺迪亚3/4 | 2,109 | 物品记录、类别、图标、原生描述/属性和原始证据 |
| 最强祖师 | 9,828 | Lua 配置、品质、Tips 风格、图标与静态属性 |
| 大海贼 | 792 | 138 装备、125 道具、51 技能、49 职业、234 人物、195 怪物 |
| 英雄守卫2 | 99 | 39 物品卡、24 商店物品、34 兵种、2 英雄 |
| 家园7 | 266 | 32 资源、20 生产项、38 建筑、176 条规则 |

艾诺迪亚的完整描述不能只读 ITEMDATABASE。默认实例链还依赖数据表、物品池、词条池、文本表和平台格式化桥：

```text
解码 game / memorytext
  → EXCELDATA_Create
  → ITEMPOOL_Initialize + ITEMOPTPOOL_Initialize
  → ITEMSYSTEM_CreatePerfectItem
  → DESC_MakeItem / UIDesc_MakeItem
  → 控制码清洗与属性分类
  → 游戏原生释放路径
```

批量结果必须区分“有正文、仅控制码、正常空文本、调用失败”，并为每项结构化属性保留 `sourceLine`。站点详情只呈现一个最高证据事实层；静态表和原始 bytes 留作搜索与诊断，不能与原生输出混装。

## 7. 本地站点结构

站点位于 `site/`，技术栈为 Node.js 22+、React 19、Next 16（vinext/Vite 适配）、Vite 8 与 TypeScript。核心页面分为：

- **图片图库**：目录树、搜索、纯色/单图/图集/已处理/含 Frame/含 Clip 筛选；
- **Sprite Studio**：读取 legacy `.sprite` 或统一 JSON，展示 Frame 行内缩略图、Clip、装备/Variant、缩放与全屏预览；
- **物品与对象页**：按 App Adapter 展示物品、职业、人物、怪物、建筑和规则；
- **Spine 预览**：加载 skeleton、atlas、page，组合 Skin 并保留 Alpha/逻辑尺寸语义；
- **目录规则**：`gallery-exclusions.json` 持久化屏蔽与保留子目录，保留规则优先；写入前显示 App、目录和影响数量，可恢复。

Vite 本地中间件只在受控根目录内解析路径：

| 路由 | 用途 |
|---|---|
| `/media/*` | 读取 `picapk` 图片，禁缓存以避免旧像素配新 metadata |
| `/sprite-metadata/*` | 从 `unapk` 解析 JSON 或 legacy `.sprite` |
| `/spine-assets/*` | 只提供 `.skel/.atlas/.png` |
| `/gallery-exclusions` | 读取、添加、删除屏蔽/保留规则，限制请求体和真实图片路径 |

索引与 catalog 是生成物，主要文件为 `site/public/gallery-data.json`、`inotia-items.json`、`zqzs-items.json`、`kairo-catalog.json`、`hero-guard-2.json` 和 `townsmen-catalog.json`。

## 8. 主要工具入口

```powershell
# APK 解包与通用图片提取
powershell -NoProfile -ExecutionPolicy Bypass -File .\script\unpack-apk.ps1 -Root .
powershell -NoProfile -ExecutionPolicy Bypass -File .\script\extract-pictures.ps1 -Root .

# 各 App Adapter
.\script\extract-inotia-assets.ps1
.\script\extract-inotia-items.ps1
.\script\extract-hero-guard-2.ps1
.\script\extract-townsmen-assets.ps1
.\script\extract-townsmen-catalog.ps1
.\script\extract-kairo-assets.ps1
.\script\extract-kairo-catalog.ps1
python .\script\pull_zqzs_cdn.py
python .\script\extract_zqzs_assets.py
.\script\extract-zqzs-items.ps1

# 图库索引与站点
.\script\build-gallery.ps1
.\script\start-gallery.ps1
cd site
npm test
npm run lint
npm run build
```

主要 Python Adapter 位于 `tools/inotia_assets.py`、`inotia_items.py`、`hero_guard_2.py`、`townsmen_assets.py`、`townsmen_catalog.py`、`kairo_assets.py`、`kairo_catalog.py` 与 `zqzs_items.py`。

## 9. 验证与完成定义

一次格式恢复只有同时满足以下条件才能标记完成：

1. **结构**：版本、count、offset、record 尾、解压长度、PNG 重解码都闭合；
2. **引用**：Frame、Texture、Item、Costume、PPtr、CAB、Skin/Slot 引用无静默越界；
3. **动画**：顺序、时长、循环、图层、Pivot、取景、装备、Alpha 和首尾边界有专项样本；
4. **属性**：全量记录连续、无重复/缺失，并统计正文、空文本、降级、错误与释放；
5. **UI**：在真实浏览器检查短/长数字、两类图标、列表/详情缩放，以及错帧、错层、黑边、抖动；
6. **站点**：单元测试、lint、build 通过，生成数据可重新加载。

当前专项测试集已达到 154 项；这只证明结构和回归契约，视觉是否等价仍以同资源、同状态的页面抽查为准。

## 10. 新 App 的最短正确路径

```text
建立文件清单与 magic 报告
  → 识别引擎和真实资源入口
  → 找 metadata / loader / native 构造
  → 用少量样本证明字节序、记录宽度和引用链
  → 建独立 Adapter 与 diagnostics
  → 输出统一 SpriteDocument / catalog
  → 选包含旋转、Trim、镜像、半透明、跨包引用的代表样本
  → 全量结构校验
  → 接入站点并做浏览器视觉抽查
```

如果原始动画、描述或运行时绑定不可获得，应保留静态资源并明确标记缺口；“不支持”比生成错误 Clip、错误武器或错误属性更有价值。

## 11. 维护入口

- `experience.md`：六 App 固定经验、执行顺序、禁止项和完成定义；
- `unapk.md`：APK、LZMA、XOR、字节序、字段偏移、Unity/CAB 与原生库分析；
- `sprite.md`：统一 Sprite 模型、格式映射、渲染语义与技术演进；
- `todo.md`：按 todo 编号记录设计、实施与验证；
- `README.md`：常用命令和入口说明，历史统计可能滞后，数量以当前生成 JSON 为准。

这套工程的核心资产不是 37,447 张 PNG，而是每张图片、每个 Frame、每段 Clip 和每项属性都能回到原文件、原始 ID、对象引用与运行时边界。