diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index 495bafac..e4aa1b1c 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -3,6 +3,7 @@ import { mergeAchievements } from "./merge-achievements"; import fs, { readdirSync } from "node:fs"; import { findAchievementFileInExecutableDirectory, + findAchievementFileInSteamPath, findAchievementFiles, findAllAchievementFiles, getAlternativeObjectIds, @@ -43,6 +44,10 @@ const watchAchievementsWindows = async () => { gameAchievementFiles.push( ...findAchievementFileInExecutableDirectory(game) ); + + gameAchievementFiles.push( + ...(await findAchievementFileInSteamPath(game)) + ); } for (const file of gameAchievementFiles) { @@ -66,6 +71,8 @@ const watchAchievementsWithWine = async () => { gameAchievementFiles.push(...achievementFileInsideDirectory); + gameAchievementFiles.push(...(await findAchievementFileInSteamPath(game))); + for (const file of gameAchievementFiles) { await compareFile(game, file); } @@ -179,6 +186,8 @@ export class AchievementWatcherManager { gameAchievementFiles.push(...achievementFileInsideDirectory); + gameAchievementFiles.push(...(await findAchievementFileInSteamPath(game))); + const unlockedAchievements: UnlockedAchievement[] = []; for (const achievementFile of gameAchievementFiles) { @@ -259,7 +268,7 @@ export class AchievementWatcherManager { const gameAchievementFilesMap = findAllAchievementFiles(); return Promise.all( - games.map((game) => { + games.map(async (game) => { const achievementFiles: AchievementFile[] = []; for (const objectId of getAlternativeObjectIds(game.objectId)) { @@ -270,6 +279,10 @@ export class AchievementWatcherManager { achievementFiles.push( ...findAchievementFileInExecutableDirectory(game) ); + + achievementFiles.push( + ...(await findAchievementFileInSteamPath(game)) + ); } return { game, achievementFiles }; @@ -284,13 +297,15 @@ export class AchievementWatcherManager { .then((games) => games.filter((game) => !game.isDeleted)); return Promise.all( - games.map((game) => { + games.map(async (game) => { const achievementFiles = findAchievementFiles(game); const achievementFileInsideDirectory = findAchievementFileInExecutableDirectory(game); achievementFiles.push(...achievementFileInsideDirectory); + achievementFiles.push(...(await findAchievementFileInSteamPath(game))); + return { game, achievementFiles }; }) ); diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index 7a531388..c6b3a6fc 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -4,6 +4,7 @@ import type { Game, AchievementFile } from "@types"; import { Cracker } from "@shared"; import { achievementsLogger } from "../logger"; import { SystemPath } from "../system-path"; +import { getSteamLocation, getSteamUsersIds } from "../steam"; const getAppDataPath = () => { if (process.platform === "win32") { @@ -273,6 +274,37 @@ export const findAchievementFiles = (game: Game) => { return achievementFiles; }; +const steamUserIds = await getSteamUsersIds(); +const steamPath = await getSteamLocation(); + +export const findAchievementFileInSteamPath = async (game: Game) => { + if (!steamUserIds.length) { + return []; + } + + const achievementFiles: AchievementFile[] = []; + + for (const steamUserId of steamUserIds) { + const gameAchievementPath = path.join( + steamPath, + "userdata", + steamUserId.toString(), + "config", + "librarycache", + `${game.objectId}.json` + ); + + if (fs.existsSync(gameAchievementPath)) { + achievementFiles.push({ + type: Cracker.Steam, + filePath: gameAchievementPath, + }); + } + } + + return achievementFiles; +}; + export const findAchievementFileInExecutableDirectory = ( game: Game ): AchievementFile[] => { diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index e2b663d8..214735c2 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -19,6 +19,32 @@ const getModifiedSinceHeader = ( : undefined; }; +const getModifiedSinceHeader = async ( + cachedAchievements: GameAchievement | undefined +): Promise => { + const hasActiveSubscription = await db + .get(levelKeys.user, { valueEncoding: "json" }) + .then((user) => { + const expiresAt = new Date(user?.subscription?.expiresAt ?? 0); + return expiresAt > new Date(); + }); + + if (!cachedAchievements) { + return undefined; + } + + const hasAchievementsPoints = + cachedAchievements.achievements[0].points != undefined; + + if (hasActiveSubscription !== hasAchievementsPoints) { + return undefined; + } + + return cachedAchievements.updatedAt + ? new Date(cachedAchievements.updatedAt) + : undefined; +}; + export const getGameAchievementData = async ( objectId: string, shop: GameShop, diff --git a/src/main/services/achievements/parse-achievement-file.ts b/src/main/services/achievements/parse-achievement-file.ts index 726d8d0f..44827782 100644 --- a/src/main/services/achievements/parse-achievement-file.ts +++ b/src/main/services/achievements/parse-achievement-file.ts @@ -75,6 +75,11 @@ export const parseAchievementFile = ( return processRazor1911(filePath); } + if (type === Cracker.Steam) { + const parsed = jsonParse(filePath); + return processSteamCacheAchievement(parsed); + } + achievementsLogger.log( `Unprocessed ${type} achievements found on ${filePath}` ); @@ -234,6 +239,35 @@ const processGoldberg = (unlockedAchievements: any): UnlockedAchievement[] => { return newUnlockedAchievements; }; +const processSteamCacheAchievement = ( + unlockedAchievements: any[] +): UnlockedAchievement[] => { + const newUnlockedAchievements: UnlockedAchievement[] = []; + + const achievementIndex = unlockedAchievements.findIndex( + (element) => element[0] === "achievements" + ); + + if (achievementIndex === -1) { + achievementsLogger.info("No achievements found in Steam cache file"); + return []; + } + + const unlockedAchievementsData = + unlockedAchievements[achievementIndex][1]["data"]["vecHighlight"]; + + for (const achievement of unlockedAchievementsData) { + if (achievement.bAchieved) { + newUnlockedAchievements.push({ + name: achievement.strID, + unlockTime: achievement.rtUnlocked * 1000, + }); + } + } + + return newUnlockedAchievements; +}; + const process3DM = (unlockedAchievements: any): UnlockedAchievement[] => { const newUnlockedAchievements: UnlockedAchievement[] = []; diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 448b7eec..851aec49 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -35,6 +35,7 @@ export enum Cracker { onlineFix = "OnlineFix", goldberg = "Goldberg", userstats = "user_stats", + Steam = "Steam", rld = "RLD!", empress = "EMPRESS", skidrow = "SKIDROW",