diff --git a/vollerei/abc/launcher/game.py b/vollerei/abc/launcher/game.py
index bd667a7..6857775 100644
--- a/vollerei/abc/launcher/game.py
+++ b/vollerei/abc/launcher/game.py
@@ -83,12 +83,6 @@ class GameABC(ABC):
"""
pass
- def get_voiceover_update(self, language: str):
- """
- Get the voiceover update
- """
- pass
-
def get_channel(self):
"""
Get the game channel
diff --git a/vollerei/cli/hsr.py b/vollerei/cli/hsr.py
index 9b142a9..d8458da 100644
--- a/vollerei/cli/hsr.py
+++ b/vollerei/cli/hsr.py
@@ -339,7 +339,40 @@ class UpdateCommand(Command):
f"Couldn't apply update: {e} ({e.__context__})"
)
return
- progress.finish("Update applied.")
+ progress.finish("Update applied for base game.")
+ # Get installed voicepacks
+ installed_voicepacks = State.game.get_installed_voicepacks()
+ # Voicepack update
+ for remote_voicepack in update_diff.voice_packs:
+ if remote_voicepack.language not in installed_voicepacks:
+ continue
+ # Voicepack is installed, update it
+ archive_file = State.game._cache.joinpath(remote_voicepack.name)
+ try:
+ download_result = utils.download(
+ update_diff.path, archive_file, file_len=update_diff.size
+ )
+ except Exception as e:
+ self.line_error(f"Couldn't download update: {e}")
+ return
+ if not download_result:
+ self.line_error("Download failed.")
+ return
+ self.line("Download completed.")
+ progress = utils.ProgressIndicator(self)
+ progress.start("Applying update package...")
+ try:
+ State.game.apply_update_archive(
+ archive_file=archive_file, auto_repair=auto_repair
+ )
+ except Exception as e:
+ progress.finish(
+ f"Couldn't apply update: {e} ({e.__context__})"
+ )
+ return
+ progress.finish(
+ f"Update applied for language {remote_voicepack.language}."
+ )
self.line("Setting version config... ")
self.set_version_config()
self.line(
diff --git a/vollerei/hsr/launcher/game.py b/vollerei/hsr/launcher/game.py
index 3d2d357..4b37d3a 100644
--- a/vollerei/hsr/launcher/game.py
+++ b/vollerei/hsr/launcher/game.py
@@ -1,3 +1,4 @@
+from configparser import ConfigParser
from hashlib import md5
from io import IOBase
from os import PathLike
@@ -5,6 +6,7 @@ from pathlib import Path
from vollerei.abc.launcher.game import GameABC
from vollerei.common import ConfigFile, functions
from vollerei.common.api import resource
+from vollerei.common.enums import VoicePackLanguage
from vollerei.exceptions.game import (
GameAlreadyUpdatedError,
GameNotInstalledError,
@@ -111,6 +113,42 @@ class Game(GameABC):
return False
return True
+ def get_channel(self) -> GameChannel:
+ """
+ Gets the current game channel.
+
+ Only works for Star Rail version 1.0.5, other versions will return the
+ overridden channel or GameChannel.Overseas if no channel is overridden.
+
+ This is not needed for game patching, since the patcher will automatically
+ detect the channel.
+
+ Returns:
+ GameChannel: The current game channel.
+ """
+ version = self._version_override or self.get_version()
+ if version == (1, 0, 5):
+ for channel, v in MD5SUMS["1.0.5"].values():
+ for file, md5sum in v.values():
+ if (
+ md5(self._path.joinpath(file).read_bytes()).hexdigest()
+ != md5sum
+ ):
+ continue
+ match channel:
+ case "cn":
+ return GameChannel.China
+ case "os":
+ return GameChannel.Overseas
+ else:
+ # if self._path.joinpath("StarRail_Data").is_dir():
+ # return GameChannel.Overseas
+ # elif self._path.joinpath("StarRail_Data").exists():
+ # return GameChannel.China
+ # No reliable method there, so we'll just return the overridden channel or
+ # fallback to overseas.
+ return self._channel_override or GameChannel.Overseas
+
def get_version_config(self) -> tuple[int, int, int]:
"""
Gets the current installed game version from config.ini.
@@ -147,11 +185,24 @@ class Game(GameABC):
This method is meant to keep compatibility with the official launcher only.
"""
cfg_file = self._path.joinpath("config.ini")
- if not cfg_file.exists():
- raise FileNotFoundError("config.ini not found.")
- cfg = ConfigFile(cfg_file)
- cfg.set("General", "game_version", self.get_version_str())
- cfg.save()
+ if cfg_file.exists():
+ cfg = ConfigFile(cfg_file)
+ cfg.set("General", "game_version", self.get_version_str())
+ cfg.save()
+ else:
+ cfg = ConfigParser()
+ cfg.read_dict(
+ {
+ "General": {
+ "channel": 1,
+ "cps": "hoyoverse_PC",
+ "game_version": self.get_version_str(),
+ "sub_channel": 1,
+ "plugin_2_version": "0.0.1",
+ }
+ }
+ )
+ cfg.write(cfg_file.open("w"))
def get_version(self) -> tuple[int, int, int]:
"""
@@ -232,41 +283,27 @@ class Game(GameABC):
"""
return ".".join(str(i) for i in self.get_version())
- def get_channel(self) -> GameChannel:
+ def get_installed_voicepacks(self) -> list[VoicePackLanguage]:
"""
- Gets the current game channel.
-
- Only works for Star Rail version 1.0.5, other versions will return the
- overridden channel or GameChannel.Overseas if no channel is overridden.
-
- This is not needed for game patching, since the patcher will automatically
- detect the channel.
+ Gets the installed voicepacks.
Returns:
- GameChannel: The current game channel.
+ list[VoicePackLanguage]: A list of installed voicepacks.
"""
- version = self._version_override or self.get_version()
- if version == (1, 0, 5):
- for channel, v in MD5SUMS["1.0.5"].values():
- for file, md5sum in v.values():
- if (
- md5(self._path.joinpath(file).read_bytes()).hexdigest()
- != md5sum
- ):
- continue
- match channel:
- case "cn":
- return GameChannel.China
- case "os":
- return GameChannel.Overseas
- else:
- # if self._path.joinpath("StarRail_Data").is_dir():
- # return GameChannel.Overseas
- # elif self._path.joinpath("StarRail_Data").exists():
- # return GameChannel.China
- # No reliable method there, so we'll just return the overridden channel or
- # fallback to overseas.
- return self._channel_override or GameChannel.Overseas
+ if not self.is_installed():
+ raise GameNotInstalledError("Game is not installed.")
+ voicepacks = []
+ for child in (
+ self.data_folder()
+ .joinpath("Persistent/Audio/AudioPackage/Windows/")
+ .iterdir()
+ ):
+ if child.is_dir():
+ try:
+ voicepacks.append(VoicePackLanguage(child.name))
+ except ValueError:
+ pass
+ return voicepacks
def get_remote_game(self, pre_download: bool = False) -> resource.Game:
"""
@@ -393,7 +430,20 @@ class Game(GameABC):
update_info = self.get_update()
if not update_info or update_info.version == self.get_version_str():
raise GameAlreadyUpdatedError("Game is already updated.")
+ # Base game update
archive_file = self._cache.joinpath(update_info.name)
download(update_info.path, archive_file)
self.apply_update_archive(archive_file=archive_file, auto_repair=auto_repair)
+ # Get installed voicepacks
+ installed_voicepacks = self.get_installed_voicepacks()
+ # Voicepack update
+ for remote_voicepack in update_info.voice_packs:
+ if remote_voicepack.language not in installed_voicepacks:
+ continue
+ # Voicepack is installed, update it
+ archive_file = self._cache.joinpath(remote_voicepack.name)
+ download(remote_voicepack.path, archive_file)
+ self.apply_update_archive(
+ archive_file=archive_file, auto_repair=auto_repair
+ )
self.set_version_config()