Skip to content

Commit ad27375

Browse files
committed
Add manifest-driven extensions and CLI support
Introduce a manifest-driven external command layer and CLI integration. Adds a full extension implementation (src/extensions/*) with manifest schema, discovery, validation, and runtime contract (JSON stdin payload + env vars), plus CLI flags --list-plugins, --run-plugin and --plugin-dir and plumbing to collect selected archive entries for commands. Documentation added/updated (docs/EXTENSIONS.md, CORE_LIBRARY.md, SUPPORT_MATRIX.md, README and DEPENDENCIES updates) and a new pakfu_core umbrella target/header for non-UI code. Build/test infra enhanced: meson option to enable fuzzing, many fuzz/test targets added, and a GitHub workflow (/.github/workflows/hardening.yml) to run sanitizer+fuzzing CI.
1 parent 9b9ddc1 commit ad27375

28 files changed

Lines changed: 2947 additions & 45 deletions

.github/workflows/hardening.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: parser-hardening
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
10+
jobs:
11+
sanitize-and-fuzz:
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 30
14+
env:
15+
CC: clang
16+
CXX: clang++
17+
ASAN_OPTIONS: detect_leaks=0
18+
UBSAN_OPTIONS: print_stacktrace=1
19+
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Setup Python
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: "3.x"
28+
29+
- name: Install Meson + Ninja
30+
run: python -m pip install --upgrade pip meson ninja
31+
32+
- name: Install Clang
33+
run: |
34+
sudo apt-get update
35+
sudo apt-get install -y clang lld
36+
37+
- name: Install Qt
38+
uses: jurplel/install-qt-action@v4
39+
with:
40+
version: "6.10.1"
41+
arch: "linux_gcc_64"
42+
modules: qtmultimedia qtimageformats
43+
44+
- name: Configure Hardening Build
45+
run: >
46+
meson setup builddir
47+
--backend ninja
48+
--buildtype=debugoptimized
49+
-Dvulkan_preview=disabled
50+
-Dfuzzing=enabled
51+
-Db_sanitize=address,undefined
52+
53+
- name: Build
54+
run: meson compile -C builddir
55+
56+
- name: Run Sanitized Tests
57+
run: meson test -C builddir --print-errorlogs

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ PakFu can also check for updates at runtime (GUI) and via CLI (`--check-updates`
7171
- Nested container mounting (open archives inside archives).
7272
- One-click `Extract Selected` and `Extract All` workflows for the active archive tab.
7373
- Batch conversion tool for selected assets with category tabs (images, video, archives, models, sound, maps, text, other).
74+
- Manifest-driven extension commands for archive-aware external tools in the GUI and CLI.
75+
- A reusable `pakfu_core` static library for non-UI archive, format, search, and extension code.
7476
- Image batch conversion supports output to all supported image formats (`png`, `jpg`, `jpeg`, `bmp`, `gif`, `tga`, `tif`, `tiff`, `pcx`, `wal`, `swl`, `mip`, `lmp`, `ftx`, `dds`) with format-aware settings (quality, compression, palette source, dithering, alpha threshold, embedded palette where applicable).
7577
- Modern platform-native open/save/folder dialogs with standard breadcrumbs/bookmarks and robust cross-platform behavior.
7678
- 3D preview renderer selection with Vulkan/OpenGL behavior and fallback.
@@ -82,6 +84,10 @@ PakFu can also check for updates at runtime (GUI) and via CLI (`--check-updates`
8284

8385
## Supported Formats
8486

87+
For the fixture-backed support contract and current verification status, see
88+
[`docs/SUPPORT_MATRIX.md`](docs/SUPPORT_MATRIX.md). The tables below are the
89+
quick reference.
90+
8591
### Archive And Container Support
8692
| Type | Extensions | Open/List | Extract | Save/Rebuild |
8793
|---|---|---|---|---|
@@ -144,6 +150,19 @@ meson setup builddir --backend ninja
144150
meson compile -C builddir
145151
```
146152

153+
### Core Library Target
154+
```sh
155+
meson compile -C builddir pakfu_core
156+
meson test -C builddir core-api-smoke
157+
```
158+
159+
### Parser Hardening Build
160+
For parser hardening smoke tests with libFuzzer + sanitizers:
161+
```sh
162+
CC=clang CXX=clang++ meson setup builddir-fuzz --backend ninja --buildtype=debugoptimized -Dvulkan_preview=disabled -Dfuzzing=enabled -Db_sanitize=address,undefined
163+
meson test -C builddir-fuzz --print-errorlogs
164+
```
165+
147166
### Run
148167
```sh
149168
./builddir/src/pakfu
@@ -169,13 +188,16 @@ Core actions:
169188
- `--save-as <archive>` : rebuild selected entries into a new archive.
170189
- `--convert <format>` : convert selected images to a supported image-writer output (`png`, `jpg`, `bmp`, `gif`, `tga`, `tiff`, `pcx`, `wal`, `swl`, `mip`, `lmp`, `ftx`, `dds`) or IDWAV audio to `wav`.
171190
- `--preview-export <entry>` : export the CLI preview rendition of one entry; images write an image file, text and binary entries write bytes.
191+
- `--list-plugins` : list discovered extension commands.
192+
- `--run-plugin <plugin[:command]>` : run one extension command against the loaded archive.
172193
- `--check-updates` : query GitHub Releases.
173194
- `--qa-practical` : run practical archive-ops smoke QA (selection/marquee/modifier checks).
174195

175196
Archive selection and mounting:
176-
- `--entry <path>` : limit `--list`, `--extract`, `--save-as`, or `--convert` to an exact archive entry. Repeat for multiple entries.
177-
- `--prefix <dir>` : limit `--list`, `--extract`, `--save-as`, or `--convert` to entries under a directory prefix. Repeat for multiple prefixes.
197+
- `--entry <path>` : limit `--list`, `--extract`, `--save-as`, `--convert`, or `--run-plugin` to an exact archive entry. Repeat for multiple entries.
198+
- `--prefix <dir>` : limit `--list`, `--extract`, `--save-as`, `--convert`, or `--run-plugin` to entries under a directory prefix. Repeat for multiple prefixes.
178199
- `--mount <entry>` : mount a nested archive entry first, then run the requested action against the mounted archive.
200+
- `--plugin-dir <dir>` : add an extension search directory (repeat for more than one).
179201

180202
Save-as options:
181203
- `--format <pak|sin|zip|pk3|pk4|pkz|wad|wad2>` : choose the output archive format. If omitted, PakFu infers it from `--save-as`.
@@ -205,6 +227,8 @@ Examples:
205227
./builddir/src/pakfu --cli --convert png --prefix textures -o converted_textures path/to/archive.pk3
206228
./builddir/src/pakfu --cli --preview-export gfx/conback.lmp -o previews path/to/pak0.pak
207229
./builddir/src/pakfu --cli --mount nested/maps.pk3 --list path/to/archive.pk3
230+
./builddir/src/pakfu --cli --list-plugins
231+
./builddir/src/pakfu --cli --run-plugin example:inspect-cfg --entry scripts/autoexec.cfg path/to/archive.pk3
208232
./builddir/src/pakfu --cli --check-updates
209233
./builddir/src/pakfu --cli --qa-practical
210234
./builddir/src/pakfu --cli --list-game-installs
@@ -235,6 +259,7 @@ Examples:
235259
- Double-click nested container files to mount and browse in-place.
236260
- Multiple nested layers are supported.
237261
- `File` menu and context menu actions support `Extract Selected`, `Extract All` (archive views), and `Convert Selected Assets...`.
262+
- The `Extensions` menu discovers manifest-backed external tools and validates the current archive selection before launch.
238263
- Safety:
239264
- Pure PAK Protector is enabled by default for official archives (read-only protection with Save As workflow).
240265

@@ -295,7 +320,9 @@ For full policy details, see `docs/RELEASES.md`.
295320
| `QT_MEDIA_BACKEND` | Override Qt multimedia backend selection. |
296321

297322
## Documentation
323+
- `docs/CORE_LIBRARY.md` : `pakfu_core` scope, umbrella header, and intended build contract.
298324
- `docs/DEPENDENCIES.md` : dependency baseline and optional libraries.
325+
- `docs/EXTENSIONS.md` : manifest schema, JSON payload contract, and CLI/GUI extension workflow.
299326
- `docs/QA_ARCHIVE_FILE_OPS.md` : practical QA checklist for selection, drag/drop, cut/copy/paste, and modifiers.
300327
- `docs/RELEASES.md` : versioning, release rules, and asset contract.
301328
- `docs/UI_BUTTON_ICONS.md` : UI action icon inventory.

docs/CORE_LIBRARY.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# PakFu Core Library
2+
3+
PakFu now builds a non-UI static library target named `pakfu_core`.
4+
5+
This is the first stable seam for archive, format, search, game-profile, and
6+
extension-contract code that does not depend on the desktop UI layer.
7+
8+
## Scope
9+
10+
`pakfu_core` includes:
11+
- archive loading and extraction
12+
- PAK/WAD/ZIP/resources backends
13+
- archive search indexing
14+
- image/model/cinematic/BSP parsing helpers
15+
- game profile and auto-detect helpers
16+
- manifest-driven extension command loading/execution
17+
18+
`pakfu_core` does not include:
19+
- Qt Widgets or viewer windows
20+
- `PakTab`, `MainWindow`, or preview widgets
21+
- updater UI flow
22+
- platform shell integration
23+
24+
## Umbrella header
25+
26+
The stable umbrella header is:
27+
28+
```text
29+
src/pakfu_core.h
30+
```
31+
32+
It aggregates the primary non-UI APIs used by the app, tests, and helper tools.
33+
34+
## Build target
35+
36+
Build just the core layer:
37+
38+
```sh
39+
meson compile -C builddir pakfu_core
40+
```
41+
42+
Build the core API smoke test:
43+
44+
```sh
45+
meson compile -C builddir core_api_smoke_test
46+
meson test -C builddir core-api-smoke
47+
```
48+
49+
## Intended use
50+
51+
This target is meant to:
52+
- reduce UI-to-backend coupling
53+
- give tests and helper binaries a stable link target
54+
- provide a clean place for future CLI and scripting integrations
55+
56+
It is not yet installed as a packaged SDK, and its API is stable by repository
57+
contract rather than by external semantic-version promises.

docs/DEPENDENCIES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
## Planned / Optional
3333
- Vulkan runtime + drivers (enables the Vulkan 3D preview renderer; OpenGL remains the fallback)
34+
- Clang/LLVM with libFuzzer support (parser fuzzing targets and sanitizer CI workflow)
3435
- OGG (if not handled by Qt Multimedia on a platform)
3536
- Third-party decoding libraries as needed, scoped to formats that Qt does not
3637
cover on all platforms

docs/EXTENSIONS.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# PakFu Extensions
2+
3+
PakFu's first extension phase is a manifest-driven external command layer.
4+
Extensions are discovered from JSON manifests, and PakFu launches the declared
5+
command with a structured JSON payload on `stdin`.
6+
7+
This phase is intentionally small:
8+
- manifest discovery
9+
- read-only archive context
10+
- selected-entry materialization to temporary files/directories
11+
- GUI launch from the `Extensions` menu
12+
- CLI launch with `--list-plugins` and `--run-plugin`
13+
14+
There is no binary plugin ABI yet, and there is no write-back/import contract
15+
from extensions into archives in this phase.
16+
17+
## Discovery
18+
19+
PakFu searches these directories for manifests:
20+
- `<pakfu-dir>/plugins`
21+
- `./plugins` from the current working directory
22+
- the platform app-data `plugins` directory for PakFu
23+
24+
CLI callers can add more search roots with:
25+
26+
```text
27+
--plugin-dir <dir>
28+
```
29+
30+
PakFu looks for either:
31+
- `plugin.json`
32+
- `*.pakfu-plugin.json`
33+
34+
## Manifest schema
35+
36+
Schema version: `1`
37+
38+
Minimal example:
39+
40+
```json
41+
{
42+
"schema_version": 1,
43+
"id": "example",
44+
"name": "Example Tools",
45+
"description": "Sample extension commands.",
46+
"commands": [
47+
{
48+
"id": "inspect-cfg",
49+
"name": "Inspect CFG",
50+
"description": "Run an external checker on one selected CFG file.",
51+
"command": ["python", "inspect_cfg.py"],
52+
"working_directory": ".",
53+
"requires_entries": true,
54+
"allow_multiple": false,
55+
"extensions": ["cfg"]
56+
}
57+
]
58+
}
59+
```
60+
61+
## Manifest fields
62+
63+
Top-level fields:
64+
- `schema_version` : currently `1`
65+
- `id` : plugin identifier (`A-Z`, `a-z`, `0-9`, `_`, `.`, `-`)
66+
- `name` : display label
67+
- `description` : optional description
68+
- `commands` : array of command objects
69+
70+
Command fields:
71+
- `id` : command identifier
72+
- `name` : display label
73+
- `description` : optional description
74+
- `command` : argv array; first item is the executable
75+
- `working_directory` : optional; relative paths resolve from the manifest directory
76+
- `requires_entries` : optional boolean, default `false`
77+
- `allow_multiple` : optional boolean, default `true`
78+
- `extensions` : optional list of allowed selected-file extensions without dots
79+
80+
## Execution contract
81+
82+
PakFu launches the external process with:
83+
- JSON payload on `stdin`
84+
- separate `stdout` and `stderr` capture
85+
- environment variables:
86+
- `PAKFU_EXTENSION_SCHEMA`
87+
- `PAKFU_EXTENSION_PLUGIN_ID`
88+
- `PAKFU_EXTENSION_COMMAND_ID`
89+
90+
Relative executable paths are resolved from the command's working directory.
91+
92+
## JSON payload
93+
94+
The payload schema id is:
95+
96+
```text
97+
pakfu-extension/v1
98+
```
99+
100+
Example payload:
101+
102+
```json
103+
{
104+
"schema": "pakfu-extension/v1",
105+
"command": {
106+
"plugin_id": "example",
107+
"plugin_name": "Example Tools",
108+
"command_id": "inspect-cfg",
109+
"command_name": "Inspect CFG",
110+
"description": "Run an external checker on one selected CFG file.",
111+
"manifest_path": "/absolute/path/to/plugin.json"
112+
},
113+
"archive": {
114+
"path": "/absolute/path/to/archive.pk3",
115+
"readable_path": "/absolute/path/to/archive.pk3",
116+
"format": "zip",
117+
"mounted_entry": "",
118+
"current_prefix": "scripts/",
119+
"quakelive_encrypted_pk3": false,
120+
"wad3": false,
121+
"doom_wad": false
122+
},
123+
"entries": [
124+
{
125+
"archive_name": "scripts/autoexec.cfg",
126+
"local_path": "/absolute/path/to/temp/scripts/autoexec.cfg",
127+
"is_dir": false,
128+
"size": 128,
129+
"mtime_utc_secs": 1714156800
130+
}
131+
]
132+
}
133+
```
134+
135+
## GUI behavior
136+
137+
Open an archive tab, select entries if the command requires them, then use:
138+
139+
```text
140+
Extensions -> <command>
141+
```
142+
143+
PakFu validates the current selection before launch. If a command is disabled in
144+
the menu, its tooltip explains why.
145+
146+
## CLI behavior
147+
148+
List discovered commands:
149+
150+
```text
151+
pakfu --cli --list-plugins
152+
```
153+
154+
Run a command against an archive:
155+
156+
```text
157+
pakfu --cli --run-plugin example:inspect-cfg path/to/archive.pk3
158+
```
159+
160+
Pass selected files with existing archive selectors:
161+
162+
```text
163+
pakfu --cli --run-plugin example:inspect-cfg --entry scripts/autoexec.cfg path/to/archive.pk3
164+
pakfu --cli --run-plugin example:inspect-cfg --prefix scripts path/to/archive.pk3
165+
```
166+
167+
In the CLI path for this phase, `--entry` and `--prefix` pass matching files as
168+
materialized temp files to the extension command.

0 commit comments

Comments
 (0)