From 820bc70e9d077d54203eb3ca2b2629f37f6f2854 Mon Sep 17 00:00:00 2001 From: tretrauit Date: Wed, 18 Jan 2023 12:38:43 +0700 Subject: [PATCH] feat: hdiff in voiceover update --- setup.py | 2 +- worthless/__init__.py | 2 + worthless/installer.py | 151 ++++++++++++++++++++++------------------- 3 files changed, 85 insertions(+), 70 deletions(-) diff --git a/setup.py b/setup.py index 34a6560..cfb2e18 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ README = (HERE / "README.md").read_text() setup( name='worthless', - version='2.2.11', + version='2.2.12', packages=['worthless', 'worthless.classes', 'worthless.classes.launcher', 'worthless.classes.installer'], url='https://git.froggi.es/tretrauit/worthless-launcher', license='MIT License', diff --git a/worthless/__init__.py b/worthless/__init__.py index 87899de..5d259fe 100644 --- a/worthless/__init__.py +++ b/worthless/__init__.py @@ -3,3 +3,5 @@ from worthless import launcher, installer Launcher = launcher.Launcher Installer = installer.Installer + +__version__ = "2.2.12" diff --git a/worthless/installer.py b/worthless/installer.py index e9afe36..7cdcd0d 100644 --- a/worthless/installer.py +++ b/worthless/installer.py @@ -326,6 +326,83 @@ class Installer: voiceovers.append(file.name) return voiceovers + async def _update(self, game_archive: str | Path | AsyncPath): + archive = zipfile.ZipFile(game_archive, 'r') + + if not self._hdiffpatch.get_hpatchz_executable(): + await self._hdiffpatch.download_latest_release() + + files = archive.namelist() + + # Don't extract these files (they're useless and if the game isn't patched then it'll + # raise 31-4xxx error ingame) + for file in ["deletefiles.txt", "hdifffiles.txt"]: + try: + files.remove(file) + except ValueError: + pass + + # hdiffpatch implementation + try: + hdifffiles = [] + for x in (await asyncio.to_thread(archive.read, "hdifffiles.txt")).decode().split("\n"): + if x: + hdifffiles.append(json.loads(x)["remoteName"]) + patch_jobs = [] + for file in hdifffiles: + current_game_file = self._gamedir.joinpath(file) + if not await current_game_file.exists(): + # Not patching since we don't have the file + continue + + patch_file = str(file) + ".hdiff" + + async def extract_and_patch(old_file, diff_file): + patch_path = self.temp_path.joinpath(diff_file) + patch_path.unlink(missing_ok=True) + try: + print(diff_file) + await asyncio.to_thread(archive.extract, diff_file, self.temp_path) + except FileExistsError: + print("Failed to extract diff file", diff_file) + return + old_suffix = old_file.suffix + old_file = await old_file.rename(old_file.with_suffix(".bak")) + proc = await self._hdiffpatch.patch_file(old_file, old_file.with_suffix(old_suffix), + patch_path, wait=True) + patch_path.unlink() + if proc.returncode == 0: + await old_file.unlink() + return + # Let the game download the file. + print("Failed to patch {}, reverting and let the in-game updater do the job...".format( + old_file.with_suffix(old_suffix)) + ) + await old_file.rename(old_file.with_suffix(old_suffix)) + + files.remove(patch_file) + patch_jobs.append(extract_and_patch(current_game_file, patch_file)) + + await asyncio.gather(*patch_jobs) + except Exception as e: + print(f"Error while reading hdifffiles.txt: {e}") + + try: + deletefiles = archive.read("deletefiles.txt").decode().split("\n") + for file in deletefiles: + current_game_file = Path(self._gamedir.joinpath(file)) + if current_game_file == Path(self._gamedir): + continue + if not current_game_file.is_file(): + continue + print("Deleting ", file) + current_game_file.unlink(missing_ok=True) + except Exception as e: + print(f"Error while reading deletefiles.txt: {e}") + + await asyncio.to_thread(archive.extractall, self._gamedir, members=files) + archive.close() + async def update_game(self, game_archive: str | Path | AsyncPath): if not await self.get_game_data_path().exists(): raise FileNotFoundError(f"Game not found in {self._gamedir}") @@ -334,74 +411,7 @@ class Installer: if not game_archive.exists(): raise FileNotFoundError(f"Update archive {game_archive} not found") - archive = zipfile.ZipFile(game_archive, 'r') - - if not self._hdiffpatch.get_hpatchz_executable(): - await self._hdiffpatch.download_latest_release() - - files = archive.namelist() - # Don't extract these files (they're useless and if the game isn't patched then it'll - # raise 31-4xxx error ingame) - for file in ["deletefiles.txt", "hdifffiles.txt"]: - try: - files.remove(file) - except ValueError: - pass - - # hdiffpatch implementation - hdifffiles = [] - for x in (await asyncio.to_thread(archive.read, "hdifffiles.txt")).decode().split("\n"): - if x: - hdifffiles.append(json.loads(x)["remoteName"]) - patch_jobs = [] - for file in hdifffiles: - current_game_file = self._gamedir.joinpath(file) - if not await current_game_file.exists(): - # Not patching since we don't have the file - continue - - patch_file = str(file) + ".hdiff" - - async def extract_and_patch(old_file, diff_file): - patch_path = self.temp_path.joinpath(diff_file) - patch_path.unlink(missing_ok=True) - try: - print(diff_file) - await asyncio.to_thread(archive.extract, diff_file, self.temp_path) - except FileExistsError: - print("Failed to extract diff file", diff_file) - return - old_suffix = old_file.suffix - old_file = await old_file.rename(old_file.with_suffix(".bak")) - proc = await self._hdiffpatch.patch_file(old_file, old_file.with_suffix(old_suffix), - patch_path, wait=True) - patch_path.unlink() - if proc.returncode == 0: - await old_file.unlink() - return - # Let the game download the file. - print("Failed to patch {}, reverting and let the in-game updater do the job...".format( - old_file.with_suffix(old_suffix)) - ) - await old_file.rename(old_file.with_suffix(old_suffix)) - - files.remove(patch_file) - patch_jobs.append(extract_and_patch(current_game_file, patch_file)) - - await asyncio.gather(*patch_jobs) - - deletefiles = archive.read("deletefiles.txt").decode().split("\n") - for file in deletefiles: - current_game_file = Path(self._gamedir.joinpath(file)) - if current_game_file == Path(self._gamedir): - continue - if not current_game_file.is_file(): - continue - print("Deleting ", file) - current_game_file.unlink(missing_ok=True) - - await asyncio.to_thread(archive.extractall, self._gamedir, members=files) - archive.close() + self._update(game_archive=game_archive) # Update game version on local variable. self._version = await self.get_game_version() self.set_version_config() @@ -443,7 +453,10 @@ class Installer: # making this function universal for both cases if not await self.get_game_data_path().exists(): raise FileNotFoundError(f"Game not found in {self._gamedir}") - await self._extract_game_file(voiceover_archive) + if isinstance(voiceover_archive, str | Path): + voiceover_archive = Path(voiceover_archive).resolve() + await self._update(voiceover_archive) + # await self._extract_game_file(voiceover_archive) async def install_game(self, game_archive: str | Path | AsyncPath, force_reinstall: bool = False): """Installs the game to the current directory