From 86de5aa89e95cecfea0026c773c5c318351616a5 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 13 May 2025 22:57:33 +0100 Subject: [PATCH 01/10] feat: adding possibility to create steam shortcut --- package.json | 4 + src/locales/en/translation.json | 2 + src/locales/es/translation.json | 2 + src/locales/pt-BR/translation.json | 2 + src/main/events/index.ts | 1 + .../events/library/create-steam-shortcut.ts | 131 ++++++++++++++++++ src/main/main.ts | 4 +- src/main/services/python-rpc.ts | 20 --- src/main/services/steam.ts | 124 ++++++++++++++++- src/preload/index.ts | 2 + src/renderer/src/declaration.d.ts | 1 + .../modals/game-options-modal.tsx | 20 +++ src/types/steam.types.ts | 19 +++ yarn.lock | 34 ++++- 14 files changed, 342 insertions(+), 24 deletions(-) create mode 100644 src/main/events/library/create-steam-shortcut.ts diff --git a/package.json b/package.json index 18d28cd0..ff23ffba 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "classnames": "^2.5.1", "color": "^4.2.3", "color.js": "^1.2.0", + "crc": "^4.3.2", "create-desktop-shortcuts": "^1.11.1", "date-fns": "^3.6.0", "dexie": "^4.0.10", @@ -70,10 +71,12 @@ "react-router-dom": "^6.22.3", "react-tooltip": "^5.28.0", "sound-play": "^1.1.0", + "steam-shortcut-editor": "^3.1.3", "sudo-prompt": "^9.2.1", "tar": "^7.4.3", "tough-cookie": "^5.1.1", "user-agents": "^1.1.387", + "winreg": "^1.2.5", "ws": "^8.18.1", "yaml": "^2.6.1", "yup": "^1.5.0", @@ -99,6 +102,7 @@ "@types/react-dom": "^18.2.18", "@types/sound-play": "^1.1.3", "@types/user-agents": "^1.0.4", + "@types/winreg": "^1.2.36", "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.2.1", "electron": "^31.7.7", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index d226e8e1..675f691f 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -130,7 +130,9 @@ "download_in_progress": "Download in progress", "download_paused": "Download paused", "last_downloaded_option": "Last downloaded option", + "create_steam_shortcut": "Create Steam shortcut", "create_shortcut_success": "Shortcut created successfully", + "you_might_need_to_restart_steam": "You might need to restart Steam to see the changes", "create_shortcut_error": "Error creating shortcut", "nsfw_content_title": "This game contains innapropriate content", "nsfw_content_description": "{{title}} contains content that may not be suitable for all ages. Are you sure you want to continue?", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 82dad2ba..716f7b33 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -130,8 +130,10 @@ "danger_zone_section_description": "Eliminar este juego de tu librería o los archivos descargados por Hydra (Esto solo eliminará los archivos de instalación y no el juego instalado)", "download_in_progress": "Descarga en progreso", "download_paused": "Descarga pausada", + "create_steam_shortcut": "Crear atajo de Steam", "last_downloaded_option": "Última opción descargada", "create_shortcut_success": "Atajo creado con éxito", + "you_might_need_to_restart_steam": "Es posible que necesites reiniciar Steam para ver los cambios", "create_shortcut_error": "Error al crear un atajo", "nsfw_content_title": "Este juego contiene contenido inapropiado.", "nsfw_content_description": "{{title}} puede ser no adecuado para todas las edades por su contenido. \n¿Deseas continuar de igual forma?", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 56dc8d44..5509e07b 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -118,7 +118,9 @@ "download_in_progress": "Download em andamento", "download_paused": "Download pausado", "last_downloaded_option": "Última opção baixada", + "create_steam_shortcut": "Criar atalho na Steam", "create_shortcut_success": "Atalho criado com sucesso", + "you_might_need_to_restart_steam": "Você pode precisar reiniciar a Steam para ver as alterações", "create_shortcut_error": "Erro ao criar atalho", "nsfw_content_title": "Este jogo contém conteúdo inapropriado", "nsfw_content_description": "{{title}} contém conteúdo que pode não ser apropriado para todas as idades. Você deseja continuar?", diff --git a/src/main/events/index.ts b/src/main/events/index.ts index acc589f9..ad72163e 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -35,6 +35,7 @@ import "./library/select-game-wine-prefix"; import "./library/reset-game-achievements"; import "./library/toggle-automatic-cloud-sync"; import "./library/get-default-wine-prefix-selection-path"; +import "./library/create-steam-shortcut"; import "./misc/open-checkout"; import "./misc/open-external"; import "./misc/show-open-dialog"; diff --git a/src/main/events/library/create-steam-shortcut.ts b/src/main/events/library/create-steam-shortcut.ts new file mode 100644 index 00000000..f0a27670 --- /dev/null +++ b/src/main/events/library/create-steam-shortcut.ts @@ -0,0 +1,131 @@ +import { registerEvent } from "../register-event"; +import type { GameShop, GameStats } from "@types"; +import { gamesSublevel, levelKeys } from "@main/level"; +import { + composeSteamShortcut, + getSteamLocation, + getSteamShortcuts, + getSteamUserId, + HydraApi, + logger, + SystemPath, + writeSteamShortcuts, +} from "@main/services"; +import fs from "node:fs"; +import axios from "axios"; +import path from "node:path"; + +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 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 HydraApi.get( + `/games/stats?objectId=${objectId}&shop=${shop}` + ); + + const steamUserId = getSteamUserId(); + + if (!steamUserId) { + logger.error("No Steam user ID found"); + return; + } + + logger.info("Got Steam user id", steamUserId); + + const steamShortcuts = await getSteamShortcuts(steamUserId); + + if ( + steamShortcuts.some( + (shortcut) => + shortcut.Exe === game.executablePath && + shortcut.appname === game.title + ) + ) { + return; + } + + const icon = await downloadAsset( + path.join( + SystemPath.getPath("userData"), + "Icons", + `${game.shop}-${game.objectId}.ico` + ), + assets?.iconUrl + ); + + const newShortcut = composeSteamShortcut( + game.title, + game.executablePath, + icon + ); + + const gridPath = path.join( + await getSteamLocation(), + "userdata", + steamUserId.toString(), + "config", + "grid" + ); + + fs.mkdirSync(gridPath, { recursive: true }); + + await Promise.allSettled([ + downloadAsset( + path.join(gridPath, `${newShortcut.appid}_hero.jpg`), + assets?.libraryHeroImageUrl + ), + downloadAsset( + path.join(gridPath, `${newShortcut.appid}_logo.png`), + assets?.logoImageUrl + ), + downloadAsset( + path.join(gridPath, `${newShortcut.appid}p.jpg`), + assets?.coverImageUrl + ), + downloadAsset( + path.join(gridPath, `${newShortcut.appid}.jpg`), + assets?.libraryImageUrl + ), + ]); + + steamShortcuts.push(newShortcut); + + logger.info(newShortcut); + logger.info("Writing Steam shortcuts", steamShortcuts); + + return writeSteamShortcuts(steamUserId, steamShortcuts); + } +}; + +registerEvent("createSteamShortcut", createSteamShortcut); diff --git a/src/main/main.ts b/src/main/main.ts index b0e142fc..ec524747 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -29,7 +29,9 @@ export const loadState = async () => { await import("./events"); - Aria2.spawn(); + if (process.platform !== "darwin") { + Aria2.spawn(); + } if (userPreferences?.realDebridApiToken) { RealDebridClient.authorize(userPreferences.realDebridApiToken); diff --git a/src/main/services/python-rpc.ts b/src/main/services/python-rpc.ts index 1577b1e6..da4f1e71 100644 --- a/src/main/services/python-rpc.ts +++ b/src/main/services/python-rpc.ts @@ -22,12 +22,6 @@ const binaryNameByPlatform: Partial> = { win32: "hydra-python-rpc.exe", }; -const rustBinaryNameByPlatform: Partial> = { - darwin: "hydra-httpdl", - linux: "hydra-httpdl", - win32: "hydra-httpdl.exe", -}; - export class PythonRPC { public static readonly BITTORRENT_PORT = "5881"; public static readonly RPC_PORT = "8084"; @@ -72,20 +66,6 @@ export class PythonRPC { rpcPassword, initialDownload ? JSON.stringify(initialDownload) : "", initialSeeding ? JSON.stringify(initialSeeding) : "", - app.isPackaged - ? path.join( - process.resourcesPath, - rustBinaryNameByPlatform[process.platform]! - ) - : path.join( - __dirname, - "..", - "..", - "rust_rpc", - "target", - "debug", - rustBinaryNameByPlatform[process.platform]! - ), ]; if (app.isPackaged) { diff --git a/src/main/services/steam.ts b/src/main/services/steam.ts index 18cd8d11..fff87422 100644 --- a/src/main/services/steam.ts +++ b/src/main/services/steam.ts @@ -1,8 +1,14 @@ import axios from "axios"; +import path from "node:path"; +import fs from "node:fs"; +import { crc32 } from "crc"; +import WinReg from "winreg"; +import { parseBuffer, writeBuffer } from "steam-shortcut-editor"; -import type { SteamAppDetails } from "@types"; +import type { SteamAppDetails, SteamShortcut } from "@types"; import { logger } from "./logger"; +import { SystemPath } from "./system-path"; export interface SteamAppDetailsResponse { [key: string]: { @@ -11,6 +17,36 @@ export interface SteamAppDetailsResponse { }; } +export const getSteamLocation = async (): Promise => { + if (process.platform === "linux") { + return path.join(SystemPath.getPath("home"), ".local", "share", "Steam"); + } + + if (process.platform === "darwin") { + return path.join( + SystemPath.getPath("home"), + "Library", + "Application Support", + "Steam" + ); + } + + const regKey = new WinReg({ + hive: WinReg.HKCU, + key: "\\Software\\Valve\\Steam", + }); + + return new Promise((resolve, reject) => { + regKey.get("SteamPath", (err, value) => { + if (err) { + reject(err); + } + + resolve(value.value); + }); + }); +}; + export const getSteamAppDetails = async ( objectId: string, language: string @@ -40,3 +76,89 @@ export const getSteamAppDetails = async ( return null; }); }; + +export const getSteamUserId = () => { + const userDataPath = path.join( + SystemPath.getPath("appData"), + "Steam", + "userdata" + ); + + const userIds = fs.readdirSync(userDataPath, { withFileTypes: true }); + + const [steamUserId] = userIds.filter((dir) => dir.isDirectory()); + if (!steamUserId) { + return null; + } + + return Number(steamUserId.name); +}; + +export const getSteamShortcuts = async (steamUserId: number) => { + const shortcuts = parseBuffer( + fs.readFileSync( + path.join( + await getSteamLocation(), + "userdata", + steamUserId.toString(), + "config", + "shortcuts.vdf" + ) + ) + ); + + return shortcuts.shortcuts as SteamShortcut[]; +}; + +export const generateSteamShortcutAppId = ( + exePath: string, + gameName: string +) => { + const input = exePath + gameName; + const crcValue = crc32(input) >>> 0; + const steamAppId = (crcValue | 0x80000000) >>> 0; + return steamAppId; +}; + +export const composeSteamShortcut = ( + title: string, + executablePath: string, + iconPath: string | null +): SteamShortcut => { + return { + appid: generateSteamShortcutAppId(executablePath, title), + appname: title, + Exe: executablePath, + StartDir: path.dirname(executablePath), + icon: iconPath ?? "", + ShortcutPath: "", + LaunchOptions: "", + IsHidden: false, + AllowDesktopConfig: true, + AllowOverlay: true, + OpenVR: false, + Devkit: false, + DevkitGameID: "", + DevkitOverrideAppID: false, + LastPlayTime: false, + FlatpakAppID: "", + }; +}; + +export const writeSteamShortcuts = async ( + steamUserId: number, + shortcuts: SteamShortcut[] +) => { + const buffer = writeBuffer({ shortcuts }); + + fs.writeFileSync( + path.join( + await getSteamLocation(), + "userdata", + steamUserId.toString(), + "config", + "shortcuts.vdf" + ), + buffer + ); +}; diff --git a/src/preload/index.ts b/src/preload/index.ts index 981901d3..6695fa2b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -191,6 +191,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("extractGameDownload", shop, objectId), getDefaultWinePrefixSelectionPath: () => ipcRenderer.invoke("getDefaultWinePrefixSelectionPath"), + createSteamShortcut: (shop: GameShop, objectId: string) => + ipcRenderer.invoke("createSteamShortcut", shop, objectId), onGamesRunning: ( cb: ( gamesRunning: Pick[] diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 0dee5767..7eaa4ee0 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -180,6 +180,7 @@ declare global { cb: (shop: GameShop, objectId: string) => void ) => () => Electron.IpcRenderer; getDefaultWinePrefixSelectionPath: () => Promise; + createSteamShortcut: (shop: GameShop, objectId: string) => Promise; /* Download sources */ putDownloadSource: ( diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index 991f6a9d..c178d0c1 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -8,8 +8,10 @@ import { useDownload, useToast, useUserDetails } from "@renderer/hooks"; import { RemoveGameFromLibraryModal } from "./remove-from-library-modal"; import { ResetAchievementsModal } from "./reset-achievements-modal"; import { FileDirectoryIcon, FileIcon } from "@primer/octicons-react"; +import SteamLogo from "@renderer/assets/steam-logo.svg?react"; import { debounce } from "lodash-es"; import "./game-options-modal.scss"; +import { logger } from "@renderer/logger"; export interface GameOptionsModalProps { visible: boolean; @@ -107,6 +109,20 @@ export function GameOptionsModal({ } }; + const handleCreateSteamShortcut = async () => { + try { + await window.electron.createSteamShortcut(game.shop, game.objectId); + + showSuccessToast( + t("create_shortcut_success"), + t("you_might_need_to_restart_steam") + ); + } catch (error: unknown) { + logger.error("Failed to create Steam shortcut", error); + showErrorToast(t("create_shortcut_error")); + } + }; + const handleCreateShortcut = async (location: ShortcutLocation) => { window.electron .createGameShortcut(game.shop, game.objectId, location) @@ -298,6 +314,10 @@ export function GameOptionsModal({ > {t("create_shortcut")} + {shouldShowCreateStartMenuShortcut && ( - From 521d9faa0c45178833a88585f39efb13026b41f8 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 14 May 2025 00:50:30 +0100 Subject: [PATCH 07/10] feat: automatically adding wine prefix --- .../events/library/create-steam-shortcut.ts | 58 ++++++++++++------- .../get-default-wine-prefix-selection-path.ts | 6 +- .../modals/game-options-modal.tsx | 7 ++- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/main/events/library/create-steam-shortcut.ts b/src/main/events/library/create-steam-shortcut.ts index 436d4ac1..df139403 100644 --- a/src/main/events/library/create-steam-shortcut.ts +++ b/src/main/events/library/create-steam-shortcut.ts @@ -61,36 +61,30 @@ const createSteamShortcut = async ( return; } + const icon = await downloadAsset( + path.join( + SystemPath.getPath("userData"), + "Icons", + `${game.shop}-${game.objectId}.ico` + ), + assets?.iconUrl + ); + + const newShortcut = composeSteamShortcut( + game.title, + game.executablePath, + icon + ); + for (const steamUserId of steamUserIds) { logger.info("Adding shortcut for Steam user", steamUserId); const steamShortcuts = await getSteamShortcuts(steamUserId); - if ( - steamShortcuts.some( - (shortcut) => - shortcut.Exe === game.executablePath && - shortcut.appname === game.title - ) - ) { + if (steamShortcuts.some((shortcut) => shortcut.appname === game.title)) { continue; } - const icon = await downloadAsset( - path.join( - SystemPath.getPath("userData"), - "Icons", - `${game.shop}-${game.objectId}.ico` - ), - assets?.iconUrl - ); - - const newShortcut = composeSteamShortcut( - game.title, - game.executablePath, - icon - ); - const gridPath = path.join( await getSteamLocation(), "userdata", @@ -127,6 +121,26 @@ const createSteamShortcut = async ( await writeSteamShortcuts(steamUserId, steamShortcuts); } + + if (process.platform === "linux" && !game.winePrefixPath) { + const steamWinePrefixes = path.join( + SystemPath.getPath("home"), + ".local", + "share", + "Steam", + "steamapps", + "compatdata" + ); + + await gamesSublevel.put(gameKey, { + ...game, + winePrefixPath: path.join( + steamWinePrefixes, + newShortcut.appid.toString(), + "pfx" + ), + }); + } } }; diff --git a/src/main/events/library/get-default-wine-prefix-selection-path.ts b/src/main/events/library/get-default-wine-prefix-selection-path.ts index 94f57d38..67f5a82e 100644 --- a/src/main/events/library/get-default-wine-prefix-selection-path.ts +++ b/src/main/events/library/get-default-wine-prefix-selection-path.ts @@ -16,11 +16,7 @@ const getDefaultWinePrefixSelectionPath = async ( "compatdata" ); - if (fs.existsSync(steamWinePrefixes)) { - return fs.promises.realpath(steamWinePrefixes); - } - - return null; + return await fs.promises.realpath(steamWinePrefixes); } catch (err) { logger.error("Failed to get default wine prefix selection path", err); diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index 2087e264..6ccfb627 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -119,6 +119,8 @@ export function GameOptionsModal({ t("create_shortcut_success"), t("you_might_need_to_restart_steam") ); + + updateGame(); } catch (error: unknown) { logger.error("Failed to create Steam shortcut", error); showErrorToast(t("create_shortcut_error")); @@ -162,9 +164,12 @@ export function GameOptionsModal({ }; const handleChangeWinePrefixPath = async () => { + const defaultPath = + await window.electron.getDefaultWinePrefixSelectionPath(); + const { filePaths } = await window.electron.showOpenDialog({ properties: ["openDirectory"], - defaultPath: await window.electron.getDefaultWinePrefixSelectionPath(), + defaultPath: defaultPath ?? game?.winePrefixPath ?? "", }); if (filePaths && filePaths.length > 0) { From e8e524182a598d27d643a03e76e114099a5d5cfa Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 14 May 2025 10:11:29 +0100 Subject: [PATCH 08/10] feat: only downloading files once --- src/main/constants.ts | 2 + .../events/library/create-steam-shortcut.ts | 91 +++++++++++++------ 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/main/constants.ts b/src/main/constants.ts index 0f892af7..243891e7 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -40,4 +40,6 @@ export const backupsPath = path.join(SystemPath.getPath("userData"), "Backups"); export const appVersion = app.getVersion() + (isStaging ? "-staging" : ""); +export const ASSETS_PATH = path.join(SystemPath.getPath("userData"), "Assets"); + export const MAIN_LOOP_INTERVAL = 1500; diff --git a/src/main/events/library/create-steam-shortcut.ts b/src/main/events/library/create-steam-shortcut.ts index df139403..ec408447 100644 --- a/src/main/events/library/create-steam-shortcut.ts +++ b/src/main/events/library/create-steam-shortcut.ts @@ -14,6 +14,7 @@ import { import fs from "node:fs"; import axios from "axios"; import path from "node:path"; +import { ASSETS_PATH } from "@main/constants"; const downloadAsset = async (downloadPath: string, url?: string | null) => { try { @@ -37,6 +38,40 @@ const downloadAsset = async (downloadPath: string, url?: string | null) => { } }; +const downloadAssetsFromSteam = async ( + shop: GameShop, + objectId: string, + assets: GameStats["assets"] +) => { + 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)) { + await fs.promises.cp(sourcePath, destinationPath); + } +}; + const createSteamShortcut = async ( _event: Electron.IpcMainInvokeEvent, shop: GameShop, @@ -61,19 +96,13 @@ const createSteamShortcut = async ( return; } - const icon = await downloadAsset( - path.join( - SystemPath.getPath("userData"), - "Icons", - `${game.shop}-${game.objectId}.ico` - ), - assets?.iconUrl - ); + const [iconImage, heroImage, logoImage, coverImage, libraryImage] = + await downloadAssetsFromSteam(game.shop, game.objectId, assets); const newShortcut = composeSteamShortcut( game.title, game.executablePath, - icon + iconImage ); for (const steamUserId of steamUserIds) { @@ -93,27 +122,29 @@ const createSteamShortcut = async ( "grid" ); - fs.mkdirSync(gridPath, { recursive: true }); + await fs.promises.mkdir(gridPath, { recursive: true }); - await Promise.allSettled([ - downloadAsset( - path.join(gridPath, `${newShortcut.appid}_hero.jpg`), - assets?.libraryHeroImageUrl + await Promise.all([ + copyAssetIfExists( + heroImage, + path.join(gridPath, `${newShortcut.appid}_hero.jpg`) ), - downloadAsset( - path.join(gridPath, `${newShortcut.appid}_logo.png`), - assets?.logoImageUrl + copyAssetIfExists( + logoImage, + path.join(gridPath, `${newShortcut.appid}_logo.png`) ), - downloadAsset( - path.join(gridPath, `${newShortcut.appid}p.jpg`), - assets?.coverImageUrl + copyAssetIfExists( + coverImage, + path.join(gridPath, `${newShortcut.appid}p.jpg`) ), - downloadAsset( - path.join(gridPath, `${newShortcut.appid}.jpg`), - assets?.libraryImageUrl + copyAssetIfExists( + libraryImage, + path.join(gridPath, `${newShortcut.appid}.jpg`) ), ]); + fs.mkdirSync(gridPath, { recursive: true }); + steamShortcuts.push(newShortcut); logger.info(newShortcut); @@ -132,13 +163,17 @@ const createSteamShortcut = async ( "compatdata" ); + const winePrefixPath = path.join( + steamWinePrefixes, + newShortcut.appid.toString(), + "pfx" + ); + + await fs.promises.mkdir(winePrefixPath, { recursive: true }); + await gamesSublevel.put(gameKey, { ...game, - winePrefixPath: path.join( - steamWinePrefixes, - newShortcut.appid.toString(), - "pfx" - ), + winePrefixPath, }); } } From 5c7a28929910cd08a7bd0690d5c6b78c276b6d9f Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 14 May 2025 10:17:44 +0100 Subject: [PATCH 09/10] fix: adding greptile fixes --- src/locales/en/translation.json | 2 +- src/main/services/steam.ts | 8 ++++---- .../src/pages/game-details/modals/game-options-modal.tsx | 2 +- src/types/steam.types.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 675f691f..2902218e 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -134,7 +134,7 @@ "create_shortcut_success": "Shortcut created successfully", "you_might_need_to_restart_steam": "You might need to restart Steam to see the changes", "create_shortcut_error": "Error creating shortcut", - "nsfw_content_title": "This game contains innapropriate content", + "nsfw_content_title": "This game contains inappropriate content", "nsfw_content_description": "{{title}} contains content that may not be suitable for all ages. Are you sure you want to continue?", "allow_nsfw_content": "Continue", "refuse_nsfw_content": "Go back", diff --git a/src/main/services/steam.ts b/src/main/services/steam.ts index 3b655b1a..70653fd0 100644 --- a/src/main/services/steam.ts +++ b/src/main/services/steam.ts @@ -17,7 +17,7 @@ export interface SteamAppDetailsResponse { }; } -export const getSteamLocation = async (): Promise => { +export const getSteamLocation = async () => { if (process.platform === "linux") { return path.join(SystemPath.getPath("home"), ".local", "share", "Steam"); } @@ -36,7 +36,7 @@ export const getSteamLocation = async (): Promise => { key: "\\Software\\Valve\\Steam", }); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { regKey.get("SteamPath", (err, value) => { if (err) { reject(err); @@ -137,7 +137,7 @@ export const composeSteamShortcut = ( Devkit: false, DevkitGameID: "", DevkitOverrideAppID: false, - LastPlayTime: false, + LastPlayTime: 0, FlatpakAppID: "", }; }; @@ -148,7 +148,7 @@ export const writeSteamShortcuts = async ( ) => { const buffer = writeBuffer({ shortcuts }); - fs.writeFileSync( + return fs.promises.writeFile( path.join( await getSteamLocation(), "userdata", diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index 6ccfb627..4c415f7b 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -328,7 +328,7 @@ export function GameOptionsModal({ theme="outline" disabled={creatingSteamShortcut} > - + {t("create_steam_shortcut")} {shouldShowCreateStartMenuShortcut && ( diff --git a/src/types/steam.types.ts b/src/types/steam.types.ts index b75aa84d..2721bb47 100644 --- a/src/types/steam.types.ts +++ b/src/types/steam.types.ts @@ -69,6 +69,6 @@ export interface SteamShortcut { Devkit: boolean; DevkitGameID: string; DevkitOverrideAppID: boolean; - LastPlayTime: boolean; + LastPlayTime: number; FlatpakAppID: string; } From 8c167790524ac451dcabf8a435fb0ef119998fb2 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Wed, 14 May 2025 10:58:40 +0100 Subject: [PATCH 10/10] feat: adding logging to steam copy --- src/main/events/library/create-steam-shortcut.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/events/library/create-steam-shortcut.ts b/src/main/events/library/create-steam-shortcut.ts index ec408447..faa69266 100644 --- a/src/main/events/library/create-steam-shortcut.ts +++ b/src/main/events/library/create-steam-shortcut.ts @@ -68,6 +68,7 @@ const copyAssetIfExists = async ( destinationPath: string ) => { if (sourcePath && fs.existsSync(sourcePath)) { + logger.info("Copying Steam asset", sourcePath, destinationPath); await fs.promises.cp(sourcePath, destinationPath); } }; @@ -143,8 +144,6 @@ const createSteamShortcut = async ( ), ]); - fs.mkdirSync(gridPath, { recursive: true }); - steamShortcuts.push(newShortcut); logger.info(newShortcut);