Scanners Steam, Epic, GOG, Ubisoft Connect et EA app (registre, VDF/ACF, manifestes), isolés derrière une interface commune

This commit is contained in:
yasder5 2026-10-06 21:04:55 +02:00
parent b8b1afc626
commit bcc96d5fe5
16 changed files with 601 additions and 0 deletions

13
tests/fixtures/appmanifest_367520.acf vendored Normal file
View file

@ -0,0 +1,13 @@
"AppState"
{
"appid" "367520"
"universe" "1"
"name" "Hollow Knight"
"StateFlags" "4"
"installdir" "Hollow Knight"
// commentaire
"UserConfig"
{
"language" "french"
}
}

13
tests/fixtures/epic.item vendored Normal file
View file

@ -0,0 +1,13 @@
{
"FormatVersion": 0,
"bIsIncompleteInstall": false,
"LaunchCommand": "",
"LaunchExecutable": "Binaries/Win64/Game.exe",
"DisplayName": "Outer Wilds",
"InstallLocation": "C:\\Program Files\\Epic Games\\OuterWilds",
"CatalogNamespace": "ns123",
"CatalogItemId": "item456",
"AppName": "Bison",
"MainGameAppName": "Bison",
"AppCategories": ["public", "games", "applications"]
}

19
tests/fixtures/gog-reg.txt vendored Normal file
View file

@ -0,0 +1,19 @@
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\GOG.com\Games
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\GOG.com\Games\1207658924
gameName REG_SZ The Witcher 3: Wild Hunt
exe REG_SZ D:\GOG Games\The Witcher 3\bin\x64\witcher3.exe
path REG_SZ D:\GOG Games\The Witcher 3
workingDir REG_SZ D:\GOG Games\The Witcher 3\bin\x64
launchParam REG_SZ
gameID REG_SZ 1207658924
dependsOn REG_SZ
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\GOG.com\Games\1640424747
gameName REG_SZ Witcher 3 DLC
exe REG_SZ D:\GOG Games\The Witcher 3\bin\x64\witcher3.exe
dependsOn REG_SZ 1207658924
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\GOG.com\Games\1207658924\sub
x REG_DWORD 0x1

13
tests/fixtures/installerdata.xml vendored Normal file
View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<DiPManifest version="4.0">
<gameTitles>
<gameTitle locale="en_US">Mass Effect Legendary Edition</gameTitle>
<gameTitle locale="fr_FR">Mass Effect édition Légendaire</gameTitle>
</gameTitles>
<contentIDs><contentID>Origin.OFR.50.0004012</contentID></contentIDs>
<runtime>
<launcher>
<filePath>[HKEY_LOCAL_MACHINE\SOFTWARE\BioWare\Mass Effect Legendary Edition\Install Dir]\Game\Launcher\MassEffectLauncher.exe</filePath>
</launcher>
</runtime>
</DiPManifest>

21
tests/fixtures/libraryfolders.vdf vendored Normal file
View file

@ -0,0 +1,21 @@
"libraryfolders"
{
"0"
{
"path" "C:\\Program Files (x86)\\Steam"
"label" ""
"apps"
{
"228980" "1234"
"367520" "9100000000"
}
}
"1"
{
"path" "D:\\SteamLibrary"
"apps"
{
"1245620" "50000000000"
}
}
}

99
tests/scanners.test.ts Normal file
View file

