chore(cli): migrate to one file
Easier to maintain but slightly slower execution speed lol
This commit is contained in:
@ -129,7 +129,7 @@ class GameABC(ABC):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_remote_game(self, pre_download: bool = False) -> resource.Game:
|
def get_remote_game(self, pre_download: bool = False) -> resource.Main | resource.PreDownload:
|
||||||
"""
|
"""
|
||||||
Gets the current game information from remote.
|
Gets the current game information from remote.
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
from cleo.application import Application
|
from cleo.application import Application
|
||||||
from vollerei.cli import hsr, genshin
|
from vollerei.cli import commands
|
||||||
|
|
||||||
application = Application()
|
application = Application()
|
||||||
for command in hsr.commands + genshin.commands:
|
for command in commands.exports:
|
||||||
application.add(command())
|
application.add(command)
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
# THIS FILE CURRENTLY DOESN'T WORK YET.
|
|
||||||
import copy
|
import copy
|
||||||
import traceback
|
import traceback
|
||||||
from cleo.commands.command import Command
|
from cleo.commands.command import Command
|
||||||
@ -130,7 +129,7 @@ class VoicepackList(Command):
|
|||||||
remote_game = State.game.get_remote_game(pre_download=pre_download)
|
remote_game = State.game.get_remote_game(pre_download=pre_download)
|
||||||
available_voicepacks_str = [
|
available_voicepacks_str = [
|
||||||
f"<comment>{x.language.name} ({x.language.value})</comment>"
|
f"<comment>{x.language.name} ({x.language.value})</comment>"
|
||||||
for x in remote_game.latest.voice_packs
|
for x in remote_game.major.audio_pkgs
|
||||||
]
|
]
|
||||||
self.line(f"Available voicepacks: {', '.join(available_voicepacks_str)}")
|
self.line(f"Available voicepacks: {', '.join(available_voicepacks_str)}")
|
||||||
|
|
||||||
@ -939,7 +938,7 @@ class ApplyUpdateArchive(Command):
|
|||||||
set_version_config()
|
set_version_config()
|
||||||
|
|
||||||
# This is the list for HSR commands, we'll add Genshin commands later
|
# This is the list for HSR commands, we'll add Genshin commands later
|
||||||
hsr_exports = [
|
classes = [
|
||||||
ApplyInstallArchive,
|
ApplyInstallArchive,
|
||||||
ApplyUpdateArchive,
|
ApplyUpdateArchive,
|
||||||
GetVersionCommand,
|
GetVersionCommand,
|
||||||
@ -959,10 +958,15 @@ hsr_exports = [
|
|||||||
VoicepackUpdate,
|
VoicepackUpdate,
|
||||||
]
|
]
|
||||||
exports = []
|
exports = []
|
||||||
for command in hsr_exports:
|
for command in classes:
|
||||||
exports.append(command)
|
init_command = command()
|
||||||
|
exports.append(init_command)
|
||||||
if "patch" in command.name:
|
if "patch" in command.name:
|
||||||
continue
|
continue
|
||||||
new_command = copy.deepcopy(command)
|
command_name = command.name[4:]
|
||||||
new_command.name = f"genshin {new_command.name[4:]}"
|
genshin_init_command = copy.deepcopy(init_command)
|
||||||
exports.append(new_command)
|
genshin_init_command.name = f"genshin {command_name}"
|
||||||
|
exports.append(genshin_init_command)
|
||||||
|
zzz_init_command = copy.deepcopy(init_command)
|
||||||
|
zzz_init_command.name = f"zzz {command_name}"
|
||||||
|
exports.append(zzz_init_command)
|
||||||
|
|||||||
@ -1,747 +0,0 @@
|
|||||||
import traceback
|
|
||||||
from cleo.commands.command import Command
|
|
||||||
from cleo.helpers import option, argument
|
|
||||||
from pathlib import PurePath
|
|
||||||
from vollerei.common.enums import GameChannel, VoicePackLanguage
|
|
||||||
from vollerei.cli import utils
|
|
||||||
from vollerei.exceptions.game import GameError
|
|
||||||
from vollerei.genshin import Game
|
|
||||||
from vollerei import paths
|
|
||||||
|
|
||||||
|
|
||||||
default_options = [
|
|
||||||
option("channel", "c", description="Game channel.", flag=False, default="overseas"),
|
|
||||||
option("force", "f", description="Force the command to run."),
|
|
||||||
option(
|
|
||||||
"game-path",
|
|
||||||
"g",
|
|
||||||
description="Path to the game installation.",
|
|
||||||
flag=False,
|
|
||||||
default=".",
|
|
||||||
),
|
|
||||||
option("temporary-path", "t", description="Temporary path.", flag=False),
|
|
||||||
option("silent", "s", description="Silent mode."),
|
|
||||||
option("noconfirm", "y", description="Do not ask for confirmation (yes to all)."),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class State:
|
|
||||||
game: Game = None
|
|
||||||
|
|
||||||
|
|
||||||
def callback(
|
|
||||||
command: Command,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Base callback for all commands
|
|
||||||
"""
|
|
||||||
game_path = command.option("game-path")
|
|
||||||
channel = command.option("channel")
|
|
||||||
silent = command.option("silent")
|
|
||||||
noconfirm = command.option("noconfirm")
|
|
||||||
temporary_path = command.option("temporary-path")
|
|
||||||
if isinstance(channel, str):
|
|
||||||
channel = GameChannel[channel.capitalize()]
|
|
||||||
elif isinstance(channel, int):
|
|
||||||
channel = GameChannel(channel)
|
|
||||||
if temporary_path:
|
|
||||||
paths.set_base_path(temporary_path)
|
|
||||||
State.game = Game(game_path, temporary_path)
|
|
||||||
if channel:
|
|
||||||
State.game.channel_override = channel
|
|
||||||
utils.silent_message = silent
|
|
||||||
if noconfirm:
|
|
||||||
utils.no_confirm = noconfirm
|
|
||||||
|
|
||||||
def confirm(
|
|
||||||
question: str, default: bool = False, true_answer_regex: str = r"(?i)^y"
|
|
||||||
):
|
|
||||||
command.line(
|
|
||||||
f"<question>{question} (yes/no)</question> [<comment>{'yes' if default else 'no'}</comment>] y"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
command.confirm = confirm
|
|
||||||
command.add_style("warn", fg="yellow")
|
|
||||||
|
|
||||||
|
|
||||||
def set_version_config(self: Command):
|
|
||||||
self.line("Setting version config... ")
|
|
||||||
try:
|
|
||||||
State.game.set_version_config()
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(f"<warn>Couldn't set version config: {e}</warn>")
|
|
||||||
self.line_error(
|
|
||||||
"This won't affect the overall experience, but if you're using the official launcher"
|
|
||||||
)
|
|
||||||
self.line_error(
|
|
||||||
"you may have to edit the file 'config.ini' manually to reflect the latest version."
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The game has been updated to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VoicepackListInstalled(Command):
|
|
||||||
name = "genshin voicepack list-installed"
|
|
||||||
description = "Gets the installed voicepacks."
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
installed_voicepacks_str = [
|
|
||||||
f"<comment>{x.name}</comment>"
|
|
||||||
for x in State.game.get_installed_voicepacks()
|
|
||||||
]
|
|
||||||
self.line(f"Installed voicepacks: {', '.join(installed_voicepacks_str)}")
|
|
||||||
|
|
||||||
|
|
||||||
class VoicepackList(Command):
|
|
||||||
name = "genshin voicepack list"
|
|
||||||
description = "Gets all available voicepacks."
|
|
||||||
options = default_options + [
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
remote_game = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
available_voicepacks_str = [
|
|
||||||
f"<comment>{x.language.name} ({x.language.value})</comment>"
|
|
||||||
for x in remote_game.latest.voice_packs
|
|
||||||
]
|
|
||||||
self.line(f"Available voicepacks: {', '.join(available_voicepacks_str)}")
|
|
||||||
|
|
||||||
|
|
||||||
class VoicepackInstall(Command):
|
|
||||||
name = "genshin voicepack install"
|
|
||||||
description = (
|
|
||||||
"Installs the specified installed voicepacks."
|
|
||||||
)
|
|
||||||
options = default_options + [
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
]
|
|
||||||
arguments = [
|
|
||||||
argument(
|
|
||||||
"language", description="Languages to install", multiple=True, optional=True
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
# Typing manually because pylance detect it as Any
|
|
||||||
languages: list[str] = self.argument("language")
|
|
||||||
# Get installed voicepacks
|
|
||||||
language_objects = []
|
|
||||||
for language in languages:
|
|
||||||
language = language.lower()
|
|
||||||
try:
|
|
||||||
language_objects.append(VoicePackLanguage[language.capitalize()])
|
|
||||||
except KeyError:
|
|
||||||
try:
|
|
||||||
language_objects.append(VoicePackLanguage.from_remote_str(language))
|
|
||||||
except ValueError:
|
|
||||||
self.line_error(f"<error>Invalid language: {language}</error>")
|
|
||||||
if len(language_objects) == 0:
|
|
||||||
self.line_error(
|
|
||||||
"<error>No valid languages specified, you must specify a language to install</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Fetching install package information... ")
|
|
||||||
try:
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Fetching failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
"<comment>Installation information fetched successfully.</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to install the specified voicepacks?"):
|
|
||||||
self.line("<error>Installation aborted.</error>")
|
|
||||||
return
|
|
||||||
# Voicepack update
|
|
||||||
for remote_voicepack in game_info.major.audio_pkgs:
|
|
||||||
if remote_voicepack.language not in language_objects:
|
|
||||||
continue
|
|
||||||
self.line(
|
|
||||||
f"Downloading install package for language: <comment>{remote_voicepack.language.name}</comment>... "
|
|
||||||
)
|
|
||||||
archive_file = State.game.cache.joinpath(PurePath(remote_voicepack.url).name)
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
remote_voicepack.url, archive_file, file_len=remote_voicepack.size
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(f"<error>Couldn't download package: {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("Installing package...")
|
|
||||||
try:
|
|
||||||
State.game.install_archive(archive_file)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't apply package: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
f"<comment>Package applied for language {remote_voicepack.language.name}.</comment>"
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The voicepacks have been installed to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VoicepackUpdate(Command):
|
|
||||||
name = "genshin voicepack update"
|
|
||||||
description = (
|
|
||||||
"Updates the specified installed voicepacks, if not specified, updates all."
|
|
||||||
)
|
|
||||||
options = default_options + [
|
|
||||||
option(
|
|
||||||
"auto-repair", "R", description="Automatically repair the game if needed."
|
|
||||||
),
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
option(
|
|
||||||
"from-version", description="Update from a specific version", flag=False
|
|
||||||
),
|
|
||||||
]
|
|
||||||
arguments = [
|
|
||||||
argument(
|
|
||||||
"language", description="Languages to update", multiple=True, optional=True
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
auto_repair = self.option("auto-repair")
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
from_version = self.option("from-version")
|
|
||||||
# Typing manually because pylance detect it as Any
|
|
||||||
languages: list[str] = self.argument("language")
|
|
||||||
if auto_repair:
|
|
||||||
self.line("<comment>Auto-repair is enabled.</comment>")
|
|
||||||
if from_version:
|
|
||||||
self.line(f"Updating from version: <comment>{from_version}</comment>")
|
|
||||||
State.game.version_override = from_version
|
|
||||||
# Get installed voicepacks
|
|
||||||
if len(languages) == 0:
|
|
||||||
self.line(
|
|
||||||
"<comment>No languages specified, updating all installed voicepacks...</comment>"
|
|
||||||
)
|
|
||||||
installed_voicepacks = State.game.get_installed_voicepacks()
|
|
||||||
if len(languages) > 0:
|
|
||||||
languages = [x.lower() for x in languages]
|
|
||||||
# Support both English and en-us and en
|
|
||||||
installed_voicepacks = [
|
|
||||||
x
|
|
||||||
for x in installed_voicepacks
|
|
||||||
if x.name.lower() in languages
|
|
||||||
or x.value.lower() in languages
|
|
||||||
or x.name.lower()[:2] in languages
|
|
||||||
]
|
|
||||||
installed_voicepacks_str = [
|
|
||||||
f"<comment>{str(x.name)}</comment>" for x in installed_voicepacks
|
|
||||||
]
|
|
||||||
self.line(f"Updating voicepacks: {', '.join(installed_voicepacks_str)}")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Checking for updates... ")
|
|
||||||
try:
|
|
||||||
update_diff = State.game.get_update(pre_download=pre_download)
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Update checking failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if update_diff is None:
|
|
||||||
progress.finish("<comment>Game is already updated.</comment>")
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Update available.</comment>")
|
|
||||||
self.line(
|
|
||||||
f"The current version is: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The latest version is: <comment>{game_info.latest.version}</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to update the game?"):
|
|
||||||
self.line("<error>Update aborted.</error>")
|
|
||||||
return
|
|
||||||
# Voicepack update
|
|
||||||
for remote_voicepack in update_diff.voice_packs:
|
|
||||||
if remote_voicepack.language not in installed_voicepacks:
|
|
||||||
continue
|
|
||||||
# Voicepack is installed, update it
|
|
||||||
self.line(
|
|
||||||
f"Downloading update package for language: <comment>{remote_voicepack.language.name}</comment>... "
|
|
||||||
)
|
|
||||||
archive_file = State.game.cache.joinpath(remote_voicepack.name)
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
remote_voicepack.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} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
f"<comment>Update applied for language {remote_voicepack.language.name}.</comment>"
|
|
||||||
)
|
|
||||||
set_version_config(self=self)
|
|
||||||
self.line(
|
|
||||||
f"The game has been updated to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GetVersionCommand(Command):
|
|
||||||
name = "genshin version"
|
|
||||||
description = "Gets the local game version."
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
try:
|
|
||||||
self.line(
|
|
||||||
f"<comment>Version:</comment> {'.'.join(str(x) for x in State.game.get_version())}"
|
|
||||||
)
|
|
||||||
except GameError as e:
|
|
||||||
self.line_error(f"<error>Couldn't get game version: {e}</error>")
|
|
||||||
|
|
||||||
|
|
||||||
class InstallCommand(Command):
|
|
||||||
name = "genshin install"
|
|
||||||
description = (
|
|
||||||
"Installs the latest version of the game to the specified path (default: current directory). "
|
|
||||||
+ "Note that this will not install the default voicepack (English), you need to install it manually."
|
|
||||||
)
|
|
||||||
options = default_options + [
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Fetching install package information... ")
|
|
||||||
try:
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Fetching failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
"<comment>Installation information fetched successfully.</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to install the game?"):
|
|
||||||
self.line("<error>Installation aborted.</error>")
|
|
||||||
return
|
|
||||||
self.line("Downloading install package...")
|
|
||||||
first_pkg_out_path = None
|
|
||||||
for game_pkg in game_info.major.game_pkgs:
|
|
||||||
out_path = State.game.cache.joinpath(PurePath(game_pkg.url).name)
|
|
||||||
if not first_pkg_out_path:
|
|
||||||
first_pkg_out_path = out_path
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
game_pkg.url, out_path, file_len=game_pkg.size
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(
|
|
||||||
f"<error>Couldn't download install package: {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("Installing package...")
|
|
||||||
try:
|
|
||||||
State.game.install_archive(first_pkg_out_path)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't install package: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Package applied for the base game.</comment>")
|
|
||||||
self.line("Setting version config... ")
|
|
||||||
State.game.version_override = game_info.major.version
|
|
||||||
set_version_config()
|
|
||||||
State.game.version_override = None
|
|
||||||
self.line(
|
|
||||||
f"The game has been installed to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateCommand(Command):
|
|
||||||
name = "genshin update"
|
|
||||||
description = "Updates the local game if available"
|
|
||||||
options = default_options + [
|
|
||||||
option(
|
|
||||||
"auto-repair", "R", description="Automatically repair the game if needed."
|
|
||||||
),
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
option(
|
|
||||||
"from-version", description="Update from a specific version", flag=False
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
auto_repair = self.option("auto-repair")
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
from_version = self.option("from-version")
|
|
||||||
if auto_repair:
|
|
||||||
self.line("<comment>Auto-repair is enabled.</comment>")
|
|
||||||
if from_version:
|
|
||||||
self.line(f"Updating from version: <comment>{from_version}</comment>")
|
|
||||||
State.game.version_override = from_version
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Checking for updates... ")
|
|
||||||
try:
|
|
||||||
update_diff = State.game.get_update(pre_download=pre_download)
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Update checking failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if update_diff is None or isinstance(game_info.major, str | None):
|
|
||||||
progress.finish("<comment>Game is already updated.</comment>")
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Update available.</comment>")
|
|
||||||
self.line(
|
|
||||||
f"The current version is: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The latest version is: <comment>{game_info.major.version}</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to update the game?"):
|
|
||||||
self.line("<error>Update aborted.</error>")
|
|
||||||
return
|
|
||||||
self.line("Downloading update package...")
|
|
||||||
update_game_url = update_diff.game_pkgs[0].url
|
|
||||||
out_path = State.game.cache.joinpath(PurePath(update_game_url).name)
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
update_game_url, out_path, file_len=update_diff.game_pkgs[0].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(out_path, auto_repair=auto_repair)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't apply update: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
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.audio_pkgs:
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
f"<comment>Update applied for language {remote_voicepack.language.name}.</comment>"
|
|
||||||
)
|
|
||||||
self.line("Setting version config... ")
|
|
||||||
State.game.version_override = game_info.major.version
|
|
||||||
set_version_config()
|
|
||||||
State.game.version_override = None
|
|
||||||
self.line(
|
|
||||||
f"The game has been updated to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RepairCommand(Command):
|
|
||||||
name = "genshin repair"
|
|
||||||
description = "Tries to repair the local game"
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
self.line(
|
|
||||||
"This command will try to repair the game by downloading missing/broken files."
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
"There will be no progress available, so please be patient and just wait."
|
|
||||||
)
|
|
||||||
if not self.confirm(
|
|
||||||
"Do you want to repair the game (this will take a long time!)?"
|
|
||||||
):
|
|
||||||
self.line("<error>Repairation aborted.</error>")
|
|
||||||
return
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Repairing game files (no progress available)... ")
|
|
||||||
try:
|
|
||||||
State.game.repair_game()
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Repairation failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Repairation completed.</comment>")
|
|
||||||
|
|
||||||
|
|
||||||
class InstallDownloadCommand(Command):
|
|
||||||
name = "genshin install download"
|
|
||||||
description = (
|
|
||||||
"Downloads the latest version of the game. "
|
|
||||||
+ "Note that this will not download the default voicepack (English), you need to download it manually."
|
|
||||||
)
|
|
||||||
options = default_options + [
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Fetching install package information... ")
|
|
||||||
try:
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Fetching failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
"<comment>Installation information fetched successfully.</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to download the game?"):
|
|
||||||
self.line("<error>Download aborted.</error>")
|
|
||||||
return
|
|
||||||
self.line("Downloading install package...")
|
|
||||||
first_pkg_out_path = None
|
|
||||||
for game_pkg in game_info.major.game_pkgs:
|
|
||||||
out_path = State.game.cache.joinpath(PurePath(game_pkg.url).name)
|
|
||||||
if not first_pkg_out_path:
|
|
||||||
first_pkg_out_path = out_path
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
game_pkg.url, out_path, file_len=game_pkg.size
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(
|
|
||||||
f"<error>Couldn't download install package: {e}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if not download_result:
|
|
||||||
self.line_error("<error>Download failed.</error>")
|
|
||||||
return
|
|
||||||
self.line("Download completed.")
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateDownloadCommand(Command):
|
|
||||||
name = "genshin update download"
|
|
||||||
description = "Download the update for the local game if available"
|
|
||||||
options = default_options + [
|
|
||||||
option(
|
|
||||||
"auto-repair", "R", description="Automatically repair the game if needed."
|
|
||||||
),
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
option(
|
|
||||||
"from-version", description="Update from a specific version", flag=False
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
auto_repair = self.option("auto-repair")
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
from_version = self.option("from-version")
|
|
||||||
if auto_repair:
|
|
||||||
self.line("<comment>Auto-repair is enabled.</comment>")
|
|
||||||
if from_version:
|
|
||||||
self.line(f"Updating from version: <comment>{from_version}</comment>")
|
|
||||||
State.game.version_override = from_version
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Checking for updates... ")
|
|
||||||
try:
|
|
||||||
update_diff = State.game.get_update(pre_download=pre_download)
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Update checking failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if update_diff is None or isinstance(game_info.major, str | None):
|
|
||||||
progress.finish("<comment>Game is already updated.</comment>")
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Update available.</comment>")
|
|
||||||
self.line(
|
|
||||||
f"The current version is: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The latest version is: <comment>{game_info.major.version}</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to download the update?"):
|
|
||||||
self.line("<error>Download aborted.</error>")
|
|
||||||
return
|
|
||||||
self.line("Downloading update package...")
|
|
||||||
update_game_url = update_diff.game_pkgs[0].url
|
|
||||||
out_path = State.game.cache.joinpath(PurePath(update_game_url).name)
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
update_game_url, out_path, file_len=update_diff.game_pkgs[0].size
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(
|
|
||||||
f"<error>Couldn't download update: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not download_result:
|
|
||||||
self.line_error("<error>Download failed.</error>")
|
|
||||||
return
|
|
||||||
self.line("Download completed.")
|
|
||||||
# Get installed voicepacks
|
|
||||||
installed_voicepacks = State.game.get_installed_voicepacks()
|
|
||||||
# Voicepack update
|
|
||||||
for remote_voicepack in update_diff.audio_pkgs:
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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.")
|
|
||||||
|
|
||||||
|
|
||||||
class ApplyInstallArchive(Command):
|
|
||||||
name = "genshin install apply-archive"
|
|
||||||
description = "Applies the install archive"
|
|
||||||
arguments = [argument("path", description="Path to the install archive")]
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
install_archive = self.argument("path")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Applying install package...")
|
|
||||||
try:
|
|
||||||
State.game.install_archive(install_archive)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't apply package: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Package applied.</comment>")
|
|
||||||
set_version_config(self=self)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplyUpdateArchive(Command):
|
|
||||||
name = "genshin update apply-archive"
|
|
||||||
description = "Applies the update archive to the local game"
|
|
||||||
arguments = [argument("path", description="Path to the update archive")]
|
|
||||||
options = default_options + [
|
|
||||||
option(
|
|
||||||
"auto-repair", "R", description="Automatically repair the game if needed."
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
update_archive = self.argument("path")
|
|
||||||
auto_repair = self.option("auto-repair")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Applying update package...")
|
|
||||||
try:
|
|
||||||
State.game.apply_update_archive(update_archive, auto_repair=auto_repair)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't apply update: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Update applied.</comment>")
|
|
||||||
set_version_config()
|
|
||||||
|
|
||||||
|
|
||||||
commands = [
|
|
||||||
ApplyInstallArchive,
|
|
||||||
ApplyUpdateArchive,
|
|
||||||
GetVersionCommand,
|
|
||||||
InstallCommand,
|
|
||||||
InstallDownloadCommand,
|
|
||||||
RepairCommand,
|
|
||||||
UpdateCommand,
|
|
||||||
UpdateDownloadCommand,
|
|
||||||
VoicepackInstall,
|
|
||||||
VoicepackList,
|
|
||||||
VoicepackListInstalled,
|
|
||||||
VoicepackUpdate,
|
|
||||||
]
|
|
||||||
@ -1,954 +0,0 @@
|
|||||||
import traceback
|
|
||||||
from cleo.commands.command import Command
|
|
||||||
from cleo.helpers import option, argument
|
|
||||||
from copy import deepcopy
|
|
||||||
from pathlib import PurePath
|
|
||||||
from platform import system
|
|
||||||
from vollerei.common.enums import GameChannel, VoicePackLanguage
|
|
||||||
from vollerei.cli import utils
|
|
||||||
from vollerei.exceptions.game import GameError
|
|
||||||
from vollerei.hsr import Game, Patcher
|
|
||||||
from vollerei.exceptions.patcher import PatcherError, PatchUpdateError
|
|
||||||
from vollerei.hsr.patcher import PatchType
|
|
||||||
from vollerei import paths
|
|
||||||
|
|
||||||
patcher = Patcher()
|
|
||||||
|
|
||||||
|
|
||||||
default_options = [
|
|
||||||
option("channel", "c", description="Game channel", flag=False, default="overseas"),
|
|
||||||
option("force", "f", description="Force the command to run"),
|
|
||||||
option(
|
|
||||||
"game-path",
|
|
||||||
"g",
|
|
||||||
description="Path to the game installation",
|
|
||||||
flag=False,
|
|
||||||
default=".",
|
|
||||||
),
|
|
||||||
option("patch-type", "p", description="Patch type", flag=False),
|
|
||||||
option("temporary-path", "t", description="Temporary path", flag=False),
|
|
||||||
option("silent", "s", description="Silent mode"),
|
|
||||||
option("noconfirm", "y", description="Do not ask for confirmation (yes to all)"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class State:
|
|
||||||
game: Game = None
|
|
||||||
|
|
||||||
|
|
||||||
def callback(
|
|
||||||
command: Command,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Base callback for all commands
|
|
||||||
"""
|
|
||||||
game_path = command.option("game-path")
|
|
||||||
patch_type = command.option("patch-type")
|
|
||||||
channel = command.option("channel")
|
|
||||||
silent = command.option("silent")
|
|
||||||
noconfirm = command.option("noconfirm")
|
|
||||||
temporary_path = command.option("temporary-path")
|
|
||||||
if isinstance(channel, str):
|
|
||||||
channel = GameChannel[channel.capitalize()]
|
|
||||||
elif isinstance(channel, int):
|
|
||||||
channel = GameChannel(channel)
|
|
||||||
if temporary_path:
|
|
||||||
paths.set_base_path(temporary_path)
|
|
||||||
State.game = Game(game_path, temporary_path)
|
|
||||||
if channel:
|
|
||||||
State.game.channel_override = channel
|
|
||||||
if patch_type is None:
|
|
||||||
patch_type = PatchType.Jadeite
|
|
||||||
elif isinstance(patch_type, str):
|
|
||||||
patch_type = PatchType[patch_type]
|
|
||||||
elif isinstance(patch_type, int):
|
|
||||||
patch_type = PatchType(patch_type)
|
|
||||||
patcher.patch_type = patch_type
|
|
||||||
utils.silent_message = silent
|
|
||||||
if noconfirm:
|
|
||||||
utils.no_confirm = noconfirm
|
|
||||||
|
|
||||||
def confirm(
|
|
||||||
question: str, default: bool = False, true_answer_regex: str = r"(?i)^y"
|
|
||||||
):
|
|
||||||
command.line(
|
|
||||||
f"<question>{question} (yes/no)</question> [<comment>{'yes' if default else 'no'}</comment>] y"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
command.confirm = confirm
|
|
||||||
command.add_style("warn", fg="yellow")
|
|
||||||
|
|
||||||
|
|
||||||
def set_version_config(self: Command):
|
|
||||||
self.line("Setting version config... ")
|
|
||||||
try:
|
|
||||||
State.game.set_version_config()
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(f"<warn>Couldn't set version config: {e}</warn>")
|
|
||||||
self.line_error(
|
|
||||||
"This won't affect the overall experience, but if you're using the official launcher"
|
|
||||||
)
|
|
||||||
self.line_error(
|
|
||||||
"you may have to edit the file 'config.ini' manually to reflect the latest version."
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The game has been updated to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VoicepackListInstalled(Command):
|
|
||||||
name = "hsr voicepack list-installed"
|
|
||||||
description = "Get the installed voicepacks"
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
installed_voicepacks_str = [
|
|
||||||
f"<comment>{x.name}</comment>"
|
|
||||||
for x in State.game.get_installed_voicepacks()
|
|
||||||
]
|
|
||||||
self.line(f"Installed voicepacks: {', '.join(installed_voicepacks_str)}")
|
|
||||||
|
|
||||||
|
|
||||||
class VoicepackList(Command):
|
|
||||||
name = "hsr voicepack list"
|
|
||||||
description = "Get all available voicepacks"
|
|
||||||
options = default_options + [
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
remote_game = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
available_voicepacks_str = [
|
|
||||||
f"<comment>{x.language.name} ({x.language.value})</comment>"
|
|
||||||
for x in remote_game.latest.voice_packs
|
|
||||||
]
|
|
||||||
self.line(f"Available voicepacks: {', '.join(available_voicepacks_str)}")
|
|
||||||
|
|
||||||
|
|
||||||
class VoicepackInstall(Command):
|
|
||||||
name = "hsr voicepack install"
|
|
||||||
description = (
|
|
||||||
"Installs the specified installed voicepacks"
|
|
||||||
)
|
|
||||||
options = default_options + [
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
]
|
|
||||||
arguments = [
|
|
||||||
argument(
|
|
||||||
"language", description="Languages to install", multiple=True, optional=True
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
# Typing manually because pylance detect it as Any
|
|
||||||
languages: list[str] = self.argument("language")
|
|
||||||
# Get installed voicepacks
|
|
||||||
language_objects = []
|
|
||||||
for language in languages:
|
|
||||||
language = language.lower()
|
|
||||||
try:
|
|
||||||
language_objects.append(VoicePackLanguage[language.capitalize()])
|
|
||||||
except KeyError:
|
|
||||||
try:
|
|
||||||
language_objects.append(VoicePackLanguage.from_remote_str(language))
|
|
||||||
except ValueError:
|
|
||||||
self.line_error(f"<error>Invalid language: {language}</error>")
|
|
||||||
if len(language_objects) == 0:
|
|
||||||
self.line_error(
|
|
||||||
"<error>No valid languages specified, you must specify a language to install</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Fetching install package information... ")
|
|
||||||
try:
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Fetching failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
"<comment>Installation information fetched successfully.</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to install the specified voicepacks?"):
|
|
||||||
self.line("<error>Installation aborted.</error>")
|
|
||||||
return
|
|
||||||
# Voicepack update
|
|
||||||
for remote_voicepack in game_info.major.audio_pkgs:
|
|
||||||
if remote_voicepack.language not in language_objects:
|
|
||||||
continue
|
|
||||||
self.line(
|
|
||||||
f"Downloading install package for language: <comment>{remote_voicepack.language.name}</comment>... "
|
|
||||||
)
|
|
||||||
archive_file = State.game.cache.joinpath(PurePath(remote_voicepack.url).name)
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
remote_voicepack.url, archive_file, file_len=remote_voicepack.size
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(f"<error>Couldn't download package: {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("Installing package...")
|
|
||||||
try:
|
|
||||||
State.game.install_archive(archive_file)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't apply package: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
f"<comment>Package applied for language {remote_voicepack.language.name}.</comment>"
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The voicepacks have been installed to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VoicepackUpdate(Command):
|
|
||||||
name = "hsr voicepack update"
|
|
||||||
description = (
|
|
||||||
"Updates the specified installed voicepacks, if not specified, updates all"
|
|
||||||
)
|
|
||||||
options = default_options + [
|
|
||||||
option(
|
|
||||||
"auto-repair", "R", description="Automatically repair the game if needed."
|
|
||||||
),
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
option(
|
|
||||||
"from-version", description="Update from a specific version", flag=False
|
|
||||||
),
|
|
||||||
]
|
|
||||||
arguments = [
|
|
||||||
argument(
|
|
||||||
"language", description="Languages to update", multiple=True, optional=True
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
auto_repair = self.option("auto-repair")
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
from_version = self.option("from-version")
|
|
||||||
# Typing manually because pylance detect it as Any
|
|
||||||
languages: list[str] = self.argument("language")
|
|
||||||
if auto_repair:
|
|
||||||
self.line("<comment>Auto-repair is enabled.</comment>")
|
|
||||||
if from_version:
|
|
||||||
self.line(f"Updating from version: <comment>{from_version}</comment>")
|
|
||||||
State.game.version_override = from_version
|
|
||||||
# Get installed voicepacks
|
|
||||||
if len(languages) == 0:
|
|
||||||
self.line(
|
|
||||||
"<comment>No languages specified, updating all installed voicepacks...</comment>"
|
|
||||||
)
|
|
||||||
installed_voicepacks = State.game.get_installed_voicepacks()
|
|
||||||
if len(languages) > 0:
|
|
||||||
languages = [x.lower() for x in languages]
|
|
||||||
# Support both English and en-us and en
|
|
||||||
installed_voicepacks = [
|
|
||||||
x
|
|
||||||
for x in installed_voicepacks
|
|
||||||
if x.name.lower() in languages
|
|
||||||
or x.value.lower() in languages
|
|
||||||
or x.name.lower()[:2] in languages
|
|
||||||
]
|
|
||||||
installed_voicepacks_str = [
|
|
||||||
f"<comment>{str(x.name)}</comment>" for x in installed_voicepacks
|
|
||||||
]
|
|
||||||
self.line(f"Updating voicepacks: {', '.join(installed_voicepacks_str)}")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Checking for updates... ")
|
|
||||||
try:
|
|
||||||
update_diff = State.game.get_update(pre_download=pre_download)
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Update checking failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if update_diff is None:
|
|
||||||
progress.finish("<comment>Game is already updated.</comment>")
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Update available.</comment>")
|
|
||||||
self.line(
|
|
||||||
f"The current version is: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The latest version is: <comment>{game_info.latest.version}</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to update the game?"):
|
|
||||||
self.line("<error>Update aborted.</error>")
|
|
||||||
return
|
|
||||||
# Voicepack update
|
|
||||||
for remote_voicepack in update_diff.voice_packs:
|
|
||||||
if remote_voicepack.language not in installed_voicepacks:
|
|
||||||
continue
|
|
||||||
# Voicepack is installed, update it
|
|
||||||
self.line(
|
|
||||||
f"Downloading update package for language: <comment>{remote_voicepack.language.name}</comment>... "
|
|
||||||
)
|
|
||||||
archive_file = State.game.cache.joinpath(remote_voicepack.name)
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
remote_voicepack.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} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
f"<comment>Update applied for language {remote_voicepack.language.name}.</comment>"
|
|
||||||
)
|
|
||||||
set_version_config(self=self)
|
|
||||||
self.line(
|
|
||||||
f"The game has been updated to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PatchTypeCommand(Command):
|
|
||||||
name = "hsr patch type"
|
|
||||||
description = "Get the patch type of the game"
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
self.line(f"Patch type: <comment>{patcher.patch_type.name}</comment>")
|
|
||||||
|
|
||||||
|
|
||||||
class UpdatePatchCommand(Command):
|
|
||||||
name = "hsr patch update"
|
|
||||||
description = "Updates the patch"
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Updating patch... ")
|
|
||||||
try:
|
|
||||||
patcher.update_patch()
|
|
||||||
except PatchUpdateError as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Patch update failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
progress.finish("<comment>Patch updated!</comment>")
|
|
||||||
|
|
||||||
|
|
||||||
class PatchInstallCommand(Command):
|
|
||||||
name = "hsr patch install"
|
|
||||||
description = "Installs the patch"
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def jadeite(self):
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Installing patch... ")
|
|
||||||
try:
|
|
||||||
jadeite_dir = patcher.patch_game(game=State.game)
|
|
||||||
except PatcherError as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Patch installation failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Patch installed!</comment>")
|
|
||||||
print()
|
|
||||||
exe_path = jadeite_dir.joinpath("jadeite.exe")
|
|
||||||
self.line(f"Jadeite executable is located at: <question>{exe_path}</question>")
|
|
||||||
self.line(
|
|
||||||
"You need to <warn>run the game using Jadeite</warn> to use the patch."
|
|
||||||
)
|
|
||||||
self.line(f'E.g: <question>{exe_path} "{State.game.path}"</question>')
|
|
||||||
print()
|
|
||||||
self.line(
|
|
||||||
"To activate the experimental patching method, set the environment variable BREAK_CATHACK=1"
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
"Read more about it here: https://codeberg.org/mkrsym1/jadeite/issues/37"
|
|
||||||
)
|
|
||||||
print()
|
|
||||||
self.line(
|
|
||||||
"Please don't spread this project to public, we just want to play the game."
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
"And for your own sake, please only <warn>use test accounts</warn>, as there is an <warn>extremely high risk of getting banned.</warn>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def astra(self):
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Installing patch... ")
|
|
||||||
try:
|
|
||||||
patcher.patch_game(game=State.game)
|
|
||||||
except PatcherError as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Patch installation failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Patch installed!</comment>")
|
|
||||||
self.line()
|
|
||||||
self.line(
|
|
||||||
"Please don't spread this project to public, we just want to play the game."
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
"And for your own sake, please only use testing accounts, as there is an extremely high risk of getting banned."
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
if system() == "Windows":
|
|
||||||
self.line(
|
|
||||||
"Windows is <comment>officialy supported</comment> by the game, so no patching is needed."
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
"By patching the game, <warn>you are violating the ToS of the game.</warn>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to patch the game?"):
|
|
||||||
self.line("<error>Patching aborted.</error>")
|
|
||||||
return
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Checking telemetry hosts... ")
|
|
||||||
telemetry_list = patcher.check_telemetry()
|
|
||||||
if telemetry_list:
|
|
||||||
progress.finish("<warn>Telemetry hosts were found.</warn>")
|
|
||||||
self.line("Below is the list of telemetry hosts that need to be blocked:")
|
|
||||||
print()
|
|
||||||
for host in telemetry_list:
|
|
||||||
self.line(f"{host}")
|
|
||||||
print()
|
|
||||||
self.line(
|
|
||||||
"To prevent the game from sending data about the patch, "
|
|
||||||
+ "we need to <comment>block these hosts.</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to block them?"):
|
|
||||||
self.line("<error>Patching aborted.</error>")
|
|
||||||
self.line(
|
|
||||||
"<error>Please block these hosts manually then try again.</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
patcher.block_telemetry(telemetry_list=telemetry_list)
|
|
||||||
except Exception:
|
|
||||||
self.line_error(
|
|
||||||
f"<error>Couldn't block telemetry hosts: {traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
# There's a good reason for this.
|
|
||||||
if system() != "Windows":
|
|
||||||
self.line(
|
|
||||||
"<error>Cannot continue, please block them manually then try again.</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
self.line("<warn>Continuing anyway...</warn>")
|
|
||||||
else:
|
|
||||||
progress.finish("<comment>No telemetry hosts found.</comment>")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Updating patch... ")
|
|
||||||
try:
|
|
||||||
patcher.update_patch()
|
|
||||||
except PatchUpdateError as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Patch update failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
progress.finish("<comment>Patch updated.</comment>")
|
|
||||||
match patcher.patch_type:
|
|
||||||
case PatchType.Jadeite:
|
|
||||||
self.jadeite()
|
|
||||||
case PatchType.Astra:
|
|
||||||
self.astra()
|
|
||||||
|
|
||||||
|
|
||||||
PatchCommand = deepcopy(PatchInstallCommand)
|
|
||||||
PatchCommand.name = "hsr patch"
|
|
||||||
|
|
||||||
|
|
||||||
class PatchTelemetryCommand(Command):
|
|
||||||
name = "hsr patch telemetry"
|
|
||||||
description = "Checks for telemetry hosts and block them."
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Checking telemetry hosts... ")
|
|
||||||
telemetry_list = patcher.check_telemetry()
|
|
||||||
if telemetry_list:
|
|
||||||
progress.finish("<warn>Telemetry hosts were found.</warn>")
|
|
||||||
self.line("Below is the list of telemetry hosts that need to be blocked:")
|
|
||||||
print()
|
|
||||||
for host in telemetry_list:
|
|
||||||
self.line(f"{host}")
|
|
||||||
print()
|
|
||||||
self.line(
|
|
||||||
"To prevent the game from sending data about the patch, "
|
|
||||||
+ "we need to <comment>block these hosts.</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to block them?"):
|
|
||||||
self.line("<error>Blocking aborted.</error>")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
patcher.block_telemetry(telemetry_list=telemetry_list)
|
|
||||||
except Exception:
|
|
||||||
self.line_error(
|
|
||||||
f"<error>Couldn't block telemetry hosts: {traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
progress.finish("<comment>No telemetry hosts found.</comment>")
|
|
||||||
|
|
||||||
|
|
||||||
class GetVersionCommand(Command):
|
|
||||||
name = "hsr version"
|
|
||||||
description = "Gets the local game version"
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
try:
|
|
||||||
self.line(
|
|
||||||
f"<comment>Version:</comment> {'.'.join(str(x) for x in State.game.get_version())}"
|
|
||||||
)
|
|
||||||
except GameError as e:
|
|
||||||
self.line_error(f"<error>Couldn't get game version: {e}</error>")
|
|
||||||
|
|
||||||
|
|
||||||
class InstallCommand(Command):
|
|
||||||
name = "hsr install"
|
|
||||||
description = (
|
|
||||||
"Installs the latest version of the game to the specified path (default: current directory). "
|
|
||||||
+ "Note that this will not install the default voicepack (English), you need to install it manually."
|
|
||||||
)
|
|
||||||
options = default_options + [
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Fetching install package information... ")
|
|
||||||
try:
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Fetching failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
"<comment>Installation information fetched successfully.</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to install the game?"):
|
|
||||||
self.line("<error>Installation aborted.</error>")
|
|
||||||
return
|
|
||||||
self.line("Downloading install package...")
|
|
||||||
first_pkg_out_path = None
|
|
||||||
for game_pkg in game_info.major.game_pkgs:
|
|
||||||
out_path = State.game.cache.joinpath(PurePath(game_pkg.url).name)
|
|
||||||
if not first_pkg_out_path:
|
|
||||||
first_pkg_out_path = out_path
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
game_pkg.url, out_path, file_len=game_pkg.size
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(
|
|
||||||
f"<error>Couldn't download install package: {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("Installing package...")
|
|
||||||
try:
|
|
||||||
State.game.install_archive(first_pkg_out_path)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't install package: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Package applied for the base game.</comment>")
|
|
||||||
self.line("Setting version config... ")
|
|
||||||
State.game.version_override = game_info.major.version
|
|
||||||
set_version_config()
|
|
||||||
State.game.version_override = None
|
|
||||||
self.line(
|
|
||||||
f"The game has been installed to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateCommand(Command):
|
|
||||||
name = "hsr update"
|
|
||||||
description = "Updates the local game if available"
|
|
||||||
options = default_options + [
|
|
||||||
option(
|
|
||||||
"auto-repair", "R", description="Automatically repair the game if needed."
|
|
||||||
),
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
option(
|
|
||||||
"from-version", description="Update from a specific version", flag=False
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
auto_repair = self.option("auto-repair")
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
from_version = self.option("from-version")
|
|
||||||
if auto_repair:
|
|
||||||
self.line("<comment>Auto-repair is enabled.</comment>")
|
|
||||||
if from_version:
|
|
||||||
self.line(f"Updating from version: <comment>{from_version}</comment>")
|
|
||||||
State.game.version_override = from_version
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Checking for updates... ")
|
|
||||||
try:
|
|
||||||
update_diff = State.game.get_update(pre_download=pre_download)
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Update checking failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if update_diff is None or isinstance(game_info.major, str | None):
|
|
||||||
progress.finish("<comment>Game is already updated.</comment>")
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Update available.</comment>")
|
|
||||||
self.line(
|
|
||||||
f"The current version is: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The latest version is: <comment>{game_info.major.version}</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to update the game?"):
|
|
||||||
self.line("<error>Update aborted.</error>")
|
|
||||||
return
|
|
||||||
self.line("Downloading update package...")
|
|
||||||
update_game_url = update_diff.game_pkgs[0].url
|
|
||||||
out_path = State.game.cache.joinpath(PurePath(update_game_url).name)
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
update_game_url, out_path, file_len=update_diff.game_pkgs[0].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(out_path, auto_repair=auto_repair)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't apply update: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
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.audio_pkgs:
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
f"<comment>Update applied for language {remote_voicepack.language.name}.</comment>"
|
|
||||||
)
|
|
||||||
self.line("Setting version config... ")
|
|
||||||
State.game.version_override = game_info.major.version
|
|
||||||
set_version_config()
|
|
||||||
State.game.version_override = None
|
|
||||||
self.line(
|
|
||||||
f"The game has been updated to version: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RepairCommand(Command):
|
|
||||||
name = "hsr repair"
|
|
||||||
description = "Tries to repair the local game"
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
self.line(
|
|
||||||
"This command will try to repair the game by downloading missing/broken files."
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
"There will be no progress available, so please be patient and just wait."
|
|
||||||
)
|
|
||||||
if not self.confirm(
|
|
||||||
"Do you want to repair the game (this will take a long time!)?"
|
|
||||||
):
|
|
||||||
self.line("<error>Repairation aborted.</error>")
|
|
||||||
return
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Repairing game files (no progress available)... ")
|
|
||||||
try:
|
|
||||||
State.game.repair_game()
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Repairation failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Repairation completed.</comment>")
|
|
||||||
|
|
||||||
|
|
||||||
class InstallDownloadCommand(Command):
|
|
||||||
name = "hsr install download"
|
|
||||||
description = (
|
|
||||||
"Downloads the latest version of the game. "
|
|
||||||
+ "Note that this will not download the default voicepack (English), you need to download it manually."
|
|
||||||
)
|
|
||||||
options = default_options + [
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Fetching install package information... ")
|
|
||||||
try:
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Fetching failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish(
|
|
||||||
"<comment>Installation information fetched successfully.</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to download the game?"):
|
|
||||||
self.line("<error>Download aborted.</error>")
|
|
||||||
return
|
|
||||||
self.line("Downloading install package...")
|
|
||||||
first_pkg_out_path = None
|
|
||||||
for game_pkg in game_info.major.game_pkgs:
|
|
||||||
out_path = State.game.cache.joinpath(PurePath(game_pkg.url).name)
|
|
||||||
if not first_pkg_out_path:
|
|
||||||
first_pkg_out_path = out_path
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
game_pkg.url, out_path, file_len=game_pkg.size
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(
|
|
||||||
f"<error>Couldn't download install package: {e}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if not download_result:
|
|
||||||
self.line_error("<error>Download failed.</error>")
|
|
||||||
return
|
|
||||||
self.line("Download completed.")
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateDownloadCommand(Command):
|
|
||||||
name = "hsr update download"
|
|
||||||
description = "Download the update for the local game if available"
|
|
||||||
options = default_options + [
|
|
||||||
option(
|
|
||||||
"auto-repair", "R", description="Automatically repair the game if needed."
|
|
||||||
),
|
|
||||||
option("pre-download", description="Pre-download the game if available."),
|
|
||||||
option(
|
|
||||||
"from-version", description="Update from a specific version", flag=False
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
auto_repair = self.option("auto-repair")
|
|
||||||
pre_download = self.option("pre-download")
|
|
||||||
from_version = self.option("from-version")
|
|
||||||
if auto_repair:
|
|
||||||
self.line("<comment>Auto-repair is enabled.</comment>")
|
|
||||||
if from_version:
|
|
||||||
self.line(f"Updating from version: <comment>{from_version}</comment>")
|
|
||||||
State.game.version_override = from_version
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Checking for updates... ")
|
|
||||||
try:
|
|
||||||
update_diff = State.game.get_update(pre_download=pre_download)
|
|
||||||
game_info = State.game.get_remote_game(pre_download=pre_download)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Update checking failed with following error: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if update_diff is None or isinstance(game_info.major, str | None):
|
|
||||||
progress.finish("<comment>Game is already updated.</comment>")
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Update available.</comment>")
|
|
||||||
self.line(
|
|
||||||
f"The current version is: <comment>{State.game.get_version_str()}</comment>"
|
|
||||||
)
|
|
||||||
self.line(
|
|
||||||
f"The latest version is: <comment>{game_info.major.version}</comment>"
|
|
||||||
)
|
|
||||||
if not self.confirm("Do you want to download the update?"):
|
|
||||||
self.line("<error>Download aborted.</error>")
|
|
||||||
return
|
|
||||||
self.line("Downloading update package...")
|
|
||||||
update_game_url = update_diff.game_pkgs[0].url
|
|
||||||
out_path = State.game.cache.joinpath(PurePath(update_game_url).name)
|
|
||||||
try:
|
|
||||||
download_result = utils.download(
|
|
||||||
update_game_url, out_path, file_len=update_diff.game_pkgs[0].size
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.line_error(
|
|
||||||
f"<error>Couldn't download update: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not download_result:
|
|
||||||
self.line_error("<error>Download failed.</error>")
|
|
||||||
return
|
|
||||||
self.line("Download completed.")
|
|
||||||
# Get installed voicepacks
|
|
||||||
installed_voicepacks = State.game.get_installed_voicepacks()
|
|
||||||
# Voicepack update
|
|
||||||
for remote_voicepack in update_diff.audio_pkgs:
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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
|
|
||||||
)
|
|
||||||
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.")
|
|
||||||
|
|
||||||
|
|
||||||
class ApplyInstallArchive(Command):
|
|
||||||
name = "hsr install apply-archive"
|
|
||||||
description = "Applies the install archive"
|
|
||||||
arguments = [argument("path", description="Path to the install archive")]
|
|
||||||
options = default_options
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
install_archive = self.argument("path")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Applying install package...")
|
|
||||||
try:
|
|
||||||
State.game.install_archive(install_archive)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't apply package: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Package applied.</comment>")
|
|
||||||
set_version_config(self=self)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplyUpdateArchive(Command):
|
|
||||||
name = "hsr update apply-archive"
|
|
||||||
description = "Applies the update archive to the local game"
|
|
||||||
arguments = [argument("path", description="Path to the update archive")]
|
|
||||||
options = default_options + [
|
|
||||||
option(
|
|
||||||
"auto-repair", "R", description="Automatically repair the game if needed."
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
def handle(self):
|
|
||||||
callback(command=self)
|
|
||||||
update_archive = self.argument("path")
|
|
||||||
auto_repair = self.option("auto-repair")
|
|
||||||
progress = utils.ProgressIndicator(self)
|
|
||||||
progress.start("Applying update package...")
|
|
||||||
try:
|
|
||||||
State.game.apply_update_archive(update_archive, auto_repair=auto_repair)
|
|
||||||
except Exception as e:
|
|
||||||
progress.finish(
|
|
||||||
f"<error>Couldn't apply update: {e} \n{traceback.format_exc()}</error>"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
progress.finish("<comment>Update applied.</comment>")
|
|
||||||
set_version_config()
|
|
||||||
|
|
||||||
|
|
||||||
commands = [
|
|
||||||
ApplyInstallArchive,
|
|
||||||
ApplyUpdateArchive,
|
|
||||||
GetVersionCommand,
|
|
||||||
InstallCommand,
|
|
||||||
InstallDownloadCommand,
|
|
||||||
PatchCommand,
|
|
||||||
PatchInstallCommand,
|
|
||||||
PatchTelemetryCommand,
|
|
||||||
PatchTypeCommand,
|
|
||||||
RepairCommand,
|
|
||||||
UpdatePatchCommand,
|
|
||||||
UpdateCommand,
|
|
||||||
UpdateDownloadCommand,
|
|
||||||
VoicepackInstall,
|
|
||||||
VoicepackList,
|
|
||||||
VoicepackListInstalled,
|
|
||||||
VoicepackUpdate,
|
|
||||||
]
|
|
||||||
Reference in New Issue
Block a user