如何解包 Godot 自包含 exe(以《多肉小憩》为例)
本文记录2026-09-11解包 Steam 游戏《多肉小憩》(Succulent Break)的实际做法。

渲染中...
本文记录 **2026-09-11** 解包 Steam 游戏《多肉小憩》(Succulent Break,App ID **4536070**)的实际做法。不是通用破解教程,只适用于:你已经合法安装、资源包 **未加密**、目标是读出游戏自己带的图鉴表和图标。
解包产物以作为 **主流玩家MGs** 站内百科数据使用:[多肉小憩百科-主流玩家](https://www.maingamers.com/zh/games/succulent-break)
---
## 解包步骤简介
没有用 GDRE Tools、AssetStudio、FModel,也没有附加调试器。
流程是:
1. 看安装目录文件结构,判断引擎是 **Godot**。
2. 在 `succulent-break.exe` 末尾找到内嵌 **PCK**(资源包)。
3. 按 Godot 4.7 的 pack format **4** 解析文件目录。
4. 抽出 `assets/data/*.json`(图鉴明文)。
5. 顺着 `.png.import` 找到 `.ctex`,从 GST2 里裁出内嵌 **WebP** 当图标。
图鉴数字全部来自这些 JSON,不是猜的,也不是读玩家存档。
---
## 1. 先认引擎,再决定解包器
《多肉小憩》Steam 安装目录只有三个文件:
| 文件 | 作用 |
| --- | --- |
| `succulent-break.exe`(约 110 MB) | 引擎 + 游戏资源打成一个自包含包 |
| `steam_api64.dll` | Steamworks |
| `libgodotsteam.windows.template_debug.x86_64.dll` | GodotSteam 插件 |
后一个文件名直接暴露引擎:**Godot + GodotSteam**。单 exe、没有 `data.win` / `Game_Data` / `Paks` / `UnityPlayer.dll`,也可以侧面印证不是 Unity / Unreal。
认引擎的意义:
| 引擎 | 常见资源位置 | 本仓库已有先例 |
| --- | --- | --- |
| Godot 自包含导出 | exe 末尾内嵌 PCK,或旁边独立 `.pck` | 本文 / `scrape-pck.mjs` |
| Unity | `*_Data/sharedassets*`、Addressables | 依盖之书等走 UnityFS |
| Unreal | `*.pak` / `*.utoc` | FModel |
| Tauri / Electron | exe 里嵌 JS | Bull Run Clicker `scrape-exe.mjs` |
认错引擎就会去找不存在的 `globalgamemanagers`。这一步只看文件名和目录,不需要运行游戏。
---
## 2. 自包含 Godot 把 PCK 藏在哪
Godot 导出「嵌入 PCK」时,布局是:
```
[ PE 可执行文件(引擎) | PCK 资源包 | 12 字节页脚 ]
```
页脚固定 **12 字节**,小端:
| 偏移(相对文件末尾) | 类型 | 含义 |
| --- | --- | --- |
| `-12 .. -4` | `uint64` | PCK 字节数(不含这 12 字节页脚) |
| `-4 .. 0` | `uint32` | 魔数 `GDPC`(`0x43504447`) |
本游戏实测:
```
文件大小 = 115247400
末尾 4 字节 ASCII = GDPC
PCK 大小 = 12264220
PCK 起点 = 115247400 - 12 - 12264220 = 102983168
```
Node 里对应代码:
```js
const magic = buffer.readUInt32LE(buffer.length - 4); // 必须是 0x43504447
const pckSize = Number(buffer.readBigUInt64LE(buffer.length - 12));
const offset = buffer.length - 12 - pckSize;
```
如果末尾不是 `GDPC`,再检查同目录有没有独立的 `succulent-break.pck`。两种都没有,才轮到「资源加密 / 非 Godot」分支。
---
## 3. 解析 PCK 头和文件目录
PCK 起点再读一组头(Godot 4.x,format ≥ 2):
| 字段 | 大小 | 本游戏 |
| --- | --- | --- |
| magic | 4 | 又一个 `GDPC` |
| pack format | 4 | **4**(Godot 4.5+ 把目录挪到包尾) |
| 引擎主/次/补丁 | 4+4+4 | **4.7.1** |
| flags | 4 | `2` = `PACK_REL_FILEBASE`(文件偏移相对 `file_base`) |
| file_base | 8 | `112` |
| reserved[16] | 64 | `reserved[0] = 12181696`,即 **目录相对 PCK 起点的偏移** |
format 2 的旧包:目录紧跟在 reserved 后面。
format 3/4:目录在包尾,偏移放在 `reserved[0]`。本游戏若把 reserved 后面的 4 字节当 `file_count`,会读到 **0**——这是目录已搬家的信号,不要停。
目录条目(每个文件):
```
uint32 path_len
bytes path // UTF-8,Godot 资源路径,无 "res://" 前缀
uint64 offset
uint64 size
bytes md5[16]
uint32 flags // 加密 / 删除标记;本包全部为 0
```
本包 **862** 个文件。真正读内容时:
```
绝对偏移 = pck起点 + (PACK_REL_FILEBASE ? file_base : 0) + 条目.offset
```
扩展名分布能直接告诉你数据在哪:
| 扩展名 | 大约数量 | 含义 |
| --- | --- | --- |
| `.ctex` / `.import` | 各三百多 | 导入后的贴图和 sidecar |
| `.gdc` / `.remap` | 几十 | 编译后的 GDScript,本轮不读 |
| `.json` | **7** | 图鉴真源 |
| `.scn` / `.sample` / 音频 | 其余 | 场景和音效 |
独立游戏常见「表在 JSON / CSV / Tres,代码在脚本」。先按路径搜 `assets/data/`、`data/`、`json`,不要先反编译。
---
## 4. 抽出图鉴 JSON
本包七张表都在 `assets/data/`:
| 文件 | 用途 |
| --- | --- |
| `succulents.json` | 多肉:色系、上色难度、老桩、解锁链、叶插概率 |
| `pots.json` | 花盆:系列、商店价 |
| `soils.json` | 土壤:干燥倍率、特殊效果 |
| `shelves.json` | 架子 / 桌子:槽位、价格 |
| `leaf.json` | 叶片颜色与售价 |
| `weather_tools.json` | 天气道具 |
| `translations.json` | 官方 **zh / en** 名称和说明 |
Godot 的 JSON 允许 **尾逗号**,`JSON.parse` 会失败。先去掉 `}` / `]` 前的逗号再解析:
```js
JSON.parse(text.replace(/,(\s*[}\]])/g, "$1"))
```
原文原样落到 `admin/resources/succulent-break/raw/assets/data/`;去掉尾逗号、并上中英名称后写成 `catalog.json`。不要改写源里的数值。
玩家存档是另一回事:
```
%APPDATA%\Godot\app_userdata\Succulent Break\succulentbreak.json
```
那是进度(拥有哪些花、金币、解锁勾选),**不是**图鉴定义。百科采集不要把存档当主源,也不要提交进 git。
---
## 5. 图标:`.import` → `.ctex` → WebP
导出后的工程里,源 PNG 往往已经没了,只剩:
1. `assets/sprites/ui/succulents/campfire.png.import`(文本 sidecar)
2. `.godot/imported/campfire.png-<hash>.ctex`(Godot 压缩贴图)
`.import` 里有一行:
```
path="res://.godot/imported/campfire.png-98faa0cccb7f279e7901ae49e55b370b.ctex"
```
去掉 `res://` 就能在 PCK 目录里对上 `.ctex`。
`.ctex` 以 `GST2` 开头(Godot Stream Texture 2)。本游戏 UI 图是 32×32,并且 **把 WebP 嵌在 GST2 里**(`DATA_FORMAT_WEBP`):在 blob 里找 `RIFF....WEBP`,按 RIFF 的 `uint32` 长度裁出完整 WebP。
```js
const start = blob.indexOf(Buffer.from("RIFF"));
const size = blob.readUInt32LE(start + 4); // RIFF 载荷长度(不含 8 字节头)
const webp = blob.subarray(start, start + 8 + size);
```
只抽图鉴 UI(`assets/sprites/ui/...`),不要把生长阶段立绘、动画帧(例如叶片的 `orange_left` / `orange_right`)当成词条图标。本轮 UI 图标 **69** 张,缺 0。
若 `.ctex` 里没有 `RIFF`/`WEBP`,可能是 PNG 载荷、Basis、或 VRAM 压缩(DXT/BPTC)。那种要按 Godot `Image::Format` 解,不能套这套裁切。
---
## 6. 整条流水线
```mermaid
flowchart TD
A["Steam 安装目录"] --> B["看见 libgodotsteam / 单 exe"]
B --> C["读 exe 末尾 GDPC 页脚"]
C --> D["PCK 头: Godot 4.7.1 format 4"]
D --> E["reserved[0] 定位目录"]
E --> F["列出 862 个路径"]
F --> G["assets/data/*.json"]
F --> H["ui/*.png.import"]
G --> I["去尾逗号 + translations"]
I --> J["catalog.json"]
H --> K["GST2 内嵌 WebP"]
K --> L["icons-raw/"]
```
仓库内一键重跑(不启动游戏、不写数据库):
```bash
cd admin
node scripts/succulent-break/scrape-pck.mjs
```
默认路径:`D:\SteamLibrary\steamapps\common\Succulent Break\succulent-break.exe`
覆盖:环境变量 `SUCCULENT_BREAK_EXE`,或把 exe 路径当作第一个参数。
产物在 `admin/resources/succulent-break/`(已被 git ignore):
| 路径 | 内容 |
| --- | --- |
| `raw/assets/data/*.json` | PCK 原文 |
| `raw/catalog.json` | 可解析的合并表 |
| `raw/pck-manifest.json` | 862 条路径清单 |
| `icons-raw/{type}/*.webp` | UI 图标 |
---
## 7. 明确没做的事
| 做法 | 为何不做 |
| --- | --- |
| GDRE Tools 还原工程 | 只要表和图标,不必恢复 `.tscn` / 编辑器工程 |
| 反编译 `.gdc` | 本包脚本是编译字节码(头像 `GDSC`),未解密;浇水 / 上色公式因此标「未决」 |
| 读内存 / 附加正在运行的进程 | 静态 PCK 已经够用 |
| 当百科主源去读存档 | 存档是进度 |
| 系统翻译或 AI 译日文 | 游戏内只有 zh/en(G11) |
| 写 `game_data_*` | 本轮只采集 |
Steam 成就(店页 6 条)不在 PCK JSON 里,走共享 `GetSchemaForGame` 管线,见 `docs/060-steam-achievements-game-hub.md`。
---
## 8. 换一款 Godot 游戏时怎么复用
按这个清单走,能复用同一套解析:
1. 安装目录有 `*godot*` dll,或 exe 末尾是 `GDPC`。
2. PCK **未加密**(目录条目 `flags` 不含加密位;抽出来的 JSON 能当文本打开)。
3. 表在 `json` / `csv` / `tres`,而不是只写在脚本常量里。
需要改的通常只有:默认 exe 路径、`assets/data/` 是否换名、图标 `.import` 前缀。
下面这些情况 **不要** 套本脚本:
- Unity / Unreal / RPG Maker / GameMaker。
- PCK 目录加密(`PACK_DIR_ENCRYPTED`)或单文件加密(`PACK_FILE_ENCRYPTED`)。
- 图鉴只存在 `.gdc` 常量、没有 JSON。
- `.ctex` 不是 WebP/PNG 载荷(真 VRAM 压缩)。
那种要换工具(对应引擎的 pak 解包器、GDRE、或官方导出的调试包),不要在本脚本上硬打补丁。
---
## 10. 合规提醒
建议大家只解包自己购买并安装的客户端,且解包内容合法合规使用!
END
评论
登录后查看和发表评论
前往登录