# Builds the player's Quest game on a PC: compiles the translator's output for their own disc # against the Quest game kit (see QuestGameKit.psm1) and packages libmain.so as a .wcgame the # headset imports ("Import from computer" in the launcher). # # Two callers. A developer runs it from the checkout, where the toolchain defaults to the Android # SDK's NDK and ninja: # # powershell -ExecutionPolicy Bypass -File android/Build-QuestGame.ps1 [-Generated ] [-Kit ] # [-Headset modern|quest1|frame] [-Configuration debug|release] # [-Data ] [-Output ] [-Install] # # WiiCompiled Setup's --build-quest runs the copy staged in an installation's BuildWorkspace\android, # naming everything explicitly: the kit it extracted from the Quest app, the NDK toolchain it # downloaded, the toolkit's ninja and the workspace's recomp.yml. # # -Generated is the translator output (the dev workspace's generated/ by default). -Kit is the game # kit the Quest app carries; it defaults to the one the last APK build exported. -Product picks the # game: base, or retro_rewind (whose translation must include the mod). -Data adds the extracted # disc (the directory holding sys/ and files/) and -Mod the RetroRewind6 pack, so the headset needs # nothing else. # -Install pushes the package into the app's Import folder over adb, where the launcher picks it # up the next time it opens. [CmdletBinding()] param( [string]$Generated = '', [string]$Kit = '', [string]$Output = '', [string]$Data = '', [string]$BuildDir = '', [string]$Manifest = '', [string]$ClangCxx = '', [string]$ClangC = '', [string]$Sysroot = '', [string]$Ninja = '', [string]$BuiltBy = 'android/Build-QuestGame.ps1', [ValidateSet('modern', 'quest1', 'frame')] [string]$Headset = 'modern', [ValidateSet('debug', 'release')] [string]$Configuration = 'debug', [ValidateSet('base', 'retro_rewind')] [string]$Product = 'base', [string]$Mod = '', [int]$TranslatedJobs = 0, [switch]$Install ) $ErrorActionPreference = 'Stop' Import-Module (Join-Path $PSScriptRoot 'QuestGameKit.psm1') -Force $root = $PSScriptRoot $workspace = (Resolve-Path (Join-Path $root '..')).Path if (-not $Generated) { $Generated = Join-Path $workspace '.scratch\vr-build-workspace\BuildWorkspace\generated' } if (-not $Manifest) { $Manifest = Join-Path $workspace 'projects\mkwii\recomp.yml' } $variant = switch ($Headset) { 'quest1' { 'quest1' } 'frame' { 'steamFrame' } default { 'modernQuest' } } $variant += (Get-Culture).TextInfo.ToTitleCase($Configuration) if (-not $BuildDir) { $BuildDir = Join-Path $root "app\build\questGame\$variant\$Product" } if (-not $Kit) { # Select the requested variant explicitly. Modification time is unsafe now that flavours use # different -mcpu targets and an up-to-date native probe is not necessarily the newest one. $Kit = Join-Path $root "app\build\generated\assets\questGameKit\$variant\game_kit" } if (-not $Kit -or -not (Test-Path (Join-Path $Kit 'kit.json'))) { throw 'No game kit; run Build-Quest.ps1 first, or pass -Kit' } if (-not (Test-Path (Join-Path $Generated 'build_shards\shards.cmake'))) { throw "No translated graph at $Generated" } $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 $ClangCxx -or -not $ClangC -or -not $Sysroot) { $llvm = Join-Path $sdkRoot 'ndk\29.0.14206865\toolchains\llvm\prebuilt\windows-x86_64' if (-not $ClangCxx) { $ClangCxx = Join-Path $llvm 'bin\clang++.exe' } if (-not $ClangC) { $ClangC = Join-Path $llvm 'bin\clang.exe' } if (-not $Sysroot) { $Sysroot = Join-Path $llvm 'sysroot' } } foreach ($tool in $ClangCxx, $ClangC) { if (-not (Test-Path $tool)) { throw "Compiler not found: $tool" } } if (-not $Ninja) { $Ninja = Get-ChildItem -Path (Join-Path $sdkRoot 'cmake') -Filter ninja.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName if (-not $Ninja) { $Ninja = (Get-Command ninja.exe -ErrorAction SilentlyContinue).Source } } if (-not $Ninja -or -not (Test-Path $Ninja)) { throw 'ninja.exe not found' } # Translated shards need roughly 1.5 GB each at -O2; leave a margin for the rest of the machine. if ($TranslatedJobs -le 0) { $memoryGb = [math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB) $TranslatedJobs = [math]::Max(1, [math]::Min([Environment]::ProcessorCount, [math]::Floor(($memoryGb - 4) / 1.5))) } # The disc this translation was made from: recomp.yml's pins, which the translator enforced. $pins = [IO.File]::ReadAllText($Manifest) $gameId = [regex]::Match($pins, '\n\s*game_id:\s*(\w+)').Groups[1].Value $dolSha = [regex]::Match($pins, '(?s)\n\s*dol:.*?sha256:\s*([0-9a-fA-F]{64})').Groups[1].Value $relSha = [regex]::Match($pins, '(?s)\n\s*rel:.*?sha256:\s*([0-9a-fA-F]{64})').Groups[1].Value if (-not $gameId -or -not $dolSha -or -not $relSha) { throw "Cannot read the disc pins from $Manifest" } $stopwatch = [Diagnostics.Stopwatch]::StartNew() $library = Invoke-QuestGameBuild -KitDir $Kit -GeneratedDir $Generated -BuildDir $BuildDir ` -ClangCxx $ClangCxx -ClangC $ClangC -Sysroot $Sysroot -Ninja $Ninja -TranslatedJobs $TranslatedJobs -Product $Product Write-Host ("Built {0} in {1:N1} min" -f $library, $stopwatch.Elapsed.TotalMinutes) if (-not $Output) { $Output = Join-Path $BuildDir 'MarioKartWii.wcgame' } Write-Host 'MKWCBUILD:STEP:quest-package Packaging the game for Quest' $game = New-QuestGamePackage -Library $library -DataDir $Data -ModDir $Mod -KitDir $Kit -GameId $gameId -DolSha256 $dolSha -RelSha256 $relSha ` -BuiltBy $BuiltBy -OutputPath $Output -Product $Product Write-Host "Game package: $Output (kit $($game.kitFingerprint))" if ($Install) { $adb = Join-Path $sdkRoot 'platform-tools\adb.exe' $import = '/sdcard/Android/data/org.wiicompiled.quest/files/WiiCompiledOpenXRVR/Import' # The launcher creates Import itself and must own it to remove imported packages (see # Run-Quest.ps1 on adb-created directories), so only push into one that exists. $exists = ((& $adb shell "test -d '$import' && echo yes") | Out-String).Trim() if ($exists -ne 'yes') { throw 'The launcher has not created its Import folder yet; open the app once on the headset first.' } $remote = "$import/$([IO.Path]::GetFileName($Output))" & $adb push $Output $remote if ($LASTEXITCODE -ne 0) { throw "adb push failed ($LASTEXITCODE)" } # The folder belongs to the app; only the pushed file is the shell user's to open up. & $adb shell "chmod a+r '$remote'" Write-Host 'Pushed; the launcher imports it the next time it opens.' }