Stride Versions
Managing Stride versions is the launcher's core responsibility. This file describes how versions are discovered, how install/update/uninstall flows are wired, and where to hook new behavior.
Version kinds
flowchart TD
SVM["StrideVersionViewModel<br/>(abstract)"]
SSV["StrideStoreVersionViewModel<br/>Major.Minor from NuGet feed"]
SSA["StrideStoreAlternateVersionViewModel<br/>Alternate patch version under the same Major.Minor"]
SDV["StrideDevVersionViewModel<br/>Local build or DevRedirect package"]
SVM --> SSV
SVM --> SDV
SSV --> SSA
Each entry in MainViewModel.StrideVersions corresponds to one Major.Minor pair, and a second one for its nightly builds (IsNightly, "Stride 4.5 Nightly", right before the release row of the same Major.Minor, since its builds are newer). A StrideStoreVersionViewModel holds both the latest NugetLocalPackage installed and the latest NugetServerPackage from the feed — that's how the UI knows whether a version is installed, outdated, or download-only.
Nightly builds (PackageVersion.IsNightly, X.Y.Z-nightly.YYYYMMDD.N, see versioning.md) are kept in their own row so that a release row's default build, "Install latest", the first install and the default active version never move to a nightly. A recent project on a nightly opens with the nightly row, one on a release or prerelease with the release row; the nightlies are listed among its "open with" builds, but the upgrade arrow only suggests them to a project already on a nightly. The rows come from the nightly feed: "Show nightly builds" in the launcher settings adds it to or removes it from the user's NuGet.config (NugetStore.SetPackageSource) and refreshes the list (MainViewModel.ShowNightlyBuilds); turning it on asks first (unsigned builds, project upgrades not fully tested); installed nightlies stay listed when it's off.
A nightly row has no release notes: its notes button is "What's new" (StrideStoreVersionViewModel.WhatsNewCommand), a GitHub compare page to the newest nightly, from the newest installed nightly when older, else from the newest release or prerelease before it (of any line, never a local build: 4.4.0 for the 4.5.0 nightlies until the first 4.5 prerelease), else from the previous nightly (ChooseWhatsNewRange). The commits are read from the packages' nuspec (<repository commit>): an installed package's own, or one download of the nuspec from its feed's package base address (NugetStore.GetRepositoryCommitAsync). The tooltip names both builds, refreshed after each listing.
The icon before "Switch/update version" lists the versions again (MainViewModel.RefreshCommand), installed and on the feeds, so that a version published since the start shows without a restart. It skips the rest of the start: no launcher update check, news, first install, or cleanup of unused packages.
Version discovery
MainViewModel.FetchOnlineData orchestrates three phases:
- Local.
RetrieveLocalStrideVersionscallsNugetStore.GetPackagesInstalled(MainPackageIds)and filters withFilterStrideMainPackages()(PackageFilterExtensions.cs). Packages are grouped by{Major}.{Minor}and nightly or not. - Server.
RetrieveServerStrideVersionscallsNugetStore.FindSourcePackages(MainPackageIds, …). If the call returns nothing,IsOfflineflips on and the UI shows an error with the accumulated log. - Dev. Dev-redirect packages (detected via
NugetStore.IsDevRedirectPackage) are added asStrideDevVersionViewModelwith their real on-disk path fromNugetStore.GetRealPath. Additionally, every entry inLauncherSettings.DeveloperVersionsis added up front.
The same (Major, Minor, IsNightly) key is looked up with SortedObservableCollection.BinarySearch to merge local and server state into a single view model. The server listing only adds and updates rows: turning the nightlies off removes the nightly rows that aren't installed first.
NuGet store
Stride.Core.Packages.NugetStore is a thin facade over NuGet's v3 APIs. The launcher does not call NuGet directly — every operation goes through NugetStore:
| Method | Used by |
|---|---|
GetPackagesInstalled(ids) |
RetrieveLocalStrideVersions |
FindSourcePackages(ids, ct) |
RetrieveServerStrideVersions, VsixVersionViewModel.UpdateFromStore |
InstallPackage(id, version, frameworks, progress) |
PackageVersionViewModel download flow, SelfUpdater |
UninstallPackage(package, progress) |
PackageVersionViewModel delete flow, MainViewModel.RemoveUnusedPackages, Launcher.UninstallAsync |
GetUpdates(identity, …) |
SelfUpdater |
PurgeCache() |
SelfUpdater after a file swap |
Every call is serialized through MainViewModel.RunLockTask, which takes objectLock and runs on a background thread.
Progress is reported via IPackagesLogger — MainViewModel implements it and stores every log line in-memory so the crash report and the "offline" dialog can attach it.
Install / update flow
- User clicks Install or Update on a
StrideVersionViewModel. PackageVersionViewModel.Download(true)runs: it setsIsProcessing, callsNugetStore.InstallPackage, and updates progress viaOnDownloadProgress.- On completion,
UpdateStatusrecomputesCanBeDownloaded/CanDeleteand the UI re-binds. MainViewModel.RetrieveLocalStrideVersionsis re-run to refresh the version list and to clean up any newly-unused transitive packages viaRemoveUnusedPackages(walksDependenciesstarting from the Stride main packages and uninstalls anything no longer referenced).UpdateAvailableEditors()re-scanstools/andlib/for TFM subfolders containing a Game Studio executable (Stride.GameStudio.Avalonia.Desktop.exeon Windows,.dllon Linux).SelectedEditoris restored fromLauncherSettings.PreferredEditorif present, otherwise the first editor found is used, and the runtime combo is refreshed for the active version.
Uninstall flow
Two entry points:
Per-version, from the UI:
PackageVersionViewModel.Delete(removeFromUi: true, confirmPrompt: true)prompts the user, callsNugetStore.UninstallPackage, and updates status.Launcher uninstall, from
Stride.Launcher.exe /uninstall, which the setup runs when the launcher is uninstalled (not on an upgrade). Launcher.cs'sUninstallAsync:- Calls
UninstallHelper.CloseProcessesInPathsAsyncfor the processes started from the launcher directory: the user closes them (OK to try again) or cancels. - Deletes the
.lockfiles and the.oldfiles of the last self-update (locked ones are skipped). - Asks whether to also remove the installed Stride versions, with their size. Keep is the default: someone reinstalling the launcher wants them back. Remove uninstalls the versions and the Stride packages that only they use (
StridePackageReferences.FindRemovable, the same walk ofDependenciesas the cleanup after an uninstall in the UI). Local builds and what they use are never removed. The processes running from those packages (Game Studio) are checked first, throughNugetStore.UninstallGuard. - Cancels the app's
CancellationTokenSourceso the main loop exits.
With
/quiet(the setup passes it when the MSI shows no dialog:msiexec /x … /qn, or/qras inwinget uninstall), or when the process has no desktop (Environment.UserInteractiveis false: SYSTEM or session 0, e.g. Intune), nothing is shown, not even an app: only step 2 runs, and the versions are kept. A dialog there would be invisible, or would block the setup. Removing the versions in a script belongs to the CLI (stride sdk uninstall <version>).- Calls
UninstallHelper also subscribes to NugetStore.NugetPackageUninstalling to close lingering processes before each package is removed — this is why it lives as a disposable member on MainViewModel (uninstallHelper).
Editor discovery
StrideVersionViewModel.AvailableEditors is an ObservableList<string> populated by scanning every framework folder of the package install paths (PackageLayout.FrameworkDirectories; a dev-redirect version scans bin/<Configuration>/ of the in-tree project instead). The launcher looks for:
{InstallPath}/tools/{framework}/Stride.GameStudio.Avalonia.Desktop.{exe|dll}
{InstallPath}/lib/{framework}/Stride.GameStudio.Avalonia.Desktop.{exe|dll}
On Windows, Stride.GameStudio.exe is also considered. The first folder holding an editor wins; each editor's folder is remembered for LocateMainExecutable. Stride 4.0 shipped both a net472 and a net5.0-windows editor, which is why the legacy lib/net472/Stride.GameStudio.exe fallback still exists; since 4.1 every version ships one framework folder per editor.
Runtime choice
MainViewModel.AvailableRuntimes lists ".NET N or newer (default)" for the active version's editor's own runtimeconfig major, then ".NET M or newer" for each .NET major above it, restricted to majors with both a runtime and an SDK installed (DotNetInstall.Detect). A choice is a minimum: a project that needs a newer major still gets it, and a global.json SDK pin wins over the choice. The list is only shown when the editor takes the choice (DotNetHostSelector.SupportsHostSelection, a type-reference scan of the editor assembly), so older editors never receive --framework. An explicit choice starts the editor through dotnet exec --runtimeconfig <generated>; the default entry passes no choice, starts the apphost and lets the editor re-execute itself when the project needs a newer major.
VSIX
VsixVersionViewModel treats the VSIX as just another NuGet package but overrides the install step to invoke VSIXInstaller.exe against the detected VisualStudioVersions.AvailableInstances matching the supported range (VS2019 for major 16, VS2022AndNext for major ≥ 17). Two instances exist on MainViewModel: VsixPackage2019 and VsixPackage2022.
Adding a new version kind
- Create a subclass of
StrideVersionViewModel(orStrideStoreVersionViewModelfor "looks like a store version but from a different source"). - Override
UpdateStatusto describe when the entry is downloadable vs installed. - Populate it from
MainViewModel— either from a settings source (likeLauncherSettings.DeveloperVersions) or from a new NuGet query. - If the UI needs to distinguish it, add a DataTemplate in
MainView.axamlkeyed on the concrete type.