Files
mitch030504--Wiicompiled_VR…/android/Build-Quest.ps1
T
Claude 150062280d Add a Steam Frame flavour of the Android app with Frame controller bindings
The Steam Frame runs Android apps through Lepton with SteamVR's OpenXR
runtime. A third headset flavour, steamFrame, targets it:

- -mcpu=cortex-x4+nosve for the Snapdragon 8 Gen 3 (its firmware does
  not expose SVE, which clang otherwise auto-vectorises with), from one
  flavour-to-CPU map that the kit export now reads instead of guessing
  from the variant name.
- MKW_ANDROID_HEADSET=steam_frame defines MKW_HEADSET_STEAM_FRAME for
  the runtime's own targets, for Frame-specific defaults.
- A manifest without the Horizon OS entries, and FrameEntryActivity as
  the single real MAIN/LAUNCHER activity with the Khronos and Oculus VR
  categories, which Lepton needs to start an app in VR. It opens the
  setup panel and, when the selected game can start, the game on top.
- XR_VALVE_frame_controller_interaction: the Frame controller profile
  with its left D-pad (new dpad_* actions, the Wii Remote's D-pad or the
  gamepad's), View as menu and the left shoulder as the panel button.
  Also requested on Windows for SteamVR streaming to a Frame.
- Build-Quest.ps1, Build-QuestGame.ps1 and Run-Quest.ps1 take
  -Headset frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
2026-10-04 08:27:12 +00:00

144 lines
7.3 KiB
PowerShell

# Builds the standalone Meta Quest APK on a Windows host.
#
# powershell -ExecutionPolicy Bypass -File android/Build-Quest.ps1 [-Generated <dir>]
# [-Headset modern|quest1|frame] [-Configuration debug|release] [-Install]
#
# -Headset frame builds the Steam Frame flavour (steamFrame), which runs under Lepton, SteamOS's
# Android layer, with SteamVR's OpenXR runtime (docs/quest-port.md, "Steam Frame").
#
# One app offers both games: the APK carries a kit for the base game, and for Retro Rewind too
# when -Generated holds a translation that includes the mod.
#
# -Generated names the translator output for the disc you own: the directory
# holding data_sections_init.cpp, RuntimeConfig.h and build_shards/shards.cmake.
# It defaults to the installer's build workspace next to this checkout. The
# Windows-generated tree is used as-is; runtime/cmake/PublicProducts.cmake
# rewrites its blob assembly for ELF at configure time.
#
# Prerequisites (see docs/quest-port.md): JDK 17, the Android SDK with NDK
# 29.0.14206865 and CMake 3.22.1, and android/Prepare-QuestDependencies.ps1
# having staged the SDL3 AAR.
#
# Dawn is built from source with Aurora's patches (android/Build-QuestDawn.ps1:
# fragment density maps for foveated rendering) and cached under
# .scratch/quest-dawn; the first build takes a while. -StockDawn links the
# stock prebuilt package instead, which leaves foveated rendering unavailable.
[CmdletBinding()]
param(
[string]$Generated = '',
[string]$Dependencies = '',
[string]$CMakeDir = '',
[ValidateSet('modern', 'quest1', 'frame')] [string]$Headset = 'modern',
[ValidateSet('debug', 'release')] [string]$Configuration = 'debug',
[switch]$Install,
[switch]$StockDawn
)
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$repo = (Resolve-Path (Join-Path $root '..')).Path
if (-not $Generated) {
$Generated = Join-Path $repo '.scratch\vr-build-workspace\BuildWorkspace\generated'
}
if (-not (Test-Path (Join-Path $Generated 'build_shards\shards.cmake'))) {
throw "No translated graph at $Generated (expected build_shards\shards.cmake). Run the translator first; see docs/quest-port.md."
}
if (-not (Test-Path (Join-Path $root 'app\libs\SDL3-3.4.4.aar'))) {
throw 'SDL3 AAR missing; run android/Prepare-QuestDependencies.ps1 first.'
}
$javaHome = if ($env:JAVA_HOME) { $env:JAVA_HOME } else { 'C:\Program Files\Java\jdk-17' }
$sdkRoot = if ($env:ANDROID_HOME) { $env:ANDROID_HOME } elseif ($env:ANDROID_SDK_ROOT) { $env:ANDROID_SDK_ROOT } else { Join-Path $env:LOCALAPPDATA 'Android\Sdk' }
if (-not (Test-Path (Join-Path $javaHome 'bin\java.exe'))) { throw "JDK 17 not found at $javaHome (set JAVA_HOME)" }
if (-not (Test-Path $sdkRoot)) { throw "Android SDK not found at $sdkRoot (set ANDROID_HOME)" }
$env:JAVA_HOME = $javaHome
$env:ANDROID_HOME = $sdkRoot
$env:ANDROID_SDK_ROOT = $sdkRoot
# aurora-main requires CMake 3.25+, newer than the SDK's bundled 3.22.1. Point
# the Android Gradle plugin at a system CMake through local.properties and put
# the SDK's Ninja on PATH, which a non-bundled CMake needs.
if (-not $CMakeDir) {
$cmakeExe = Get-Command cmake.exe -ErrorAction SilentlyContinue
if ($cmakeExe) { $CMakeDir = Split-Path -Parent (Split-Path -Parent $cmakeExe.Source) }
}
if (-not $CMakeDir -or -not (Test-Path (Join-Path $CMakeDir 'bin\cmake.exe'))) {
throw 'No CMake 3.25+ found; install one or pass -CMakeDir <install root>'
}
$sdkCMake = Get-ChildItem -Path (Join-Path $sdkRoot 'cmake') -Directory | Sort-Object Name -Descending | Select-Object -First 1
if ($sdkCMake) { $env:PATH = (Join-Path $sdkCMake.FullName 'bin') + ';' + $env:PATH }
# Parenthesised: PowerShell's comma binds tighter than +, which would join both
# properties into a single line.
$localProperties = @(
('sdk.dir=' + $sdkRoot.Replace('\', '\\')),
('cmake.dir=' + $CMakeDir.Replace('\', '\\'))
)
Set-Content -Path (Join-Path $root 'local.properties') -Value $localProperties -Encoding ascii
if (-not $Dependencies) {
$candidate = Join-Path $repo '.scratch\vr-build-workspace\BuildWorkspace\Dependencies'
if (Test-Path $candidate) { $Dependencies = $candidate }
}
$variant = (Get-Culture).TextInfo.ToTitleCase($Configuration)
# The Gradle flavour and the -mcpu target its kit must record (headsetCpus in app/build.gradle.kts).
$flavourDir, $expectedCpu = switch ($Headset) {
'quest1' { 'quest1', 'kryo' }
'frame' { 'steamFrame', 'cortex-x4+nosve' }
default { 'modernQuest', 'cortex-a77' }
}
$flavour = $flavourDir.Substring(0, 1).ToUpperInvariant() + $flavourDir.Substring(1)
$task = "app:assemble$flavour$variant"
$gradleArgs = @(
'--project-dir', $root,
"-PmkwGeneratedDir=$Generated"
)
if ($Dependencies) { $gradleArgs += "-PmkwDependenciesDir=$Dependencies" }
if (-not $StockDawn) {
$dawnWork = Join-Path $repo '.scratch\quest-dawn'
& (Join-Path $root 'Build-QuestDawn.ps1') -WorkDirectory $dawnWork
$gradleArgs += "-PmkwQuestDawnDir=$(Join-Path $dawnWork 'package')"
}
$gradleArgs += $task
Write-Host "gradlew $($gradleArgs -join ' ')"
& (Join-Path $root 'gradlew.bat') @gradleArgs
if ($LASTEXITCODE -ne 0) { throw "Gradle failed ($LASTEXITCODE)" }
$apkDir = Join-Path $root "app\build\outputs\apk\$flavourDir\$Configuration"
$apk = Get-ChildItem -Path $apkDir -Filter '*.apk' | Select-Object -First 1
if (-not $apk) { throw "No APK under $apkDir" }
Write-Host "APK: $($apk.FullName)"
# The APK must be distributable: no translated game inside, only the game kit and the toolchain
# that builds one on the headset.
Add-Type -AssemblyName System.IO.Compression.FileSystem
$archive = [IO.Compression.ZipFile]::OpenRead($apk.FullName)
try {
$gameLibraries = @($archive.Entries | Where-Object { $_.FullName -match '^lib/[^/]+/libmain[^/]*\.so$' })
$kitEntry = @($archive.Entries | Where-Object { $_.FullName -eq 'assets/game_kit/kit.json' }) | Select-Object -First 1
$kitCpu = if ($kitEntry) {
$reader = New-Object IO.StreamReader($kitEntry.Open())
try { ($reader.ReadToEnd() | ConvertFrom-Json).androidCpu } finally { $reader.Dispose() }
} else { '' }
$hasToolchain = @($archive.Entries | Where-Object { $_.FullName -eq 'assets/quest_toolchain/files.zip' }).Count -gt 0
} finally { $archive.Dispose() }
if ($gameLibraries.Count -gt 0) { throw "The APK contains a translated game library: $($gameLibraries.FullName -join ', ')" }
if (-not $kitEntry) { throw 'The APK has no game kit (assets/game_kit/kit.json)' }
if ($kitCpu -ne $expectedCpu) { throw "The $Headset APK contains a game kit for CPU '$kitCpu', expected '$expectedCpu'" }
if (-not $hasToolchain) { throw 'The APK has no build toolchain (assets/quest_toolchain/files.zip)' }
if ($Install) {
$adb = Join-Path $sdkRoot 'platform-tools\adb.exe'
& $adb install -r $apk.FullName
if ($LASTEXITCODE -ne 0) { throw "adb install failed ($LASTEXITCODE)" }
if ($Headset -eq 'quest1') {
Write-Host 'Installed. Use WiiCompiled Settings for setup, then launch WiiCompiled VR directly from the library.'
} elseif ($Headset -eq 'frame') {
Write-Host 'Installed. Build a game on this PC with android/Build-QuestGame.ps1 -Headset frame -Install, then start WiiCompiled VR from the Steam library.'
} else {
Write-Host 'Installed. Build a game with Build on this Quest in the launcher, or on this PC with android/Build-QuestGame.ps1 -Install.'
}
}