Commit Graph

4 Commits

Author SHA1 Message Date
fed5be5916 gitignore: stop tracking built artifacts (PSLauncher-X.Y.Z.exe, installer output)
Last commit accidentally captured two large binaries that landed in the
working tree during the self-update test prep:
- PSLauncher-0.6.0.exe at repo root
- installer/output/PSLauncher-Setup-0.5.0.exe

Neither belongs in version control. Add /PSLauncher-*.exe and
/installer/output/ to .gitignore and `git rm --cached` the existing
entries so the next push doesn't reupload them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 10:57:56 +02:00
f2a1de9aac Self-update: UAC-aware, de-elevated relaunch, user-mode installer
Three coordinated changes so the auto-update flow works on every
existing install location.

LauncherSelfUpdater.cs
----------------------
- Probe the target directory with a write/delete of a temp file. If
  it fails (e.g. install in Program Files without admin), set
  UseShellExecute=true + Verb=runas on the Updater process so UAC
  prompts the user once for elevation. If the directory is writable
  (user-mode install or portable layout), skip elevation entirely.
- Switched from ProcessStartInfo.ArgumentList to a quoted Arguments
  string because Verb=runas requires UseShellExecute=true, which
  ignores ArgumentList. Paths get explicit quotes to survive spaces.

Updater Program.cs
------------------
After the file swap, the relaunch was a direct Process.Start(target).
With UAC elevation that propagates admin to the new launcher, then to
its child PROSERVE_UE_5_5.exe — undesirable. Replace with
`explorer.exe "<target>"`: explorer is always running in the user's
normal token, opens the exe via shell, the new process inherits the
non-elevated session. Standard de-elevation trick.

installer/PSLauncher.iss
------------------------
Switch from system-wide install (Program Files, requires admin) to
per-user (DefaultDirName={localappdata}\Programs\..., PrivilegesRequired=
lowest). Auto-update then never needs UAC at all on fresh installs.
Existing installs in Program Files keep working thanks to the runas
fallback above.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 10:57:32 +02:00
6bc5381562 installer: use x64 (Inno Setup 6.0+) instead of x64compatible (6.3+)
The value x64compatible was added in Inno Setup 6.3 — earlier 6.x
versions reject it with "Value of [Setup] section directive
ArchitecturesAllowed is invalid". Using plain x64 keeps compatibility
with the whole 6.x line. Modern users on 6.3+ are still happy since
x64 still resolves correctly there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 09:54:05 +02:00
b7de228bc9 v0.6 + v1.0: HMAC download URLs, launcher self-update, Inno Setup installer
Three deliverables shipped together so the next deployment cycle has a
clean distribution story.

(1) Auto-update of the launcher itself
---------------------------------------
- Models: RemoteManifest gains an optional `launcher` section
  (LauncherInfo: version, minRequired, download {url,size,sha256},
   releaseNotesUrl). Server-side, sign-manifest.php passes it through
  unchanged; admins edit versions.json with the new launcher entry +
  upload PSLauncher-X.Y.Z.exe to /builds/launcher/.
- Core: ILauncherSelfUpdater compares assembly version against
  manifest.launcher.version using the existing SemVer parser, and
  reuses DownloadManager (Range/resume/sha256 already proven on the
  game ZIPs) to download the new exe into
  %LocalAppData%/PSLauncher/selfupdate/.
- New project PSLauncher.Updater (~34 MB self-contained console exe):
  spawned by the main app with --target / --source / --pid / --launch.
  Waits for the main process to exit (or for the file lock to release),
  backs up the current exe to .bak, copies the new file in place, and
  restarts. .bak survives the swap so the user can roll back manually.
- App.csproj now declares Version=0.5.0 — currently shipped baseline.
  PSLauncher.App.csproj sets a fixed AssemblyVersion so reflection-based
  comparison works deterministically.
- MainViewModel.PromptLauncherUpdate: dialog after CheckForUpdates if
  the manifest advertises a newer launcher. Download with progress in
  the existing footer, then Application.Shutdown() so the Updater can
  do its job.

(2) Inno Setup installer
------------------------
installer/PSLauncher.iss + build-installer.ps1 produce a single
PSLauncher-Setup-X.Y.Z.exe (~80 MB) that installs into
Program Files\ASTERION VR\PSLauncher\, drops both PSLauncher.exe and
PSLauncher.Updater.exe side by side (the updater MUST live next to
the target), creates Start Menu + optional Desktop shortcuts, and
registers a clean uninstall entry. The user's %LocalAppData%
(license, logs, cache) is intentionally untouched on uninstall — same
license survives a reinstall.

build-installer.ps1 chains dotnet publish for both projects and ISCC
in one command. README explains the bump-version workflow.

(3) HMAC-signed download URLs
-----------------------------
- New PHP route GET /api/download-url/{version} (Authorization: Bearer
  <licenseKey> or ?key=...). Validates the license, checks
  download_entitlement_until >= minLicenseDate of the version, and
  returns a HMAC-signed URL (path|exp|licId, hash_hmac SHA-256, valid
  1 h) + sha256 + sizeBytes for verification.
- /builds/.htaccess routes every *.zip request to gate.php. gate.php
  validates exp, lic, sig (constant-time hash_equals), then streams
  the file with Range: support so the launcher's resume keeps working.
  Audit log gets a download_url_issued entry per request.
- Client-side wired transparently: LicenseService gains
  GetSignedDownloadUrlAsync(version) that GETs the endpoint with the
  decrypted license key from DPAPI. MainViewModel calls it before
  every download; if the endpoint returns 404/401/network-error, the
  client falls back to the manifest's plain download.url (graceful
  degradation for setups that haven't deployed gate.php yet).

Note on PHP streaming for 14 GB ZIPs: gate.php uses set_time_limit(0)
+ ignore_user_abort(true) + 1 MiB chunked fread with periodic flush.
Works on OVH mutualisé but holds a PHP-FPM slot for the duration. If
parallel downloads scale past a few clients, switch to
mod_xsendfile or migrate /builds/ to Cloudflare R2 with native
S3-presigned URLs and remove the gate entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 09:38:13 +02:00