mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-18 16:53:57 +00:00
feat: parse achievements from steam local cache
This commit is contained in:
@@ -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 };
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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[] => {
|
||||
|
||||
@@ -19,6 +19,32 @@ const getModifiedSinceHeader = (
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const getModifiedSinceHeader = async (
|
||||
cachedAchievements: GameAchievement | undefined
|
||||
): Promise<Date | undefined> => {
|
||||
const hasActiveSubscription = await db
|
||||
.get<string, User>(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,
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export enum Cracker {
|
||||
onlineFix = "OnlineFix",
|
||||
goldberg = "Goldberg",
|
||||
userstats = "user_stats",
|
||||
Steam = "Steam",
|
||||
rld = "RLD!",
|
||||
empress = "EMPRESS",
|
||||
skidrow = "SKIDROW",
|
||||
|
||||
Reference in New Issue
Block a user