Launcher Self-Update
SelfUpdater.cs is responsible for keeping the launcher itself up to date. It runs early during startup, before the user can interact with the main window, because some updates must complete before the regular UI is allowed to touch NugetStore.
Flow
flowchart TD
Start["MainViewModel.FetchOnlineData"]
Self["SelfUpdater.SelfUpdate"]
Updates["store.GetUpdates(Stride.Launcher, currentVersion)<br/>minus pre-releases unless opted in"]
Choose["Chosen package: the first 'checkpoint',<br/>otherwise the newest"]
Reinstall["Current version below its<br/>'reinstall-below'?"]
DL["Download its setup<br/>('setup' URL)"]
RunInstaller["Release launcher.lock,<br/>start installer,<br/>Environment.Exit(0)"]
Show["Show SelfUpdateWindow<br/>(modal, locked)"]
Install["store.InstallPackage(launcher package)"]
Swap["Move current files to .old,<br/>copy new files from tools/"]
Restart["RestartApplication<br/>(append /UpdateTargets)"]
Skip["Return, continue normal startup"]
Start --> Self --> Updates --> Choose
Choose -- "no candidate" --> Skip
Choose --> Reinstall
Reinstall -- "yes" --> DL --> RunInstaller
Reinstall -- "no" --> Show --> Install --> Swap --> Restart
Version probe
SelfUpdater.Version is read once from the assembly's AssemblyInformationalVersionAttribute. The package id is taken from AssemblyProductAttribute.Product — keep these MSBuild properties in sync with Stride.Launcher.nuspec.
store.GetUpdates returns the packages newer than the current version, pre-releases included. SelfUpdater.IsUpdateCandidate then drops the pre-releases unless the user opted in (see Pre-releases).
Update rules
Each package carries its rules in an update: line of its description, filled by Stride.Launcher.Release.targets:
update: [checkpoint] [reinstall-below=<version>] setup=<url>
checkpoint: every older launcher updates to this package before any newer one, e.g. a version that migrates something the next ones need. Set with thecheckpointinput ofrelease-launcher.yml.reinstall-below: launchers below this version can't reach this package by swapping their files, so they install its setup. SetStrideLauncherReinstallBelowinStride.Launcher.Release.targetsfor such a release, and keep it for the next ones.setup: the setup of this package, on its GitHub release.
SelfUpdater.ChooseUpdate takes the first candidate with checkpoint, otherwise the newest one. Only the rules of that package count: if the current version is below its reinstall-below, the launcher installs its setup (Windows only), otherwise it swaps its files. The rules only apply to the candidates the pre-release filter kept: a pre-release that needs a reinstall only reaches the users who opted in, with its own setup.
Unknown words are skipped, so that a later launcher can add rules without breaking the older ones.
Launchers before 6.0.1
Older launchers don't read the update: line. They read two older mechanisms, which the packages keep for them:
force-reinstall: 5.0.1 <setup>: the updater takes the newest package that has this line. Launchers below 5.0.1 install its setup. Every package keeps it with5.0.1, which rescues the 4.x launchers (the 5.x packages point to a URL that no longer exists).-reqversions: the updater takes the first-reqversion above it before any other one. These launchers have no pre-release filter, so6.0.1-req(the same exe as 6.0.1, published with thelegacy-reqinput) takes them to 6.0.1 before they see a pre-release. The exe says 6.0.1, so they don't update again to 6.0.1.release-launcher.ymlrefuses to deploy a pre-release until a 6.x-reqis on NuGet.
Pre-releases
A launcher pre-release (6.1.0-beta1) is built with a version suffix (see packaging.md). Only launchers whose user opted in update to it: the launcher settings (the gear in the title bar) have a "Receive launcher pre-releases" check box (LauncherSettings.IncludePrereleaseUpdates). The setting applies from the next start; checking right away could run alongside the update check of startup. Turning it off doesn't downgrade: the launcher stays on its pre-release until a newer release comes out.
A pre-release launcher, e.g. installed from its own setup, always takes the newer pre-releases of its own version, then its release: 6.0.1-beta1 gets 6.0.1-beta2 and 6.0.1, but 6.0.2-beta1 only with the setting on. Once on the release, only the setting decides.
Launchers before 6.0.1 have no such filter: see Launchers before 6.0.1.
NuGet compares beta10 before beta2 (text), so the pre-release number stays from 1 to 9.
File swap
When an in-place update is possible:
- A
SelfUpdateWindowis shown modally on top of the main window;LockWindow()disables its close button for the duration. NugetStore.InstallPackagedownloads the package.package.GetFiles()is filtered to entries undertools/(must match the layout in Stride.Launcher.nuspec —<file src="Stride.Launcher.exe" target="tools" />).- Each target file (the launcher exe, its
.config, and everything intools/) is first moved to<file>.old, then replaced. If any copy throws, every.oldis rolled back. The running exe's.oldcan't be deleted yet: the new launcher deletes it (and the.config.old) when its own update check starts. store.PurgeCache()clears NuGet's stream cache so subsequent launches don't reopen the old package.RestartApplicationadds/UpdateTargetstoargs, releasesLauncher.Mutex, starts a new process withUseShellExecute = true, and callsEnvironment.Exit(0).
Mutex release
Both the reinstall and the file-swap paths must release Launcher.Mutex before spawning the replacement process — otherwise the newly-started launcher would hit ServerAlreadyRunning and bail out. Every Process.Start call in SelfUpdater is immediately preceded by Launcher.Mutex?.Dispose().
Failure modes
- HTTP failure in a reinstall. Shows a
MessageBoxwithStrings.NewVersionDownloadError; the launcher keeps running against the old version. - Partial file swap.
.oldfiles are renamed back and the exception is re-thrown.SelfUpdateWindow.ForceClose()drops the modal, thenMainViewModel.FetchOnlineDatashows the full error (withLogMessages). - No network at all.
FetchOnlineData's catch swallowsHttpRequestExceptionso the launcher still starts in offline mode. Any other failure callsEnvironment.Exit(1)— the product decision is that running against a known-broken launcher is worse than not running.
Testing
Self-update is hard to test end-to-end. For local iteration:
- Build a
Stride.Launcherpackage with a bumped version viaPackageLauncher-Debug.bat. - Point a local NuGet feed at the output.
- Add the feed in
nuget.configand relaunch the installed launcher.
For a reinstall, pack with UpdateRules=reinstall-below=<version> setup=<url> and host the setup on a local HTTP server. The choice itself is unit-tested in SelfUpdaterTests.