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:
@@ -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 :
|
||||
- on desactive la tache ONSTART (evite qu'un declenchement tardif ne rearme
|
||||
FWS EN PLEIN MATCH) ;
|
||||
- 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).
|
||||
Lit (et CONSOMME) le jeton depose par FWS sur l'ESP
|
||||
(\EFI\FWS\launch.json = {"game":"<id>"}), cherche <id> dans games.json, lance
|
||||
le jeu, attend sa fermeture/crash, puis rebascule vers FWS.
|
||||
|
||||
Le filet permanent BootOrder[0]=FWS couvre tout ce que ce script raterait.
|
||||
A lancer a l'ouverture de session du compte gaming (autologin) ou a la main.
|
||||
Doit tourner EN ADMIN. #>
|
||||
Robustesse (aucune interaction requise) :
|
||||
- resolution du jeton sur TOUTES les ESP (dual-disque) + suppression apres
|
||||
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'
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$return = Join-Path $here 'fws-return.ps1'
|
||||
$taskName = 'FWS-Return-OnStart'
|
||||
$gamesDb = Join-Path $here 'games.json'
|
||||
$seen = $false
|
||||
|
||||
try {
|
||||
# Neutraliser la course avec la tache ONSTART pendant la session.
|
||||
try { Disable-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch { }
|
||||
$EFI_TYPE = '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' # GUID GPT "EFI System Partition"
|
||||
|
||||
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.
|
||||
try { & $return -Target Windows } catch { Write-Warning "Arm Windows a echoue : $_" }
|
||||
|
||||
# Lancer Valorant via le Riot Client (adapter le chemin si necessaire).
|
||||
$riot = Join-Path $Env:ProgramFiles 'Riot Games\Riot Client\RiotClientServices.exe'
|
||||
if (Test-Path $riot) {
|
||||
Start-Process $riot -ArgumentList '--launch-product=valorant','--launch-patchline=live'
|
||||
} else {
|
||||
Write-Warning "Riot Client introuvable : $riot — lance Valorant manuellement."
|
||||
# Resoudre + VALIDER le jeu (rejeter les cles '_*' et les entrees sans .type).
|
||||
$game = Get-RequestedGame
|
||||
$games = Get-Content $gamesDb -Raw | ConvertFrom-Json
|
||||
$entry = $games.$game
|
||||
if ($game -like '_*' -or -not $entry -or -not $entry.type) {
|
||||
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 /
|
||||
# une MAJ Vanguard peuvent etre longs), puis attendre sa fermeture/crash.
|
||||
$proc = 'VALORANT-Win64-Shipping'
|
||||
for ($i = 0; $i -lt 1440; $i++) { # ~2 h max d'attente d'apparition
|
||||
if (Get-Process $proc -ErrorAction SilentlyContinue) { $seen = $true; break }
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
if ($seen) {
|
||||
Get-Process $proc -ErrorAction SilentlyContinue | ForEach-Object { $_.WaitForExit() }
|
||||
# Course avec la tache ONSTART (qui arme FWS au boot) : on la laisse passer
|
||||
# puis on RE-AFFIRME Windows → fws-play est le dernier ecrivain de BootNext.
|
||||
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
|
||||
if (Get-Process $proc -ErrorAction SilentlyContinue) { $seen = $true; break }
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
if ($seen) {
|
||||
while (Get-Process $proc -ErrorAction SilentlyContinue) { Start-Sleep -Seconds 5 }
|
||||
} else {
|
||||
Write-Warning "Process '$proc' jamais detecte — pas de reboot automatique."
|
||||
}
|
||||
} else {
|
||||
Write-Warning "Jeu jamais detecte — pas de reboot automatique."
|
||||
Write-Warning "Aucun 'process' defini pour '$game' — pas d'attente ni de reboot auto."
|
||||
}
|
||||
}
|
||||
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 { Enable-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch { }
|
||||
if ($seen) {
|
||||
Start-Sleep -Seconds 2
|
||||
Restart-Computer -Force
|
||||
|
||||
Reference in New Issue
Block a user