Passa al contenuto principale

Rilevamento della libreria giochi

Il rilevamento della libreria giochi gira interamente dentro l'app Electron playroll-ui (processo main). Enumera i giochi installati localmente dai launcher PC supportati, senza richiedere alcun login agli store, poi sincronizza il risultato su Supabase in modo che possa alimentare la home view del launcher, i controlli di eleggibilità e il LaunchPad.

Il codice sorgente vive in playroll-ui sotto src/main/library/. Il recorder playroll-cpp non partecipa al rilevamento — consuma solo il game_id risultante una volta avviata una sessione.

Obiettivi

  • Rilevamento solo locale. Nessuna credenziale di store, nessuna chiamata API remota. Ogni detector legge ciò che il launcher memorizza già su disco (manifesti, registro, SQLite, VDF).
  • Best-effort, mai bloccante. Un detector che fallisce restituisce una lista vuota, non solleva mai eccezioni nell'orchestrator. Il launcher deve comunque funzionare se Steam è rotto o se GOG Galaxy manca.
  • Identità deterministica. L'id di un gioco è un UUID v5 derivato da provider:providerGameId, così la stessa installazione si hasha sempre allo stesso id tra macchine e reinstallazioni diverse.

Pipeline

Il flusso è:

  1. LibraryManager (src/main/library/library-manager.ts) istanzia ogni detector allo startup e avvia uno scan iniziale completo.
  2. LibraryScanner esegue i detector in parallelo: prima isAvailable() (controllo economico di presenza launcher), poi detectInstalled() solo per quelli disponibili. Ogni risultato è chiavato per provider:providerGameId, così rilevamenti duplicati tra detector sono impossibili.
  3. Lo scanner calcola un diff added / removed / changed rispetto allo scan precedente e spinge il risultato al renderer.
  4. LibraryWatcher osserva i file Steam appmanifest_*.acf (uno per ogni cartella di libreria Steam) e i manifesti Epic *.item per eventi di install/uninstall granulari; entrambi sono debounced (~2s) e fanno scattare un rescan single-store. Gli altri store si affidano al rescan completo periodico di 30 minuti (LIBRARY_SCAN_INTERVAL_MS).
  5. LibrarySync fa upsert della lista corrente su Supabase tramite l'RPC sync_user_game_library (con throttle). La riga Supabase è ciò che la UI legge in seguito per il matching del catalogo, l'eleggibilità e il LaunchPad.

Un modulo separato — running-games.ts — enumera i processi del sistema operativo e li confronta con gli eseguibili dei giochi rilevati per segnalare quali titoli sono attualmente in esecuzione. Questo è indipendente dal rilevamento ma usa gli stessi record DetectedGame.

Contratto del detector

Ogni detector implementa StoreDetector da src/main/library/detector.ts:

interface StoreDetector {
readonly name: StoreProvider;
isAvailable(): Promise<boolean>;
detectInstalled(): Promise<DetectedGame[]>;
}

isAvailable() è intenzionalmente economico (hit sul registro, existsSync su file, ecc.) così l'orchestrator può saltare i launcher morti senza pagare il costo di un'enumerazione completa.

Un DetectedGame trasporta l'id canonico, il provider, il path di installazione, gli eseguibili, lo stato di installazione, la dimensione su disco, il comando di lancio e metadata opzionali (icona, tempo di gioco, generi, sviluppatori, publisher, data di rilascio, piattaforma). I campi mancanti sono normali — la maggior parte degli store non-Steam espone molti meno metadata localmente.

Store supportati

