How to unpack Godot self-contained exe (using "Succulent Nap" as an example)
This article records the actual procedure for unpacking the Steam game "Succulent Break" on 2026-09-11.

Rendering...
# Extracting Data from Succulent Break (App ID 4536070)
This document records the actual procedure for unpacking the Steam game "Succulent Break" (App ID **4536070**) on **2026-09-11**. This is not a general cracking tutorial; it only applies if: you have legally installed the game, the resource package is **not encrypted**, and your goal is to extract the game's built-in encyclopedia entries and icons.
The extracted assets will be used as data for the "Succulent Break" encyclopedia on the **MainGamers MGs** website: [Succulent Break Encyclopedia - MainGamers](https://www.maingamers.com/zh/games/succulent-break)
---
## Brief Overview of Extraction Steps
This process does not use GDRE Tools, AssetStudio, FModel, or any additional debuggers.
The workflow is as follows:
1. Examine the installed directory file structure to determine the engine is **Godot**.
2. Locate the embedded **PCK** (resource package) at the end of `succulent-break.exe`.
3. Parse the file directory according to Godot 4.7's pack format **4**.
4. Extract `assets/data/*.json` (plaintext encyclopedia entries).
5. Follow `.png.import` to find `.ctex` files and extract embedded **WebP** images as icons from GST2.
All encyclopedia numbers are derived from these JSON files, not guessed or read from player saves.
---
## 1. Identify the Engine First, Then Choose the Extractor
The Steam installation directory for "Succulent Break" contains only three files:
| File | Purpose |
| --- | --- |
| `succulent-break.exe` (approx. 110 MB) | Engine + game resources packed into a self-contained package |
| `steam_api64.dll` | Steamworks |
| `libgodotsteam.windows.template_debug.x86_64.dll` | GodotSteam plugin |
The latter filename directly reveals the engine: **Godot + GodotSteam**. A single exe, without `data.win` / `Game_Data` / `Paks` / `UnityPlayer.dll`, also indirectly suggests it's not Unity / Unreal.
The significance of identifying the engine:
| Engine | Common Resource Location | Precedent in This Repository |
| --- | --- | --- |
| Godot Self-contained Export | PCK embedded at the end of the exe, or a separate `.pck` file alongside | This document / `scrape-pck.mjs` |
| Unity | `*_Data/sharedassets*`, Addressables | Via UnityFS for books like "The Book of the Covered" |
| Unreal | `*.pak` / `*.utoc` | FModel |
| Tauri / Electron | JS embedded within the exe | Bull Run Clicker `scrape-exe.mjs` |
Misidentifying the engine will lead to searching for a non-existent `globalgamemanagers`. This step only requires looking at filenames and directories; no need to run the game.
---
## 2. Where Godot's Self-Contained Export Hides the PCK
When Godot exports with "embedded PCK," the layout is:
```
[ PE Executable (Engine) | PCK Resource Package | 12-byte Footer ]
```
The footer is fixed at **12 bytes**, little-endian:
| Offset (from end of file) | Type | Meaning |
| --- | --- | --- |
| `-12 .. -4` | `uint64` | PCK byte count (excluding this 12-byte footer) |
| `-4 .. 0` | `uint32` | Magic number `GDPC` (`0x43504447`) |
Actual measurement for this game:
```
File Size = 115247400
Last 4 Bytes ASCII = GDPC
PCK Size = 12264220
PCK Start = 115247400 - 12 - 12264220 = 102983168
```
Corresponding code in Node.js:
```js
const magic = buffer.readUInt32LE(buffer.length - 4); // Must be 0x43504447
const pckSize = Number(buffer.readBigUInt64LE(buffer.length - 12));
const offset = buffer.length - 12 - pckSize;
```
If the end is not `GDPC`, check for an independent `succulent-break.pck` in the same directory. Only if neither is found do you proceed to the "encrypted resources / not Godot" branch.
---
## 3. Parsing the PCK Header and File Directory
From the PCK start, read another set of headers (Godot 4.x, format ≥ 2):
| Field | Size | This Game |
| --- | --- | --- |
| magic | 4 | Another `GDPC` |
| pack format | 4 | **4** (Godot 4.5+ moved the directory to the end of the package) |
| Engine Major/Minor/Patch | 4+4+4 | **4.7.1** |
| flags | 4 | `2` = `PACK_REL_FILEBASE` (file offsets are relative to `file_base`) |
| file_base | 8 | `112` |
| reserved[16] | 64 | `reserved[0] = 12181696`, which is the **directory offset relative to the PCK start** |
Format 2 old packages: The directory follows immediately after `reserved`.
Format 3/4: The directory is at the end of the package, with its offset in `reserved[0]`. If this game interprets the 4 bytes after `reserved` as `file_count`, it will read **0**—this is a signal that the directory has moved, so don't stop.
Directory entry (for each file):
```
uint32 path_len
bytes path // UTF-8, Godot resource path, without "res://" prefix
uint64 offset
uint64 size
bytes md5[16]
uint32 flags // Encryption / deletion flags; all are 0 in this package
```
This package has **862** files. When actually reading content:
```
Absolute Offset = pck_start + (PACK_REL_FILEBASE ? file_base : 0) + entry.offset
```
The distribution of extensions directly tells you where the data is:
| Extension | Approximate Count | Meaning |
| --- | --- | --- |
| `.ctex` / `.import` | Over 300 each | Imported textures and sidecars |
| `.gdc` / `.remap` | Dozens | Compiled GDScript, not read this round |
| `.json` | **7** | True source of encyclopedia data |
| `.scn` / `.sample` / Audio | The rest | Scenes and sound effects |
Common in indie games: "tables in JSON / CSV / Tres, code in scripts." First, search by path for `assets/data/`, `data/`, `json`, don't decompile first.
---
## 4. Extracting Encyclopedia JSON
This package has seven tables, all located in `assets/data/`:
| File | Purpose |
| --- | --- |
| `succulents.json` | Succulents: color families, difficulty of coloring, old plants, unlock chains, leaf propagation probability |
| `pots.json` | Pots: series, shop price |
| `soils.json` | Soils: drying multiplier, special effects |
| `shelves.json` | Shelves / Tables: slots, price |
| `leaf.json` | Leaf color and selling price |
| `weather_tools.json` | Weather tools |
| `translations.json` | Official **zh / en** names and descriptions |
Godot's JSON allows **trailing commas**, which will cause `JSON.parse` to fail. Remove commas before `}` / `]` before parsing:
```js
JSON.parse(text.replace(/,(\s*[}\]])/g, "$1"))
```
The original content is placed in `admin/resources/succulent-break/raw/assets/data/`; after removing trailing commas and combining with English/Chinese names, it's written as `catalog.json`. Do not modify the numerical values in the source files.
Player saves are a different matter:
```
%APPDATA%\Godot\app_userdata\Succulent Break\succulentbreak.json
```
This contains progress (which plants owned, gold, unlock status), **not** encyclopedia definitions. Encyclopedia collection should not use saves as the primary source, nor should they be committed to git.
---
## 5. Icons: `.import` → `.ctex` → WebP
In exported projects, the source PNGs are often missing, leaving only:
1. `assets/sprites/ui/succulents/campfire.png.import` (text sidecar)
2. `.godot/imported/campfire.png-<hash>.ctex` (Godot compressed texture)
The `.import` file contains a line:
```
path="res://.godot/imported/campfire.png-98faa0cccb7f279e7901ae49e55b370b.ctex"
```
Removing `res://` allows you to locate the `.ctex` file within the PCK directory.
`.ctex` starts with `GST2` (Godot Stream Texture 2). This game's UI images are 32x32 and **embed WebP within GST2** (`DATA_FORMAT_WEBP`): find `RIFF....WEBP` within the blob and extract the complete WebP using the RIFF `uint32` length.
```js
const start = blob.indexOf(Buffer.from("RIFF"));
const size = blob.readUInt32LE(start + 4); // RIFF payload length (excluding 8-byte header)
const webp = blob.subarray(start, start + 8 + size);
```
Only extract UI icons ( `assets/sprites/ui/...` ); do not treat growth stage portraits or animation frames (e.g., `orange_left` / `orange_right` for leaves) as entry icons. This round, there are **69** UI icons, with 0 missing.
If `.ctex` does not contain `RIFF`/`WEBP`, it might be a PNG payload, Basis, or VRAM compression (DXT/BPTC). Those require decoding according to Godot `Image::Format` and cannot use this extraction method.
---
## 6. The Complete Pipeline
```mermaid
flowchart TD
A["Steam Installation Directory"] --> B["See libgodotsteam / Single exe"]
B --> C["Read GDPC footer at exe end"]
C --> D["PCK Header: Godot 4.7.1 format 4"]
D --> E["reserved[0] locates directory"]
E --> F["List 862 paths"]
F --> G["assets/data/*.json"]
F --> H["ui/*.png.import"]
G --> I["Remove trailing commas + translations"]
I --> J["catalog.json"]
H --> K["GST2 embedded WebP"]
K --> L["icons-raw/"]
```
One-click re-run within the repository (without starting the game or writing to the database):
```bash
cd admin
node scripts/succulent-break/scrape-pck.mjs
```
Default path: `D:\SteamLibrary\steamapps\common\Succulent Break\succulent-break.exe`
Override: Environment variable `SUCCULENT_BREAK_EXE`, or pass the exe path as the first argument.
The output is in `admin/resources/succulent-break/` (ignored by git):
| Path | Content |
| --- | --- |
| `raw/assets/data/*.json` | Original PCK content |
| `raw/catalog.json` | Parsable combined table |
| `raw/pck-manifest.json` | List of 862 paths |
| `icons-raw/{type}/*.webp` | UI icons |
---
## 7. Explicitly What Was Not Done
| Action | Reason for Not Doing |
| --- | --- |
| Restore project with GDRE Tools | Only tables and icons are needed; no need to restore `.tscn` or editor projects |
| Decompile `.gdc` | This package's scripts are compiled bytecode (avatar `GDSC`), not decrypted; watering/coloring formulas are therefore marked "undetermined" |
| Read memory / Attach to a running process | Static PCK extraction is sufficient |
| Use as the primary source for encyclopedia data by reading saves | Saves represent progress |
| System translation or AI translation of Japanese | The game only has zh/en (G11) |
| Write `game_data_*` | This round is only for data collection |
Steam achievements (6 items on the store page) are not in the PCK JSON; they are accessed via the shared `GetSchemaForGame` pipeline, see `docs/060-steam-achievements-game-hub.md`.
---
## 8. Reusing for a Different Godot Game
Follow this checklist for reuse of the same parsing logic:
1. The installation directory contains `*godot*` DLLs, or the exe ends with `GDPC`.
2. The PCK is **not encrypted** (directory entry `flags` do not include the encryption bit; extracted JSON can be opened as text).
3. Tables are in `json` / `csv` / `tres`, not solely hardcoded in script constants.
What usually needs to be changed is: the default exe path, whether `assets/data/` is renamed, and the prefix for icon `.import` files.
The following situations **should not** use this script:
- Unity / Unreal / RPG Maker / GameMaker.
- PCK directory encryption (`PACK_DIR_ENCRYPTED`) or single-file encryption (`PACK_FILE_ENCRYPTED`).
- Encyclopedia data only exists in `.gdc` constants, without JSON.
- `.ctex` is not a WebP/PNG payload (actual VRAM compression).
For those cases, you need different tools (pak unpackers for the corresponding engine, GDRE, or official exported debug builds), not hard patches to this script.
---
## 10. Compliance Reminder
It is recommended that everyone only unpack clients they have purchased and installed, and that the unpacked content is used legally and compliantly!Comments
Please login to view and post comments
Go to Login