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..f16c0e2c 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 []; }; @@ -69,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; @@ -139,6 +143,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[] = []; @@ -195,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 6c0fed8f..963ce602 100644 --- a/src/main/services/achievements/update-local-unlocked-achivements.ts +++ b/src/main/services/achievements/update-local-unlocked-achivements.ts @@ -2,35 +2,41 @@ 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"; import type { UnlockedAchievement } from "@types"; import { getGameAchievementData } from "./get-game-achievement-data"; +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), }, @@ -47,13 +53,20 @@ 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, + parsedAchievements + ); } - mergeAchievements(objectId, "steam", unlockedAchievements, false); + mergeAchievements(game.objectID, "steam", unlockedAchievements, false); } }; @@ -71,6 +84,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) { 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"); } 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", }