mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 08:43:48 +03:00
feat: try add FLT and fix possible bug on unlockedAchievements
This commit is contained in:
parent
71e7f1ee58
commit
002028130b
@ -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<string, number> = 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,5 +1,18 @@
|
|||||||
import { gameRepository } from "@main/repository";
|
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<string, number> = new Map();
|
||||||
|
const fltFiles: Map<string, Set<string>> = new Map();
|
||||||
|
|
||||||
export const watchAchievements = async () => {
|
export const watchAchievements = async () => {
|
||||||
const games = await gameRepository.find({
|
const games = await gameRepository.find({
|
||||||
@ -12,3 +25,96 @@ export const watchAchievements = async () => {
|
|||||||
|
|
||||||
await searchForAchievements(games);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -24,6 +24,7 @@ const crackers = [
|
|||||||
Cracker.skidrow,
|
Cracker.skidrow,
|
||||||
Cracker.smartSteamEmu,
|
Cracker.smartSteamEmu,
|
||||||
Cracker.empress,
|
Cracker.empress,
|
||||||
|
Cracker.flt,
|
||||||
];
|
];
|
||||||
|
|
||||||
const getPathFromCracker = async (cracker: Cracker) => {
|
const getPathFromCracker = async (cracker: Cracker) => {
|
||||||
@ -131,7 +132,7 @@ const getPathFromCracker = async (cracker: Cracker) => {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
folderPath: path.join(appData, "SmartSteamEmu"),
|
folderPath: path.join(appData, "SmartSteamEmu"),
|
||||||
fileLocation: ["User", "Achievements"],
|
fileLocation: ["User", "Achievements.ini"],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -140,6 +141,15 @@ const getPathFromCracker = async (cracker: Cracker) => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cracker === Cracker.flt) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
folderPath: path.join(appData, "FLT"),
|
||||||
|
fileLocation: ["stats"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
achievementsLogger.error(`Cracker ${cracker} not implemented`);
|
achievementsLogger.error(`Cracker ${cracker} not implemented`);
|
||||||
throw new Error(`Cracker ${cracker} not implemented`);
|
throw new Error(`Cracker ${cracker} not implemented`);
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,7 @@ export const mergeAchievements = async (
|
|||||||
|
|
||||||
const unlockedAchievements = JSON.parse(
|
const unlockedAchievements = JSON.parse(
|
||||||
localGameAchievement?.unlockedAchievements || "[]"
|
localGameAchievement?.unlockedAchievements || "[]"
|
||||||
);
|
).filter((achievement) => achievement.name);
|
||||||
|
|
||||||
const newAchievements = achievements
|
const newAchievements = achievements
|
||||||
.filter((achievement) => {
|
.filter((achievement) => {
|
||||||
@ -60,7 +60,7 @@ export const mergeAchievements = async (
|
|||||||
.map((achievement) => {
|
.map((achievement) => {
|
||||||
return {
|
return {
|
||||||
name: achievement.name.toUpperCase(),
|
name: achievement.name.toUpperCase(),
|
||||||
unlockTime: achievement.unlockTime * 1000,
|
unlockTime: achievement.unlockTime,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { Cracker } from "@shared";
|
import { Cracker } from "@shared";
|
||||||
import { UnlockedAchievement } from "@types";
|
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 readline from "node:readline";
|
||||||
import { achievementsLogger } from "../logger";
|
import { achievementsLogger } from "../logger";
|
||||||
|
|
||||||
@ -50,6 +55,17 @@ export const parseAchievementFile = async (
|
|||||||
return process3DM(parsed);
|
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}`);
|
achievementsLogger.log(`${type} achievements found on ${filePath}`);
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
@ -101,7 +117,7 @@ const processOnlineFix = (unlockedAchievements: any): UnlockedAchievement[] => {
|
|||||||
if (unlockedAchievement?.achieved) {
|
if (unlockedAchievement?.achieved) {
|
||||||
parsedUnlockedAchievements.push({
|
parsedUnlockedAchievements.push({
|
||||||
name: achievement,
|
name: achievement,
|
||||||
unlockTime: unlockedAchievement.timestamp,
|
unlockTime: unlockedAchievement.timestamp * 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +135,7 @@ const processSkidrow = (unlockedAchievements: any): UnlockedAchievement[] => {
|
|||||||
if (unlockedAchievement[0] === "1") {
|
if (unlockedAchievement[0] === "1") {
|
||||||
parsedUnlockedAchievements.push({
|
parsedUnlockedAchievements.push({
|
||||||
name: achievement,
|
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) {
|
if (unlockedAchievement?.earned) {
|
||||||
newUnlockedAchievements.push({
|
newUnlockedAchievements.push({
|
||||||
name: achievement,
|
name: achievement,
|
||||||
unlockTime: unlockedAchievement.earned_time,
|
unlockTime: unlockedAchievement.earned_time * 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,9 +171,10 @@ const process3DM = (unlockedAchievements: any): UnlockedAchievement[] => {
|
|||||||
|
|
||||||
newUnlockedAchievements.push({
|
newUnlockedAchievements.push({
|
||||||
name: achievement,
|
name: achievement,
|
||||||
unlockTime: new DataView(
|
unlockTime:
|
||||||
|
new DataView(
|
||||||
new Uint8Array(Buffer.from(time.toString(), "hex")).buffer
|
new Uint8Array(Buffer.from(time.toString(), "hex")).buffer
|
||||||
).getUint32(0, true),
|
).getUint32(0, true) * 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,7 +191,7 @@ const processDefault = (unlockedAchievements: any): UnlockedAchievement[] => {
|
|||||||
if (unlockedAchievement?.Achieved) {
|
if (unlockedAchievement?.Achieved) {
|
||||||
newUnlockedAchievements.push({
|
newUnlockedAchievements.push({
|
||||||
name: achievement,
|
name: achievement,
|
||||||
unlockTime: unlockedAchievement.UnlockTime,
|
unlockTime: unlockedAchievement.UnlockTime * 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,11 +210,12 @@ const processRld = (unlockedAchievements: any): UnlockedAchievement[] => {
|
|||||||
if (unlockedAchievement?.State) {
|
if (unlockedAchievement?.State) {
|
||||||
newUnlockedAchievements.push({
|
newUnlockedAchievements.push({
|
||||||
name: achievement,
|
name: achievement,
|
||||||
unlockTime: new DataView(
|
unlockTime:
|
||||||
|
new DataView(
|
||||||
new Uint8Array(
|
new Uint8Array(
|
||||||
Buffer.from(unlockedAchievement.Time.toString(), "hex")
|
Buffer.from(unlockedAchievement.Time.toString(), "hex")
|
||||||
).buffer
|
).buffer
|
||||||
).getUint32(0, true),
|
).getUint32(0, true) * 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,7 +240,7 @@ const processUserStats = (unlockedAchievements: any): UnlockedAchievement[] => {
|
|||||||
if (!isNaN(unlockTime)) {
|
if (!isNaN(unlockTime)) {
|
||||||
newUnlockedAchievements.push({
|
newUnlockedAchievements.push({
|
||||||
name: achievement.replace(/"/g, ``),
|
name: achievement.replace(/"/g, ``),
|
||||||
unlockTime: unlockTime,
|
unlockTime: unlockTime * 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,4 +36,5 @@ export enum Cracker {
|
|||||||
creamAPI = "CreamAPI",
|
creamAPI = "CreamAPI",
|
||||||
smartSteamEmu = "SmartSteamEmu",
|
smartSteamEmu = "SmartSteamEmu",
|
||||||
_3dm = "3dm",
|
_3dm = "3dm",
|
||||||
|
flt = "FLT",
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user