@ -0,0 +1,99 @@
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { parseInstallerData } from '../src/main/scanners/ea'
import { parseEpicItem } from '../src/main/scanners/epic'
import { gogFromReg } from '../src/main/scanners/gog'
import { runScanners } from '../src/main/scanners/index'
import { parseReg } from '../src/main/scanners/reg'
import { libraryPaths, parseManifest, steam } from '../src/main/scanners/steam'
import type { ScanEnv, Scanner } from '../src/main/scanners/types'
import { ubisoftFromReg, UBI_KEY } from '../src/main/scanners/ubisoft'
import { parseVdf } from '../src/main/scanners/vdf'
import { DEFAULT_SETTINGS } from '../src/shared/types'
const fx = (f: string) => readFileSync(join(__dirname, 'fixtures', f), 'utf8')
describe('VDF', () => {
it('lit objets imbriqués, échappements et commentaires', () => {
const v = parseVdf(fx('appmanifest_367520.acf')) as any
expect(v.AppState.name).toBe('Hollow Knight')
expect(v.AppState.UserConfig.language).toBe('french')
expect((parseVdf('"a" { "p" "C:\\\\x\\"y" }') as any).a.p).toBe('C:\\x"y')
})
})
describe('Steam', () => {
it('trouve toutes les bibliothèques (nouveau et ancien format)', () => {
expect(libraryPaths(fx('libraryfolders.vdf'), 'C:\\Program Files (x86)\\Steam')).toEqual(['C:\\Program Files (x86)\\Steam', 'D:\\SteamLibrary'])
expect(libraryPaths('"LibraryFolders" { "TimeNextStatsReport" "1" "1" "E:\\\\Jeux" }', 'C:\\Steam')).toEqual(['C:\\Steam', 'E:\\Jeux'])
})
it('lit un manifeste et ignore les outils', () => {
expect(parseManifest(fx('appmanifest_367520.acf'), 'D:\\SteamLibrary')).toMatchObject({
platformId: '367520', name: 'Hollow Knight', installDir: 'D:\\SteamLibrary\\steamapps\\common\\Hollow Knight', launchUri: 'steam://rungameid/367520', installed: true
})
expect(parseManifest('"AppState" { "appid" "228980" "name" "Steamworks Common Redistributables" }', 'C:\\')).toBeNull()
expect(parseManifest('"AppState" { "appid" "1" "name" "Jeu" "StateFlags" "1026" }', 'C:\\')?.installed).toBe(false)
})
it('scan complet sur un faux disque', async () => {
const files: Record<string, string> = {
'C:\\Steam\\steamapps\\libraryfolders.vdf': '"libraryfolders" { "0" { "path" "C:\\\\Steam" } "1" { "path" "D:\\\\Lib" } }',
'C:\\Steam\\steamapps\\appmanifest_367520.acf': fx('appmanifest_367520.acf'),
'D:\\Lib\\steamapps\\appmanifest_620.acf': '"AppState" { "appid" "620" "name" "Portal 2" "StateFlags" "4" "installdir" "Portal 2" }'
}
const env: ScanEnv = {
programData: 'C:\\ProgramData',
reg: async (k) => new Map(k.startsWith('HKCU') ? [[k.toUpperCase(), { SteamPath: 'C:/Steam' }]] : []),
exists: (p) => p in files,
readText: (p) => files[p],
list: (d) => Object.keys(files).filter((f) => f.startsWith(d + '\\')).map((f) => f.slice(d.length + 1))
}
expect((await steam.scan(env)).map((g) => g.name).sort()).toEqual(['Hollow Knight', 'Portal 2'])
})
})
describe('Epic', () => {
it('lit un manifeste .item', () => {
expect(parseEpicItem(fx('epic.item'))).toMatchObject({
platformId: 'Bison', name: 'Outer Wilds',
exe: 'C:\\Program Files\\Epic Games\\OuterWilds\\Binaries\\Win64\\Game.exe',
launchUri: 'com.epicgames.launcher://apps/ns123%3Aitem456%3ABison?action=launch&silent=true'
})
})
it('ignore DLC et applications', () => {
expect(parseEpicItem(JSON.stringify({ ...JSON.parse(fx('epic.item')), MainGameAppName: 'Autre' }))).toBeNull()
expect(parseEpicItem(JSON.stringify({ ...JSON.parse(fx('epic.item')), AppCategories: ['engines'] }))).toBeNull()
})
})
describe('registre', () => {
it('parse la sortie de reg query', () => {
const m = parseReg(fx('gog-reg.txt'))
expect(m.get('HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432NODE\\GOG.COM\\GAMES\\1207658924')?.gameName).toBe('The Witcher 3: Wild Hunt')
expect(m.get('HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432NODE\\GOG.COM\\GAMES\\1207658924')?.launchParam).toBe('')
})
it('GOG : jeux sans DLC ni sous-clés', () => {
const g = gogFromReg(parseReg(fx('gog-reg.txt')))
expect(g).toHaveLength(1)
expect(g[0]).toMatchObject({ platformId: '1207658924', exe: 'D:\\GOG Games\\The Witcher 3\\bin\\x64\\witcher3.exe', args: undefined })
})
it('Ubisoft : nom tiré du dossier', () => {
const m = parseReg(`${UBI_KEY}\\635\r\n InstallDir REG_SZ C:/Program Files (x86)/Ubisoft/Ubisoft Game Launcher/games/Assassin's Creed Origins/\r\n`)
expect(ubisoftFromReg(m)[0]).toMatchObject({ name: "Assassin's Creed Origins", launchUri: 'uplay://launch/635/0' })
})
it('EA : exe et titre depuis installerdata.xml', () => {
expect(parseInstallerData(fx('installerdata.xml'))).toEqual({ exe: 'Game\\Launcher\\MassEffectLauncher.exe', title: 'Mass Effect Legendary Edition', id: 'Origin.OFR.50.0004012' })
})
})
describe('runScanners', () => {
it('isole un scanner en échec et ignore les désactivés', async () => {
const ok: Scanner = { id: 'steam', name: 'ok', scan: async () => [{ platform: 'steam', platformId: '1', name: 'A' }] }
const ko: Scanner = { id: 'epic', name: 'ko', scan: async () => { throw new Error('boum') } }
const off: Scanner = { id: 'gog', name: 'off', scan: async () => [{ platform: 'gog', platformId: '2', name: 'B' }] }
const r = await runScanners({ ...DEFAULT_SETTINGS, scanners: { ...DEFAULT_SETTINGS.scanners, gog: false } }, {} as ScanEnv, [ok, ko, off])
expect(r.ok.steam).toHaveLength(1)
expect(r.failed).toEqual(['epic'])
expect(r.ok.gog).toBeUndefined()
})
})