Godot 自己包含のexeを解凍する方法(『多肉小憩』を例に)
本文は、2026年9月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 インストールディレクトリには、3つのファイルしかありません:
| ファイル | 役割 |
| :-------------------------------------------------- | :----------------------------------- |
| `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 での対応コード:
```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` | 各 300 以上 | インポート後のテクスチャとサイドカー |
| `.gdc` / `.remap` | 数十個 | コンパイル済みの GDScript、今回は読み取らない |
| `.json` | **7 個** | 図鑑の真のソース |
| `.scn` / `.sample` / 音声 | 残り | シーンと効果音 |
独立系ゲームでよく見られるのは、「テーブルは JSON / CSV / Tres、コードはスクリプト」という形式です。まずパスで `assets/data/`、`data/`、`json` を検索し、先に逆コンパイルしないでください。
---
## 4. 図鑑 JSON の抽出
本パッケージの 7 つのテーブルはすべて `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`(テキストサイドカー)
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`)。ブロブ内で `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** 枚で、欠落はありません。
`.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. 明確に行わなかったこと
| 実施内容 | 実施しない理由
| 実施内容 | 理由
---
## 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
コメント
ログインしてコメントを閲覧・投稿してください
ログインへ