<# fws-play.ps1 — session de jeu Windows orchestree, lancement AUTO du jeu choisi. Lit (et CONSOMME) le jeton depose par FWS sur l'ESP (\EFI\FWS\launch.json = {"game":""}), cherche dans games.json, lance le jeu, attend sa fermeture/crash, puis rebascule vers FWS. 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' $gamesDb = Join-Path $here 'games.json' $seen = $false $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 : $_" } # 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 # 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 "Aucun 'process' defini pour '$game' — pas d'attente ni de reboot auto." } } finally { # Rearmer FWS, QUOI QU'IL ARRIVE. try { & $return -Target FWS } catch { Write-Warning "Arm FWS a echoue : $_ — BootOrder[0]=FWS reste le filet." } if ($seen) { Start-Sleep -Seconds 2 Restart-Computer -Force } else { Write-Warning "Session sans jeu detecte : redemarre manuellement pour revenir a FWS (BootNext deja arme vers FWS)." } }