diff --git a/vollerei/cli/hsr.py b/vollerei/cli/hsr.py
index f881c19..34070f5 100644
--- a/vollerei/cli/hsr.py
+++ b/vollerei/cli/hsr.py
@@ -148,7 +148,7 @@ class VoicepackUpdateAll(Command):
game_info = State.game.get_remote_game(pre_download=pre_download)
except Exception as e:
progress.finish(
- f"Update checking failed with following error: {e} \n({traceback.format_exc()})"
+ f"Update checking failed with following error: {e} \n{traceback.format_exc()}"
)
return
if update_diff is None:
@@ -192,7 +192,7 @@ class VoicepackUpdateAll(Command):
)
except Exception as e:
progress.finish(
- f"Couldn't apply update: {e} \n({traceback.format_exc()})"
+ f"Couldn't apply update: {e} \n{traceback.format_exc()}"
)
return
progress.finish(
@@ -228,7 +228,7 @@ class UpdatePatchCommand(Command):
patcher.update_patch()
except PatchUpdateError as e:
progress.finish(
- f"Patch update failed with following error: {e} \n({traceback.format_exc()})"
+ f"Patch update failed with following error: {e} \n{traceback.format_exc()}"
)
else:
progress.finish("Patch updated!")
@@ -246,7 +246,7 @@ class PatchInstallCommand(Command):
jadeite_dir = patcher.patch_game(game=State.game)
except PatcherError as e:
progress.finish(
- f"Patch installation failed with following error: {e} \n({traceback.format_exc()})"
+ f"Patch installation failed with following error: {e} \n{traceback.format_exc()}"
)
return
progress.finish("Patch installed!")
@@ -279,7 +279,7 @@ class PatchInstallCommand(Command):
patcher.patch_game(game=State.game)
except PatcherError as e:
progress.finish(
- f"Patch installation failed with following error: {e} \n({traceback.format_exc()})"
+ f"Patch installation failed with following error: {e} \n{traceback.format_exc()}"
)
return
progress.finish("Patch installed!")
@@ -344,7 +344,7 @@ class PatchInstallCommand(Command):
patcher.update_patch()
except PatchUpdateError as e:
progress.finish(
- f"Patch update failed with following error: {e} \n({traceback.format_exc()})"
+ f"Patch update failed with following error: {e} \n{traceback.format_exc()}"
)
else:
progress.finish("Patch updated.")
@@ -437,7 +437,7 @@ class UpdateCommand(Command):
game_info = State.game.get_remote_game(pre_download=pre_download)
except Exception as e:
progress.finish(
- f"Update checking failed with following error: {e} \n({traceback.format_exc()})"
+ f"Update checking failed with following error: {e} \n{traceback.format_exc()}"
)
return
if update_diff is None or isinstance(game_info.major, str | None):
@@ -474,7 +474,7 @@ class UpdateCommand(Command):
State.game.apply_update_archive(out_path, auto_repair=auto_repair)
except Exception as e:
progress.finish(
- f"Couldn't apply update: {e} \n({traceback.format_exc()})"
+ f"Couldn't apply update: {e} \n{traceback.format_exc()}"
)
return
progress.finish("Update applied for base game.")
@@ -485,7 +485,12 @@ class UpdateCommand(Command):
if remote_voicepack.language not in installed_voicepacks:
continue
# Voicepack is installed, update it
- archive_file = State.game.cache.joinpath(PurePath(remote_voicepack.url).name)
+ archive_file = State.game.cache.joinpath(
+ PurePath(remote_voicepack.url).name
+ )
+ self.line(
+ f"Downloading update package for voicepack language '{remote_voicepack.language.name}'..."
+ )
try:
download_result = utils.download(
remote_voicepack.url, archive_file, file_len=remote_voicepack.size
@@ -505,13 +510,14 @@ class UpdateCommand(Command):
)
except Exception as e:
progress.finish(
- f"Couldn't apply update: {e} \n({traceback.format_exc()})"
+ f"Couldn't apply update: {e} \n{traceback.format_exc()}"
)
return
progress.finish(
- f"Update applied for language {remote_voicepack.language}."
+ f"Update applied for language {remote_voicepack.language.name}."
)
self.line("Setting version config... ")
+ State.game.version_override = None
State.game.set_version_config()
self.line(
f"The game has been updated to version: {State.game.get_version_str()}"
@@ -542,7 +548,7 @@ class RepairCommand(Command):
State.game.repair_game()
except Exception as e:
progress.finish(
- f"Repairation failed with following error: {e} \n({traceback.format_exc()})"
+ f"Repairation failed with following error: {e} \n{traceback.format_exc()}"
)
return
progress.finish("Repairation completed.")
@@ -578,7 +584,7 @@ class UpdateDownloadCommand(Command):
game_info = State.game.get_remote_game(pre_download=pre_download)
except Exception as e:
progress.finish(
- f"Update checking failed with following error: {e} \n({traceback.format_exc()})"
+ f"Update checking failed with following error: {e} \n{traceback.format_exc()}"
)
return
if update_diff is None or isinstance(game_info.major, str | None):
@@ -602,7 +608,9 @@ class UpdateDownloadCommand(Command):
update_game_url, out_path, file_len=update_diff.game_pkgs[0].size
)
except Exception as e:
- self.line_error(f"Couldn't download update: {e}")
+ self.line_error(
+ f"Couldn't download update: {e} \n{traceback.format_exc()}"
+ )
return
if not download_result:
@@ -616,7 +624,12 @@ class UpdateDownloadCommand(Command):
if remote_voicepack.language not in installed_voicepacks:
continue
# Voicepack is installed, update it
- archive_file = State.game.cache.joinpath(PurePath(remote_voicepack.url).name)
+ archive_file = State.game.cache.joinpath(
+ PurePath(remote_voicepack.url).name
+ )
+ self.line(
+ f"Downloading update package for voicepack language '{remote_voicepack.language.name}'..."
+ )
try:
download_result = utils.download(
remote_voicepack.url, archive_file, file_len=remote_voicepack.size
@@ -652,7 +665,7 @@ class ApplyUpdateArchive(Command):
State.game.apply_update_archive(update_archive, auto_repair=auto_repair)
except Exception as e:
progress.finish(
- f"Couldn't apply update: {e} \n({traceback.format_exc()})"
+ f"Couldn't apply update: {e} \n{traceback.format_exc()}"
)
return
progress.finish("Update applied.")
diff --git a/vollerei/common/api/resource.py b/vollerei/common/api/resource.py
index e6dc91a..caf4956 100644
--- a/vollerei/common/api/resource.py
+++ b/vollerei/common/api/resource.py
@@ -20,13 +20,18 @@ class GamePackage:
self.decompressed_size = decompressed_size
@staticmethod
- def from_dict(data: dict) -> "GamePackage":
- return GamePackage(
- url=data["url"],
- md5=data["md5"],
- size=int(data["size"]),
- decompressed_size=int(data["decompressed_size"]),
- )
+ def from_dict(data: list[dict]) -> list["GamePackage"]:
+ game_pkgs = []
+ for pkg in data:
+ game_pkgs.append(
+ GamePackage(
+ url=pkg["url"],
+ md5=pkg["md5"],
+ size=int(pkg["size"]),
+ decompressed_size=int(pkg["decompressed_size"]),
+ )
+ )
+ return game_pkgs
class AudioPackage:
@@ -45,14 +50,19 @@ class AudioPackage:
self.decompressed_size = decompressed_size
@staticmethod
- def from_dict(data: dict) -> "AudioPackage":
- return AudioPackage(
- language=VoicePackLanguage.from_remote_str(data["language"]),
- url=data["url"],
- md5=data["md5"],
- size=int(data["size"]),
- decompressed_size=int(data["decompressed_size"]),
- )
+ def from_dict(data: list[dict]) -> "AudioPackage":
+ audio_pkgs = []
+ for pkg in data:
+ audio_pkgs.append(
+ AudioPackage(
+ language=VoicePackLanguage.from_remote_str(pkg["language"]),
+ url=pkg["url"],
+ md5=pkg["md5"],
+ size=int(pkg["size"]),
+ decompressed_size=int(pkg["decompressed_size"]),
+ )
+ )
+ return audio_pkgs
class Major:
@@ -72,8 +82,8 @@ class Major:
def from_dict(data: dict) -> "Major":
return Major(
version=data["version"],
- game_pkgs=[GamePackage(**x) for x in data["game_pkgs"]],
- audio_pkgs=[AudioPackage(**x) for x in data["audio_pkgs"]],
+ game_pkgs=GamePackage.from_dict(data["game_pkgs"]),
+ audio_pkgs=AudioPackage.from_dict(data["audio_pkgs"]),
res_list_url=data["res_list_url"],
)
@@ -104,7 +114,7 @@ class PreDownload:
@staticmethod
def from_dict(data: dict | None) -> Union["PreDownload", None]:
# pre_download can be null in the server for certain games
- # e.g. HI3:
+ # e.g. HI3:
# "pre_download": null
# while in GI it is the following:
# "pre_download": {
diff --git a/vollerei/common/functions.py b/vollerei/common/functions.py
index efc958a..0f2c504 100644
--- a/vollerei/common/functions.py
+++ b/vollerei/common/functions.py
@@ -45,10 +45,14 @@ def apply_update_archive(
pass
# Think for me a better name for this variable
txtfiles = archive.read(["deletefiles.txt", "hdifffiles.txt"])
+ # Reset archive to extract files
+ archive.reset()
try:
# miHoYo loves CRLF
deletebytes = txtfiles["deletefiles.txt"].read()
- if deletebytes is bytes:
+ if deletebytes is not str:
+ # Typing
+ deletebytes: bytes
deletebytes = deletebytes.decode()
deletefiles = deletebytes.split("\r\n")
except IOError:
@@ -69,7 +73,9 @@ def apply_update_archive(
# Read hdifffiles.txt to get the files to patch
hdifffiles = []
hdiffbytes = txtfiles["hdifffiles.txt"].read()
- if hdiffbytes is bytes:
+ if hdiffbytes is not str:
+ # Typing
+ hdiffbytes: bytes
hdiffbytes = hdiffbytes.decode()
for x in hdiffbytes.split("\r\n"):
try:
@@ -78,13 +84,9 @@ def apply_update_archive(
pass
# Patch function
- def extract_and_patch(file, patch_file):
+ def patch(file, patch_file):
patchpath = game.cache.joinpath(patch_file)
- # Delete old patch file if exists
- patchpath.unlink(missing_ok=True)
- # Extract patch file
# Spaghetti code :(, fuck my eyes.
- archive.extract(game.temppath, [patch_file])
file = file.rename(file.with_suffix(file.suffix + ".bak"))
try:
_hdiff.patch_file(file, file.with_suffix(""), patchpath)
@@ -106,18 +108,9 @@ def apply_update_archive(
# Remove old file, since we don't need it anymore.
file.unlink()
- def extract_or_repair(file: str):
- # Extract file
- try:
- archive.extract(game.path, [file])
- except Exception as e:
- # Repair file
- if not auto_repair:
- raise e
- game.repair_file(game.path.joinpath(file))
-
# Multi-threaded patching
patch_jobs = []
+ patch_files = []
for file_str in hdifffiles:
file = game.path.joinpath(file_str)
if not file.exists():
@@ -126,8 +119,13 @@ def apply_update_archive(
patch_file: str = file_str + ".hdiff"
# Remove hdiff files from files list to extract
files.remove(patch_file)
- patch_jobs.append([extract_and_patch, [file, patch_file]])
+ # Add file to extract list
+ patch_files.append(patch_file)
+ patch_jobs.append([patch, [file, patch_file]])
+ # Extract patch files to temporary dir
+ archive.extract(game.cache, patch_files)
+ archive.reset() # For the next extraction
# Create new ThreadPoolExecutor for patching
patch_executor = concurrent.futures.ThreadPoolExecutor()
for job in patch_jobs:
@@ -135,15 +133,7 @@ def apply_update_archive(
patch_executor.shutdown(wait=True)
# Extract files from archive after we have filtered out the patch files
- # Using ProcessPoolExecutor instead of archive.extractall() because
- # archive.extractall() can crash with large archives, and it doesn't
- # handle broken files.
- # ProcessPoolExecutor is faster than ThreadPoolExecutor, and it shouldn't
- # cause any problems here.
- extract_executor = concurrent.futures.ProcessPoolExecutor()
- for file in files:
- extract_executor.submit(extract_or_repair, file)
- extract_executor.shutdown(wait=True)
+ archive.extract(game.path, files)
# Close the archive
archive.close()
diff --git a/vollerei/hsr/launcher/game.py b/vollerei/hsr/launcher/game.py
index 1b2d1b7..f682164 100644
--- a/vollerei/hsr/launcher/game.py
+++ b/vollerei/hsr/launcher/game.py
@@ -106,7 +106,6 @@ class Game(GameABC):
return False
if (
not self._path.joinpath("StarRail.exe").exists()
- or not self._path.joinpath("StarRailBase.dll").exists()
or not self._path.joinpath("StarRail_Data").exists()
):
return False
@@ -166,11 +165,13 @@ class Game(GameABC):
if not cfg_file.exists():
return (0, 0, 0)
cfg = ConfigFile(cfg_file)
- if "General" not in cfg.sections():
+ # Fk u miHoYo
+ if "general" in cfg.sections():
+ version_str = cfg.get("general", "game_version", fallback="0.0.0")
+ elif "General" in cfg.sections():
+ version_str = cfg.get("General", "game_version", fallback="0.0.0")
+ else:
return (0, 0, 0)
- if "game_version" not in cfg["General"]:
- return (0, 0, 0)
- version_str = cfg["General"]["game_version"]
if version_str.count(".") != 2:
return (0, 0, 0)
try:
@@ -196,10 +197,14 @@ class Game(GameABC):
{
"General": {
"channel": 1,
- "cps": "hoyoverse_PC",
+ "cps": "hyp_hoyoverse",
"game_version": self.get_version_str(),
"sub_channel": 1,
"plugin_2_version": "0.0.1",
+ "uapc": {
+ "hkrpg_global": {"uapc": "f5c7c6262812_"},
+ "hyp": {"uapc": ""},
+ }, # Honestly what's this?
}
}
)
@@ -294,19 +299,22 @@ class Game(GameABC):
if not self.is_installed():
raise GameNotInstalledError("Game is not installed.")
voicepacks = []
+ blacklisted_words = ["SFX"]
for child in (
self.data_folder()
.joinpath("Persistent/Audio/AudioPackage/Windows/")
.iterdir()
):
- if child.is_dir():
+ if child.resolve().is_dir() and child.name not in blacklisted_words:
try:
voicepacks.append(VoicePackLanguage[child.name])
except ValueError:
pass
return voicepacks
- def get_remote_game(self, pre_download: bool = False) -> resource.Main | resource.PreDownload:
+ def get_remote_game(
+ self, pre_download: bool = False
+ ) -> resource.Main | resource.PreDownload:
"""
Gets the current game information from remote.
@@ -352,9 +360,7 @@ class Game(GameABC):
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("\\", "/")
- )
+ url = game.major.res_list_url + "/" + str(relative_file).replace("\\", "/")
# Backup the file
if file.exists():
backup_file = file.with_suffix(file.suffix + ".bak")