diff --git a/worthless/game/game.py b/worthless/game/game.py index d02d728..119da2f 100644 --- a/worthless/game/game.py +++ b/worthless/game/game.py @@ -6,7 +6,7 @@ import json import hashlib import logging from os import PathLike -from pathlib import Path +from pathlib import Path, PurePath from worthless import constants from worthless.launcher import Launcher @@ -24,6 +24,28 @@ from worthless.game.launcherconfig import LauncherConfig _logger = logging.getLogger("worthless.Game") +def _calculate_md5(file: str | bytes | PathLike): + file = Path(file) + if not file.exists(): + return "" + with file.open("rb") as f: + file_hash = hashlib.md5() + while chunk := f.read(8196): + file_hash.update(chunk) + return file_hash.hexdigest() + + +def _parse_pkg_version(pkg_version: str | bytes | PathLike): + contents = Path(pkg_version).read_text() + pkg_version = [] + for content in contents.split("\r\n"): + if not content.strip(): + continue + info = json.loads(content) + pkg_version.append(info) + return pkg_version + + def voicepack_lang_translate(lang: str, base_language="game") -> str | None: """ Translates the voicepack language to the language code used by the game (and reverse) @@ -58,7 +80,9 @@ def voicepack_lang_translate(lang: str, base_language="game") -> str | None: return -def get_voicepack_archive_language(archive: str | bytes | PathLike) -> VoicepackArchiveLanguage: +def get_voicepack_archive_language( + archive: str | bytes | PathLike, +) -> VoicepackArchiveLanguage: """ Gets voicepack archive language. @@ -100,6 +124,27 @@ def get_voicepack_archive_type(archive: str | bytes | PathLike) -> VoicepackArch return VoicepackArchiveType.FULL +def verify_game_file( + file: str | bytes | PathLike, + pkg_version: list[dict] | str | bytes | PathLike, + ignore_mismatch=True, +): + file = Path(file) + if not isinstance(pkg_version, list): + pkg_version = _parse_pkg_version(pkg_version=pkg_version) + for info in pkg_version: + if PurePath(info["remoteName"]).name != file.name: + continue + file_md5 = _calculate_md5(file) + if file_md5 == info["md5"]: + return None + if ignore_mismatch: + return file, info["md5"], file_md5 + raise ValueError( + f"MD5 does not match for {file}, expected md5: {info['md5']}, actual md5: {file_md5}" + ) + + class Game: """ Manages game & voicepacks installation. @@ -191,18 +236,8 @@ class Game: async def _verify_from_pkg_version(self, pkg_version: Path, ignore_mismatch=False): contents = pkg_version.read_text() - def calculate_md5(file_to_calculate): - file_to_calculate = Path(file_to_calculate) - if not file_to_calculate.exists(): - return "" - with file_to_calculate.open("rb") as f: - file_hash = hashlib.md5() - while chunk := f.read(8196): - file_hash.update(chunk) - return file_hash.hexdigest() - def verify_file(file_to_verify, md5): - file_md5 = calculate_md5(file_to_verify) + file_md5 = _calculate_md5(file_to_verify) if file_md5 == md5: return None if ignore_mismatch: @@ -270,14 +305,18 @@ class Game: return voicepack_lang_translate(lang=lang, base_language=base_language) @staticmethod - def get_voicepack_archive_language(archive: str | bytes | PathLike) -> VoicepackArchiveLanguage: + def get_voicepack_archive_language( + archive: str | bytes | PathLike, + ) -> VoicepackArchiveLanguage: """ This function is an alias to worthless.game.get_voicepack_archive_language """ return get_voicepack_archive_language(archive=archive) @staticmethod - def get_voicepack_archive_type(archive: str | bytes | PathLike) -> VoicepackArchiveType: + def get_voicepack_archive_type( + archive: str | bytes | PathLike, + ) -> VoicepackArchiveType: """ This function is an alias to worthless.game.get_voicepack_archive_type """ @@ -307,7 +346,9 @@ class Game: """ return self._game_dir.joinpath(self.get_game_data_name()) - def get_archive_game_version(self, game_archive: str | bytes | PathLike) -> str | None: + def get_archive_game_version( + self, game_archive: str | bytes | PathLike + ) -> str | None: """ Gets the game version in the archive @@ -493,7 +534,10 @@ class Game: self._extract_game_file(archive) async def install_game( - self, game_archive: str | bytes | PathLike, force_reinstall: bool = False, callback=None + self, + game_archive: str | bytes | PathLike, + force_reinstall: bool = False, + callback=None, ): """Installs the game to the current directory @@ -547,7 +591,24 @@ class Game: if v.version == from_version: return v - async def verify_game(self, pkg_version: str | bytes | PathLike = None, ignore_mismatch=None): + def verify_game_file( + self, + file: str | bytes | PathLike, + pkg_version: list[dict] | str | bytes | PathLike = None, + ignore_mismatch=True, + ): + """ + This function is an alias to worthless.game.get_voicepack_archive_type + """ + if pkg_version is None: + pkg_version = self._game_dir.joinpath("pkg_version") + return verify_game_file( + file=file, pkg_version=pkg_version, ignore_mismatch=ignore_mismatch + ) + + async def verify_game_files( + self, pkg_version: str | bytes | PathLike = None, ignore_mismatch=True + ): """ Verifies the current game installation @@ -561,8 +622,6 @@ class Game: """ if pkg_version is None: pkg_version = self._game_dir.joinpath("pkg_version") - if ignore_mismatch is None: - ignore_mismatch = True return await self._verify_from_pkg_version(pkg_version, ignore_mismatch) def delete_game(self): diff --git a/worthless/game/helper.py b/worthless/game/helper.py index 97f1f5c..045420c 100644 --- a/worthless/game/helper.py +++ b/worthless/game/helper.py @@ -85,7 +85,9 @@ class Helper(Game): game = await self._game._get_game(pre_download=pre_download) if self._game.version == game.latest.version: raise ValueError("Game is already up to date.") - diff_archive = await self._game.get_game_diff_archive(from_version, pre_download) + diff_archive = await self._game.get_game_diff_archive( + from_version, pre_download + ) if diff_archive is None: raise ValueError( "Game diff archive is not available for this version, please reinstall."