Status: channels, publishing, and in-game update client. Author's decision (2026-10-10): the zips are uploaded by hand to the site's server (prism.am, prism-admin release add), which publishes a public releases.json list; players
update from inside the game, against this site feed, never against GitHub (the repository is private: no token in the client).
The signed feed, Velopack, OVH, and executable signing remain later phases (docs/etude-mises-a-jour.md, branch
docs/autoupdate-study); what follows describes what exists.
Two channels, two installations
stable |
pbe |
|
|---|---|---|
channel file (next to prism.exe) |
stable or absent |
pbe |
| Data folder | %LOCALAPPDATA%\Prism\PrismNG\data (unchanged) |
%LOCALAPPDATA%\Prism\PrismNG-PBE\data |
| Player's database, replays, mods, skins, themes | those in the data folder | those in its own folder |
WebView2 profile (localStorage: settings, skinConfig, Workshop drafts) |
<data>\webview2 |
<data>\webview2 (of the PBE channel) |
| Window titles | Prism, Prism — Auto… |
Prism PBE, Prism PBE — Auto… |
| Badge | none | "PBE" in the drawer header (Esc) |
| Single instance (Windows mutex) | Local\PrismNG-stable |
Local\PrismNG-pbe |
The channel file is read only once, at startup (apps/desktop/src/app/install.rs, called by startup.rs).
It consists of a single word (stable or pbe, case and whitespace ignored). Missing: stable, with no message. Unreadable, too long, or an
unknown word: stable and a line in the console (PRISM: … is not usable). The channel is never compiled in: this is what
makes it possible to promote the exact same executable bytes from PBE to stable.
Both channels run side by side. A second instance of the same channel does not start: it brings the first one's window to the
foreground (by searching for visible windows whose title is the channel's, within a prism.exe), or, if
no window exists yet, shows "Prism is already running" and then exits. --write-ts-packages, --check-library,
--bench-gameplay, and the --smoke-* flags never take this lock.
The PRISM_MODS_DIR and PRISM_SKINS_DIR variables work as before. Settings → About
shows the channel and the version (Cargo.toml, the same for the PBE and stable zips of a given build).
WebView2 profile outside the executable's folder
Previously, WebView2 wrote prism.exe.WebView2 next to the exe: a tool that replaces the exe's folder would have wiped
the settings. It now lives in <data>\webview2 (app/profile.rs, WEBVIEW2_USER_DATA_FOLDER set before any
WebView). On first launch, an old prism.exe.WebView2 is copied (never moved) to <data>\webview2:
- only if the target does not exist (an existing target always wins, even if empty); idempotent;
- copy into
webview2.copyingfollowed by a rename: an interrupted copy does not leave a bogus profile; - the browser's lock files (
lockfile,LOCK) are ignored, any other unreadable file is logged (PRISM: profile file … not copied) and skipped; the rest is copied; - a
WEBVIEW2_USER_DATA_FOLDERvariable that is already set is respected (nothing is migrated).
Consequence: deleting the data folder now also resets the settings (previously, they survived). Diagnostics
(--smoke-*, --bench-gameplay) keep their disposable profile in the temp folder.
Cutting a release with GitHub CI (set aside for now)
This path exists (.github/workflows/release.yml, never run) but is not the one chosen: see "Cutting a release and publishing it on the site (no CI: the chosen path)".
Everything goes through .github/workflows/release.yml (triggered by hand: Actions → release → Run workflow, or by a v*
tag). The workflow never starts on its own on a push.
- Change the workspace version (
Cargo.toml) if it is a new version; merge into the branch to be published. - PBE: run the workflow with
channel = pbe. It builds, verifies, packagesPrism-<version>-pbe.<n>-windows-x64.zip(channel=pbe), and publishes the releasev<version>-pbe.<n>as a prerelease withSHA256SUMS.txt(the "draft" box creates it as a draft). - Test this zip (unzip it, run
prism.exe, check the PBE badge and thePrismNG-PBEfolder). - Promote without recompiling (the normal path to stable):
gh release download v0.1.0-pbe.3 --pattern "Prism-*-pbe.3-windows-x64.zip" --dir dist
.\scripts\promote.ps1 -Zip dist\Prism-0.1.0-pbe.3-windows-x64.zip -OutDir dist\stable
gh release create v0.1.0 dist\stable\Prism-0.1.0-windows-x64.zip dist\stable\SHA256SUMS.txt --title "Prism v0.1.0" --notes-file dist\stable\NOTES.md
promote.ps1 rejects a zip that is not pbe, checks that prism.exe matches the SHA-256 in RELEASE.txt, writes
channel = stable, updates RELEASE.txt (promoted-from), rebuilds the Prism-<version>-windows-x64.zip zip,
checks that the SHA-256 of prism.exe is unchanged, and writes SHA256SUMS.txt. Write NOTES.md yourself (or use
--generate-notes). The workflow also accepts channel = stable (full rebuild), to be reserved for the very first
zip or for an emergency.
The zip contains: prism.exe, channel, RELEASE.txt (version, channel, commit, date, SHA-256 of the exe), LISEZMOI.txt,
mods\, skins\, themes\. A stable zip keeps its historical name; a PBE zip carries -pbe.<n>. Locally:
.\scripts\package.ps1 [-Channel pbe] [-Label pbe.3] [-SkipBuild].
What the workflow checks (deliberately little)
Locked release build (--locked, static runtime), then: bun run check and the pure TypeScript tests
(src/lib, src/menu, src/theme: language parity, settings, menus, themes); cargo test --release -p themes -p skin -p modding (the shipped content: themes, skins, mods); the committed TypeScript packages must be the ones written by the exe
that was just built. The tests of the desktop crate do not run there: they would recompile the entire application in
test profile, almost as long as the build itself. The full suite and the smoke tests (GPU, WebView2) stay in the
integrator's workflow. The workflow has never run yet: the first run needs to be watched.
Cost (estimate)
Private repository, free plan: 2,000 minutes per month, with Windows minutes counting double (GitHub's factor of 2). A cold
build on windows-latest: 25 to 40 min [estimate, not measured]; with the cargo cache: 8 to 15 min
[estimate]. A PBE release would therefore cost on the order of 20 to 80 billed minutes cold, 16 to 30 with cache: the free
plan only offers about thirty a month cold. These are reasons not to run the workflow on every merge.
The update client (Settings → About → Updates)
Everything lives in the crates/updater crate (no new dependencies: ureq and ring are already in the tree); the host
(apps/desktop/src/app/updates.rs) routes the page's commands and states (updateState). Each step runs on its own thread,
never on the window thread, never during a play session, a preview, or a replay (a download in progress stops when a
play session starts; the partial file is kept and resumed).
| Step | What happens |
|---|---|
| Check | a single GET of the list (https://prism.am/releases.json by default), with no identifier, no version, no cookie (agent Prism-updater). Automatic at most once a day (20 s after the interface is ready, then the deadline is rechecked hourly; an attempt counts, so a site that is down is not polled every hour), "Check" button on demand |
| Download | only on click. Progress, Range resume, size and SHA-256 verified against the list; a bad file is deleted |
| Install | on clicking "Restart to update" (or at the next launch if the package is ready): files are moved with a backup, then restart |
Settings (visible, Settings → About): updateCheck (enabled by default: it only reads the list), updateChannel
(empty = the channel of this installation, stable or pbe), updateFeedUrl (empty = the site; PRISM_UPDATE_FEED takes precedence). None
is exported (.pvsettings.json). The feed address must be https (http only for localhost/127.0.0.1), and
every address followed (package, redirect) must have the same scheme, the same host, and the same port as the feed; at most 3 redirects,
followed by the client itself. A site that is unreachable or not yet deployed only yields "Updates unavailable".
The releases.json feed (the site's contract)
Per version: channel, seq, build, publishedAt (or date), url (relative to the site or absolute), sha256, size, notesMd
(or notes), notesEnMd, commit, semver, builtFromBuild, withdrawn / status: "withdrawn", signature (optional, see below). Unknown fields are ignored; an unusable entry is discarded on its own. A channel's order is seq, never build
(free-form label, never parsed). Same seq as the installed version: nothing to do. A "latest" older than the installed version (withdrawn): never a
downgrade. Another channel: its latest version is always offered, with a warning that the data folder changes
(PrismNG ↔ PrismNG-PBE: the package's channel moves the installation).
Which version is installed
The seq is assigned by the site's tooling after the zip is built: the zip cannot always carry it. The game infers it, from
the most reliable source to the least reliable:
- its own record (
update-state.json, next to the exe), written when this client installed the version, with the SHA-256 of the installed exe: it only counts as long as the running exe has this hash (a zip manually extracted over it invalidates it); seq:inRELEASE.txtif the packaging step received it (package.ps1 -Seq N); ignored if the list knows thisseqwith a differentcommit;- the
commitinRELEASE.txtagainst the one in the list (prefix, 7 to 40 hexadecimal characters; several matches: the one whosebuildis the package's label, otherwise the smallestseq); - the label (
label:=pbe.3) equal to abuild.
Nothing matches: "Not recognized". The latest version is still offered, but never installed without a click. For the game to always
recognize itself, publish with --commit (the one from RELEASE.txt) or give -Seq to the packaging step (see the procedure below).
Ed25519 signature (key slot empty for now)
The list can carry, per version, signature: 128 hexadecimal characters, an Ed25519 signature of the bytes
prism-update-v1\n<canal>\n<seq>\n<sha256 en minuscules>\n (the hash and not the file: verifiable before any download, and the channel
and the order are bound to the package). crates/updater/src/signature.rs::TRUSTED_KEYS is empty: nothing is verified and the interface displays
"Unsigned". As soon as a public key (64 hexadecimal characters) is put there, a version with no signature or with an invalid signature is rejected
(the latest is not replaced by an older signed one). Several keys are allowed (rotation: a version that embeds the
next key before the old one is retired). The private key is never in this repository.
Proposal for the site's administration tool (to be implemented by the site's worker; nothing has been changed in its repository):
prism-admin keys generate --out <fichier> (Ed25519, PKCS#8 private key kept outside the repository and the database, permissions 0600, offline backup;
prints the public key in hexadecimal to paste into TRUSTED_KEYS); prism-admin release add … --sign-key <fichier> computes the
signature of the message above and stores it in releases.json (signature field); verify rechecks it; regenerate keeps it
(never re-signed for another seq); release next-seq --channel pbe prints the next seq for package.ps1 -Seq. Test
vector (Ed25519 being deterministic): seed [7; 32], public key ea4a6c63e29c520abef5507b132ec5f9954776aebebe7b92421eea691446d22c,
message (channel pbe, seq 7, hash ab×32) prism-update-v1\npbe\n7\nabababababababababababababababababababababababababababababababab\n, signature
281558931de70d1d028affffbe5b7941fcd0cb1150a5d9376e3102ce6c2322f6b743cd20ba601d65ab3370a3b2c08e45a2abe61ce800c88178b3e45525bbc000
(test the_signing_vector_published_for_the_site_verifies).
Installation, backup, rollback
A running prism.exe can be renamed on Windows (it can be neither overwritten nor deleted; proven by a test with a copy of a running system program). Everything stays next to the exe, never in the data folder (except update-check.json, the time of the last check):
update-staging/download/…zip.partial the zip while downloading (resumed)
update-staging/files/… the unpacked package (downloader's archive rules: no `..`, no absolute path, no link) and verified
update-staging/ready.json written last: the package is complete
update-backup/<seq>/files/… each replaced file, as it was (the last two backups are kept)
update-backup/<seq>/manifest.json replaced / added
update-state.json installed version, launch counter, last rollback
The package must contain prism.exe (with an MZ header) and a channel file equal to the announced channel; names starting with update- are reserved. Policy for shipped folders (mods\, skins\, themes\): a file in the package replaces the file at the same path (shipped content follows the game) and the old one is kept in update-backup/<seq>/ if it differs; anything the package does not contain is never touched or deleted (folders and files added by the player); a shipped file that a version removes stays in place (removing it requires the hash manifest of the next phase). In-place edits to a shipped file are therefore overwritten but backed up.
At launch, before the window opens: the launch counter of a freshly installed version is incremented; the ready interface (ready) resets it to zero. Two launches in a row without reaching the interface: on the third, the files are restored (the faulty files go into update-backup/<seq>/failed/), the previous version restarts and Settings → About says so. An interrupted installation (power cut, killed process) is rolled back at the next launch (the manifest is written before the first move). The new process waits for the old one to end (--after-update <pid>) before taking the instance lock and the WebView2 profile. Limitation: the safeguard runs inside the new exe; an exe that fails to load at all cannot restore itself (restore update-backup/<seq>/files/prism.exe). The program assumes the exe is named prism.exe and that its folder is writable (not in Program Files: otherwise "folder not writable").
Cutting a release and publishing it on the site (no CI: the chosen path)
.\scripts\package.ps1 -Channel pbe -Label pbe.4 [-Seq N](theseqis only used for identification:release next-seqon the site, if the tool offers it).RELEASE.txtcarries the version, the channel, the label, theseqif given, the commit, the date and the SHA-256 of the exe.- Upload the zip to the server;
prism-admin release add --channel pbe --build "PBE 0004" --zip … --commit <commit from RELEASE.txt> --notes …. Players' games see the version at the next check (once a day, or "Check now"). - Promote without recompiling:
.\scripts\promote.ps1 -Zip <pbe zip>produces the stable zip (the PBE'sseq:is removed from it: the site assigns the stable one), then publish it the same way with--channel stable. To withdraw a version:prism-admin release withdraw(it disappears from the list; a client that had already seen it no longer downloads it).
.github/workflows/release.yml remains in the repository but the author's decision is not to use GitHub CI for now.
What is not proven
Tested: the whole crate against a real local HTTP server (feed, zip, wrong hash, wrong or missing signature, withdrawn version, interrupted then resumed download, zip-slip, same seq, channel change, redirects to another host), installation, rollback, and renaming a running executable. Not proven, only a real run against the site will show it: the site's real TLS and headers (Range, Content-Range, Caddy redirects), the identification of a published version with the real fields, the actual restart (new process, mutex, WebView2) and the application at launch. No prism.exe was launched for this work.
Next steps
- Generate the signing key and put it in
TRUSTED_KEYS(author's decision: where the private key lives, its backup, rotation). - Hash manifest for shipped folders, "Duplicate to customize", tombstones (removing a shipped file).
- Installer (WebView2 bootstrapper), executable signing, deltas, staged rollout.