From 5caec70da9cd82336a35e775c913666c47a4adac Mon Sep 17 00:00:00 2001 From: iChris4 Date: Wed, 9 Sep 2026 20:40:23 +0200 Subject: [PATCH] Added Support for VR in Launcher Setup --- .github/workflows/package.yml | 159 ++++-------------- DISTRIBUTION.md | 56 ++++++ Launcher/Build-Installer.ps1 | 4 +- Launcher/Directory.Build.props | 5 + Launcher/LocalBuild.ps1 | 2 +- Launcher/Test-PayloadBoundary.ps1 | 6 +- Launcher/Test-Recompilation.ps1 | 7 +- Launcher/Verify-Release.ps1 | 49 ++++++ .../RuntimeConfiguration.cs | 2 +- .../WiiCompiled.Setup.Windows/CommandLine.cs | 2 + .../ConsoleCommands.cs | 10 ++ .../GameLaunchService.cs | 1 + .../InstallerEngine.cs | 4 + Launcher/WiiCompiled.Setup.Windows/Models.cs | 2 + .../ProductOwnership.cs | 29 ++++ .../ProductRepairService.cs | 1 + Launcher/WiiCompiled.Setup.Windows/Program.cs | 13 +- .../WiiCompiled.Setup.Windows/SelfTests.cs | 29 +++- .../ShellIntegration.cs | 2 +- .../UninstallService.cs | 3 + .../WiiCompiled.Setup.Windows.csproj | 3 +- Launcher/dist-vr/SHA256SUMS.txt | 2 + OPENXR.md | 13 +- README.md | 28 ++- RELEASE_VALIDATION.md | 85 ++++++++++ runtime/CMakeLists.txt | 5 +- runtime/include/runtime_config.h | 4 + runtime/src/settings_overlay.cpp | 28 +++ 28 files changed, 397 insertions(+), 157 deletions(-) create mode 100644 DISTRIBUTION.md create mode 100644 Launcher/Directory.Build.props create mode 100644 Launcher/Verify-Release.ps1 create mode 100644 Launcher/WiiCompiled.Setup.Windows/ProductOwnership.cs create mode 100644 Launcher/dist-vr/SHA256SUMS.txt create mode 100644 RELEASE_VALIDATION.md diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index b97c62a..a9d9aba 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -1,160 +1,65 @@ -name: Package installers - -# Builds the per-platform installer/setup tool (WiiCompiled-Setup.exe / -# WiiCompiled-Setup-x86_64.AppImage) via Launcher/Build-Installer.ps1 and -# Launcher/build-appimage.sh respectively - the same scripts a maintainer runs by hand today to -# produce a GitHub Release asset. This does NOT build the actual translated game executable: -# that step requires the end user's own Mario Kart Wii dump (Assets/main.dol, Assets/StaticR.rel), -# which is proprietary and not present in this repository or in CI. +name: Package WiiCompiled OpenXR VR on: push: - tags: - - '*' + tags: ['v*'] workflow_dispatch: - permissions: contents: read - concurrency: - group: package-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - + group: package-vr-${{ github.ref }} + cancel-in-progress: false jobs: recompilation: - name: Recompilation test uses: ./.github/workflows/recomp-test.yml - - linux-appimage: - name: Linux (AppImage, ${{ matrix.arch }}) - strategy: - fail-fast: false - matrix: - include: - - runner: ubuntu-22.04 - arch: x86_64 - - runner: ubuntu-22.04-arm - arch: aarch64 - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - - uses: actions/setup-dotnet@v6 - with: - dotnet-version: '8.0.x' - - # appimagetool ships as an AppImage itself and needs to self-mount via FUSE to run directly; - # GitHub's ubuntu-latest runners don't have libfuse2 preinstalled. - - name: Install dependencies (required by appimagetool and SDL3 build) - run: | - sudo apt-get update - sudo apt-get install -y libfuse2 build-essential git make \ - pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \ - libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \ - libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \ - libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ - libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev libthai-dev libusb-1.0-0-dev \ - libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev - - - name: Build AppImage - run: bash Launcher/build-appimage.sh - - - uses: actions/upload-artifact@v7 - with: - name: WiiCompiled-Setup-linux-${{ matrix.arch }} - path: Launcher/dist/WiiCompiled-Setup-${{ matrix.arch }}.AppImage - if-no-files-found: error - archive: false - - windows-installer: - name: Windows (installer exe) + windows: runs-on: windows-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - - uses: actions/setup-dotnet@v6 with: dotnet-version: '8.0.x' - - - name: Build installer + - name: Verify release version shell: pwsh - run: ./Launcher/Build-Installer.ps1 - + env: + RELEASE_TAG: ${{ startsWith(github.ref, 'refs/tags/v') && github.ref_name || '' }} + run: ./Launcher/Verify-Release.ps1 -Tag $env:RELEASE_TAG + - name: Test translator + run: dotnet test translator/Translator.sln -c Release + - name: Build and audit setup payload + shell: pwsh + run: | + ./Launcher/Build-Installer.ps1 + ./Launcher/Verify-Release.ps1 -SetupPath (Resolve-Path Launcher/dist/WiiCompiled-Setup.exe) + ./Launcher/dist/WiiCompiled-Setup.exe --self-test + if ($LASTEXITCODE -ne 0) { throw 'Setup self-tests failed.' } + $hash = (Get-FileHash Launcher/dist/WiiCompiled-Setup.exe -Algorithm SHA256).Hash.ToLowerInvariant() + "$hash WiiCompiled-Setup.exe" | Set-Content Launcher/dist/SHA256SUMS.txt - uses: actions/upload-artifact@v7 with: - name: WiiCompiled-Setup-windows-x64 - path: Launcher/dist/WiiCompiled-Setup.exe + name: WiiCompiled-OpenXR-VR-windows-x64 + path: | + Launcher/dist/WiiCompiled-Setup.exe + Launcher/dist/SHA256SUMS.txt if-no-files-found: error - archive: false - - # Publishes the packaged installers as a GitHub Release whenever a v* tag is pushed. Wheel Wizard - # discovers updates from these releases, so the contract it relies on is enforced here: a full - # (non-prerelease) release whose tag is v, carrying an asset named exactly - # WiiCompiled-Setup.exe, produced by a setup host that reports that same version. release: - name: Publish GitHub Release if: startsWith(github.ref, 'refs/tags/v') - needs: [linux-appimage, windows-installer, recompilation] - runs-on: ubuntu-latest + needs: [windows, recompilation] + runs-on: windows-latest permissions: contents: write steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - # Wheel Wizard runs the downloaded setup with --version and refuses it when the reported - # version differs from the release tag, so a tag that was pushed without bumping every pinned - # version would ship an update nobody can install. Catch that before anything is published. - - name: Verify the tag matches the pinned setup version - env: - TAG: ${{ github.ref_name }} - run: | - set -euo pipefail - version="${TAG#v}" - status=0 - check() { - if ! grep -Fq "$2" "$1"; then - echo "::error file=$1::expected '$2' for tag $TAG" - status=1 - fi - } - check Launcher/WiiCompiled.Setup.Windows/Program.cs "public const string Version = \"$version\";" - check Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj "$version" - check Launcher/Build-Installer.ps1 "ProductVersion = '$version'" - exit $status - - # The build jobs upload with `archive: false`, which stores each installer as a raw file - # rather than a zip (and names the artifact after the file). Only download-artifact v8 and - # later understand such direct uploads; v7 tries to unzip everything and fails on them. - uses: actions/download-artifact@v8 with: + name: WiiCompiled-OpenXR-VR-windows-x64 path: artifacts - merge-multiple: true - - - name: Publish release + - name: Publish Windows setup + shell: pwsh env: GH_TOKEN: ${{ github.token }} TAG: ${{ github.ref_name }} + REPOSITORY: ${{ github.repository }} run: | - set -euo pipefail - ls -lR artifacts - assets=() - for name in WiiCompiled-Setup.exe WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage; do - found="$(find artifacts -type f -name "$name" | head -n 1)" - [ -n "$found" ] && [ -s "$found" ] || { echo "::error::missing release asset $name"; exit 1; } - assets+=("$found") - done - # A re-run of a tag whose release already exists only refreshes the assets. - if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then - gh release upload "$TAG" "${assets[@]}" --repo "$GITHUB_REPOSITORY" --clobber - else - gh release create "$TAG" "${assets[@]}" \ - --repo "$GITHUB_REPOSITORY" \ - --title "$TAG" \ - --verify-tag \ - --generate-notes - fi + gh release create $env:TAG artifacts/WiiCompiled-Setup.exe artifacts/SHA256SUMS.txt --repo $env:REPOSITORY --verify-tag --title "WiiCompiled OpenXR VR $env:TAG (beta)" --notes "Windows x64 OpenXR beta. Install through WheelWizard VR. Requires your own clean PAL RMCP01 disc image; game code and assets are compiled locally." + if ($LASTEXITCODE -ne 0) { throw 'Release publication failed.' } diff --git a/DISTRIBUTION.md b/DISTRIBUTION.md new file mode 100644 index 0000000..1857822 --- /dev/null +++ b/DISTRIBUTION.md @@ -0,0 +1,56 @@ +# Windows VR distribution + +Distribute only `WiiCompiled-Setup.exe` and its checksum from +[iChris4/Wiicompiled_VR](https://github.com/iChris4/Wiicompiled_VR/releases), and +`WheelWizardVRWindows.exe` from [iChris4/WheelWizard_VR](https://github.com/iChris4/WheelWizard_VR/releases). +Installation requires the user's clean PAL RMCP01 image. Neither release includes a disc image, +extracted game assets, translated game code, compiled game executables, saves, or ghosts. + +The backend version is declared once in `Launcher/Directory.Build.props`. Before choosing a new +patch version, compare source pins, published releases, and tags in the VR fork. Tags use `vX.Y.Z`. +The Windows workflow checks the tag, executable, and payload contract and runs the translator, +synthetic runtime, setup self-tests, pinned dependency preparation, payload boundary audit, and +native dependency audit. OpenXR uses the pinned SDK sources and static loader; license notices +are included in the installer. + +Build from the repository root on Windows: + +```powershell +./Launcher/Build-Installer.ps1 -OutputDirectory Launcher/dist-vr +./Launcher/Verify-Release.ps1 -Tag v0.2.32 -SetupPath (Resolve-Path Launcher/dist-vr/WiiCompiled-Setup.exe) +./Launcher/dist-vr/WiiCompiled-Setup.exe --self-test +./Launcher/Test-Recompilation.ps1 -StageDirectory build/vr-synthetic-validation +dotnet test translator/Translator.sln -c Release +``` + +`--version` still prints a plain version. `--info-json` additionally reports `productId` as +`wiicompiled-openxr-vr`, `version`, and `openxrD3D12: true`. VR refuses installation, repair, +launch, or uninstall of a normal or unidentified installation. Nonportable VR installations use +`WiiCompiledOpenXRVR` application data and a distinct uninstall registration and shortcut. + +WheelWizard VR keeps its own `config-vr.json`, importing `config.json` only on first use. Its +`Recomp` and `RecompVR` roots own their setup caches, toolkits, build workspaces, extracted assets, +binaries, configuration, runtime caches, and logs. Only the normal NAND and canonical Retro Rewind +content are shared. Controller mappings are seeded from normal once when available. Uninstall +removes the selected `Install` and `Cache` directories and retains `UserData` and imported NAND. + +A shared file lease covers supported launcher operations through mod preparation, reconciliation, +and game exit, including shared-data edits and uninstall. Installation locks and running processes +are also checked. Arbitrary direct launches of older game binaries do not participate in this +guarantee. Close games before using another launcher or editing shared data externally. + +Retro Rewind remains supplied by WheelWizard's existing distribution service. The chosen backend +builds both products with its own toolkit. Asset-only changes do not require compilation; changes +to Code.pul, translation inputs, or Retro-WFC payloads are reconciled before Retro Rewind launches. +The inactive backend is checked on its next selection. Base launches use `--launch-base`. + +WheelWizard VR prepares Retro Rewind updates and reinstalls in a sibling staging tree. Only a +completed update is published. The journal supports recovery after interrupted directory swaps; +existing ghosts, saves, XML save directories, and local patches survive package replacement. + +Before publishing, validate both artifacts and install beside an existing normal installation. +Check all four backend/game combinations, saves and Miis across switches and reinstall/uninstall, +Retro Rewind ghosts and content updates, cancellation, interrupted downloads, rollback, and +overlapping processes. Headset acceptance also requires menus, races, headset absence, and runtime +loss for both VR products. Record unperformed checks explicitly. Publish the backend first; the +launcher release workflow requires a published VR backend before releasing the launcher. diff --git a/Launcher/Build-Installer.ps1 b/Launcher/Build-Installer.ps1 index 3e12790..0f37c08 100644 --- a/Launcher/Build-Installer.ps1 +++ b/Launcher/Build-Installer.ps1 @@ -15,6 +15,7 @@ Set-StrictMode -Version 3.0 . (Join-Path $PSScriptRoot 'NativeBuildFlags.ps1') $repoRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) +$releaseVersion = ([xml](Get-Content -LiteralPath (Join-Path $PSScriptRoot 'Directory.Build.props') -Raw)).Project.PropertyGroup.Version $outputRoot = [IO.Path]::GetFullPath((Join-Path $repoRoot $OutputDirectory)) $portableTools = [IO.Path]::GetFullPath((Join-Path $repoRoot $PortableToolsDirectory)) $dependencySources = [IO.Path]::GetFullPath((Join-Path $repoRoot $DependencySourceDirectory)) @@ -282,7 +283,8 @@ foreach ($required in @('ToolkitFingerprint','TranslationFingerprint','NativeToo $manifest = [ordered]@{ SchemaVersion = 2 - ProductVersion = '0.2.31' + ProductVersion = $releaseVersion + ProductId = 'wiicompiled-openxr-vr' ExpectedGameId = $pins.GameId ExpectedDolSha256 = $pins.DolSha256 ExpectedRelSha256 = $pins.RelSha256 diff --git a/Launcher/Directory.Build.props b/Launcher/Directory.Build.props new file mode 100644 index 0000000..d0acf2a --- /dev/null +++ b/Launcher/Directory.Build.props @@ -0,0 +1,5 @@ + + + 0.2.32 + + diff --git a/Launcher/LocalBuild.ps1 b/Launcher/LocalBuild.ps1 index cf8c399..04cb49c 100644 --- a/Launcher/LocalBuild.ps1 +++ b/Launcher/LocalBuild.ps1 @@ -390,7 +390,7 @@ try { -SourceDirectory (Join-Path $Workspace 'runtime') -BuildDirectory $build ` -Ninja $ninja -CCompiler $cc -CxxCompiler $cxx -ResourceCompiler $windres ` -DependenciesDirectory $dependencies -NativePrebuiltDirectory $nativePrebuilt ` - -AdditionalArguments @("-DMKW_TRANSLATED_COMPILE_JOBS=$translatedJobs") + -AdditionalArguments @("-DMKW_TRANSLATED_COMPILE_JOBS=$translatedJobs", '-DMKW_ENABLE_OPENXR=ON') Invoke-Checked $cmake $configure 'Configuring the bundled native toolchain' -StepId 'configure-native' if (-not [string]::IsNullOrWhiteSpace($NativeToolchainFingerprint)) { [ordered]@{ SchemaVersion = 1; NativeToolchainFingerprint = $NativeToolchainFingerprint } | diff --git a/Launcher/Test-PayloadBoundary.ps1 b/Launcher/Test-PayloadBoundary.ps1 index 79bdc3f..76ace65 100644 --- a/Launcher/Test-PayloadBoundary.ps1 +++ b/Launcher/Test-PayloadBoundary.ps1 @@ -9,9 +9,11 @@ $forbiddenNames = @( 'WiiCompiled.exe', 'RetroRewind.exe', 'mkw_recompiled.exe', 'main.dol', 'StaticR.rel', 'Code.pul', 'base_translation_sources.bin', 'data_sections_init.cpp', 'data_sections_init_blobs.S', 'mkwii_base_manifest.json', - 'generated_func_aliases.cpp', 'translated_function_decls.h' + 'generated_func_aliases.cpp', 'translated_function_decls.h', + 'rksys.dat', 'RFL_DB.dat', 'Config.toml', 'config-vr.json', 'install-state.json' ) -$forbiddenExtensions = @('.iso', '.gcm', '.gcz', '.ciso', '.wbfs', '.wia', '.rvz', '.dol', '.rel', '.pul') +$forbiddenExtensions = @('.iso', '.gcm', '.gcz', '.ciso', '.wbfs', '.wia', '.rvz', '.dol', '.rel', '.pul', + '.szs', '.brres', '.thp', '.brsar', '.rkg') $forbiddenSegments = @( '\BuildWorkspace\generated\', '\BuildWorkspace\runtime\build\', '\GameAssets\', '\BuildWorkspace\Assets\', '\BuildWorkspace\PulsarPacks\', '\BuildWorkspace\build\base\', '\BuildWorkspace\build\mods\' diff --git a/Launcher/Test-Recompilation.ps1 b/Launcher/Test-Recompilation.ps1 index 789e4dd..cf9914d 100644 --- a/Launcher/Test-Recompilation.ps1 +++ b/Launcher/Test-Recompilation.ps1 @@ -45,13 +45,14 @@ foreach ($relative in $sourceFiles | Sort-Object -Unique) { } # One synthetic text section: li r3,40; addi r3,r3,2; nop; blr. -# Native HLE wrappers also call these eight guest symbols directly. Give each +# Native HLE and VR wrappers also call these guest symbols directly. Give each # its own generated blr function so the real product can link without game code. # Keep this list explicit: a new unresolved guest dependency must fail the test. [uint32]$entry = 0x80001000L [uint32[]]$guestCallbacks = @( 0x8012B830L, 0x801A0620L, 0x801A1ED8L, 0x801A961CL, - 0x801AADE0L, 0x801D8D30L, 0x801D9E94L, 0x8055531CL + 0x801AADE0L, 0x801D8D30L, 0x801D9E94L, 0x8055531CL, + 0x8056A470L, 0x8056A580L, 0x805A6C58L ) $textSize = [int]($guestCallbacks[-1] - $entry + 4) $dataOffset = 0x100 + $textSize @@ -135,7 +136,7 @@ try { -Ninja $ninja -CCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-clang.exe') ` -CxxCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-clang++.exe') ` -ResourceCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-windres.exe') ` - -DependenciesDirectory $dependencies -AdditionalArguments @('-DMKW_BUILD_PRODUCTS=ON') + -DependenciesDirectory $dependencies -AdditionalArguments @('-DMKW_BUILD_PRODUCTS=ON', '-DMKW_ENABLE_OPENXR=ON') Invoke-Checked $cmake $configure 'Configuring the production Windows runtime' ` -WaitForProcessTree $false Invoke-Checked $cmake @('--build', $nativeBuild, '--target', 'WiiCompiled', '--parallel', "$Parallel") ` diff --git a/Launcher/Verify-Release.ps1 b/Launcher/Verify-Release.ps1 new file mode 100644 index 0000000..8b06021 --- /dev/null +++ b/Launcher/Verify-Release.ps1 @@ -0,0 +1,49 @@ +[CmdletBinding()] +param([string]$Tag, [string]$SetupPath) +$ErrorActionPreference = 'Stop' +$version = ([xml](Get-Content -LiteralPath (Join-Path $PSScriptRoot 'Directory.Build.props') -Raw)).Project.PropertyGroup.Version +if ($Tag -and $Tag -cne "v$version") { throw "Release tag must be v$version, got $Tag" } +if ($SetupPath) { + $reported = (& $SetupPath --version | Out-String).Trim() + if ($LASTEXITCODE -ne 0 -or $reported -cne $version) { throw 'Setup executable version mismatch.' } + $info = (& $SetupPath --info-json | Out-String) | ConvertFrom-Json + if ($LASTEXITCODE -ne 0 -or $info.productId -cne 'wiicompiled-openxr-vr' -or + $info.version -cne $version -or $info.openxrD3D12 -ne $true) { throw 'Setup VR identity mismatch.' } + # Inspect the appended payload as well: a correctly versioned host can still wrap stale tools. + Add-Type -AssemblyName System.IO.Compression + $stream = [IO.File]::OpenRead((Resolve-Path -LiteralPath $SetupPath)) + $payload = [IO.MemoryStream]::new() + try { + if ($stream.Length -lt 24) { throw 'Missing payload footer.' } + $stream.Position = $stream.Length - 24 + $reader = [IO.BinaryReader]::new($stream, [Text.Encoding]::ASCII, $true) + try { + $magic = [Text.Encoding]::ASCII.GetString($reader.ReadBytes(8)) + $offset = $reader.ReadInt64() + $length = $reader.ReadInt64() + } finally { $reader.Dispose() } + if ($magic -cne 'MKWCPAY1' -or $offset -lt 0 -or $length -le 0 -or $offset + $length -ne $stream.Length - 24) { + throw 'Invalid payload footer.' + } + $stream.Position = $offset + $buffer = [byte[]]::new(1048576) + while ($length -gt 0) { + $count = $stream.Read($buffer, 0, [int][Math]::Min($length, $buffer.Length)) + if ($count -eq 0) { throw 'Truncated payload.' } + $payload.Write($buffer, 0, $count) + $length -= $count + } + $payload.Position = 0 + $zip = [IO.Compression.ZipArchive]::new($payload, [IO.Compression.ZipArchiveMode]::Read, $true) + try { + $entry = $zip.GetEntry('payload-manifest.json') + if (-not $entry) { throw 'Missing payload manifest.' } + $textReader = [IO.StreamReader]::new($entry.Open()) + try { $manifest = $textReader.ReadToEnd() | ConvertFrom-Json } finally { $textReader.Dispose() } + if ($manifest.ProductId -cne $info.productId -or $manifest.ProductVersion -cne $version) { + throw 'Payload identity/version does not match the executable.' + } + } finally { $zip.Dispose() } + } finally { $payload.Dispose(); $stream.Dispose() } +} +Write-Output "Verified WiiCompiled OpenXR VR $version" diff --git a/Launcher/WiiCompiled.Setup.Common/RuntimeConfiguration.cs b/Launcher/WiiCompiled.Setup.Common/RuntimeConfiguration.cs index 9a4f88a..56b0eae 100644 --- a/Launcher/WiiCompiled.Setup.Common/RuntimeConfiguration.cs +++ b/Launcher/WiiCompiled.Setup.Common/RuntimeConfiguration.cs @@ -26,7 +26,7 @@ public static class RuntimeConfiguration /// The per-user configuration an ordinary (non-portable) installation shares. public static string ApplicationDataConfigPath => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "WiiCompiled", ConfigFileName); + OperatingSystem.IsWindows() ? "WiiCompiledOpenXRVR" : "WiiCompiled", ConfigFileName); /// /// The configuration file that governs . This mirrors diff --git a/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs b/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs index 14cd664..4d2ffd6 100644 --- a/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs +++ b/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs @@ -14,6 +14,7 @@ internal enum AppMode CheckProducts, RepairProducts, Version, + InfoJson, EmitPayloadIdentities } @@ -58,6 +59,7 @@ internal sealed class CommandLine switch (args[i].ToLowerInvariant()) { case "--version": result.Mode = AppMode.Version; break; + case "--info-json": result.Mode = AppMode.InfoJson; break; case "--silent": result.Mode = AppMode.SilentInstall; break; case "--verify-inputs": result.Mode = AppMode.VerifyInputs; break; case "--uninstall": result.Mode = AppMode.Uninstall; break; diff --git a/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs b/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs index fbe9a16..ba2bf7f 100644 --- a/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs +++ b/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs @@ -19,6 +19,7 @@ internal static class ConsoleCommands Console.Out.WriteLine(" --launch-retro | --launch-base"); Console.Out.WriteLine(" --uninstall --install-dir "); Console.Out.WriteLine(" --version"); + Console.Out.WriteLine(" --info-json"); return 0; } @@ -28,6 +29,15 @@ internal static class ConsoleCommands return 0; } + public static int InfoJson() + { + Console.Out.WriteLine(JsonSerializer.Serialize(new + { + productId = ProductInfo.Id, version = ProductInfo.Version, openxrD3D12 = true + })); + return 0; + } + public static int EmitPayloadIdentities(CommandLine command) { diff --git a/Launcher/WiiCompiled.Setup.Windows/GameLaunchService.cs b/Launcher/WiiCompiled.Setup.Windows/GameLaunchService.cs index 6bdf7d2..d055efd 100644 --- a/Launcher/WiiCompiled.Setup.Windows/GameLaunchService.cs +++ b/Launcher/WiiCompiled.Setup.Windows/GameLaunchService.cs @@ -7,6 +7,7 @@ internal static class GameLaunchService public static Task LaunchAsync(BuildProfile profile) { var installation = new Installation(AppContext.BaseDirectory); + ProductOwnership.Ensure(installation.Root); using var operation = InstallOperationLock.Acquire(installation.Root); // A portable installation the user moved is reconciled before anything is read from its // recorded location. Launch never needs the discarded native build tree. diff --git a/Launcher/WiiCompiled.Setup.Windows/InstallerEngine.cs b/Launcher/WiiCompiled.Setup.Windows/InstallerEngine.cs index fb96d3c..f9bab84 100644 --- a/Launcher/WiiCompiled.Setup.Windows/InstallerEngine.cs +++ b/Launcher/WiiCompiled.Setup.Windows/InstallerEngine.cs @@ -12,6 +12,7 @@ internal sealed class InstallerEngine { var installDirectory = Path.GetFullPath(options.InstallDirectory); ValidateInstallDirectory(installDirectory); + ProductOwnership.Ensure(installDirectory, allowEmpty: true); InputValidation.ValidateExtension(options.GamePath); var parent = Directory.GetParent(installDirectory)?.FullName @@ -37,6 +38,8 @@ internal sealed class InstallerEngine using var payload = PayloadArchive.OpenCurrent(); var manifest = payload.ReadManifest(); + if (manifest.ProductId != ProductInfo.Id || manifest.ProductVersion != ProductInfo.Version) + throw new InvalidDataException("This setup payload does not match WiiCompiled OpenXR VR and its version."); var toolkit = InstalledLayout.Toolkit(staging); var workspace = InstalledLayout.Workspace(staging); @@ -366,6 +369,7 @@ internal sealed class InstallerEngine ProductVersion = manifest.ProductVersion, InstallDir = installDirectory, InstalledUtc = previousState?.InstalledUtc ?? DateTime.UtcNow, + ProductId = ProductInfo.Id, RetroRewindInstalled = previousState?.RetroRewindInstalled ?? false, ToolkitReleaseTag = manifest.ToolkitReleaseTag, DolSha256 = manifest.ExpectedDolSha256, diff --git a/Launcher/WiiCompiled.Setup.Windows/Models.cs b/Launcher/WiiCompiled.Setup.Windows/Models.cs index e62b261..32e6a21 100644 --- a/Launcher/WiiCompiled.Setup.Windows/Models.cs +++ b/Launcher/WiiCompiled.Setup.Windows/Models.cs @@ -35,6 +35,7 @@ internal sealed class InstallOptions internal sealed class PayloadManifest { + public string ProductId { get; set; } = ""; public int SchemaVersion { get; set; } public string ProductVersion { get; set; } = ""; public string ExpectedGameId { get; set; } = "RMCP01"; @@ -72,6 +73,7 @@ internal sealed class DiscHeader internal sealed class InstallState { + public string ProductId { get; set; } = ""; [JsonRequired] public int SchemaVersion { get; set; } = 1; diff --git a/Launcher/WiiCompiled.Setup.Windows/ProductOwnership.cs b/Launcher/WiiCompiled.Setup.Windows/ProductOwnership.cs new file mode 100644 index 0000000..92c02d9 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Windows/ProductOwnership.cs @@ -0,0 +1,29 @@ +using System.Text.Json; + +namespace WiiCompiled.Setup.Windows; + +internal static class ProductOwnership +{ + public static void Ensure(string directory, bool allowEmpty = false) + { + for (var current = new DirectoryInfo(Path.GetFullPath(directory)); current is not null; current = current.Parent) + if (current.Exists && (current.Attributes & FileAttributes.ReparsePoint) != 0) + throw new InvalidDataException("Use a VR installation directory without junctions or symbolic links."); + if (!Directory.Exists(directory)) + { + if (allowEmpty) return; + throw new InvalidDataException("WiiCompiled OpenXR VR is not installed in this directory."); + } + // A junction must not let an apparently separate VR destination overwrite another install. + if (allowEmpty && !Directory.EnumerateFileSystemEntries(directory).Any()) return; + var statePath = Path.Combine(directory, InstalledLayout.InstallStateFileName); + if (File.Exists(statePath)) + { + using var state = JsonDocument.Parse(File.ReadAllText(statePath)); + if (state.RootElement.TryGetProperty("ProductId", out var id) && id.GetString() == ProductInfo.Id) + return; + } + throw new InvalidDataException("This folder belongs to a normal or unidentified WiiCompiled installation. " + + "Choose a separate empty folder for OpenXR VR; existing files have not been changed."); + } +} diff --git a/Launcher/WiiCompiled.Setup.Windows/ProductRepairService.cs b/Launcher/WiiCompiled.Setup.Windows/ProductRepairService.cs index 6ff4d94..614a5f9 100644 --- a/Launcher/WiiCompiled.Setup.Windows/ProductRepairService.cs +++ b/Launcher/WiiCompiled.Setup.Windows/ProductRepairService.cs @@ -77,6 +77,7 @@ internal sealed class ProductRepairService RetroWfcPayloadMode requestedPayloadMode, ReconcileOptions options, CancellationToken cancellationToken) { + ProductOwnership.Ensure(_installation.Root); if (!_installation.IsPresent) throw new InvalidOperationException("WiiCompiled is not installed here."); if (!_installation.HasToolkit) diff --git a/Launcher/WiiCompiled.Setup.Windows/Program.cs b/Launcher/WiiCompiled.Setup.Windows/Program.cs index 7995f17..d273177 100644 --- a/Launcher/WiiCompiled.Setup.Windows/Program.cs +++ b/Launcher/WiiCompiled.Setup.Windows/Program.cs @@ -16,7 +16,7 @@ internal static class Program { Console.Out.WriteLine( "Mario Kart WiiCompiled is installed through Wheel Wizard - download it from " + - "https://github.com/TeamWheelWizard/WheelWizard"); + "https://github.com/iChris4/WheelWizard_VR"); Console.ReadKey(intercept: true); return 0; } @@ -37,6 +37,8 @@ internal static class Program if (command.Mode == AppMode.Version) return ConsoleCommands.Version(); + if (command.Mode == AppMode.InfoJson) + return ConsoleCommands.InfoJson(); if (command.Mode == AppMode.Help) return ConsoleCommands.Help(); @@ -120,8 +122,9 @@ internal static class PlatformChecks internal static class ProductInfo { - public const string Name = "WiiCompiled"; - public const string Version = "0.2.31"; + public const string Name = "WiiCompiled OpenXR VR"; + public const string Id = "wiicompiled-openxr-vr"; + public static string Version => typeof(ProductInfo).Assembly.GetName().Version!.ToString(3); /// /// The setup executable is copied into the installation under this name. It is the launcher and @@ -129,8 +132,8 @@ internal static class ProductInfo /// public const string SetupCopyName = "WiiCompiled-Setup.exe"; - public const string UninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\WiiCompiled"; + public const string UninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\WiiCompiledOpenXRVR"; public static string DefaultInstallDirectory => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "Programs", "WiiCompiled"); + "Programs", "WiiCompiledOpenXRVR"); } diff --git a/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs b/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs index 21956a7..5ff387f 100644 --- a/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs +++ b/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs @@ -6,9 +6,36 @@ namespace WiiCompiled.Setup.Windows; internal static class SelfTests { + private static void TestVrOwnership() + { + var root = Path.Combine(Path.GetTempPath(), "WiiCompiled-VR-ownership-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(root); + try + { + ProductOwnership.Ensure(root, allowEmpty: true); + var statePath = Path.Combine(root, InstalledLayout.InstallStateFileName); + File.WriteAllText(statePath, "{\"SetupVersion\":\"0.2.32\"}"); + var original = File.ReadAllText(statePath); + try { ProductOwnership.Ensure(root, allowEmpty: true); throw new Exception("Accepted an unidentified install."); } + catch (InvalidDataException) { } + if (File.ReadAllText(statePath) != original) throw new Exception("Normal state was changed by rejection."); + File.WriteAllText(statePath, "{\"ProductId\":\"wiicompiled\"}"); + try { ProductOwnership.Ensure(root); throw new Exception("Accepted a normal install."); } + catch (InvalidDataException) { } + JsonState.Write(statePath, new InstallState { ProductId = ProductInfo.Id, InstallDir = root }); + ProductOwnership.Ensure(root); + if (RuntimeConfiguration.ApplicationDataConfigPath.Contains("WiiCompiled\\Config.toml", StringComparison.Ordinal)) + throw new Exception("The VR setup still uses normal application data."); + if (!ProductInfo.UninstallKey.EndsWith("WiiCompiledOpenXRVR", StringComparison.Ordinal)) + throw new Exception("The VR uninstall identity is not isolated."); + } + finally { Directory.Delete(root, recursive: true); } + } + public static int Run() { var failures = new List(); + Test("VR installation ownership", TestVrOwnership, failures); Test("ISO extension", () => TestAcceptedExtension(".iso"), failures); Test("GCM extension", () => TestAcceptedExtension(".gcm"), failures); Test("GCZ extension", () => TestAcceptedExtension(".gcz"), failures); @@ -1291,7 +1318,7 @@ internal static class SelfTests RetroWfcPayloadMode = "skipped" }); JsonState.Write(installation.InstallStatePath, new InstallState { - InstallDir = root, RetroRewindInstalled = true, + ProductId = ProductInfo.Id, InstallDir = root, RetroRewindInstalled = true, DolSha256 = dolSha, RelSha256 = relSha, RetroRewindCodePulSha256 = inputsA.CodePulSha256, RetroRewindCompileInputsSha256 = inputsA.CompileInputsSha256, diff --git a/Launcher/WiiCompiled.Setup.Windows/ShellIntegration.cs b/Launcher/WiiCompiled.Setup.Windows/ShellIntegration.cs index a17e1f4..2a2d940 100644 --- a/Launcher/WiiCompiled.Setup.Windows/ShellIntegration.cs +++ b/Launcher/WiiCompiled.Setup.Windows/ShellIntegration.cs @@ -4,7 +4,7 @@ namespace WiiCompiled.Setup.Windows; internal static class ShellIntegration { - private const string ShortcutFileName = "wiicompiled (base) (beta).lnk"; + private const string ShortcutFileName = "WiiCompiled OpenXR VR (base) (beta).lnk"; public static void RegisterUninstaller(string installDirectory, bool retroInstalled) { diff --git a/Launcher/WiiCompiled.Setup.Windows/UninstallService.cs b/Launcher/WiiCompiled.Setup.Windows/UninstallService.cs index 490f174..de1c294 100644 --- a/Launcher/WiiCompiled.Setup.Windows/UninstallService.cs +++ b/Launcher/WiiCompiled.Setup.Windows/UninstallService.cs @@ -28,6 +28,9 @@ internal static class UninstallService public static int RunWorker(string installDirectory, bool quiet) { installDirectory = Path.GetFullPath(installDirectory); + ProductOwnership.Ensure(installDirectory); + using var operation = InstallOperationLock.Acquire(installDirectory); + RunningProductGuard.EnsureProductsNotRunning(installDirectory); Thread.Sleep(750); TryCleanup("remove shortcuts", quiet, ShellIntegration.RemoveShortcuts); diff --git a/Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj b/Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj index 0efaa73..387ee0e 100644 --- a/Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj +++ b/Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj @@ -7,9 +7,8 @@ WiiCompiled.Setup WiiCompiled.Setup.Windows app.manifest - 0.2.31 patchzy - WiiCompiled + WiiCompiled OpenXR VR Command-line installer and launcher for WiiCompiled embedded en diff --git a/Launcher/dist-vr/SHA256SUMS.txt b/Launcher/dist-vr/SHA256SUMS.txt new file mode 100644 index 0000000..55f23e2 --- /dev/null +++ b/Launcher/dist-vr/SHA256SUMS.txt @@ -0,0 +1,2 @@ +64fdd492f6ce71fd223b49f493e0809d9a7f9ed45ebaa3605f626eb77282dbe4 WiiCompiled-Setup.exe +b319b86d46bea6ebfa52965c65706976d6004abc01914ff82a2b73c71de84def WheelWizardVRWindows.exe diff --git a/OPENXR.md b/OPENXR.md index 920f0a7..dc2210a 100644 --- a/OPENXR.md +++ b/OPENXR.md @@ -15,8 +15,14 @@ This is an experimental renderer, not yet a release-ready VR mode. - A build made with `MKW_ENABLE_OPENXR=ON`, which defaults on for Windows and off elsewhere while the Vulkan bridge remains capability-gated. -OpenXR remains disabled until requested in `Config.toml`. The file is next to the installed game -configuration and is created with the following defaults: +For managed installation, use [WheelWizard VR](https://github.com/iChris4/WheelWizard_VR/releases/latest) +and enable **Settings → Other → WiiCompiled (beta) → Enable WiiCompiled OpenXR VR (beta)**. +The launcher sets `vr.enabled=true`, `vr.required=false`, and `video.graphics_api="d3d12"` before +each VR launch, preserving other preferences. Its portable configuration lives at +`RecompVR/UserData/Config.toml` beneath WheelWizard's data folder. Normal graphics settings remain +in `Recomp/UserData/Config.toml`. Both backends use the normal installation's effective NAND. + +Standalone launches remain opt-in. `Config.toml` is created with the following defaults: ```toml [vr] @@ -46,7 +52,8 @@ at launch. The in-game F10 settings bar also exposes the enable switch, but a re required. `required = false` is the safe default: an absent runtime, disconnected headset, unsupported GPU, -or graphics-binding failure is logged and the game continues in ordinary desktop mode. Set it to +or graphics-binding failure is logged and the game continues in ordinary desktop mode. A temporary +notification explains the failure; the message remains available under **F10 → VR**. Set it to `true` only when a failed VR startup should stop the game with an error. `mirror_view` chooses what the desktop window shows while the headset is running: `"normal"` diff --git a/README.md b/README.md index 0d871a5..aa6063c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# WiiCompiled +# WiiCompiled OpenXR VR A native PC port of Mario Kart Wii, made with static recompilation. @@ -12,7 +12,7 @@ anywhere at runtime. > toolchain, the translation runs on your machine against your disc image, and nothing ever gets > uploaded. -[What is a github, I just want to play](https://github.com/TeamWheelWizard/WheelWizard/releases/latest) +[Download WheelWizard VR](https://github.com/iChris4/WheelWizard_VR/releases/latest) --- @@ -96,8 +96,7 @@ Known limitations of the Wii Remote path: - GPU: GTX 1650 / RX 6400 / Arc A310 or higher - CPU: Intel Core i5-8400 / AMD Ryzen 5 2600 (4c/6c, ~3.5GHz+) or higher - About 20 GB of free disk space during installation (Final game size ~5 GB) -- macOS 14 (Sonoma) or later on Apple Silicon -- On macOS, Apple Xcode Command Line Tools (Setup opens Apple's installer when they are missing) +- This fork's packaged release supports Windows x64. Other platforms are not release targets. - A clean, unmodified **PAL `RMCP01`** disc image of Mario Kart Wii, dumped by you. ISO, GCM, GCZ, CISO, WBFS, WIA and RVZ are accepted. @@ -113,15 +112,26 @@ regions, patched executables) is rejected outright. ## Installing -For an easy experience, use [Wheel Wizard](https://github.com/TeamWheelWizard/WheelWizard). Pick your clean PAL `RMCP01` -image under Settings, turn on **WiiCompiled (beta)**, and hit install from the Home page. -Wheel Wizard downloads the setup tool from this repo and walks you through install, updates and -launching. The backend itself is deliberately command-line only, Wheel Wizard is a wrapper around it. +Use [WheelWizard VR](https://github.com/iChris4/WheelWizard_VR/releases/latest). Select your clean PAL +`RMCP01` image in Settings, then open **Settings → Other → WiiCompiled (beta)** and enable +**Enable WiiCompiled OpenXR VR (beta)**. Press Install on Home. Installation builds both Base game +and Retro Rewind locally using the bundled toolchain; a developer toolchain is not required. + +Home lets you choose **Base game** or **Retro Rewind**. The normal WiiCompiled switch selects the +original backend; turning both switches off selects Dolphin. Only one recompilation switch can +be enabled at a time. VR uses a separate `RecompVR` installation beside the normal `Recomp` folder. +Saves and Miis use the normal installation's effective NAND; Retro Rewind retains its separate +XML-directed saves and ghosts. Graphics, VR preferences, caches, and compiled binaries stay separate. +Uninstalling either backend in WheelWizard VR preserves configuration and shared progress. + +Managed VR launches enable OpenXR with D3D12. If the runtime or headset is unavailable, the game +continues on the desktop and displays the failure briefly; **F10 → VR** retains the explanation. +See [OpenXR configuration](OPENXR.md) and [distribution and validation](DISTRIBUTION.md). > [!CAUTION] > Only take builds from this repository's -> [Releases](https://github.com/patchzyy/Wiicompiled/releases) page. If someone's sharing an +> [Releases](https://github.com/iChris4/Wiicompiled_VR/releases) page. If someone's sharing an > installer through Discord or some random download site, don't touch it!! ## A note on related projects diff --git a/RELEASE_VALIDATION.md b/RELEASE_VALIDATION.md new file mode 100644 index 0000000..db0fab6 --- /dev/null +++ b/RELEASE_VALIDATION.md @@ -0,0 +1,85 @@ +# WheelWizard VR integration validation + +Validated locally on Windows x64 on 2026-09-09. These are release candidates; public release +acceptance is not complete. + +## Candidates + +| Product | Version | Local artifact | SHA-256 | +| --- | --- | --- | --- | +| WiiCompiled OpenXR VR setup | 0.2.32 | `Launcher/dist-vr/WiiCompiled-Setup.exe` | `64fdd492f6ce71fd223b49f493e0809d9a7f9ed45ebaa3605f626eb77282dbe4` | +| WheelWizard VR | 2.5.5 | `Launcher/dist-vr/WheelWizardVRWindows.exe` | `b319b86d46bea6ebfa52965c65706976d6004abc01914ff82a2b73c71de84def` | + +The launcher artifact is also in the launcher checkout at `artifacts/WheelWizardVRWindows.exe`. +`Launcher/dist-vr/SHA256SUMS.txt` contains both checksums. Only these executables and their checksums +are intended for distribution. The setup contains the toolkit and source payload, not a compiled +game. All extracted assets, normal installation copies, compiled games, and test NANDs remain in +ignored local validation directories. + +The existing local setup in `Launcher/dist` is the user's 0.2.31 rebuild. Its SHA-256 matches the +setup asset published under the VR fork's `0.2` tag. The backend fork also has tag `0.1`; the +launcher fork has no published release. These were checked through the GitHub API before finalizing +the candidates. The next source patch versions are 0.2.32 and 2.5.5. New release tags use `vX.Y.Z` +and must agree with the single version source in each product's `Directory.Build.props`. + +## Passed + +- WheelWizard suite: **255 tests**, including backend selection/migration, configuration isolation, + all four backend/game launch command paths, same-version wrong-product rejection, separate + release routing, exact self-update asset selection, shared NAND configuration, Mii edit blocking, + and uninstall preservation of both backends' configuration, NAND, and ghosts. +- Retro Rewind transaction tests: staged replacement, cancellation, preservation of saves/ghosts + and XML save directories, current distribution ZIP layout, archive path validation, interrupted + publication recovery, and a real Windows locked-file failure after one directory was published. +- Translator suite: **576 tests**. +- Packaged setup self-tests, including product ownership, configuration, portable move healing, + install transaction rollback, interrupted-install recovery, Retro-WFC snapshot/retry behavior, + compile-input fingerprints, and product report contracts. +- Full synthetic PowerPC translation and native OpenXR runtime link. The synthetic fixture now + includes the three callbacks used by the VR camera; no game translation is used for this test. +- Pinned-fact audit and setup payload boundary audit. Native dependency audit inspected **524 + binaries and 5,277 imports**. The pinned OpenXR SDK and static loader were built; licenses remain + included. A configured bundled SDK takes precedence over a developer machine's OpenXR package. +- Executable identity/version and embedded setup payload identity/version agree. Both Windows + release workflows use existing GitHub Actions versions and have no Linux/macOS release jobs. +- Final packaged setup installed both VR products from the owned clean PAL image into an isolated + portable installation. Updating that installation with the final package succeeded, and both + products reported `current` afterward. +- Real product checks after modifying isolated Retro Rewind content: an asset-only change kept + both products current; a Code.pul change reported `code-pul-changed` for Retro Rewind and left + Base current; restoring the content returned both products to current. +- Normal Base, normal Retro Rewind, VR Base, and VR Retro Rewind each stayed running during a + 20-second startup observation through their installed setup host and closed with setup exit 0. + Normal tests used an isolated copy of the existing normal installation. Test configurations + pointed to the same isolated NAND and isolated Retro Rewind content. +- Both VR runs used a child-process-only missing OpenXR runtime override. Logs confirmed + `vr.enabled=true`, `vr.required=false`, D3D12, and desktop fallback. System OpenXR registration + was not changed. +- The final single-file launcher started using an isolated portable configuration, displayed + `WheelWizard VR` as its window title, and closed with exit 0. Its startup log contained no + application-start or unhandled-exception errors. Publishing succeeded with existing Windows + registry platform-analyzer warnings. +- Source synchronization checked destination hashes before replacing files and retained backups. + Both repositories pass `git diff --check`. Source changes are local and have not been committed + or pushed as part of this validation. + +## Still required for release acceptance + +- Interactive Home/settings verification and complete install/launch flows for all four selections. + Command-level tests and isolated startup observations do not establish menu or race correctness. +- Headset menus and races for Base and Retro Rewind, physical headset absence, runtime loss during + a session, and visual confirmation of the nonblocking fallback notification. +- Real player progress/Miis/ghosts exercised across gameplay, updates, reinstall, and uninstall. + Automated preservation tests passed; live player data was deliberately excluded from smoke tests. +- Fresh installation on a clean Windows machine without developer toolchains. The tested package + used its bundled tools on this workstation; a pristine-machine run has not been performed. +- Live network-interruption/download tests and interactive overlapping-launcher scenarios. + File leases, content cancellation, interrupted publication, and setup rollback have automated + coverage, but the complete manual matrix remains outstanding. + +After acceptance, publish the VR backend first, then the launcher. The launcher release workflow +downloads the published backend and checks its VR identity, capability, and tag/version agreement +before publishing. Until that backend release exists, a fresh launcher install cannot resolve this +local 0.2.32 candidate from GitHub automatically. + +Detailed local logs and isolated installations are under `.scratch/` (excluded from distribution). diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index b44fda5..084a061 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -254,7 +254,10 @@ if(MKW_ENABLE_OPENXR) message(WARNING "OpenXR is not wired for this platform; disabling MKW_ENABLE_OPENXR") set(MKW_ENABLE_OPENXR OFF) else() - find_package(OpenXR CONFIG QUIET) + # An installer-supplied SDK must win over any developer machine package. + if(NOT MKW_OPENXR_SDK_DIR AND NOT FETCHCONTENT_SOURCE_DIR_OPENXR) + find_package(OpenXR CONFIG QUIET) + endif() if(NOT TARGET OpenXR::openxr_loader AND NOT TARGET openxr_loader) include(FetchContent) set(BUILD_API_LAYERS OFF CACHE BOOL "" FORCE) diff --git a/runtime/include/runtime_config.h b/runtime/include/runtime_config.h index 25e8183..8bb4c2e 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -130,7 +130,11 @@ inline std::filesystem::path PathFromUtf8(std::string_view text) { } inline constexpr const char* kConfigFileName = "Config.toml"; +#ifdef _WIN32 +inline constexpr const char* kApplicationDirectoryName = "WiiCompiledOpenXRVR"; +#else inline constexpr const char* kApplicationDirectoryName = "WiiCompiled"; +#endif // Portable layout. A directory holding kPortableMarkerFileName is a portable root; every piece of // runtime user state (Config.toml, NAND, Cache, Logs) lives in /UserData instead of diff --git a/runtime/src/settings_overlay.cpp b/runtime/src/settings_overlay.cpp index 8a0ae42..ae60188 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -907,6 +907,11 @@ void DrawGraphicsSettings() { // group, and keeping them out of Graphics stops that menu from running off the // bottom of the screen. void DrawVrSettings() { + const auto xrError = mkw::vr::OpenXRLastError(); + if (!xrError.empty()) { + ImGui::TextWrapped("OpenXR unavailable: %s", xrError.c_str()); + ImGui::TextWrapped("Playing on the desktop. Check your headset and active OpenXR runtime, then restart."); + } if (ImGui::Checkbox("Enable OpenXR VR", &g_vrEnabled)) { RuntimeConfigFile::SetVrEnabled(g_vrEnabled); } @@ -1420,6 +1425,29 @@ void Draw() noexcept { // Wait for the frame worker's DONE phase: it has replayed the previous frame's ImGui draw lists // and started the next ImGui frame, so all overlay callers can now safely issue ImGui commands. aurora_wait_for_frame_worker(); + // Explain fallback without interrupting gameplay or capturing input. + static std::string shownXrError; + static double xrNoticeUntil = 0.0; + const auto xrError = mkw::vr::OpenXRLastError(); + if (!xrError.empty() && xrError != shownXrError) { + shownXrError = xrError; + xrNoticeUntil = ImGui::GetTime() + 15.0; + } + if (!shownXrError.empty() && ImGui::GetTime() < xrNoticeUntil) { + ImGui::SetNextWindowPos(ImVec2(16.0f, 60.0f), ImGuiCond_Always); + ImGui::SetNextWindowBgAlpha(0.9f); + if (ImGui::Begin("OpenXR desktop fallback", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoFocusOnAppearing)) { + ImGui::PushTextWrapPos(440.0f); + ImGui::TextUnformatted("OpenXR unavailable - playing on the desktop."); + ImGui::TextUnformatted(shownXrError.c_str()); + ImGui::TextUnformatted("Check your headset and active OpenXR runtime, then restart. Details: F10 > VR."); + ImGui::PopTextWrapPos(); + } + ImGui::End(); + } // Also drive the Wii Remote rescan from here: PADRead runs it too, but this // runs once per presented frame whatever the game is doing (e.g. sitting in // its "communications interrupted" prompt without polling pads). Same guest