feat(hsr): implement voicepack update

This commit is contained in:
tretrauit 2024-01-28 17:11:16 +07:00
parent 4c851b999a
commit 9f9a63c9b0
3 changed files with 120 additions and 43 deletions

View File

@ -83,12 +83,6 @@ class GameABC(ABC):
""" """
pass pass
def get_voiceover_update(self, language: str):
"""
Get the voiceover update
"""
pass
def get_channel(self): def get_channel(self):
""" """
Get the game channel Get the game channel

View File

@ -339,7 +339,40 @@ class UpdateCommand(Command):
f"<error>Couldn't apply update: {e} ({e.__context__})</error>" f"<error>Couldn't apply update: {e} ({e.__context__})</error>"
) )
return return
progress.finish("<comment>Update applied.</comment>") progress.finish("<comment>Update applied for base game.</comment>")
# 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"<error>Couldn't download update: {e}</error>")
return
if not download_result:
self.line_error("<error>Download failed.</error>")
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"<error>Couldn't apply update: {e} ({e.__context__})</error>"
)
return
progress.finish(
f"<comment>Update applied for language {remote_voicepack.language}.</comment>"
)
self.line("Setting version config... ") self.line("Setting version config... ")
self.set_version_config() self.set_version_config()
self.line( self.line(

View File

@ -1,3 +1,4 @@
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
@ -5,6 +6,7 @@ from pathlib import Path
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
from vollerei.common.enums import VoicePackLanguage
from vollerei.exceptions.game import ( from vollerei.exceptions.game import (
GameAlreadyUpdatedError, GameAlreadyUpdatedError,
GameNotInstalledError, GameNotInstalledError,
@ -111,6 +113,42 @@ class Game(GameABC):
return False return False
return True 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]: def get_version_config(self) -> tuple[int, int, int]:
""" """
Gets the current installed game version from config.ini. 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. This method is meant to keep compatibility with the official launcher only.
""" """
cfg_file = self._path.joinpath("config.ini") cfg_file = self._path.joinpath("config.ini")
if not cfg_file.exists(): if cfg_file.exists():
raise FileNotFoundError("config.ini not found.") cfg = ConfigFile(cfg_file)
cfg = ConfigFile(cfg_file) cfg.set("General", "game_version", self.get_version_str())
cfg.set("General", "game_version", self.get_version_str()) cfg.save()
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]: 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()) 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. Gets the installed voicepacks.
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: Returns:
GameChannel: The current game channel. list[VoicePackLanguage]: A list of installed voicepacks.
""" """
version = self._version_override or self.get_version() if not self.is_installed():
if version == (1, 0, 5): raise GameNotInstalledError("Game is not installed.")
for channel, v in MD5SUMS["1.0.5"].values(): voicepacks = []
for file, md5sum in v.values(): for child in (
if ( self.data_folder()
md5(self._path.joinpath(file).read_bytes()).hexdigest() .joinpath("Persistent/Audio/AudioPackage/Windows/")
!= md5sum .iterdir()
): ):
continue if child.is_dir():
match channel: try:
case "cn": voicepacks.append(VoicePackLanguage(child.name))
return GameChannel.China except ValueError:
case "os": pass
return GameChannel.Overseas return voicepacks
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_remote_game(self, pre_download: bool = False) -> resource.Game: def get_remote_game(self, pre_download: bool = False) -> resource.Game:
""" """
@ -393,7 +430,20 @@ class Game(GameABC):
update_info = self.get_update() update_info = self.get_update()
if not update_info or update_info.version == self.get_version_str(): if not update_info or update_info.version == self.get_version_str():
raise GameAlreadyUpdatedError("Game is already updated.") raise GameAlreadyUpdatedError("Game is already updated.")
# Base game update
archive_file = self._cache.joinpath(update_info.name) archive_file = self._cache.joinpath(update_info.name)
download(update_info.path, archive_file) download(update_info.path, archive_file)
self.apply_update_archive(archive_file=archive_file, auto_repair=auto_repair) 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() self.set_version_config()