import { registerEvent } from "../register-event"; import type { GameShop, ShopAssets } from "@types"; import { gamesSublevel, levelKeys } from "@main/level"; import { composeSteamShortcut, getSteamLocation, getSteamShortcuts, getSteamUsersIds, logger, SystemPath, writeSteamShortcuts, } from "@main/services"; import fs from "node:fs"; import axios from "axios"; import path from "node:path"; import { ASSETS_PATH } from "@main/constants"; import { getGameAssets } from "../catalogue/get-game-assets"; const downloadAsset = async (downloadPath: string, url?: string | null) => { try { if (fs.existsSync(downloadPath)) { return downloadPath; } if (!url) { return null; } fs.mkdirSync(path.dirname(downloadPath), { recursive: true }); const response = await axios.get(url, { responseType: "arraybuffer" }); fs.writeFileSync(downloadPath, response.data); return downloadPath; } catch (error) { logger.error("Failed to download asset", error); return null; } }; const downloadAssetsFromSteam = async ( shop: GameShop, objectId: string, assets: ShopAssets | null ) => { const gameAssetsPath = path.join(ASSETS_PATH, `${shop}-${objectId}`); return await Promise.all([ downloadAsset(path.join(gameAssetsPath, "icon.ico"), assets?.iconUrl), downloadAsset( path.join(gameAssetsPath, "hero.jpg"), assets?.libraryHeroImageUrl ), downloadAsset(path.join(gameAssetsPath, "logo.png"), assets?.logoImageUrl), downloadAsset( path.join(gameAssetsPath, "cover.jpg"), assets?.coverImageUrl ), downloadAsset( path.join(gameAssetsPath, "library.jpg"), assets?.libraryImageUrl ), ]); }; const copyAssetIfExists = async ( sourcePath: string | null, destinationPath: string ) => { if (sourcePath && fs.existsSync(sourcePath)) { logger.info("Copying Steam asset", sourcePath, destinationPath); await fs.promises.cp(sourcePath, destinationPath); } }; const createSteamShortcut = async ( _event: Electron.IpcMainInvokeEvent, shop: GameShop, objectId: string ) => { const gameKey = levelKeys.game(shop, objectId); const game = await gamesSublevel.get(gameKey); if (game) { if (!game.executablePath) { throw new Error("No executable path found for game"); } const assets = await getGameAssets(objectId, shop); const steamUserIds = await getSteamUsersIds(); if (!steamUserIds.length) { logger.error("No Steam user ID found"); throw new Error("No Steam user ID found"); } const [iconImage, heroImage, logoImage, coverImage, libraryImage] = await downloadAssetsFromSteam(game.shop, game.objectId, assets); const newShortcut = composeSteamShortcut( game.title, game.executablePath, iconImage ); for (const steamUserId of steamUserIds) { logger.info("Adding shortcut for Steam user", steamUserId); const steamShortcuts = await getSteamShortcuts(steamUserId); if (steamShortcuts.some((shortcut) => shortcut.appname === game.title)) { continue; } const gridPath = path.join( await getSteamLocation(), "userdata", steamUserId.toString(), "config", "grid" ); await fs.promises.mkdir(gridPath, { recursive: true }); await Promise.all([ copyAssetIfExists( heroImage, path.join(gridPath, `${newShortcut.appid}_hero.jpg`) ), copyAssetIfExists( logoImage, path.join(gridPath, `${newShortcut.appid}_logo.png`) ), copyAssetIfExists( coverImage, path.join(gridPath, `${newShortcut.appid}p.jpg`) ), copyAssetIfExists( libraryImage, path.join(gridPath, `${newShortcut.appid}.jpg`) ), ]); steamShortcuts.push(newShortcut); logger.info(newShortcut); logger.info("Writing Steam shortcuts", steamShortcuts); await writeSteamShortcuts(steamUserId, steamShortcuts); } if (process.platform === "linux" && !game.winePrefixPath) { const steamWinePrefixes = path.join( SystemPath.getPath("home"), ".local", "share", "Steam", "steamapps", "compatdata" ); const winePrefixPath = path.join( steamWinePrefixes, newShortcut.appid.toString(), "pfx" ); await fs.promises.mkdir(winePrefixPath, { recursive: true }); await gamesSublevel.put(gameKey, { ...game, winePrefixPath, }); } } }; registerEvent("createSteamShortcut", createSteamShortcut);