CRYSTALBOUND 0.2.1 — SIGNED LAUNCHER SELF-UPDATES

Player behavior
---------------
Crystalbound checks its own public HTTPS feed before sign-in. A newer signed
version is downloaded, verified and backed up, then Crystalbound closes briefly
and reopens. No separate program needs to be installed or managed. Keep FFXI,
xiloader and Windower closed during this update. Options > Check launcher updates
retries on demand; the version appears in Options. An unavailable feed leaves the
current launcher usable. A failed startup restores the previous executable and
suppresses automatic retries of that same release. An explicit Options retry or
a newer release can try again.

Launchers older than 0.2.0 need ONE manual replacement from aletheiagames.com.
The server can add a pinned migration note only to these legacy news requests.
0.2.0+ sends X-Crystalbound-Version and does not receive that manual-download note.
The note is generated per request, not inserted into the operator's stored news.
Enable LAUNCHER_MIGRATION_VERSION only once the new website ZIP is downloadable.

Configuration, saved preferences, Windower profiles/addons, and game files are
not self-update targets. The account-based game updater remains separate. No game
credentials are needed, saved, or transferred when updating the launcher.

Trust and recovery
------------------
The existing crystalbound.json release public key verifies a separate RSA-PSS
SHA256 envelope with purpose crystalbound-launcher-v1, dev channel, win-x64,
three-part version, increasing sequence, expiry, and exact executable size/hash.
Only Crystalbound.exe can be replaced. The game manifest's MinimumLauncher now
uses the actual assembly version. Self-update cannot change its trust key/config.

LauncherFeed is https://updates-dev.aletheiagames.com/launcher/dev/.
That route is public, like the website download. Game files and news still require
game-account authorization. HTTPS redirects, link/junction paths, changed config,
external file edits, corrupt downloads, and invalid backups are rejected. An older
signed version cannot downgrade the running version. Confirmed sequences persist.

A verified copy of the old executable runs as a temporary hidden helper using
the same Windows identity. It checks its own hash plus parent path/creation time,
waits for the parent to exit, and uses an installation lock during replacement.
The new process must load WebView2, initialize the UI, and confirm its signed
version/hash before success is recorded. Failure closes only that new launcher
process and restores the verified backup. Interrupted updates recover on the next
startup. External edits stop recovery for review instead of being overwritten.

State is under .crystalbound/launcher/: pending.json records the transaction;
installed-dev.json records the confirmed sequence; failed.json prevents retry
loops. transactions/<id>/ retains the original backup, helper, signed manifest,
and health records. Backups are retained, not automatically pruned in this version.
Free-space checks allow for download, helper and recovery copies plus 100 MiB.
Errors are in last-error.txt, or %LOCALAPPDATA%/Crystalbound/update-startup-error.txt
if the helper could not start. Game update transactions remain separate.

Windows executable code signing is still separate and is not implemented. Signed
updates use the pinned release key. Initial Windows approval/SmartScreen behavior
may still occur; existing Windower elevation handling remains in place.

Build and release
-----------------
Feature branch: codex/launcher-self-updates. No dev/live promotion.
Use the pinned SDK. From the installed workspace, runtime/dotnet/dotnet.exe:
  run --project repositories/ffxi-family-client/launcher/SelfUpdate.Tests -c Release
  run --project repositories/ffxi-family-client/launcher/Tests -c Release
  run --project repositories/ffxi-family-client/launcher/Desktop.Tests -c Release
Publish Desktop with -p:Version=0.2.0 and -p:Version=0.2.1 into separate output
folders. SelfUpdate.Tests accepts OLD_EXE NEW_EXE OUTPUT_DIR for real Windows
success, failed-start and interrupted-update tests with a disposable signing key.
These tests never touch the installed game or sign test payloads with a real key.

Tools creates a release:
  launcher-release VERSION SEQUENCE PRIVATE_KEY CONFIG EXE EMPTY_OUTPUT
Hosting/scripts/publish-launcher.mjs CONFIG OUTPUT --publish-dev uploads immutable
objects/history, rechecks the prior pointer, publishes it last, and reads it back.
Run one owner publisher at a time. Keep keys outside Git/packages. New releases
increase both version and sequence. To restore older code after confirmed release,
rebuild it as a new higher version. Publish matching website ZIP/source/licenses
for each release. Published ZIPs and sequences must never be replaced in place.

Nine transaction/security checks, three real Windows flows, 28 core checks, four
controller checks, browser flows at three sizes, and hosting/migration checks
passed during implementation. Evidence is in var/crystalbound-self-update.
The optional C++ ticket fixture test is unrelated; issuer and bridge are unchanged.

References:
https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview
https://learn.microsoft.com/en-us/dotnet/api/system.io.file.move?view=net-10.0
