mirror of
				https://github.com/hydralauncher/hydra.git
				synced 2025-03-09 15:40:26 +00:00 
			
		
		
		
	Merge branch 'feature/game-achievements' into chore/test-preview
This commit is contained in:
		
						commit
						8fdc6c4ab2
					
				
					 6 changed files with 96 additions and 34 deletions
				
			
		| 
						 | 
					@ -62,9 +62,7 @@ export const checkAchievementFileChange = async (games: Game[]) => {
 | 
				
			||||||
    const achievementFileInsideDirectory =
 | 
					    const achievementFileInsideDirectory =
 | 
				
			||||||
      findAchievementFileInExecutableDirectory(game);
 | 
					      findAchievementFileInExecutableDirectory(game);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (achievementFileInsideDirectory) {
 | 
					    gameAchievementFiles.push(...achievementFileInsideDirectory);
 | 
				
			||||||
      gameAchievementFiles.push(achievementFileInsideDirectory);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!gameAchievementFiles.length) continue;
 | 
					    if (!gameAchievementFiles.length) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -136,6 +136,10 @@ const getPathFromCracker = async (cracker: Cracker) => {
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (cracker === Cracker._3dm) {
 | 
				
			||||||
 | 
					    return [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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`);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -163,22 +167,33 @@ export const findAchievementFiles = async (game: Game) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const findAchievementFileInExecutableDirectory = (
 | 
					export const findAchievementFileInExecutableDirectory = (
 | 
				
			||||||
  game: Game
 | 
					  game: Game
 | 
				
			||||||
): AchievementFile | null => {
 | 
					): AchievementFile[] => {
 | 
				
			||||||
  if (!game.executablePath) {
 | 
					  if (!game.executablePath) {
 | 
				
			||||||
    return null;
 | 
					    return [];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const steamDataPath = path.join(
 | 
					  return [
 | 
				
			||||||
    game.executablePath,
 | 
					    {
 | 
				
			||||||
    "..",
 | 
					      type: Cracker.userstats,
 | 
				
			||||||
    "SteamData",
 | 
					      filePath: path.join(
 | 
				
			||||||
    "user_stats.ini"
 | 
					        game.executablePath,
 | 
				
			||||||
  );
 | 
					        "..",
 | 
				
			||||||
 | 
					        "SteamData",
 | 
				
			||||||
  return {
 | 
					        "user_stats.ini"
 | 
				
			||||||
    type: Cracker.userstats,
 | 
					      ),
 | 
				
			||||||
    filePath: steamDataPath,
 | 
					    },
 | 
				
			||||||
  };
 | 
					    {
 | 
				
			||||||
 | 
					      type: Cracker._3dm,
 | 
				
			||||||
 | 
					      filePath: path.join(
 | 
				
			||||||
 | 
					        game.executablePath,
 | 
				
			||||||
 | 
					        "..",
 | 
				
			||||||
 | 
					        "3DMGAME",
 | 
				
			||||||
 | 
					        "Player",
 | 
				
			||||||
 | 
					        "stats",
 | 
				
			||||||
 | 
					        "achievements.ini"
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const findAllAchievementFiles = async () => {
 | 
					export const findAllAchievementFiles = async () => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,6 +45,11 @@ export const parseAchievementFile = async (
 | 
				
			||||||
    return processSkidrow(parsed);
 | 
					    return processSkidrow(parsed);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (type === Cracker._3dm) {
 | 
				
			||||||
 | 
					    const parsed = await iniParse(filePath);
 | 
				
			||||||
 | 
					    return process3DM(parsed);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  achievementsLogger.log(`${type} achievements found on ${filePath}`);
 | 
					  achievementsLogger.log(`${type} achievements found on ${filePath}`);
 | 
				
			||||||
  return [];
 | 
					  return [];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -69,11 +74,10 @@ const iniParse = async (filePath: string) => {
 | 
				
			||||||
        object[objectName] = {};
 | 
					        object[objectName] = {};
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        const [name, ...value] = line.split("=");
 | 
					        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;
 | 
					    return object;
 | 
				
			||||||
  } catch {
 | 
					  } catch {
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
| 
						 | 
					@ -139,6 +143,28 @@ const processGoldberg = (unlockedAchievements: any): UnlockedAchievement[] => {
 | 
				
			||||||
  return newUnlockedAchievements;
 | 
					  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 processDefault = (unlockedAchievements: any): UnlockedAchievement[] => {
 | 
				
			||||||
  const newUnlockedAchievements: UnlockedAchievement[] = [];
 | 
					  const newUnlockedAchievements: UnlockedAchievement[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -195,7 +221,7 @@ const processUserStats = (unlockedAchievements: any): UnlockedAchievement[] => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!isNaN(unlockTime)) {
 | 
					    if (!isNaN(unlockTime)) {
 | 
				
			||||||
      newUnlockedAchievements.push({
 | 
					      newUnlockedAchievements.push({
 | 
				
			||||||
        name: achievement,
 | 
					        name: achievement.replace(/"/g, ``),
 | 
				
			||||||
        unlockTime: unlockTime,
 | 
					        unlockTime: unlockTime,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,35 +2,41 @@ import { gameAchievementRepository, gameRepository } from "@main/repository";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  findAllAchievementFiles,
 | 
					  findAllAchievementFiles,
 | 
				
			||||||
  findAchievementFiles,
 | 
					  findAchievementFiles,
 | 
				
			||||||
 | 
					  findAchievementFileInExecutableDirectory,
 | 
				
			||||||
} from "./find-achivement-files";
 | 
					} from "./find-achivement-files";
 | 
				
			||||||
import { parseAchievementFile } from "./parse-achievement-file";
 | 
					import { parseAchievementFile } from "./parse-achievement-file";
 | 
				
			||||||
import { mergeAchievements } from "./merge-achievements";
 | 
					import { mergeAchievements } from "./merge-achievements";
 | 
				
			||||||
import type { UnlockedAchievement } from "@types";
 | 
					import type { UnlockedAchievement } from "@types";
 | 
				
			||||||
import { getGameAchievementData } from "./get-game-achievement-data";
 | 
					import { getGameAchievementData } from "./get-game-achievement-data";
 | 
				
			||||||
 | 
					import { achievementsLogger } from "../logger";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const updateAllLocalUnlockedAchievements = async () => {
 | 
					export const updateAllLocalUnlockedAchievements = async () => {
 | 
				
			||||||
  const gameAchievementFilesMap = await findAllAchievementFiles();
 | 
					  const gameAchievementFilesMap = await findAllAchievementFiles();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (const objectId of gameAchievementFilesMap.keys()) {
 | 
					  const games = await gameRepository.find({
 | 
				
			||||||
    const gameAchievementFiles = gameAchievementFilesMap.get(objectId)!;
 | 
					    where: {
 | 
				
			||||||
 | 
					      isDeleted: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [game, localAchievements] = await Promise.all([
 | 
					  for (const game of games) {
 | 
				
			||||||
      gameRepository.findOne({
 | 
					    const gameAchievementFiles =
 | 
				
			||||||
        where: { objectID: objectId, shop: "steam", isDeleted: false },
 | 
					      gameAchievementFilesMap.get(game.objectID) || [];
 | 
				
			||||||
      }),
 | 
					    const achievementFileInsideDirectory =
 | 
				
			||||||
      gameAchievementRepository.findOne({
 | 
					      findAchievementFileInExecutableDirectory(game);
 | 
				
			||||||
        where: { objectId, shop: "steam" },
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    ]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!game) continue;
 | 
					    gameAchievementFiles.push(...achievementFileInsideDirectory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const localAchievements = await gameAchievementRepository.findOne({
 | 
				
			||||||
 | 
					      where: { objectId: game.objectID, shop: "steam" },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!localAchievements || !localAchievements.achievements) {
 | 
					    if (!localAchievements || !localAchievements.achievements) {
 | 
				
			||||||
      await getGameAchievementData(objectId, "steam")
 | 
					      await getGameAchievementData(game.objectID, "steam")
 | 
				
			||||||
        .then((achievements) => {
 | 
					        .then((achievements) => {
 | 
				
			||||||
          return gameAchievementRepository.upsert(
 | 
					          return gameAchievementRepository.upsert(
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
              objectId,
 | 
					              objectId: game.objectID,
 | 
				
			||||||
              shop: "steam",
 | 
					              shop: "steam",
 | 
				
			||||||
              achievements: JSON.stringify(achievements),
 | 
					              achievements: JSON.stringify(achievements),
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
| 
						 | 
					@ -47,13 +53,20 @@ export const updateAllLocalUnlockedAchievements = async () => {
 | 
				
			||||||
        achievementFile.filePath,
 | 
					        achievementFile.filePath,
 | 
				
			||||||
        achievementFile.type
 | 
					        achievementFile.type
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      console.log("Parsed for", game.title, parsedAchievements);
 | 
					
 | 
				
			||||||
      if (parsedAchievements.length) {
 | 
					      if (parsedAchievements.length) {
 | 
				
			||||||
        unlockedAchievements.push(...parsedAchievements);
 | 
					        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 gameAchievementFiles = await findAchievementFiles(game);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const achievementFileInsideDirectory =
 | 
				
			||||||
 | 
					    findAchievementFileInExecutableDirectory(game);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gameAchievementFiles.push(...achievementFileInsideDirectory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  console.log("Achievements files for", game.title, gameAchievementFiles);
 | 
					  console.log("Achievements files for", game.title, gameAchievementFiles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!localAchievements || !localAchievements.achievements) {
 | 
					  if (!localAchievements || !localAchievements.achievements) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,10 @@ log.transports.file.resolvePathFn = (
 | 
				
			||||||
    return path.join(logsPath, "pythoninstance.txt");
 | 
					    return path.join(logsPath, "pythoninstance.txt");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (message?.scope == "achievements") {
 | 
				
			||||||
 | 
					    return path.join(logsPath, "achievements.txt");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (message?.level === "error") {
 | 
					  if (message?.level === "error") {
 | 
				
			||||||
    return path.join(logsPath, "error.txt");
 | 
					    return path.join(logsPath, "error.txt");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,4 +35,5 @@ export enum Cracker {
 | 
				
			||||||
  skidrow = "SKIDROW",
 | 
					  skidrow = "SKIDROW",
 | 
				
			||||||
  creamAPI = "CreamAPI",
 | 
					  creamAPI = "CreamAPI",
 | 
				
			||||||
  smartSteamEmu = "SmartSteamEmu",
 | 
					  smartSteamEmu = "SmartSteamEmu",
 | 
				
			||||||
 | 
					  _3dm = "3dm",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue