chore: move repair-related functions to common
Preparation for Genshin Impact support xD
This commit is contained in:
@ -3,10 +3,17 @@ import json
|
|||||||
import hashlib
|
import hashlib
|
||||||
import py7zr
|
import py7zr
|
||||||
from io import IOBase
|
from io import IOBase
|
||||||
|
from os import PathLike
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from shutil import move, copyfile
|
||||||
from vollerei.abc.launcher.game import GameABC
|
from vollerei.abc.launcher.game import GameABC
|
||||||
from vollerei.exceptions.game import RepairError
|
from vollerei.common.api import resource
|
||||||
from vollerei.utils import HDiffPatch, HPatchZPatchError
|
from vollerei.exceptions.game import (
|
||||||
|
RepairError,
|
||||||
|
GameNotInstalledError,
|
||||||
|
ScatteredFilesNotAvailableError,
|
||||||
|
)
|
||||||
|
from vollerei.utils import HDiffPatch, HPatchZPatchError, download
|
||||||
|
|
||||||
|
|
||||||
_hdiff = HDiffPatch()
|
_hdiff = HDiffPatch()
|
||||||
@ -139,6 +146,74 @@ def apply_update_archive(
|
|||||||
archive.close()
|
archive.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _repair_file(game: GameABC, file: PathLike, game_info: resource.Main) -> None:
|
||||||
|
# .replace("\\", "/") is needed because Windows uses backslashes :)
|
||||||
|
relative_file = file.relative_to(game.path)
|
||||||
|
url = game_info.major.res_list_url + "/" + str(relative_file).replace("\\", "/")
|
||||||
|
# Backup the file
|
||||||
|
if file.exists():
|
||||||
|
backup_file = file.with_suffix(file.suffix + ".bak")
|
||||||
|
if backup_file.exists():
|
||||||
|
backup_file.unlink()
|
||||||
|
file.rename(backup_file)
|
||||||
|
dest_file = file.with_suffix("")
|
||||||
|
else:
|
||||||
|
dest_file = file
|
||||||
|
try:
|
||||||
|
# Download the file
|
||||||
|
temp_file = game.cache.joinpath(relative_file)
|
||||||
|
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
print(f"Downloading repair file {url} to {temp_file}")
|
||||||
|
download(url, temp_file, overwrite=True, stream=True)
|
||||||
|
# Move the file
|
||||||
|
move(temp_file, dest_file)
|
||||||
|
print("OK")
|
||||||
|
except Exception as e:
|
||||||
|
# Restore the backup
|
||||||
|
print("Failed", e)
|
||||||
|
if file.exists():
|
||||||
|
file.rename(file.with_suffix(""))
|
||||||
|
raise e
|
||||||
|
# Delete the backup
|
||||||
|
if file.exists():
|
||||||
|
file.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def repair_files(
|
||||||
|
game: GameABC,
|
||||||
|
files: list[PathLike],
|
||||||
|
pre_download: bool = False,
|
||||||
|
game_info: resource.Game = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Repairs multiple game files.
|
||||||
|
|
||||||
|
This will automatically handle backup and restore the file if the repair
|
||||||
|
fails.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
game (GameABC): The game to repair the files for.
|
||||||
|
file (PathLike): The file to repair.
|
||||||
|
pre_download (bool): Whether to get the pre-download version.
|
||||||
|
Defaults to False.
|
||||||
|
"""
|
||||||
|
if not game.is_installed():
|
||||||
|
raise GameNotInstalledError("Game is not installed.")
|
||||||
|
files_path = [Path(file) for file in files]
|
||||||
|
for file in files_path:
|
||||||
|
if not file.is_relative_to(game.path):
|
||||||
|
raise ValueError("File is not in the game folder.")
|
||||||
|
if not game_info:
|
||||||
|
game_info = game.get_remote_game(pre_download=pre_download)
|
||||||
|
if game_info.latest.decompressed_path is None:
|
||||||
|
raise ScatteredFilesNotAvailableError("Scattered files are not available.")
|
||||||
|
executor = concurrent.futures.ThreadPoolExecutor()
|
||||||
|
for file in files_path:
|
||||||
|
executor.submit(_repair_file, file, game=game_info)
|
||||||
|
# self._repair_file(file, game=game)
|
||||||
|
executor.shutdown(wait=True)
|
||||||
|
|
||||||
|
|
||||||
def repair_game(
|
def repair_game(
|
||||||
game: GameABC,
|
game: GameABC,
|
||||||
pre_download: bool = False,
|
pre_download: bool = False,
|
||||||
@ -154,6 +229,8 @@ def repair_game(
|
|||||||
# Most code here are copied from worthless-launcher.
|
# Most code here are copied from worthless-launcher.
|
||||||
# worthless-launcher uses asyncio for multithreading while this one uses
|
# worthless-launcher uses asyncio for multithreading while this one uses
|
||||||
# ThreadPoolExecutor, probably better for this use case.
|
# ThreadPoolExecutor, probably better for this use case.
|
||||||
|
if not game.is_installed():
|
||||||
|
raise GameNotInstalledError("Game is not installed.")
|
||||||
game_info = game.get_remote_game(pre_download=pre_download)
|
game_info = game.get_remote_game(pre_download=pre_download)
|
||||||
pkg_version_file = game.path.joinpath("pkg_version")
|
pkg_version_file = game.path.joinpath("pkg_version")
|
||||||
pkg_version: dict[str, dict[str, str]] = {}
|
pkg_version: dict[str, dict[str, str]] = {}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import concurrent.futures
|
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from io import IOBase
|
from io import IOBase
|
||||||
from os import PathLike
|
from os import PathLike
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
from shutil import move, copyfile
|
|
||||||
from vollerei.abc.launcher.game import GameABC
|
from vollerei.abc.launcher.game import GameABC
|
||||||
from vollerei.common import ConfigFile, functions
|
from vollerei.common import ConfigFile, functions
|
||||||
from vollerei.common.api import resource
|
from vollerei.common.api import resource
|
||||||
@ -13,7 +11,6 @@ from vollerei.exceptions.game import (
|
|||||||
GameAlreadyUpdatedError,
|
GameAlreadyUpdatedError,
|
||||||
GameNotInstalledError,
|
GameNotInstalledError,
|
||||||
PreDownloadNotAvailable,
|
PreDownloadNotAvailable,
|
||||||
ScatteredFilesNotAvailableError,
|
|
||||||
)
|
)
|
||||||
from vollerei.hsr.constants import MD5SUMS
|
from vollerei.hsr.constants import MD5SUMS
|
||||||
from vollerei.hsr.launcher import api
|
from vollerei.hsr.launcher import api
|
||||||
@ -357,38 +354,6 @@ class Game(GameABC):
|
|||||||
return patch
|
return patch
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _repair_file(self, file: PathLike, game: resource.Main) -> None:
|
|
||||||
# .replace("\\", "/") is needed because Windows uses backslashes :)
|
|
||||||
relative_file = file.relative_to(self._path)
|
|
||||||
url = game.major.res_list_url + "/" + str(relative_file).replace("\\", "/")
|
|
||||||
# Backup the file
|
|
||||||
if file.exists():
|
|
||||||
backup_file = file.with_suffix(file.suffix + ".bak")
|
|
||||||
if backup_file.exists():
|
|
||||||
backup_file.unlink()
|
|
||||||
file.rename(backup_file)
|
|
||||||
dest_file = file.with_suffix("")
|
|
||||||
else:
|
|
||||||
dest_file = file
|
|
||||||
try:
|
|
||||||
# Download the file
|
|
||||||
temp_file = self.cache.joinpath(relative_file)
|
|
||||||
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
print(f"Downloading repair file {url} to {temp_file}")
|
|
||||||
download(url, temp_file, overwrite=True, stream=True)
|
|
||||||
# Move the file
|
|
||||||
copyfile(temp_file, dest_file)
|
|
||||||
print("OK")
|
|
||||||
except Exception as e:
|
|
||||||
# Restore the backup
|
|
||||||
print("Failed", e)
|
|
||||||
if file.exists():
|
|
||||||
file.rename(file.with_suffix(""))
|
|
||||||
raise e
|
|
||||||
# Delete the backup
|
|
||||||
if file.exists():
|
|
||||||
file.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
def repair_file(
|
def repair_file(
|
||||||
self,
|
self,
|
||||||
file: PathLike,
|
file: PathLike,
|
||||||
@ -406,18 +371,7 @@ class Game(GameABC):
|
|||||||
pre_download (bool): Whether to get the pre-download version.
|
pre_download (bool): Whether to get the pre-download version.
|
||||||
Defaults to False.
|
Defaults to False.
|
||||||
"""
|
"""
|
||||||
if not self.is_installed():
|
return self.repair_files([file], pre_download=pre_download, game_info=game_info)
|
||||||
raise GameNotInstalledError("Game is not installed.")
|
|
||||||
file = Path(file)
|
|
||||||
if not file.is_relative_to(self._path):
|
|
||||||
raise ValueError("File is not in the game folder.")
|
|
||||||
if not game_info:
|
|
||||||
game = self.get_remote_game(pre_download=pre_download)
|
|
||||||
else:
|
|
||||||
game = game_info
|
|
||||||
if isinstance(game.major, str | None) or game.major.res_list_url in [None, ""]:
|
|
||||||
raise ScatteredFilesNotAvailableError("Scattered files are not available.")
|
|
||||||
self._repair_file(file, game=game)
|
|
||||||
|
|
||||||
def repair_files(
|
def repair_files(
|
||||||
self,
|
self,
|
||||||
@ -436,31 +390,15 @@ class Game(GameABC):
|
|||||||
pre_download (bool): Whether to get the pre-download version.
|
pre_download (bool): Whether to get the pre-download version.
|
||||||
Defaults to False.
|
Defaults to False.
|
||||||
"""
|
"""
|
||||||
if not self.is_installed():
|
functions.repair_files(
|
||||||
raise GameNotInstalledError("Game is not installed.")
|
self, files, pre_download=pre_download, game_info=game_info
|
||||||
files_path = [Path(file) for file in files]
|
)
|
||||||
for file in files_path:
|
|
||||||
if not file.is_relative_to(self._path):
|
|
||||||
raise ValueError("File is not in the game folder.")
|
|
||||||
if not game_info:
|
|
||||||
game = self.get_remote_game(pre_download=pre_download)
|
|
||||||
else:
|
|
||||||
game = game_info
|
|
||||||
if game.latest.decompressed_path is None:
|
|
||||||
raise ScatteredFilesNotAvailableError("Scattered files are not available.")
|
|
||||||
executor = concurrent.futures.ThreadPoolExecutor()
|
|
||||||
for file in files_path:
|
|
||||||
executor.submit(self._repair_file, file, game=game)
|
|
||||||
# self._repair_file(file, game=game)
|
|
||||||
executor.shutdown(wait=True)
|
|
||||||
|
|
||||||
def repair_game(self) -> None:
|
def repair_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
Tries to repair the game by reading "pkg_version" file and downloading the
|
Tries to repair the game by reading "pkg_version" file and downloading the
|
||||||
mismatched files from the server.
|
mismatched files from the server.
|
||||||
"""
|
"""
|
||||||
if not self.is_installed():
|
|
||||||
raise GameNotInstalledError("Game is not installed.")
|
|
||||||
functions.repair_game(self)
|
functions.repair_game(self)
|
||||||
|
|
||||||
def apply_update_archive(
|
def apply_update_archive(
|
||||||
|
|||||||
Reference in New Issue
Block a user