import { REPO, YamlFile } from "."; import { SteamGameCache, SteamGameCacheFile } from "./steam"; import { WikiGameCache, parseTemplates } from "./wiki"; export type Os = "dos" | "linux" | "mac" | "windows"; export type Bit = 32 | 64; export type Store = "epic" | "gog" | "microsoft" | "steam" | "uplay" | "origin"; export type Tag = "config" | "save"; export interface Manifest { [game: string]: Game; } export interface Game { files?: { [path: string]: { when?: Array>, tags?: Array, } }; installDir?: { [name: string]: {} }; launch?: { [path: string]: Array<{ arguments?: string; workingDir?: string; when?: Array, }> }, registry?: { [path: string]: { when?: Array>, tags?: Array, } }; steam?: { id?: number }; } export interface Constraint { os?: Os; bit?: Bit; store?: Store; } function normalizeLaunchPath(raw: string): string | undefined { if (raw.includes("://")) { return raw; } const standardized = raw .replace(/\\/g, "/") .replace(/\/\//g, "/") .replace(/\/(?=$)/g, "") .replace(/^.\//, "") .replace(/^\/+/, "") .trim(); if (standardized.length === 0 || standardized === ".") { return undefined; } return `/${standardized}`; } function doLaunchPathsMatch(fromSteam: string | undefined, fromManifest: string | undefined): boolean { if (fromSteam === undefined) { return fromManifest === undefined; } else { return normalizeLaunchPath(fromSteam) === fromManifest; } } function integrateWikiData(game: Game, cache: WikiGameCache[""]): void { if (cache.steam !== undefined) { game.steam = { id: cache.steam }; } const info = parseTemplates(cache.templates ?? []); game.files = info.files; game.registry = info.registry; } function integrateSteamData(game: Game, appInfo: SteamGameCache[""]): void { if (appInfo.installDir !== undefined) { game.installDir = { [appInfo.installDir]: {} }; } if (appInfo.launch !== undefined) { delete game.launch; for (const incoming of appInfo.launch) { if ( incoming.executable === undefined || incoming.executable.includes("://") || ![undefined, "default", "none"].includes(incoming.type) || incoming.config?.betakey !== undefined || incoming.config?.ownsdlc !== undefined ) { continue; } const os: Os | undefined = { "windows": "windows", "macos": "mac", "macosx": "mac", "linux": "linux", }[incoming.config?.oslist] as Os; const bit: Bit | undefined = { "32": 32, "64": 64, }[incoming.config?.osarch] as Bit; const when: Constraint = { os, bit, store: "steam" }; if (when.os === undefined) { delete when.os; } if (when.bit === undefined) { delete when.bit; } let foundExisting = false; for (const [existingExecutable, existingOptions] of Object.entries(game.launch ?? {})) { for (const existing of existingOptions) { if ( incoming.arguments === existing.arguments && doLaunchPathsMatch(incoming.executable, existingExecutable) && doLaunchPathsMatch(incoming.workingdir, existing.workingDir) ) { foundExisting = true; if (existing.when === undefined) { existing.when = []; } if (existing.when.every(x => x.os !== os && x.bit !== bit && x.store !== "steam")) { existing.when.push(when); } if (existing.when.length === 0) { delete existing.when; } } } } if (!foundExisting) { const key = normalizeLaunchPath(incoming.executable); if (key === undefined) { continue; } const candidate: Game["launch"][""][0] = { when: [when] }; if (incoming.arguments !== undefined) { candidate.arguments = incoming.arguments; } if (incoming.workingdir !== undefined) { const workingDir = normalizeLaunchPath(incoming.workingdir); if (workingDir !== undefined) { candidate.workingDir = workingDir; } } if (game.launch === undefined) { game.launch = {}; } if (game.launch[key] === undefined) { game.launch[key] = []; } game.launch[key].push(candidate); } } } } export class ManifestFile extends YamlFile { path = `${REPO}/data/manifest.yaml`; defaultData = {}; async updateGames( wikiCache: WikiGameCache, games: Array, steamCache: SteamGameCacheFile, override: ManifestOverrideFile, ): Promise { this.data = {}; for (const [title, info] of Object.entries(wikiCache).sort()) { const overridden = override.data[title]; if (overridden?.omit) { continue; } if (games?.length > 0 && !games.includes(title)) { continue; } const game: Game = {}; integrateWikiData(game, info); if (game.files === undefined && game.registry === undefined && game.steam?.id === undefined) { continue; } if (game.steam?.id !== undefined) { const appInfo = await steamCache.getAppInfo(game.steam.id); integrateSteamData(game, appInfo); } this.data[title] = game; } } } export interface ManifestOverride { [game: string]: { omit?: boolean; } } export class ManifestOverrideFile extends YamlFile { path = `${REPO}/data/manifest-override.yaml`; defaultData = {}; }