import re import appdirs import zipfile from pathlib import Path from configparser import ConfigParser from worthless import constants from worthless.launcher import Launcher def read_version_from_game_file(globalgamemanagers: Path | bytes): if isinstance(globalgamemanagers, Path): with globalgamemanagers.open("rb") as f: data = f.read().decode("ascii", errors="ignore") else: data = globalgamemanagers.decode("ascii", errors="ignore") result = re.search(r"([1-9]+\.[0-9]+\.[0-9]+)_[\d]+_[\d]+", data) if not result: raise ValueError("Could not find version in game file") return result.group(1) class Installer: def _read_version_from_config(self): if not self._config_file.exists(): raise FileNotFoundError(f"Config file {self._config_file} not found") cfg = ConfigParser() cfg.read(str(self._config_file)) return cfg.get("General", "game_version") def get_game_data_name(self): if self._overseas: return "GenshinImpact_Data/" else: return "YuanShen_Data/" def get_game_data_path(self): return self._gamedir.joinpath(self.get_game_data_name()) # https://gitlab.com/KRypt0n_/an-anime-game-launcher/-/blob/main/src/ts/Game.ts#L26 def get_game_version(self): if self._config_file.exists(): return self._read_version_from_config() else: globalgamemanagers = self.get_game_data_path().joinpath("./globalgamemanagers") if not globalgamemanagers.exists(): return return read_version_from_game_file(globalgamemanagers) def __init__(self, gamedir: str | Path = Path.cwd(), overseas: bool = True, data_dir: str | Path = None): if isinstance(gamedir, str): gamedir = Path(gamedir) self._gamedir = gamedir if not data_dir: self._appdirs = appdirs.AppDirs(constants.APP_NAME, constants.APP_AUTHOR) self._temp_path = Path(self._appdirs.user_cache_dir).joinpath("Installer") else: if not isinstance(data_dir, Path): data_dir = Path(data_dir) self._temp_path = data_dir.joinpath("Temp/Installer/") config_file = self._gamedir.joinpath("config.ini") self._config_file = config_file.resolve() self._version = None self._overseas = overseas self._launcher = Launcher(self._gamedir, overseas=self._overseas) self._version = self.get_game_version() def get_archive_version(self, game_archive: str | Path): if not game_archive.exists(): raise FileNotFoundError(f"Game archive {game_archive} not found") archive = zipfile.ZipFile(game_archive, 'r') return read_version_from_game_file(archive.read(self.get_game_data_name() + "globalgamemanagers")) def apply_voiceover(self, voiceover_archive: str | Path): if not self.get_game_data_path().exists(): raise FileNotFoundError(f"Game not found in {self._gamedir}") if isinstance(voiceover_archive, str): game_archive = Path(voiceover_archive).resolve() if not voiceover_archive.exists(): raise FileNotFoundError(f"Voiceover archive {voiceover_archive} not found") archive = zipfile.ZipFile(voiceover_archive, 'r') archive.extractall(self._gamedir) archive.close() def update_game(self, game_archive: str | Path): if not self.get_game_data_path().exists(): raise FileNotFoundError(f"Game not found in {self._gamedir}") if isinstance(game_archive, str): game_archive = Path(game_archive).resolve() if not game_archive.exists(): raise FileNotFoundError(f"Update archive {game_archive} not found") archive = zipfile.ZipFile(game_archive, 'r') deletefiles = archive.read("deletefiles.txt").decode().split("\n") for file in deletefiles: current_game_file = self._gamedir.joinpath(file) if not current_game_file.exists(): continue if current_game_file.is_file(): current_game_file.unlink(missing_ok=True) archive.extractall(self._gamedir) archive.close() self._gamedir.joinpath("deletefiles.txt").unlink(missing_ok=True) self._gamedir.joinpath("hdifffiles.txt").unlink(missing_ok=True) async def install_game(self, force_reinstall: bool = False): """Installs the game to the current directory If `from_version` is not specified, it will be taken from the game version. If `to_version` is not specified, it will be taken from the game version. """ raise NotImplementedError("Not implemented yet") # if not force: # if self._temp_path.exists(): # raise FileExistsError(f"Directory {self._temp_path} already exists") # self._temp_path.mkdir(parents=True, exist_ok=True) # self._launcher.set_temp_path(self._temp_path) # await self._launcher.download_game_diff_archive(from_version, to_version) # await self._launcher.extract_game_diff_archive() # await self._launcher.install_game_diff_archive() # self._launcher.set_temp_path(None) # self._temp_path.rmdir() async def get_game_diff_archive(self, from_version: str = None): """Gets a diff archive from `from_version` to the latest one If from_version is not specified, it will be taken from the game version. """ if not from_version: if self._version: from_version = self._version else: from_version = self._version = self.get_game_version() if not from_version: raise ValueError("No game version found") game_resource = await self._launcher.get_resource_info() if not game_resource: raise ValueError("Could not fetch game resource") for v in game_resource.game.diffs: if v.version == from_version: return v