feat(gameboot-windows): implement automatic game launch with consume-once token mechanism

Replace hardcoded Valorant launch with flexible game resolution system:

- Add Get-RequestedGame() to read and consume launch.json tokens from all ESP partitions (dual-disk support)
- Add Resolve-RiotClient() to locate Riot Client via RiotClientInstalls.json canonical source instead of hardcoded paths
- Add Start-Game() to support multiple game types (riot, uri, exe) via games.json configuration
- Implement token consume-once pattern to prevent replay of stale launch requests
- Remove scheduled task management (FWS-Return-OnStart) - now handled purely via BootNext assertions
- Add re-assertion of BootNext=Windows after 25s delay to win race against ONSTART task
- Validate requested game against games.json with fallback to valorant
- Improve robustness: handle dual disks, missing tokens, unreadable tokens, and missing processes gracefully
- Update documentation to reflect new auto-launch architecture and admin requirement
This commit is contained in:
2026-07-08 03:21:21 +02:00
parent 057898524a
commit e6c0224db9
@@ -1,57 +1,136 @@
<# <#
fws-play.ps1 — session de jeu Windows orchestree pour le retour vers FWS. fws-play.ps1 — session de jeu Windows orchestree, lancement AUTO du jeu choisi.
Flux robuste : Lit (et CONSOMME) le jeton depose par FWS sur l'ESP
- on desactive la tache ONSTART (evite qu'un declenchement tardif ne rearme (\EFI\FWS\launch.json = {"game":"<id>"}), cherche <id> dans games.json, lance
FWS EN PLEIN MATCH) ; le jeu, attend sa fermeture/crash, puis rebascule vers FWS.
- on arme le boot suivant vers WINDOWS (un crash/reboot pendant le jeu revient
sur Windows, pas ejecte vers FWS au milieu d'un match) ;
- on lance Valorant et on attend sa fermeture OU son crash ;
- dans un FINALLY (donc quoi qu'il arrive) : on rearme le boot suivant vers
FWS, on reactive la tache ONSTART, et on reboote — SEULEMENT si le jeu a
bien ete vu (pas de reboot force en plein 1er telechargement de patch).
Le filet permanent BootOrder[0]=FWS couvre tout ce que ce script raterait. Robustesse (aucune interaction requise) :
A lancer a l'ouverture de session du compte gaming (autologin) ou a la main. - resolution du jeton sur TOUTES les ESP (dual-disque) + suppression apres
Doit tourner EN ADMIN. #> lecture (consume-once : pas de rejeu d'un ancien choix) ;
- resolution du Riot Client par sa source canonique (pas de chemin en dur) ;
- re-affirmation de BootNext=Windows apres le lancement, pour gagner la course
contre la tache ONSTART (fws-play = dernier ecrivain) — un crash pendant le
jeu revient donc bien sur Windows ;
- FINALLY : rearme FWS et reboote SI le jeu a bien ete vu.
Filet permanent cote FWS : BootOrder[0]=FWS. A lancer a l'ouverture de session
(autologin du compte gaming, qui DOIT etre admin local). Tourne EN ADMIN. #>
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path $here = Split-Path -Parent $MyInvocation.MyCommand.Path
$return = Join-Path $here 'fws-return.ps1' $return = Join-Path $here 'fws-return.ps1'
$taskName = 'FWS-Return-OnStart' $gamesDb = Join-Path $here 'games.json'
$seen = $false $seen = $false
try { $EFI_TYPE = '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' # GUID GPT "EFI System Partition"
# Neutraliser la course avec la tache ONSTART pendant la session.
try { Disable-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch { }
function Get-RequestedGame {
# Sonde toutes les ESP, lit \EFI\FWS\launch.json, le SUPPRIME (consume-once).
# Defaut : valorant. Distingue "jeton absent" de "jeton illisible" (log).
$default = 'valorant'; $result = $default
$mounts = @()
try { $esps = Get-Partition -ErrorAction SilentlyContinue | Where-Object { $_.GptType -eq $EFI_TYPE } }
catch { $esps = @() }
foreach ($p in $esps) {
$used = (Get-PSDrive -PSProvider FileSystem -ErrorAction SilentlyContinue).Name
$letter = (67..90 | ForEach-Object { [char]$_ }) |
Where-Object { $used -notcontains "$_" -and ($mounts.Letter -notcontains "$_`:") } |
Select-Object -First 1
if (-not $letter) { continue }
try {
Add-PartitionAccessPath -DiskNumber $p.DiskNumber -PartitionNumber $p.PartitionNumber -AccessPath "$letter`:" -ErrorAction Stop
$mounts += @{ Letter = "$letter`:"; Disk = $p.DiskNumber; Part = $p.PartitionNumber }
} catch { }
}
foreach ($m in $mounts) {
$tok = "$($m.Letter)\EFI\FWS\launch.json"
if (Test-Path $tok) {
try {
$g = (Get-Content $tok -Raw -ErrorAction Stop | ConvertFrom-Json).game
if ($g) { $result = "$g".ToLower() }
} catch { Write-Warning "Jeton illisible ($tok) : $_ — repli sur $default." }
Remove-Item $tok -Force -ErrorAction SilentlyContinue # consume-once
break
}
}
foreach ($m in $mounts) {
try { Remove-PartitionAccessPath -DiskNumber $m.Disk -PartitionNumber $m.Part -AccessPath $m.Letter -ErrorAction SilentlyContinue } catch { }
}
return $result
}
function Resolve-RiotClient {
# Riot s'installe par defaut a la RACINE (C:\Riot Games\...), pas sous
# Program Files. Source canonique : RiotClientInstalls.json (rc_default).
$cands = @()
$inst = Join-Path $Env:ProgramData 'Riot Games\RiotClientInstalls.json'
if (Test-Path $inst) { try { $cands += (Get-Content $inst -Raw | ConvertFrom-Json).rc_default } catch { } }
$cands += 'C:\Riot Games\Riot Client\RiotClientServices.exe'
$cands += (Join-Path ${Env:ProgramFiles} 'Riot Games\Riot Client\RiotClientServices.exe')
if (${Env:ProgramFiles(x86)}) { $cands += (Join-Path ${Env:ProgramFiles(x86)} 'Riot Games\Riot Client\RiotClientServices.exe') }
return $cands | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
}
function Start-Game($entry) {
switch ($entry.type) {
'riot' {
$riot = Resolve-RiotClient
if ($riot) { Start-Process $riot -ArgumentList "--launch-product=$($entry.product)","--launch-patchline=$($entry.patchline)" }
else { Write-Warning "Riot Client introuvable (ni RiotClientInstalls.json, ni chemins connus)." }
}
'uri' { Start-Process $entry.uri }
'exe' {
if ($entry.args) { Start-Process -FilePath $entry.path -ArgumentList @($entry.args) }
else { Start-Process -FilePath $entry.path }
}
default { Write-Warning "Type de lancement inconnu : $($entry.type)" }
}
}
try {
# Pendant la session, un crash/reboot doit rester sur WINDOWS. # Pendant la session, un crash/reboot doit rester sur WINDOWS.
try { & $return -Target Windows } catch { Write-Warning "Arm Windows a echoue : $_" } try { & $return -Target Windows } catch { Write-Warning "Arm Windows a echoue : $_" }
# Lancer Valorant via le Riot Client (adapter le chemin si necessaire). # Resoudre + VALIDER le jeu (rejeter les cles '_*' et les entrees sans .type).
$riot = Join-Path $Env:ProgramFiles 'Riot Games\Riot Client\RiotClientServices.exe' $game = Get-RequestedGame
if (Test-Path $riot) { $games = Get-Content $gamesDb -Raw | ConvertFrom-Json
Start-Process $riot -ArgumentList '--launch-product=valorant','--launch-patchline=live' $entry = $games.$game
} else { if ($game -like '_*' -or -not $entry -or -not $entry.type) {
Write-Warning "Riot Client introuvable : $riot — lance Valorant manuellement." Write-Warning "Jeu '$game' invalide/absent de games.json — repli sur valorant."
$game = 'valorant'; $entry = $games.valorant
} }
if (-not $entry) { throw "Entree 'valorant' absente de games.json." }
Write-Host "Lancement du jeu : $game"
Start-Game $entry
# Attendre l'apparition du process (SANS plafond serre : un 1er patch Riot / # Course avec la tache ONSTART (qui arme FWS au boot) : on la laisse passer
# une MAJ Vanguard peuvent etre longs), puis attendre sa fermeture/crash. # puis on RE-AFFIRME Windows → fws-play est le dernier ecrivain de BootNext.
$proc = 'VALORANT-Win64-Shipping' Start-Sleep -Seconds 25
try { & $return -Target Windows } catch { Write-Warning "Re-arm Windows a echoue : $_" }
# Attendre l'apparition PUIS la disparition du process (boucle de presence,
# robuste aux relances client). Ne rebooter que si le jeu a ete VU.
$proc = $entry.process
if ($proc) {
for ($i = 0; $i -lt 1440; $i++) { # ~2 h max d'attente d'apparition for ($i = 0; $i -lt 1440; $i++) { # ~2 h max d'attente d'apparition
if (Get-Process $proc -ErrorAction SilentlyContinue) { $seen = $true; break } if (Get-Process $proc -ErrorAction SilentlyContinue) { $seen = $true; break }
Start-Sleep -Seconds 5 Start-Sleep -Seconds 5
} }
if ($seen) { if ($seen) {
Get-Process $proc -ErrorAction SilentlyContinue | ForEach-Object { $_.WaitForExit() } while (Get-Process $proc -ErrorAction SilentlyContinue) { Start-Sleep -Seconds 5 }
} else { } else {
Write-Warning "Jeu jamais detecte — pas de reboot automatique." Write-Warning "Process '$proc' jamais detecte — pas de reboot automatique."
}
} else {
Write-Warning "Aucun 'process' defini pour '$game' — pas d'attente ni de reboot auto."
} }
} }
finally { finally {
# Rearmer FWS et reactiver la tache, QUOI QU'IL ARRIVE. # Rearmer FWS, QUOI QU'IL ARRIVE.
try { & $return -Target FWS } catch { Write-Warning "Arm FWS a echoue : $_ — BootOrder[0]=FWS reste le filet." } try { & $return -Target FWS } catch { Write-Warning "Arm FWS a echoue : $_ — BootOrder[0]=FWS reste le filet." }
try { Enable-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch { }
if ($seen) { if ($seen) {
Start-Sleep -Seconds 2 Start-Sleep -Seconds 2
Restart-Computer -Force Restart-Computer -Force