StoreproviderSorgente di rilevamentoWatch install/uninstall
Steamsteamlibraryfolders.vdf + appmanifest_*.acf per ogni cartella di libreria, più i path di registro dei mod GoldSrc e SourceSì (chokidar su steamapps/appmanifest_*.acf di ogni libreria)
Epic GamesepicManifesti in %PROGRAMDATA%\Epic\EpicGamesLauncher\Data\Manifests\*.item + LauncherInstalled.dat. Le sub-app/DLC vengono saltate quando MainGameAppName è presente e diverso da AppName; i titoli standalone hanno "MainGameAppName": "" e vengono tenuti (EC-523). Il software non-gioco distribuito da Epic (Discord, Voicemod, …) viene saltato quando AppCategories non contiene gamesSì (chokidar su *.item)
GOG GalaxygogRegistro di disinstallazione Windows (*_is1, sia view x86 che x64) + goggame-{id}.info per giocoNo — solo rescan periodico
Xbox / Microsoft StorexboxEnumerazione PowerShell di pacchetti UWP firmati dallo Store + parse di AppxManifest.xml, con cache token XboxAccountClient per metadata più ricchiNo — solo rescan periodico
EA (EA Desktop)ea%PROGRAMDATA%\EA Desktop\machine.ini + dir dati di installazione EA Desktop + registro, manifesti parsati con fast-xml-parserNo — solo rescan periodico
Ubisoft ConnectubisoftHKLM\SOFTWARE\ubisoft\Launcher\Installs (dir di installazione per gameId, sia view x86 che x64)No — solo rescan periodico
Battle.netbattlenetC:\ProgramData\Battle.net\Agent\product.db + registro disinstallazione Battle.net, con un catalogo prodotti integrato per i titoli default e classiciNo — solo rescan periodico
Amazon Gamesamazon%LOCALAPPDATA%\Amazon Games\Data\Games\Sql\GameInstallInfo.sqliteNo — solo rescan periodico
itch.ioitchioButler DB in %APPDATA%\itch\db\butler.db (filtrato a classification in tool)No — solo rescan periodico
Riot ClientriotChiavi di registro del Riot client (HKCR\riotclient, HKLM\SOFTWARE\Classes\riotclient) + tabella prodotti integrata (League, Valorant, TFT, Wild Rift, 2XKO)No — solo rescan periodico
Rockstar Games LauncherrockstarRegistro Uninstall di Windows (voci Launcher.exe … -uninstall=<titleId>) + chiavi per-gioco HKLM\SOFTWARE\(WOW6432Node\)Rockstar Games\<game>. La chiave per-gioco copre installazioni interrotte e copie gestite da altri store: i valori vengono letti nell'ordine InstallFolder (gestita da Rockstar), InstallFolderEpic (acquistata su Epic — l'installazione è gestita dal launcher Rockstar, quindi non compare nei manifest di Epic), poi qualsiasi variante InstallFolder* sconosciuta. InstallFolderSteam è lasciata deliberatamente al detector Steam (la copia ha un appmanifest Steam; una seconda riga duplicherebbe il gioco) ma viene conteggiata nella telemetria come steam_managed_only.No — solo rescan periodico

L'union StoreProvider include anche microsoft, other e manual per voci legacy / aggiunte dall'utente che non provengono da un detector live.

Identità, nomi e deduplicazione

  • generateGameId(provider, providerGameId) produce un UUID v5 deterministico sotto un namespace Playroll fisso, così due macchine diverse con Hades installato via Steam finiscono con lo stesso game_id.
  • normalizeGameName rimuove ™ ® ©, collassa gli spazi bianchi e fa il trim, così il matching lato display non deve gestire la punteggiatura specifica del launcher.
  • Lo scanner chiavizza i giochi per provider:providerGameId, non per nome — un titolo installato sia su Steam che su Epic compare come due voci di libreria separate (corretto, dato che ogni installazione ha il proprio eseguibile e comando di lancio).

Modello di fallimento

  • Un detector che solleva eccezioni durante detectInstalled() viene catturato dallo scanner e trattato come "ha restituito []" — il resto dello scan si completa comunque.
  • Un detector che solleva eccezioni durante isAvailable() viene trattato come non disponibile.
  • I fallimenti di sync risalgono al listener del manager come onError("sync", ...) ma non interrompono lo scanning.
  • Lo scanner rifiuta di eseguire un secondo scan mentre uno è già in volo; i trigger concorrenti (timer + watcher) collassano in un singolo passaggio.