An unattended pipeline that keeps a data + icon set for the game Satisfactory in sync with Steam. On a schedule it detects new game builds, extracts the shipped documentation and icon textures, normalizes them, and hands the result off to a co-located API.
It runs headless on Linux (developed against Ubuntu Server 24.04).
For each branch (stable / experimental), once per run:
- Detect - query Steam for the current build id (anonymous, no login).
- Download -
steamcmd-update the game client for that branch. - Version - read the human game version (e.g.
1.2.3.1) + engine version from the install. - Docs diff - locate the
Docsfile and skip the rest if it hasn't changed. - Parse - run the
satisfactory-tools/docs-parserpackage (once per variant: default and-ficsmas), producing the item / recipe / building / schematic data and the list of referenced icon textures. - Extract - pull those textures out of the UE5 pak files with CUE4Parse and normalize each to 256×256 and 64×64 PNGs.
- Content-address + dedup - name each image by a hash of its contents, so identical icons are stored once and unchanged images are never re-uploaded.
- Rewrite + publish - replace every icon reference in the parsed data with its image id, wrap it as
{ "data": …, "metadata": {} }, and drop the images + versioned JSON into the API's data directory, then trigger the API import.
State (per-branch build id / docs hash, published image ids, run history) lives in MariaDB. Runs are locked per-branch, guarded for disk space and extraction failures, and report success/failure to a log file and (optionally) Discord.
Three cooperating components, decoupled by a CLI + JSON/file contract:
| Component | Tech | Role |
|---|---|---|
orchestrator/ |
PHP 8.5 (Symfony Console) | The pipeline: version check, download, docs diff, publish, state, scheduling, notifications. Shells out to the other two. |
extractor-net/ |
.NET 10 + CUE4Parse + SkiaSharp | Decodes UE5 textures and writes normalized PNGs + per-asset content hashes. |
| docs parser | satisfactory-tools/docs-parser (Composer) |
Parses the game Docs into structured data. Driven as a console command. |
The heavy lifting that must be .NET (CUE4Parse) is isolated in extractor-net/; everything else is PHP.
orchestrator/ PHP orchestrator (bin/extractor, src/, config/)
extractor-net/ .NET image extractor (Program.cs, Extractor.csproj)
systemd/ systemd units
data/ runtime working dir (git-ignored): downloads, artifacts, images
- Linux,
steamcmd, a Steam account that owns Satisfactory (the Docs + icon paks ship only with the game client, not the free dedicated server) - PHP 8.5 (
pdo_mysql,curl,iconv) + Composer - .NET 10 SDK/runtime
- MariaDB or equivalent
- Ample disk (~30 GB per branch for the game install)
Run everything below from the repository root unless noted.
1. Build the components
# PHP orchestrator (installs the docs-parser package + Symfony)
cd orchestrator && composer install && cd ..
# .NET image extractor
cd extractor-net && dotnet build -c Release && cd ..2. Create the database + user (MariaDB/MySQL)
sudo mariadb <<'SQL'
CREATE DATABASE IF NOT EXISTS sf_extractor CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'sf_extractor'@'localhost' IDENTIFIED BY 'change-me';
GRANT ALL PRIVILEGES ON sf_extractor.* TO 'sf_extractor'@'localhost';
FLUSH PRIVILEGES;
SQL3. Configure
cp orchestrator/config/config.example.yaml orchestrator/config/config.yamlEdit orchestrator/config/config.yaml (it's commented throughout). At minimum set:
database.*- DSN / user / password matching step 2steam.username- the Steam account that owns Satisfactoryextraction.dotnet- path to the .NET 10 runtime (e.g.dotnet) andextraction.dll- path to the builtextractor-net/bin/Release/net10.0/Extractor.dllpublish.api_data_dir- where the co-located API reads data + imagespaths.data- working dir for downloads/artifacts (needs ~30 GB per branch)- (optional)
notifications.discord.webhook_urlfor success/failure pings
Then create the state tables:
php orchestrator/bin/extractor db:migrate4. Steam login (one-time)
The Docs + icon paks ship only with the game client, so downloads need an owning
account. Log in once interactively so steamcmd caches the session (later
non-interactive runs reuse it):
steamcmd +login <username> # enter password + Steam Guard, then `quit`5. First run
php orchestrator/bin/extractor run --branch=stable6. Schedule (hourly systemd timer)
systemd/sf-extractor@.service is a template - copy the units, then edit the
three CHANGE_ME/placeholder lines in the installed service to match your host:
User=- the account that owns the cachedsteamcmdsession + .NET runtimeWorkingDirectory=- absolute path to this repo'sorchestrator/ExecStart=- thephpbinary path if it isn't/usr/bin/php
sudo cp systemd/sf-extractor@.service systemd/sf-extractor@.timer /etc/systemd/system/
sudoedit /etc/systemd/system/sf-extractor@.service # set User=, WorkingDirectory=, php path
sudo systemctl daemon-reload
sudo systemctl enable --now sf-extractor@stable.timer # instance name = branch(You can instead edit the copies in systemd/ before cp - either works.)
Only stable is scheduled by default; add experimental (disk permitting) with
systemctl enable --now sf-extractor@experimental.timer.
php orchestrator/bin/extractor check # new build? (no credentials/download)
php orchestrator/bin/extractor run --branch=stable # run one branch
php orchestrator/bin/extractor run --branch=all # both branches (default)
php orchestrator/bin/extractor run --branch=stable --force # ignore the "unchanged" check
php orchestrator/bin/extractor db:migrate # create/verify state tablescheck needs no Steam credentials and works even before the DB exists (it just
skips the comparison). run requires the DB.
Operating the scheduled unit:
systemctl start sf-extractor@stable.service # trigger a run now
journalctl -u sf-extractor@stable.service -e # the last run's logs
systemctl list-timers 'sf-extractor@*' # next scheduled runFailures are written to notifications.log_file and (if configured) a Discord webhook.
MIT - see LICENSE. Copyright (c) 2026 greeny.
Built on CUE4Parse, SkiaSharp, and satisfactory-tools/docs-parser. Not affiliated with Coffee Stain Studios.