From 5e313a037418693f9fe87197729f76120316e525 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:37:01 -0300 Subject: [PATCH 01/18] feat: add 3dm file inside game directory --- .../achievements/achievement-file-observer.ts | 4 +- .../achievements/find-achivement-files.ts | 41 +++++++++++++------ .../achievements/parse-achievement-file.ts | 27 ++++++++++++ .../update-local-unlocked-achivements.ts | 9 +++- src/shared/constants.ts | 1 + 5 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/main/services/achievements/achievement-file-observer.ts b/src/main/services/achievements/achievement-file-observer.ts index d008304e..281ba58f 100644 --- a/src/main/services/achievements/achievement-file-observer.ts +++ b/src/main/services/achievements/achievement-file-observer.ts @@ -62,9 +62,7 @@ export const checkAchievementFileChange = async (games: Game[]) => { const achievementFileInsideDirectory = findAchievementFileInExecutableDirectory(game); - if (achievementFileInsideDirectory) { - gameAchievementFiles.push(achievementFileInsideDirectory); - } + gameAchievementFiles.push(...achievementFileInsideDirectory); if (!gameAchievementFiles.length) continue; diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index 0b694578..09823ca3 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -136,6 +136,10 @@ const getPathFromCracker = async (cracker: Cracker) => { ]; } + if (cracker === Cracker._3dm) { + return []; + } + achievementsLogger.error(`Cracker ${cracker} not implemented`); throw new Error(`Cracker ${cracker} not implemented`); }; @@ -163,22 +167,33 @@ export const findAchievementFiles = async (game: Game) => { export const findAchievementFileInExecutableDirectory = ( game: Game -): AchievementFile | null => { +): AchievementFile[] => { if (!game.executablePath) { - return null; + return []; } - const steamDataPath = path.join( - game.executablePath, - "..", - "SteamData", - "user_stats.ini" - ); - - return { - type: Cracker.userstats, - filePath: steamDataPath, - }; + return [ + { + type: Cracker.userstats, + filePath: path.join( + game.executablePath, + "..", + "SteamData", + "user_stats.ini" + ), + }, + { + type: Cracker._3dm, + filePath: path.join( + game.executablePath, + "..", + "3DMGAME", + "Player", + "stats", + "achievements.ini" + ), + }, + ]; }; export const findAllAchievementFiles = async () => { diff --git a/src/main/services/achievements/parse-achievement-file.ts b/src/main/services/achievements/parse-achievement-file.ts index e239c05e..2e05b0e2 100644 --- a/src/main/services/achievements/parse-achievement-file.ts +++ b/src/main/services/achievements/parse-achievement-file.ts @@ -45,6 +45,11 @@ export const parseAchievementFile = async ( return processSkidrow(parsed); } + if (type === Cracker._3dm) { + const parsed = await iniParse(filePath); + return process3DM(parsed); + } + achievementsLogger.log(`${type} achievements found on ${filePath}`); return []; }; @@ -139,6 +144,28 @@ const processGoldberg = (unlockedAchievements: any): UnlockedAchievement[] => { return newUnlockedAchievements; }; +const process3DM = (unlockedAchievements: any): UnlockedAchievement[] => { + const newUnlockedAchievements: UnlockedAchievement[] = []; + + const achievements = unlockedAchievements["State"]; + const times = unlockedAchievements["Time"]; + + for (const achievement of Object.keys(achievements)) { + if (achievements[achievement] == "0101") { + const time = times[achievement]; + + newUnlockedAchievements.push({ + name: achievement, + unlockTime: new DataView( + new Uint8Array(Buffer.from(time.toString(), "hex")).buffer + ).getUint32(0, true), + }); + } + } + + return newUnlockedAchievements; +}; + const processDefault = (unlockedAchievements: any): UnlockedAchievement[] => { const newUnlockedAchievements: UnlockedAchievement[] = []; diff --git a/src/main/services/achievements/update-local-unlocked-achivements.ts b/src/main/services/achievements/update-local-unlocked-achivements.ts index 6c0fed8f..4d21a603 100644 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ b/src/main/services/achievements/update-local-unlocked-achivements.ts @@ -7,6 +7,7 @@ import { parseAchievementFile } from "./parse-achievement-file"; import { mergeAchievements } from "./merge-achievements"; import type { UnlockedAchievement } from "@types"; import { getGameAchievementData } from "./get-game-achievement-data"; +import { achievementsLogger } from "../logger"; export const updateAllLocalUnlockedAchievements = async () => { const gameAchievementFilesMap = await findAllAchievementFiles(); @@ -47,10 +48,16 @@ export const updateAllLocalUnlockedAchievements = async () => { achievementFile.filePath, achievementFile.type ); - console.log("Parsed for", game.title, parsedAchievements); + if (parsedAchievements.length) { unlockedAchievements.push(...parsedAchievements); } + + achievementsLogger.log( + "Achievement file for", + game.title, + achievementFile.filePath + ); } mergeAchievements(objectId, "steam", unlockedAchievements, false); diff --git a/src/shared/constants.ts b/src/shared/constants.ts index e22d7712..4a826dcd 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -35,4 +35,5 @@ export enum Cracker { skidrow = "SKIDROW", creamAPI = "CreamAPI", smartSteamEmu = "SmartSteamEmu", + _3dm = "3dm", } From 2a6b757e3771a67e58b15ff6df6153fe3068b76a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 5 Oct 2024 00:49:52 -0300 Subject: [PATCH 02/18] feat: get achievement from game directory on launch --- .../update-local-unlocked-achivements.ts | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/main/services/achievements/update-local-unlocked-achivements.ts b/src/main/services/achievements/update-local-unlocked-achivements.ts index 4d21a603..e2defc35 100644 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ b/src/main/services/achievements/update-local-unlocked-achivements.ts @@ -2,6 +2,7 @@ import { gameAchievementRepository, gameRepository } from "@main/repository"; import { findAllAchievementFiles, findAchievementFiles, + findAchievementFileInExecutableDirectory, } from "./find-achivement-files"; import { parseAchievementFile } from "./parse-achievement-file"; import { mergeAchievements } from "./merge-achievements"; @@ -12,26 +13,30 @@ import { achievementsLogger } from "../logger"; export const updateAllLocalUnlockedAchievements = async () => { const gameAchievementFilesMap = await findAllAchievementFiles(); - for (const objectId of gameAchievementFilesMap.keys()) { - const gameAchievementFiles = gameAchievementFilesMap.get(objectId)!; + const games = await gameRepository.find({ + where: { + isDeleted: false, + }, + }); - const [game, localAchievements] = await Promise.all([ - gameRepository.findOne({ - where: { objectID: objectId, shop: "steam", isDeleted: false }, - }), - gameAchievementRepository.findOne({ - where: { objectId, shop: "steam" }, - }), - ]); + for (const game of games) { + const gameAchievementFiles = + gameAchievementFilesMap.get(game.objectID) || []; + const achievementFileInsideDirectory = + findAchievementFileInExecutableDirectory(game); - if (!game) continue; + gameAchievementFiles.push(...achievementFileInsideDirectory); + + const localAchievements = await gameAchievementRepository.findOne({ + where: { objectId: game.objectID, shop: "steam" }, + }); if (!localAchievements || !localAchievements.achievements) { - await getGameAchievementData(objectId, "steam") + await getGameAchievementData(game.objectID, "steam") .then((achievements) => { return gameAchievementRepository.upsert( { - objectId, + objectId: game.objectID, shop: "steam", achievements: JSON.stringify(achievements), }, @@ -60,7 +65,7 @@ export const updateAllLocalUnlockedAchievements = async () => { ); } - mergeAchievements(objectId, "steam", unlockedAchievements, false); + mergeAchievements(game.objectID, "steam", unlockedAchievements, false); } }; @@ -78,6 +83,11 @@ export const updateLocalUnlockedAchivements = async (objectId: string) => { const gameAchievementFiles = await findAchievementFiles(game); + const achievementFileInsideDirectory = + findAchievementFileInExecutableDirectory(game); + + gameAchievementFiles.push(...achievementFileInsideDirectory); + console.log("Achievements files for", game.title, gameAchievementFiles); if (!localAchievements || !localAchievements.achievements) { From 71e7f1ee582ce8c771ddf4d09e8ab4479ba7d186 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 5 Oct 2024 20:24:35 -0300 Subject: [PATCH 03/18] fix: process user_stats.ini --- src/main/services/achievements/parse-achievement-file.ts | 5 ++--- .../achievements/update-local-unlocked-achivements.ts | 3 ++- src/main/services/logger.ts | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/services/achievements/parse-achievement-file.ts b/src/main/services/achievements/parse-achievement-file.ts index 2e05b0e2..f16c0e2c 100644 --- a/src/main/services/achievements/parse-achievement-file.ts +++ b/src/main/services/achievements/parse-achievement-file.ts @@ -74,11 +74,10 @@ const iniParse = async (filePath: string) => { object[objectName] = {}; } else { const [name, ...value] = line.split("="); - object[objectName][name.trim()] = value.join("").trim(); + object[objectName][name.trim()] = value.join("=").trim(); } } - console.log("Parsed ini", object); return object; } catch { return null; @@ -222,7 +221,7 @@ const processUserStats = (unlockedAchievements: any): UnlockedAchievement[] => { if (!isNaN(unlockTime)) { newUnlockedAchievements.push({ - name: achievement, + name: achievement.replace(/"/g, ``), unlockTime: unlockTime, }); } diff --git a/src/main/services/achievements/update-local-unlocked-achivements.ts b/src/main/services/achievements/update-local-unlocked-achivements.ts index e2defc35..963ce602 100644 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ b/src/main/services/achievements/update-local-unlocked-achivements.ts @@ -61,7 +61,8 @@ export const updateAllLocalUnlockedAchievements = async () => { achievementsLogger.log( "Achievement file for", game.title, - achievementFile.filePath + achievementFile.filePath, + parsedAchievements ); } diff --git a/src/main/services/logger.ts b/src/main/services/logger.ts index 26a8331c..e3e52290 100644 --- a/src/main/services/logger.ts +++ b/src/main/services/logger.ts @@ -10,6 +10,10 @@ log.transports.file.resolvePathFn = ( return path.join(logsPath, "pythoninstance.txt"); } + if (message?.scope == "achievements") { + return path.join(logsPath, "achievements.txt"); + } + if (message?.level === "error") { return path.join(logsPath, "error.txt"); } From 002028130b19f780f6539676051b0dc0bce6657a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 5 Oct 2024 22:17:00 -0300 Subject: [PATCH 04/18] feat: try add FLT and fix possible bug on unlockedAchievements --- .../achievements/achievement-file-observer.ts | 79 ------------- .../achievements/achievement-watcher.ts | 108 +++++++++++++++++- .../achievements/find-achivement-files.ts | 12 +- .../achievements/merge-achievements.ts | 4 +- .../achievements/parse-achievement-file.ts | 46 +++++--- src/shared/constants.ts | 1 + 6 files changed, 153 insertions(+), 97 deletions(-) delete mode 100644 src/main/services/achievements/achievement-file-observer.ts diff --git a/src/main/services/achievements/achievement-file-observer.ts b/src/main/services/achievements/achievement-file-observer.ts deleted file mode 100644 index 281ba58f..00000000 --- a/src/main/services/achievements/achievement-file-observer.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { parseAchievementFile } from "./parse-achievement-file"; -import { Game } from "@main/entity"; -import { mergeAchievements } from "./merge-achievements"; -import fs from "node:fs"; -import { - findAchievementFileInExecutableDirectory, - findAllAchievementFiles, -} from "./find-achivement-files"; -import type { AchievementFile } from "@types"; -import { logger } from "../logger"; - -const fileStats: Map = new Map(); - -const processAchievementFileDiff = async ( - game: Game, - file: AchievementFile -) => { - const unlockedAchievements = await parseAchievementFile( - file.filePath, - file.type - ); - - logger.log("Achievements from file", file.filePath, unlockedAchievements); - - if (unlockedAchievements.length) { - return mergeAchievements( - game.objectID, - game.shop, - unlockedAchievements, - true - ); - } -}; - -const compareFile = async (game: Game, file: AchievementFile) => { - try { - const stat = fs.statSync(file.filePath); - const currentFileStat = fileStats.get(file.filePath); - fileStats.set(file.filePath, stat.mtimeMs); - - if (!currentFileStat || currentFileStat === stat.mtimeMs) { - return; - } - - logger.log( - "Detected change in file", - file.filePath, - stat.mtimeMs, - fileStats.get(file.filePath) - ); - await processAchievementFileDiff(game, file); - } catch (err) { - fileStats.set(file.filePath, -1); - } -}; - -export const checkAchievementFileChange = async (games: Game[]) => { - const achievementFiles = await findAllAchievementFiles(); - - for (const game of games) { - const gameAchievementFiles = achievementFiles.get(game.objectID) || []; - const achievementFileInsideDirectory = - findAchievementFileInExecutableDirectory(game); - - gameAchievementFiles.push(...achievementFileInsideDirectory); - - if (!gameAchievementFiles.length) continue; - - logger.log( - "Achievements files to observe for:", - game.title, - gameAchievementFiles - ); - - for (const file of gameAchievementFiles) { - compareFile(game, file); - } - } -}; diff --git a/src/main/services/achievements/achievement-watcher.ts b/src/main/services/achievements/achievement-watcher.ts index d88771a8..bc00ddd3 100644 --- a/src/main/services/achievements/achievement-watcher.ts +++ b/src/main/services/achievements/achievement-watcher.ts @@ -1,5 +1,18 @@ import { gameRepository } from "@main/repository"; -import { checkAchievementFileChange as searchForAchievements } from "./achievement-file-observer"; +import { parseAchievementFile } from "./parse-achievement-file"; +import { Game } from "@main/entity"; +import { mergeAchievements } from "./merge-achievements"; +import fs, { readdirSync } from "node:fs"; +import { + findAchievementFileInExecutableDirectory, + findAllAchievementFiles, +} from "./find-achivement-files"; +import type { AchievementFile } from "@types"; +import { achievementsLogger, logger } from "../logger"; +import { Cracker } from "@shared"; + +const fileStats: Map = new Map(); +const fltFiles: Map> = new Map(); export const watchAchievements = async () => { const games = await gameRepository.find({ @@ -12,3 +25,96 @@ export const watchAchievements = async () => { await searchForAchievements(games); }; + +const processAchievementFileDiff = async ( + game: Game, + file: AchievementFile +) => { + const unlockedAchievements = await parseAchievementFile( + file.filePath, + file.type + ); + + logger.log("Achievements from file", file.filePath, unlockedAchievements); + + if (unlockedAchievements.length) { + return mergeAchievements( + game.objectID, + game.shop, + unlockedAchievements, + true + ); + } +}; + +const compareFltFolder = async (game: Game, file: AchievementFile) => { + try { + const currentAchievements = new Set(readdirSync(file.filePath)); + const previousAchievements = fltFiles.get(file.filePath); + + fltFiles.set(file.filePath, currentAchievements); + if ( + !previousAchievements || + currentAchievements.difference(previousAchievements).size === 0 + ) { + return; + } + + logger.log("Detected change in FLT folder", file.filePath); + await processAchievementFileDiff(game, file); + } catch (err) { + achievementsLogger.error(err); + fltFiles.set(file.filePath, new Set()); + } +}; + +const compareFile = async (game: Game, file: AchievementFile) => { + if (file.type === Cracker.flt) { + await compareFltFolder(game, file); + return; + } + + try { + const currentStat = fs.statSync(file.filePath); + const previousStat = fileStats.get(file.filePath); + fileStats.set(file.filePath, currentStat.mtimeMs); + + if (!previousStat || previousStat === currentStat.mtimeMs) { + return; + } + + logger.log( + "Detected change in file", + file.filePath, + currentStat.mtimeMs, + fileStats.get(file.filePath) + ); + await processAchievementFileDiff(game, file); + } catch (err) { + fileStats.set(file.filePath, -1); + } +}; + +const searchForAchievements = async (games: Game[]) => { + const achievementFiles = await findAllAchievementFiles(); + + for (const game of games) { + const gameAchievementFiles = achievementFiles.get(game.objectID) || []; + const achievementFileInsideDirectory = + findAchievementFileInExecutableDirectory(game); + + gameAchievementFiles.push(...achievementFileInsideDirectory); + + if (!gameAchievementFiles.length) continue; + + logger.log( + "Achievements files to observe for:", + game.title, + gameAchievementFiles + ); + + for (const file of gameAchievementFiles) { + compareFile(game, file); + } + } +}; diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index 09823ca3..247888e6 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -24,6 +24,7 @@ const crackers = [ Cracker.skidrow, Cracker.smartSteamEmu, Cracker.empress, + Cracker.flt, ]; const getPathFromCracker = async (cracker: Cracker) => { @@ -131,7 +132,7 @@ const getPathFromCracker = async (cracker: Cracker) => { return [ { folderPath: path.join(appData, "SmartSteamEmu"), - fileLocation: ["User", "Achievements"], + fileLocation: ["User", "Achievements.ini"], }, ]; } @@ -140,6 +141,15 @@ const getPathFromCracker = async (cracker: Cracker) => { return []; } + if (cracker === Cracker.flt) { + return [ + { + folderPath: path.join(appData, "FLT"), + fileLocation: ["stats"], + }, + ]; + } + achievementsLogger.error(`Cracker ${cracker} not implemented`); throw new Error(`Cracker ${cracker} not implemented`); }; diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index dd4bd465..b611f2a2 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -47,7 +47,7 @@ export const mergeAchievements = async ( const unlockedAchievements = JSON.parse( localGameAchievement?.unlockedAchievements || "[]" - ); + ).filter((achievement) => achievement.name); const newAchievements = achievements .filter((achievement) => { @@ -60,7 +60,7 @@ export const mergeAchievements = async ( .map((achievement) => { return { name: achievement.name.toUpperCase(), - unlockTime: achievement.unlockTime * 1000, + unlockTime: achievement.unlockTime, }; }); diff --git a/src/main/services/achievements/parse-achievement-file.ts b/src/main/services/achievements/parse-achievement-file.ts index f16c0e2c..251fd1f9 100644 --- a/src/main/services/achievements/parse-achievement-file.ts +++ b/src/main/services/achievements/parse-achievement-file.ts @@ -1,6 +1,11 @@ import { Cracker } from "@shared"; import { UnlockedAchievement } from "@types"; -import { existsSync, createReadStream, readFileSync } from "node:fs"; +import { + existsSync, + createReadStream, + readFileSync, + readdirSync, +} from "node:fs"; import readline from "node:readline"; import { achievementsLogger } from "../logger"; @@ -50,6 +55,17 @@ export const parseAchievementFile = async ( return process3DM(parsed); } + if (type === Cracker.flt) { + const achievements = readdirSync(filePath); + + return achievements.map((achievement) => { + return { + name: achievement, + unlockTime: Date.now(), + }; + }); + } + achievementsLogger.log(`${type} achievements found on ${filePath}`); return []; }; @@ -101,7 +117,7 @@ const processOnlineFix = (unlockedAchievements: any): UnlockedAchievement[] => { if (unlockedAchievement?.achieved) { parsedUnlockedAchievements.push({ name: achievement, - unlockTime: unlockedAchievement.timestamp, + unlockTime: unlockedAchievement.timestamp * 1000, }); } } @@ -119,7 +135,7 @@ const processSkidrow = (unlockedAchievements: any): UnlockedAchievement[] => { if (unlockedAchievement[0] === "1") { parsedUnlockedAchievements.push({ name: achievement, - unlockTime: unlockedAchievement[unlockedAchievement.length - 1], + unlockTime: unlockedAchievement[unlockedAchievement.length - 1] * 1000, }); } } @@ -136,7 +152,7 @@ const processGoldberg = (unlockedAchievements: any): UnlockedAchievement[] => { if (unlockedAchievement?.earned) { newUnlockedAchievements.push({ name: achievement, - unlockTime: unlockedAchievement.earned_time, + unlockTime: unlockedAchievement.earned_time * 1000, }); } } @@ -155,9 +171,10 @@ const process3DM = (unlockedAchievements: any): UnlockedAchievement[] => { newUnlockedAchievements.push({ name: achievement, - unlockTime: new DataView( - new Uint8Array(Buffer.from(time.toString(), "hex")).buffer - ).getUint32(0, true), + unlockTime: + new DataView( + new Uint8Array(Buffer.from(time.toString(), "hex")).buffer + ).getUint32(0, true) * 1000, }); } } @@ -174,7 +191,7 @@ const processDefault = (unlockedAchievements: any): UnlockedAchievement[] => { if (unlockedAchievement?.Achieved) { newUnlockedAchievements.push({ name: achievement, - unlockTime: unlockedAchievement.UnlockTime, + unlockTime: unlockedAchievement.UnlockTime * 1000, }); } } @@ -193,11 +210,12 @@ const processRld = (unlockedAchievements: any): UnlockedAchievement[] => { if (unlockedAchievement?.State) { newUnlockedAchievements.push({ name: achievement, - unlockTime: new DataView( - new Uint8Array( - Buffer.from(unlockedAchievement.Time.toString(), "hex") - ).buffer - ).getUint32(0, true), + unlockTime: + new DataView( + new Uint8Array( + Buffer.from(unlockedAchievement.Time.toString(), "hex") + ).buffer + ).getUint32(0, true) * 1000, }); } } @@ -222,7 +240,7 @@ const processUserStats = (unlockedAchievements: any): UnlockedAchievement[] => { if (!isNaN(unlockTime)) { newUnlockedAchievements.push({ name: achievement.replace(/"/g, ``), - unlockTime: unlockTime, + unlockTime: unlockTime * 1000, }); } } diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 4a826dcd..6a94c103 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -36,4 +36,5 @@ export enum Cracker { creamAPI = "CreamAPI", smartSteamEmu = "SmartSteamEmu", _3dm = "3dm", + flt = "FLT", } From 57118ec5b9d5e78218373e9f156cb42875699cf5 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 5 Oct 2024 22:43:05 -0300 Subject: [PATCH 05/18] feat: add rle folder --- .../services/achievements/find-achivement-files.ts | 13 +++++++++++++ src/shared/constants.ts | 1 + 2 files changed, 14 insertions(+) diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index 247888e6..5b3a08d5 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -150,6 +150,19 @@ const getPathFromCracker = async (cracker: Cracker) => { ]; } + if (cracker == Cracker.rle) { + return [ + { + folderPath: path.join(appData, "RLE"), + fileLocation: ["achievements.ini"], + }, + { + folderPath: path.join(appData, "RLE"), + fileLocation: ["Achievements.ini"], + }, + ]; + } + achievementsLogger.error(`Cracker ${cracker} not implemented`); throw new Error(`Cracker ${cracker} not implemented`); }; diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 6a94c103..0a466695 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -37,4 +37,5 @@ export enum Cracker { smartSteamEmu = "SmartSteamEmu", _3dm = "3dm", flt = "FLT", + rle = "RLE", } From 1ea64d7243408a7ae105fc254f02ea765c52797b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 5 Oct 2024 23:07:14 -0300 Subject: [PATCH 06/18] fix: file not being processed if it was created after watcher started --- src/main/services/achievements/achievement-watcher.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/services/achievements/achievement-watcher.ts b/src/main/services/achievements/achievement-watcher.ts index bc00ddd3..45e3852a 100644 --- a/src/main/services/achievements/achievement-watcher.ts +++ b/src/main/services/achievements/achievement-watcher.ts @@ -79,7 +79,14 @@ const compareFile = async (game: Game, file: AchievementFile) => { const previousStat = fileStats.get(file.filePath); fileStats.set(file.filePath, currentStat.mtimeMs); - if (!previousStat || previousStat === currentStat.mtimeMs) { + if (!previousStat) { + if (currentStat.mtimeMs) { + await processAchievementFileDiff(game, file); + return; + } + } + + if (previousStat === currentStat.mtimeMs) { return; } From f5da836b1be68a424a26397193c1e0fff62b4302 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sat, 5 Oct 2024 23:10:32 -0300 Subject: [PATCH 07/18] feat: remove unneeded log persist --- .../achievements/achievement-watcher.ts | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/main/services/achievements/achievement-watcher.ts b/src/main/services/achievements/achievement-watcher.ts index 45e3852a..eecb71d5 100644 --- a/src/main/services/achievements/achievement-watcher.ts +++ b/src/main/services/achievements/achievement-watcher.ts @@ -23,7 +23,27 @@ export const watchAchievements = async () => { if (games.length === 0) return; - await searchForAchievements(games); + const achievementFiles = await findAllAchievementFiles(); + + for (const game of games) { + const gameAchievementFiles = achievementFiles.get(game.objectID) || []; + const achievementFileInsideDirectory = + findAchievementFileInExecutableDirectory(game); + + gameAchievementFiles.push(...achievementFileInsideDirectory); + + if (!gameAchievementFiles.length) continue; + + console.log( + "Achievements files to observe for:", + game.title, + gameAchievementFiles + ); + + for (const file of gameAchievementFiles) { + compareFile(game, file); + } + } }; const processAchievementFileDiff = async ( @@ -101,27 +121,3 @@ const compareFile = async (game: Game, file: AchievementFile) => { fileStats.set(file.filePath, -1); } }; - -const searchForAchievements = async (games: Game[]) => { - const achievementFiles = await findAllAchievementFiles(); - - for (const game of games) { - const gameAchievementFiles = achievementFiles.get(game.objectID) || []; - const achievementFileInsideDirectory = - findAchievementFileInExecutableDirectory(game); - - gameAchievementFiles.push(...achievementFileInsideDirectory); - - if (!gameAchievementFiles.length) continue; - - logger.log( - "Achievements files to observe for:", - game.title, - gameAchievementFiles - ); - - for (const file of gameAchievementFiles) { - compareFile(game, file); - } - } -}; From 456e7ed809d769cd20becf4489e802cfa8963e81 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 6 Oct 2024 02:32:22 -0300 Subject: [PATCH 08/18] feat: add alternative objectIds --- .../achievements/find-achivement-files.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index 5b3a08d5..c7aa4950 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -167,6 +167,14 @@ const getPathFromCracker = async (cracker: Cracker) => { throw new Error(`Cracker ${cracker} not implemented`); }; +const getAlternativeObjectIds = (objectId: string) => { + if (objectId === "205100") { + return ["205100", "217980", "31292"]; + } + + return [objectId]; +}; + export const findAchievementFiles = async (game: Game) => { const achievementFiles: AchievementFile[] = []; @@ -174,13 +182,15 @@ export const findAchievementFiles = async (game: Game) => { for (const { folderPath, fileLocation } of await getPathFromCracker( cracker )) { - const filePath = path.join(folderPath, game.objectID, ...fileLocation); + for (const objectId of getAlternativeObjectIds(game.objectID)) { + const filePath = path.join(folderPath, objectId, ...fileLocation); - if (fs.existsSync(filePath)) { - achievementFiles.push({ - type: cracker, - filePath, - }); + if (fs.existsSync(filePath)) { + achievementFiles.push({ + type: cracker, + filePath, + }); + } } } } From 5d21adcbb1f3dc095cde5fe44e35a950fa543207 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 6 Oct 2024 02:48:46 -0300 Subject: [PATCH 09/18] wip: get alternate objectIds --- .../achievements/achievement-watcher.ts | 29 ++++--- .../achievements/find-achivement-files.ts | 16 ++-- .../update-local-unlocked-achivements.ts | 86 ++++++++++--------- 3 files changed, 66 insertions(+), 65 deletions(-) diff --git a/src/main/services/achievements/achievement-watcher.ts b/src/main/services/achievements/achievement-watcher.ts index eecb71d5..15496a0e 100644 --- a/src/main/services/achievements/achievement-watcher.ts +++ b/src/main/services/achievements/achievement-watcher.ts @@ -6,6 +6,7 @@ import fs, { readdirSync } from "node:fs"; import { findAchievementFileInExecutableDirectory, findAllAchievementFiles, + getAlternativeObjectIds, } from "./find-achivement-files"; import type { AchievementFile } from "@types"; import { achievementsLogger, logger } from "../logger"; @@ -23,25 +24,27 @@ export const watchAchievements = async () => { if (games.length === 0) return; - const achievementFiles = await findAllAchievementFiles(); + const achievementFiles = findAllAchievementFiles(); for (const game of games) { - const gameAchievementFiles = achievementFiles.get(game.objectID) || []; - const achievementFileInsideDirectory = - findAchievementFileInExecutableDirectory(game); + for (const objectId of getAlternativeObjectIds(game.objectID)) { + const gameAchievementFiles = achievementFiles.get(objectId) || []; + const achievementFileInsideDirectory = + findAchievementFileInExecutableDirectory(game); - gameAchievementFiles.push(...achievementFileInsideDirectory); + gameAchievementFiles.push(...achievementFileInsideDirectory); - if (!gameAchievementFiles.length) continue; + if (!gameAchievementFiles.length) continue; - console.log( - "Achievements files to observe for:", - game.title, - gameAchievementFiles - ); + console.log( + "Achievements files to observe for:", + game.title, + gameAchievementFiles + ); - for (const file of gameAchievementFiles) { - compareFile(game, file); + for (const file of gameAchievementFiles) { + compareFile(game, file); + } } } }; diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index c7aa4950..d13ffb63 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -27,7 +27,7 @@ const crackers = [ Cracker.flt, ]; -const getPathFromCracker = async (cracker: Cracker) => { +const getPathFromCracker = (cracker: Cracker) => { if (cracker === Cracker.codex) { return [ { @@ -167,7 +167,7 @@ const getPathFromCracker = async (cracker: Cracker) => { throw new Error(`Cracker ${cracker} not implemented`); }; -const getAlternativeObjectIds = (objectId: string) => { +export const getAlternativeObjectIds = (objectId: string) => { if (objectId === "205100") { return ["205100", "217980", "31292"]; } @@ -175,13 +175,11 @@ const getAlternativeObjectIds = (objectId: string) => { return [objectId]; }; -export const findAchievementFiles = async (game: Game) => { +export const findAchievementFiles = (game: Game) => { const achievementFiles: AchievementFile[] = []; for (const cracker of crackers) { - for (const { folderPath, fileLocation } of await getPathFromCracker( - cracker - )) { + for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) { for (const objectId of getAlternativeObjectIds(game.objectID)) { const filePath = path.join(folderPath, objectId, ...fileLocation); @@ -229,13 +227,11 @@ export const findAchievementFileInExecutableDirectory = ( ]; }; -export const findAllAchievementFiles = async () => { +export const findAllAchievementFiles = () => { const gameAchievementFiles = new Map(); for (const cracker of crackers) { - for (const { folderPath, fileLocation } of await getPathFromCracker( - cracker - )) { + for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) { if (!fs.existsSync(folderPath)) { continue; } diff --git a/src/main/services/achievements/update-local-unlocked-achivements.ts b/src/main/services/achievements/update-local-unlocked-achivements.ts index 963ce602..a58cd69f 100644 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ b/src/main/services/achievements/update-local-unlocked-achivements.ts @@ -3,6 +3,7 @@ import { findAllAchievementFiles, findAchievementFiles, findAchievementFileInExecutableDirectory, + getAlternativeObjectIds, } from "./find-achivement-files"; import { parseAchievementFile } from "./parse-achievement-file"; import { mergeAchievements } from "./merge-achievements"; @@ -11,7 +12,7 @@ import { getGameAchievementData } from "./get-game-achievement-data"; import { achievementsLogger } from "../logger"; export const updateAllLocalUnlockedAchievements = async () => { - const gameAchievementFilesMap = await findAllAchievementFiles(); + const gameAchievementFilesMap = findAllAchievementFiles(); const games = await gameRepository.find({ where: { @@ -20,53 +21,54 @@ export const updateAllLocalUnlockedAchievements = async () => { }); for (const game of games) { - const gameAchievementFiles = - gameAchievementFilesMap.get(game.objectID) || []; - const achievementFileInsideDirectory = - findAchievementFileInExecutableDirectory(game); + for (const objectId of getAlternativeObjectIds(game.objectID)) { + const gameAchievementFiles = gameAchievementFilesMap.get(objectId) || []; + const achievementFileInsideDirectory = + findAchievementFileInExecutableDirectory(game); - gameAchievementFiles.push(...achievementFileInsideDirectory); + gameAchievementFiles.push(...achievementFileInsideDirectory); - const localAchievements = await gameAchievementRepository.findOne({ - where: { objectId: game.objectID, shop: "steam" }, - }); + const localAchievements = await gameAchievementRepository.findOne({ + where: { objectId: game.objectID, shop: "steam" }, + }); - if (!localAchievements || !localAchievements.achievements) { - await getGameAchievementData(game.objectID, "steam") - .then((achievements) => { - return gameAchievementRepository.upsert( - { - objectId: game.objectID, - shop: "steam", - achievements: JSON.stringify(achievements), - }, - ["objectId", "shop"] - ); - }) - .catch(() => {}); - } - - const unlockedAchievements: UnlockedAchievement[] = []; - - for (const achievementFile of gameAchievementFiles) { - const parsedAchievements = await parseAchievementFile( - achievementFile.filePath, - achievementFile.type - ); - - if (parsedAchievements.length) { - unlockedAchievements.push(...parsedAchievements); + if (!localAchievements || !localAchievements.achievements) { + await getGameAchievementData(game.objectID, "steam") + .then((achievements) => { + return gameAchievementRepository.upsert( + { + objectId: game.objectID, + shop: "steam", + achievements: JSON.stringify(achievements), + }, + ["objectId", "shop"] + ); + }) + .catch(() => {}); } - achievementsLogger.log( - "Achievement file for", - game.title, - achievementFile.filePath, - parsedAchievements - ); - } + const unlockedAchievements: UnlockedAchievement[] = []; - mergeAchievements(game.objectID, "steam", unlockedAchievements, false); + for (const achievementFile of gameAchievementFiles) { + const parsedAchievements = await parseAchievementFile( + achievementFile.filePath, + achievementFile.type + ); + + if (parsedAchievements.length) { + unlockedAchievements.push(...parsedAchievements); + } + + achievementsLogger.log( + "Achievement file for", + game.title, + achievementFile.filePath, + parsedAchievements + ); + } + + mergeAchievements(game.objectID, "steam", unlockedAchievements, false); + } } }; From af83152997c13df54bf22bb0ee4c0250d41bd9e8 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 6 Oct 2024 11:18:14 -0300 Subject: [PATCH 10/18] feat: creamapi process --- .../achievements/parse-achievement-file.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/services/achievements/parse-achievement-file.ts b/src/main/services/achievements/parse-achievement-file.ts index 251fd1f9..5817b032 100644 --- a/src/main/services/achievements/parse-achievement-file.ts +++ b/src/main/services/achievements/parse-achievement-file.ts @@ -66,6 +66,11 @@ export const parseAchievementFile = async ( }); } + if (type === Cracker.creamAPI) { + const parsed = await iniParse(filePath); + return processCreamAPI(parsed); + } + achievementsLogger.log(`${type} achievements found on ${filePath}`); return []; }; @@ -125,6 +130,27 @@ const processOnlineFix = (unlockedAchievements: any): UnlockedAchievement[] => { return parsedUnlockedAchievements; }; +const processCreamAPI = (unlockedAchievements: any): UnlockedAchievement[] => { + const parsedUnlockedAchievements: UnlockedAchievement[] = []; + + for (const achievement of Object.keys(unlockedAchievements)) { + const unlockedAchievement = unlockedAchievements[achievement]; + + if (unlockedAchievement?.achieved) { + const unlockTime = unlockedAchievement.unlocktime; + parsedUnlockedAchievements.push({ + name: achievement, + unlockTime: + unlockTime.length === 7 + ? unlockTime * 1000 * 1000 + : unlockTime * 1000, + }); + } + } + + return parsedUnlockedAchievements; +}; + const processSkidrow = (unlockedAchievements: any): UnlockedAchievement[] => { const parsedUnlockedAchievements: UnlockedAchievement[] = []; const achievements = unlockedAchievements["Achievements"]; From 7f09a7796f8dd6cc0c17dd5b2d30d4eea97727dd Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 6 Oct 2024 13:39:26 -0300 Subject: [PATCH 11/18] feat: remove awaits --- .../events/catalogue/get-game-achievements.ts | 67 ++++--------------- .../events/library/add-game-to-library.ts | 4 +- .../achievements/achievement-watcher.ts | 5 +- .../achievements/find-achivement-files.ts | 1 + .../achievements/get-game-achievement-data.ts | 20 +++++- .../achievements/parse-achievement-file.ts | 51 ++++++-------- .../update-local-unlocked-achivements.ts | 64 +++++------------- src/main/services/main-loop.ts | 2 +- 8 files changed, 72 insertions(+), 142 deletions(-) diff --git a/src/main/events/catalogue/get-game-achievements.ts b/src/main/events/catalogue/get-game-achievements.ts index f0fa2c94..456babf4 100644 --- a/src/main/events/catalogue/get-game-achievements.ts +++ b/src/main/events/catalogue/get-game-achievements.ts @@ -1,88 +1,45 @@ import type { GameAchievement, GameShop } from "@types"; import { registerEvent } from "../register-event"; -import { HydraApi } from "@main/services"; -import { - gameAchievementRepository, - gameRepository, - userPreferencesRepository, -} from "@main/repository"; -import { UserNotLoggedInError } from "@shared"; -import { Game } from "@main/entity"; - -const getAchievementsDataFromApi = async ( - objectId: string, - shop: string, - game: Game | null -) => { - const userPreferences = await userPreferencesRepository.findOne({ - where: { id: 1 }, - }); - - return HydraApi.get("/games/achievements", { - objectId, - shop, - language: userPreferences?.language || "en", - }) - .then((achievements) => { - if (game) { - gameAchievementRepository.upsert( - { - objectId, - shop, - achievements: JSON.stringify(achievements), - }, - ["objectId", "shop"] - ); - } - - return achievements; - }) - .catch((err) => { - if (err instanceof UserNotLoggedInError) throw err; - return []; - }); -}; +import { gameAchievementRepository } from "@main/repository"; +import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data"; const getGameAchievements = async ( _event: Electron.IpcMainInvokeEvent, objectId: string, shop: GameShop ): Promise => { - const [game, cachedAchievements] = await Promise.all([ - gameRepository.findOne({ - where: { objectID: objectId, shop }, - }), - gameAchievementRepository.findOne({ where: { objectId, shop } }), - ]); + const cachedAchievements = await gameAchievementRepository.findOne({ + where: { objectId, shop }, + }); - const gameAchievements = cachedAchievements?.achievements + const achievementsData = cachedAchievements?.achievements ? JSON.parse(cachedAchievements.achievements) - : await getAchievementsDataFromApi(objectId, shop, game); + : await getGameAchievementData(objectId, shop); const unlockedAchievements = JSON.parse( cachedAchievements?.unlockedAchievements || "[]" ) as { name: string; unlockTime: number }[]; - return gameAchievements - .map((achievement) => { + return achievementsData + .map((achievementData) => { const unlockedAchiement = unlockedAchievements.find( (localAchievement) => { return ( localAchievement.name.toUpperCase() == - achievement.name.toUpperCase() + achievementData.name.toUpperCase() ); } ); if (unlockedAchiement) { return { - ...achievement, + ...achievementData, unlocked: true, unlockTime: unlockedAchiement.unlockTime, }; } - return { ...achievement, unlocked: false, unlockTime: null }; + return { ...achievementData, unlocked: false, unlockTime: null }; }) .sort((a, b) => { if (a.unlocked && !b.unlocked) return -1; diff --git a/src/main/events/library/add-game-to-library.ts b/src/main/events/library/add-game-to-library.ts index c6ec63c0..a47c96e5 100644 --- a/src/main/events/library/add-game-to-library.ts +++ b/src/main/events/library/add-game-to-library.ts @@ -44,10 +44,10 @@ const addGameToLibrary = async ( }); } - updateLocalUnlockedAchivements(objectID); - const game = await gameRepository.findOne({ where: { objectID } }); + updateLocalUnlockedAchivements(game!); + createGame(game!).catch(() => {}); }); }; diff --git a/src/main/services/achievements/achievement-watcher.ts b/src/main/services/achievements/achievement-watcher.ts index 15496a0e..166e6cff 100644 --- a/src/main/services/achievements/achievement-watcher.ts +++ b/src/main/services/achievements/achievement-watcher.ts @@ -53,10 +53,7 @@ const processAchievementFileDiff = async ( game: Game, file: AchievementFile ) => { - const unlockedAchievements = await parseAchievementFile( - file.filePath, - file.type - ); + const unlockedAchievements = parseAchievementFile(file.filePath, file.type); logger.log("Achievements from file", file.filePath, unlockedAchievements); diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index d13ffb63..ebfbefca 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -168,6 +168,7 @@ const getPathFromCracker = (cracker: Cracker) => { }; export const getAlternativeObjectIds = (objectId: string) => { + // Dishonored if (objectId === "205100") { return ["205100", "217980", "31292"]; } diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 79e551ac..7f1e6b5a 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -1,4 +1,7 @@ -import { userPreferencesRepository } from "@main/repository"; +import { + gameAchievementRepository, + userPreferencesRepository, +} from "@main/repository"; import { HydraApi } from "../hydra-api"; export const getGameAchievementData = async ( @@ -13,5 +16,18 @@ export const getGameAchievementData = async ( shop, objectId, language: userPreferences?.language || "en", - }); + }) + .then(async (achievements) => { + await gameAchievementRepository.upsert( + { + objectId, + shop, + achievements: JSON.stringify(achievements), + }, + ["objectId", "shop"] + ); + + return achievements; + }) + .catch(() => []); }; diff --git a/src/main/services/achievements/parse-achievement-file.ts b/src/main/services/achievements/parse-achievement-file.ts index 5817b032..47c8e805 100644 --- a/src/main/services/achievements/parse-achievement-file.ts +++ b/src/main/services/achievements/parse-achievement-file.ts @@ -1,57 +1,51 @@ import { Cracker } from "@shared"; import { UnlockedAchievement } from "@types"; -import { - existsSync, - createReadStream, - readFileSync, - readdirSync, -} from "node:fs"; -import readline from "node:readline"; +import { existsSync, readFileSync, readdirSync } from "node:fs"; import { achievementsLogger } from "../logger"; -export const parseAchievementFile = async ( +export const parseAchievementFile = ( filePath: string, type: Cracker -): Promise => { +): UnlockedAchievement[] => { if (!existsSync(filePath)) return []; if (type == Cracker.codex) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processDefault(parsed); } if (type == Cracker.rune) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processDefault(parsed); } if (type === Cracker.onlineFix) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processOnlineFix(parsed); } if (type === Cracker.goldberg) { - const parsed = await jsonParse(filePath); + const parsed = jsonParse(filePath); return processGoldberg(parsed); } if (type == Cracker.userstats) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processUserStats(parsed); } if (type == Cracker.rld) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processRld(parsed); } if (type === Cracker.skidrow) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processSkidrow(parsed); } if (type === Cracker._3dm) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return process3DM(parsed); } @@ -67,27 +61,24 @@ export const parseAchievementFile = async ( } if (type === Cracker.creamAPI) { - const parsed = await iniParse(filePath); + const parsed = iniParse(filePath); return processCreamAPI(parsed); } - achievementsLogger.log(`${type} achievements found on ${filePath}`); + achievementsLogger.log( + `Unprocessed ${type} achievements found on ${filePath}` + ); return []; }; -const iniParse = async (filePath: string) => { +const iniParse = (filePath: string) => { try { - const file = createReadStream(filePath); - - const lines = readline.createInterface({ - input: file, - crlfDelay: Infinity, - }); + const lines = readFileSync(filePath, "utf-8").split(/[\r\n]+/); let objectName = ""; const object: Record> = {}; - for await (const line of lines) { + for (const line of lines) { if (line.startsWith("###") || !line.length) continue; if (line.startsWith("[") && line.endsWith("]")) { @@ -100,7 +91,8 @@ const iniParse = async (filePath: string) => { } return object; - } catch { + } catch (err) { + achievementsLogger.error(`Error parsing ${filePath}`, err); return null; } }; @@ -108,7 +100,8 @@ const iniParse = async (filePath: string) => { const jsonParse = (filePath: string) => { try { return JSON.parse(readFileSync(filePath, "utf-8")); - } catch { + } catch (err) { + achievementsLogger.error(`Error parsing ${filePath}`, err); return null; } }; diff --git a/src/main/services/achievements/update-local-unlocked-achivements.ts b/src/main/services/achievements/update-local-unlocked-achivements.ts index a58cd69f..13f33fcd 100644 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ b/src/main/services/achievements/update-local-unlocked-achivements.ts @@ -10,6 +10,7 @@ import { mergeAchievements } from "./merge-achievements"; import type { UnlockedAchievement } from "@types"; import { getGameAchievementData } from "./get-game-achievement-data"; import { achievementsLogger } from "../logger"; +import { Game } from "@main/entity"; export const updateAllLocalUnlockedAchievements = async () => { const gameAchievementFilesMap = findAllAchievementFiles(); @@ -28,29 +29,20 @@ export const updateAllLocalUnlockedAchievements = async () => { gameAchievementFiles.push(...achievementFileInsideDirectory); - const localAchievements = await gameAchievementRepository.findOne({ - where: { objectId: game.objectID, shop: "steam" }, - }); - - if (!localAchievements || !localAchievements.achievements) { - await getGameAchievementData(game.objectID, "steam") - .then((achievements) => { - return gameAchievementRepository.upsert( - { - objectId: game.objectID, - shop: "steam", - achievements: JSON.stringify(achievements), - }, - ["objectId", "shop"] - ); - }) - .catch(() => {}); - } + gameAchievementRepository + .findOne({ + where: { objectId: game.objectID, shop: "steam" }, + }) + .then((localAchievements) => { + if (!localAchievements || !localAchievements.achievements) { + getGameAchievementData(game.objectID, "steam"); + } + }); const unlockedAchievements: UnlockedAchievement[] = []; for (const achievementFile of gameAchievementFiles) { - const parsedAchievements = await parseAchievementFile( + const parsedAchievements = parseAchievementFile( achievementFile.filePath, achievementFile.type ); @@ -72,19 +64,8 @@ export const updateAllLocalUnlockedAchievements = async () => { } }; -export const updateLocalUnlockedAchivements = async (objectId: string) => { - const [game, localAchievements] = await Promise.all([ - gameRepository.findOne({ - where: { objectID: objectId, shop: "steam", isDeleted: false }, - }), - gameAchievementRepository.findOne({ - where: { objectId, shop: "steam" }, - }), - ]); - - if (!game) return; - - const gameAchievementFiles = await findAchievementFiles(game); +export const updateLocalUnlockedAchivements = async (game: Game) => { + const gameAchievementFiles = findAchievementFiles(game); const achievementFileInsideDirectory = findAchievementFileInExecutableDirectory(game); @@ -93,25 +74,10 @@ export const updateLocalUnlockedAchivements = async (objectId: string) => { console.log("Achievements files for", game.title, gameAchievementFiles); - if (!localAchievements || !localAchievements.achievements) { - await getGameAchievementData(objectId, "steam") - .then((achievements) => { - return gameAchievementRepository.upsert( - { - objectId, - shop: "steam", - achievements: JSON.stringify(achievements), - }, - ["objectId", "shop"] - ); - }) - .catch(() => {}); - } - const unlockedAchievements: UnlockedAchievement[] = []; for (const achievementFile of gameAchievementFiles) { - const localAchievementFile = await parseAchievementFile( + const localAchievementFile = parseAchievementFile( achievementFile.filePath, achievementFile.type ); @@ -121,5 +87,5 @@ export const updateLocalUnlockedAchivements = async (objectId: string) => { } } - mergeAchievements(objectId, "steam", unlockedAchievements, false); + mergeAchievements(game.objectID, "steam", unlockedAchievements, false); }; diff --git a/src/main/services/main-loop.ts b/src/main/services/main-loop.ts index f45956f2..5ba57fc3 100644 --- a/src/main/services/main-loop.ts +++ b/src/main/services/main-loop.ts @@ -12,6 +12,6 @@ export const startMainLoop = async () => { watchAchievements(), ]); - await sleep(1000); + await sleep(1500); } }; From 7c9c27801f43e8f5150f847ecad0cd5720298970 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:52:25 -0300 Subject: [PATCH 12/18] feat: add dodi folder --- .../achievements/find-achivement-files.ts | 4 + .../pages/game-details/sidebar/sidebar.css.ts | 1 + .../pages/game-details/sidebar/sidebar.tsx | 81 +++++++++++-------- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/main/services/achievements/find-achivement-files.ts b/src/main/services/achievements/find-achivement-files.ts index ebfbefca..917e5351 100644 --- a/src/main/services/achievements/find-achivement-files.ts +++ b/src/main/services/achievements/find-achivement-files.ts @@ -86,6 +86,10 @@ const getPathFromCracker = (cracker: Cracker) => { folderPath: path.join(programData, "Steam", "Player"), fileLocation: ["stats", "achievements.ini"], }, + { + folderPath: path.join(programData, "Steam", "dodi"), + fileLocation: ["stats", "achievements.ini"], + }, ]; } diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts b/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts index 783e4ffa..d1a0e8cd 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts @@ -24,6 +24,7 @@ export const contentSidebarTitle = style({ display: "flex", alignItems: "center", backgroundColor: vars.color.background, + justifyContent: "space-between", }); export const requirementButtonContainer = style({ diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index e4d8b1b6..55b2481c 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -49,44 +49,55 @@ export function Sidebar() { /> */} {achievements.length > 0 && ( -
- {achievements.map((achievement, index) => ( -
- +
+

{t("achievements")}

+ {achievements.length} +
+
+ {achievements.map((achievement, index) => ( +
-
-

{achievement.displayName}

- {achievement.unlockTime && format(achievement.unlockTime)} + title={achievement.description} + > + {achievement.displayName} +
+

{achievement.displayName}

+ {achievement.unlockTime && format(achievement.unlockTime)} +
-
- ))} -
+ ))} +
+ )} {stats && ( From 6d4f957e2bc6e609b2a7dc062eb50f08d721844f Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:26:36 -0300 Subject: [PATCH 13/18] feat: add achievement section title --- src/locales/en/translation.json | 3 ++- src/locales/pt-BR/translation.json | 3 ++- src/locales/pt-PT/translation.json | 3 ++- .../src/pages/game-details/sidebar/sidebar.tsx | 15 ++++++++++----- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 0f907ced..2849bb20 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -130,7 +130,8 @@ "download": "Download", "executable_path_in_use": "Executable already in use by \"{{game}}\"", "warning": "Warning:", - "hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util its conclusion. In case Hydra closes before the conclusion, you will lose your progress." + "hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util its conclusion. In case Hydra closes before the conclusion, you will lose your progress.", + "achievements": "Achievements" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index ca247eda..83e35fac 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -126,7 +126,8 @@ "download": "Baixar", "executable_path_in_use": "Executável em uso por \"{{game}}\"", "warning": "Aviso:", - "hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso." + "hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.", + "achievements": "Conquistas" }, "activation": { "title": "Ativação", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 5a659945..27fbb932 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -115,7 +115,8 @@ "download": "Transferir", "executable_path_in_use": "Executável em uso por \"{{game}}\"", "warning": "Aviso:", - "hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso." + "hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.", + "achievements": "Conquistas" }, "activation": { "title": "Ativação", diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index 55b2481c..e4063217 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -54,15 +54,20 @@ export function Sidebar() { className={styles.contentSidebarTitle} style={{ border: "none" }} > -

{t("achievements")}

- {achievements.length} +

+ {t("achievements")}{" "} + + ({achievements.filter((a) => a.unlocked).length}/ + {achievements.length}) + +

{achievements.map((achievement, index) => ( @@ -78,8 +83,8 @@ export function Sidebar() { > Date: Mon, 7 Oct 2024 17:13:30 -0300 Subject: [PATCH 14/18] feat: create achievements page --- .../events/catalogue/get-game-achievements.ts | 48 +++++++++++++++---- src/preload/index.ts | 4 +- src/renderer/src/declaration.d.ts | 3 +- src/renderer/src/main.tsx | 9 +++- .../src/pages/achievement/achievements.tsx | 11 +++++ .../achievement-notification.css.ts} | 2 +- .../achievement-notification.tsx} | 4 +- .../pages/game-details/sidebar/sidebar.tsx | 15 ++++-- 8 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 src/renderer/src/pages/achievement/achievements.tsx rename src/renderer/src/pages/achievement/{achievement.css.ts => notification/achievement-notification.css.ts} (95%) rename src/renderer/src/pages/achievement/{achievement.tsx => notification/achievement-notification.tsx} (96%) diff --git a/src/main/events/catalogue/get-game-achievements.ts b/src/main/events/catalogue/get-game-achievements.ts index 456babf4..7d0baa5c 100644 --- a/src/main/events/catalogue/get-game-achievements.ts +++ b/src/main/events/catalogue/get-game-achievements.ts @@ -1,13 +1,19 @@ -import type { GameAchievement, GameShop } from "@types"; +import type { GameAchievement, GameShop, UnlockedAchievement } from "@types"; import { registerEvent } from "../register-event"; -import { gameAchievementRepository } from "@main/repository"; +import { + gameAchievementRepository, + userAuthRepository, +} from "@main/repository"; import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data"; +import { HydraApi } from "@main/services"; -const getGameAchievements = async ( - _event: Electron.IpcMainInvokeEvent, +const getAchievements = async ( + shop: string, objectId: string, - shop: GameShop -): Promise => { + userId?: string +) => { + const userAuth = await userAuthRepository.findOne({ where: { userId } }); + const cachedAchievements = await gameAchievementRepository.findOne({ where: { objectId, shop }, }); @@ -16,9 +22,33 @@ const getGameAchievements = async ( ? JSON.parse(cachedAchievements.achievements) : await getGameAchievementData(objectId, shop); - const unlockedAchievements = JSON.parse( - cachedAchievements?.unlockedAchievements || "[]" - ) as { name: string; unlockTime: number }[]; + if (!userId || userAuth) { + const unlockedAchievements = JSON.parse( + cachedAchievements?.unlockedAchievements || "[]" + ) as UnlockedAchievement[]; + + return { achievementsData, unlockedAchievements }; + } + + const unlockedAchievements = await HydraApi.get( + `/users/${userId}/games/achievements`, + { shop, objectId, language: "en" } + ); + + return { achievementsData, unlockedAchievements }; +}; + +const getGameAchievements = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop, + userId?: string +): Promise => { + const { achievementsData, unlockedAchievements } = await getAchievements( + shop, + objectId, + userId + ); return achievementsData .map((achievementData) => { diff --git a/src/preload/index.ts b/src/preload/index.ts index b24d1440..0a7e8fce 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -49,8 +49,8 @@ contextBridge.exposeInMainWorld("electron", { getGameStats: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameStats", objectId, shop), getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"), - getGameAchievements: (objectId: string, shop: GameShop) => - ipcRenderer.invoke("getGameAchievements", objectId, shop), + getGameAchievements: (objectId: string, shop: GameShop, userId?: string) => + ipcRenderer.invoke("getGameAchievements", objectId, shop, userId), onAchievementUnlocked: ( cb: ( objectId: string, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 9db51497..351b8e0a 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -68,7 +68,8 @@ declare global { getTrendingGames: () => Promise; getGameAchievements: ( objectId: string, - shop: GameShop + shop: GameShop, + userId?: string ) => Promise; onAchievementUnlocked: ( cb: ( diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index bcb8fe80..5312530d 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -28,10 +28,11 @@ import { import { store } from "./store"; import resources from "@locales"; -import { Achievement } from "./pages/achievement/achievement"; +import { AchievementNotification } from "./pages/achievement/notification/achievement-notification"; import "./workers"; import { RepacksContextProvider } from "./context"; +import { Achievement } from "./pages/achievement/achievements"; Sentry.init({}); @@ -69,8 +70,12 @@ ReactDOM.createRoot(document.getElementById("root")!).render( + - + diff --git a/src/renderer/src/pages/achievement/achievements.tsx b/src/renderer/src/pages/achievement/achievements.tsx new file mode 100644 index 00000000..40cb01c5 --- /dev/null +++ b/src/renderer/src/pages/achievement/achievements.tsx @@ -0,0 +1,11 @@ +import { useSearchParams } from "react-router-dom"; + +export function Achievement() { + const [searchParams] = useSearchParams(); + + return ( +
+

Achievement

+
+ ); +} diff --git a/src/renderer/src/pages/achievement/achievement.css.ts b/src/renderer/src/pages/achievement/notification/achievement-notification.css.ts similarity index 95% rename from src/renderer/src/pages/achievement/achievement.css.ts rename to src/renderer/src/pages/achievement/notification/achievement-notification.css.ts index d5cb180b..ba9469bb 100644 --- a/src/renderer/src/pages/achievement/achievement.css.ts +++ b/src/renderer/src/pages/achievement/notification/achievement-notification.css.ts @@ -1,5 +1,5 @@ import { recipe } from "@vanilla-extract/recipes"; -import { vars } from "../../theme.css"; +import { vars } from "../../../theme.css"; import { keyframes, style } from "@vanilla-extract/css"; const animationIn = keyframes({ diff --git a/src/renderer/src/pages/achievement/achievement.tsx b/src/renderer/src/pages/achievement/notification/achievement-notification.tsx similarity index 96% rename from src/renderer/src/pages/achievement/achievement.tsx rename to src/renderer/src/pages/achievement/notification/achievement-notification.tsx index f78d8f49..d33cb231 100644 --- a/src/renderer/src/pages/achievement/achievement.tsx +++ b/src/renderer/src/pages/achievement/notification/achievement-notification.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import achievementSound from "@renderer/assets/audio/achievement.wav"; import { useTranslation } from "react-i18next"; -import * as styles from "./achievement.css"; +import * as styles from "./achievement-notification.css"; interface AchievementInfo { displayName: string; @@ -10,7 +10,7 @@ interface AchievementInfo { const NOTIFICATION_TIMEOUT = 4000; -export function Achievement() { +export function AchievementNotification() { const { t } = useTranslation("achievement"); const [isClosing, setIsClosing] = useState(false); diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index e4063217..c0c066a1 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -1,7 +1,7 @@ import { useContext, useState } from "react"; import type { HowLongToBeatCategory, SteamAppDetails } from "@types"; import { useTranslation } from "react-i18next"; -import { Button } from "@renderer/components"; +import { Button, Link } from "@renderer/components"; import * as styles from "./sidebar.css"; import { gameDetailsContext } from "@renderer/context"; @@ -18,7 +18,7 @@ export function Sidebar() { const [activeRequirement, setActiveRequirement] = useState("minimum"); - const { gameTitle, shopDetails, stats, achievements } = + const { gameTitle, shopDetails, stats, achievements, shop, objectID } = useContext(gameDetailsContext); const { t } = useTranslation("game_details"); @@ -26,6 +26,11 @@ export function Sidebar() { const { numberFormatter } = useFormat(); + const buildGameAchievementPath = () => { + const urlParams = new URLSearchParams({ objectId: objectID!, shop }); + return `/achievements?${urlParams.toString()}`; + }; + // useEffect(() => { // if (objectID) { // setHowLongToBeat({ isLoading: true, data: null }); @@ -61,6 +66,10 @@ export function Sidebar() { {achievements.length}) + + Ver todas + +
- {achievements.map((achievement, index) => ( + {achievements.slice(0, 6).map((achievement, index) => (
Date: Mon, 7 Oct 2024 20:16:12 -0300 Subject: [PATCH 15/18] feat: refactor achievement listeners --- .../events/catalogue/get-game-achievements.ts | 14 +++++++++++--- .../achievements/merge-achievements.ts | 8 ++++---- src/preload/index.ts | 17 +++++++++++++++++ .../game-details/game-details.context.tsx | 18 ++++++++---------- src/renderer/src/declaration.d.ts | 5 +++++ .../src/pages/game-details/sidebar/sidebar.tsx | 1 - 6 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/main/events/catalogue/get-game-achievements.ts b/src/main/events/catalogue/get-game-achievements.ts index 7d0baa5c..8ab6d5d9 100644 --- a/src/main/events/catalogue/get-game-achievements.ts +++ b/src/main/events/catalogue/get-game-achievements.ts @@ -38,8 +38,7 @@ const getAchievements = async ( return { achievementsData, unlockedAchievements }; }; -const getGameAchievements = async ( - _event: Electron.IpcMainInvokeEvent, +export const getGameAchievements = async ( objectId: string, shop: GameShop, userId?: string @@ -78,4 +77,13 @@ const getGameAchievements = async ( }); }; -registerEvent("getGameAchievements", getGameAchievements); +const getGameAchievementsEvent = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop, + userId?: string +): Promise => { + return getGameAchievements(objectId, shop, userId); +}; + +registerEvent("getGameAchievements", getGameAchievementsEvent); diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index b611f2a2..6438a413 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -2,6 +2,7 @@ import { gameAchievementRepository, gameRepository } from "@main/repository"; import type { GameShop, UnlockedAchievement } from "@types"; import { WindowManager } from "../window-manager"; import { HydraApi } from "../hydra-api"; +import { getGameAchievements } from "@main/events/catalogue/get-game-achievements"; const saveAchievementsOnLocal = async ( objectId: string, @@ -17,11 +18,10 @@ const saveAchievementsOnLocal = async ( }, ["objectId", "shop"] ) - .then(() => { + .then(async () => { WindowManager.mainWindow?.webContents.send( - "on-achievement-unlocked", - objectId, - shop + `on-update-achievements-${objectId}-${shop}`, + await getGameAchievements(objectId, shop as GameShop) ); }); }; diff --git a/src/preload/index.ts b/src/preload/index.ts index 8c0486a4..40a6c101 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -14,6 +14,7 @@ import type { } from "@types"; import type { CatalogueCategory } from "@shared"; import type { AxiosProgressEvent } from "axios"; +import { GameAchievement } from "@main/entity"; contextBridge.exposeInMainWorld("electron", { /* Torrenting */ @@ -69,6 +70,22 @@ contextBridge.exposeInMainWorld("electron", { return () => ipcRenderer.removeListener("on-achievement-unlocked", listener); }, + onUpdateAchievements: ( + objectId: string, + shop: GameShop, + cb: (achievements: GameAchievement[]) => void + ) => { + const listener = ( + _event: Electron.IpcRendererEvent, + achievements: GameAchievement[] + ) => cb(achievements); + ipcRenderer.on(`on-update-achievements-${objectId}-${shop}`, listener); + return () => + ipcRenderer.removeListener( + `on-update-achievements-${objectId}-${shop}`, + listener + ); + }, /* User preferences */ getUserPreferences: () => ipcRenderer.invoke("getUserPreferences"), diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index 2a819e1b..7ff8bb09 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -132,13 +132,14 @@ export function GameDetailsContextProvider({ setIsLoading(false); }); - window.electron.getGameStats(objectId!, shop as GameShop).then((result) => { + window.electron.getGameStats(objectId, shop as GameShop).then((result) => { setStats(result); }); window.electron - .getGameAchievements(objectId!, shop as GameShop) + .getGameAchievements(objectId, shop as GameShop) .then((achievements) => { + // TODO: race condition setAchievements(achievements); }) .catch(() => { @@ -175,14 +176,11 @@ export function GameDetailsContextProvider({ }, [game?.id, isGameRunning, updateGame]); useEffect(() => { - const unsubscribe = window.electron.onAchievementUnlocked( - (objectId, shop) => { - if (objectId !== objectId || shop !== shop) return; - - window.electron - .getGameAchievements(objectId!, shop as GameShop) - .then(setAchievements) - .catch(() => {}); + const unsubscribe = window.electron.onUpdateAchievements( + objectId, + shop, + (achievements) => { + setAchievements(achievements); } ); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index d83b2b6c..677c3ee2 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -76,6 +76,11 @@ declare global { achievements?: { displayName: string; iconUrl: string }[] ) => void ) => () => Electron.IpcRenderer; + onUpdateAchievements: ( + objectId: string, + shop: GameShop, + cb: (achievements: GameAchievement[]) => void + ) => () => Electron.IpcRenderer; /* Library */ addGameToLibrary: ( diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index a72d6c90..6c6dca33 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -82,7 +82,6 @@ export function Sidebar() { > Ver todas -
Date: Mon, 7 Oct 2024 20:25:05 -0300 Subject: [PATCH 16/18] feat: preview version of achievements screen --- .../src/pages/achievement/achievements.tsx | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/renderer/src/pages/achievement/achievements.tsx b/src/renderer/src/pages/achievement/achievements.tsx index 40cb01c5..40f3c35d 100644 --- a/src/renderer/src/pages/achievement/achievements.tsx +++ b/src/renderer/src/pages/achievement/achievements.tsx @@ -1,11 +1,71 @@ +import { useDate } from "@renderer/hooks"; +import { SPACING_UNIT } from "@renderer/theme.css"; +import { GameAchievement, GameShop } from "@types"; +import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; export function Achievement() { const [searchParams] = useSearchParams(); + const objectId = searchParams.get("objectId"); + const shop = searchParams.get("shop"); + const userId = searchParams.get("userId"); + + const { format } = useDate(); + + const [achievements, setAchievements] = useState([]); + + useEffect(() => { + if (objectId && shop) { + window.electron + .getGameAchievements(objectId, shop as GameShop, userId || undefined) + .then((achievements) => { + setAchievements(achievements); + }); + } + }, []); return (

Achievement

+ +
+ {achievements.map((achievement, index) => ( +
+ {achievement.displayName} +
+

{achievement.displayName}

+ {achievement.unlockTime && format(achievement.unlockTime)} +
+
+ ))} +
); } From a557735545ac49447413fb89115daa2aecf9840b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:30:53 -0300 Subject: [PATCH 17/18] feat: update hook dependency --- src/renderer/src/pages/achievement/achievements.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/achievement/achievements.tsx b/src/renderer/src/pages/achievement/achievements.tsx index 40f3c35d..17917045 100644 --- a/src/renderer/src/pages/achievement/achievements.tsx +++ b/src/renderer/src/pages/achievement/achievements.tsx @@ -22,7 +22,7 @@ export function Achievement() { setAchievements(achievements); }); } - }, []); + }, [objectId, shop, userId]); return (
From 365178b06d02ef2f7cfeb5a519af3c17d7b01d31 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 7 Oct 2024 22:29:39 -0300 Subject: [PATCH 18/18] fix: update normalize path --- src/main/helpers/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/helpers/index.ts b/src/main/helpers/index.ts index e50b4422..db5569c8 100644 --- a/src/main/helpers/index.ts +++ b/src/main/helpers/index.ts @@ -30,4 +30,4 @@ export const isPortableVersion = () => process.env.PORTABLE_EXECUTABLE_FILE !== null; export const normalizePath = (str: string) => - path.normalize(str.replace(/\\/g, "/")); + path.posix.normalize(str).replace(/\\/g, "/");