diff --git a/docs/common/functions.md b/docs/common/functions.md
index d337612..e66dc99 100644
--- a/docs/common/functions.md
+++ b/docs/common/functions.md
@@ -1,4 +1,4 @@
-# Common function for all games
+# Function for all games
Since you should use the specific implementation for the target game instead, these functions may not be correctly documented.
diff --git a/vollerei/abc/launcher/game.py b/vollerei/abc/launcher/game.py
index 225dd18..663866a 100644
--- a/vollerei/abc/launcher/game.py
+++ b/vollerei/abc/launcher/game.py
@@ -114,7 +114,7 @@ class GameABC(ABC):
"""
pass
- def get_update(self):
+ def get_update(self) -> resource.Patch | None:
"""
Get the game update
"""
@@ -126,7 +126,9 @@ class GameABC(ABC):
"""
pass
- 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.
diff --git a/vollerei/cli/commands.py b/vollerei/cli/commands.py
index 41c63a8..8cc944b 100644
--- a/vollerei/cli/commands.py
+++ b/vollerei/cli/commands.py
@@ -5,6 +5,7 @@ from cleo.helpers import option, argument
from pathlib import PurePath
from platform import system
from vollerei.abc.launcher.game import GameABC
+from vollerei.common.api import resource
from vollerei.common.enums import GameChannel, VoicePackLanguage
from vollerei.cli import utils
from vollerei.exceptions.game import GameError
@@ -141,9 +142,7 @@ class VoicepackList(Command):
class VoicepackInstall(Command):
name = "hsr voicepack install"
- description = (
- "Installs the specified installed voicepacks"
- )
+ description = "Installs the specified installed voicepacks"
options = default_options + [
option("pre-download", description="Pre-download the game if available"),
]
@@ -196,7 +195,9 @@ class VoicepackInstall(Command):
self.line(
f"Downloading install package for language: {remote_voicepack.language.name}... "
)
- archive_file = State.game.cache.joinpath(PurePath(remote_voicepack.url).name)
+ 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
@@ -280,7 +281,7 @@ class VoicepackUpdate(Command):
progress = utils.ProgressIndicator(self)
progress.start("Checking for updates... ")
try:
- update_diff = State.game.get_update(pre_download=pre_download)
+ update_diff: resource.Patch | None = 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(
@@ -295,23 +296,25 @@ class VoicepackUpdate(Command):
f"The current version is: {State.game.get_version_str()}"
)
self.line(
- f"The latest version is: {game_info.latest.version}"
+ f"The latest version is: {game_info.major.version}"
)
if not self.confirm("Do you want to update the game?"):
self.line("Update aborted.")
return
# Voicepack update
- for remote_voicepack in update_diff.voice_packs:
+ for remote_voicepack in update_diff.audio_pkgs:
if remote_voicepack.language not in installed_voicepacks:
continue
# Voicepack is installed, update it
- self.line(
- f"Downloading update package for language: {remote_voicepack.language.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}'..."
)
- archive_file = State.game.cache.joinpath(remote_voicepack.name)
try:
download_result = utils.download(
- remote_voicepack.path, archive_file, file_len=update_diff.size
+ remote_voicepack.url, archive_file, file_len=remote_voicepack.size
)
except Exception as e:
self.line_error(f"Couldn't download update: {e}")
@@ -334,10 +337,9 @@ class VoicepackUpdate(Command):
progress.finish(
f"Update applied for language {remote_voicepack.language.name}."
)
+ State.game.version_override = game_info.major.version
set_version_config(self=self)
- self.line(
- f"The game has been updated to version: {State.game.get_version_str()}"
- )
+ State.game.version_override = None
class PatchTypeCommand(Command):
@@ -601,7 +603,7 @@ class InstallCommand(Command):
progress.finish("Package applied for the base game.")
self.line("Setting version config... ")
State.game.version_override = game_info.major.version
- set_version_config()
+ set_version_config(self=self)
State.game.version_override = None
self.line(
f"The game has been installed to version: {State.game.get_version_str()}"
@@ -719,7 +721,7 @@ class UpdateCommand(Command):
)
self.line("Setting version config... ")
State.game.version_override = game_info.major.version
- set_version_config()
+ set_version_config(self=self)
State.game.version_override = None
self.line(
f"The game has been updated to version: {State.game.get_version_str()}"
@@ -940,7 +942,8 @@ class ApplyUpdateArchive(Command):
)
return
progress.finish("Update applied.")
- set_version_config()
+ set_version_config(self=self)
+
# This is the list for HSR commands, we'll add Genshin commands later
classes = [
diff --git a/vollerei/common/enums.py b/vollerei/common/enums.py
index 70936fb..c562179 100644
--- a/vollerei/common/enums.py
+++ b/vollerei/common/enums.py
@@ -49,4 +49,4 @@ class VoicePackLanguage(Enum):
elif s == "En":
return VoicePackLanguage.English
else:
- raise ValueError(f"Invalid language string: {s}")
\ No newline at end of file
+ raise ValueError(f"Invalid language string: {s}")
diff --git a/vollerei/common/functions.py b/vollerei/common/functions.py
index 74518d9..2703a45 100644
--- a/vollerei/common/functions.py
+++ b/vollerei/common/functions.py
@@ -2,6 +2,7 @@ import concurrent.futures
import json
import hashlib
import py7zr
+import zipfile
from io import IOBase
from os import PathLike
from pathlib import Path
@@ -39,9 +40,30 @@ def apply_update_archive(
# Install HDiffPatch
_hdiff.hpatchz()
+
# Open archive
- # archive = zipfile.ZipFile(archive_file, "r")
- archive = py7zr.SevenZipFile(archive_file, "r")
+ def reset_if_py7zr(archive):
+ if isinstance(archive, py7zr.SevenZipFile):
+ archive.reset()
+
+ def extract_files(archive: py7zr.SevenZipFile | zipfile.ZipFile, files, path: PathLike):
+ if isinstance(archive, py7zr.SevenZipFile):
+ # .7z archive
+ archive.extract(path, files)
+ else:
+ # .zip archive
+ archive.extractall(path, files)
+
+ archive: py7zr.SevenZipFile | zipfile.ZipFile = None
+ try:
+ archive = py7zr.SevenZipFile(archive_file, "r")
+ except py7zr.exceptions.Bad7zFile:
+ # Try to open it as a zip file
+ try:
+ archive = zipfile.ZipFile(archive_file, "r")
+ except zipfile.BadZipFile:
+ raise ValueError("Archive is not a valid 7z or zip file.")
+
# Get files list (we don't want to extract all of them)
files = archive.namelist()
# Don't extract these files (they're useless and if the game isn't patched then
@@ -52,18 +74,23 @@ def apply_update_archive(
except ValueError:
pass
# Think for me a better name for this variable
- txtfiles = archive.read(["deletefiles.txt", "hdifffiles.txt"])
- # Reset archive to extract files
- archive.reset()
+ txtfiles = None
+ if isinstance(archive, py7zr.SevenZipFile):
+ txtfiles = archive.read(["deletefiles.txt", "hdifffiles.txt"])
+ # Reset archive to extract files
+ archive.reset()
try:
# miHoYo loves CRLF
- deletebytes = txtfiles["deletefiles.txt"].read()
+ if txtfiles is not None:
+ deletebytes = txtfiles["deletefiles.txt"].read()
+ else:
+ deletebytes = archive.read("deletefiles.txt")
if deletebytes is not str:
# Typing
deletebytes: bytes
deletebytes = deletebytes.decode()
deletefiles = deletebytes.split("\r\n")
- except IOError:
+ except (IOError, KeyError):
pass
else:
for file_str in deletefiles:
@@ -80,7 +107,10 @@ def apply_update_archive(
# hdiffpatch implementation
# Read hdifffiles.txt to get the files to patch
hdifffiles = []
- hdiffbytes = txtfiles["hdifffiles.txt"].read()
+ if txtfiles is not None:
+ hdiffbytes = txtfiles["hdifffiles.txt"].read()
+ else:
+ hdiffbytes = archive.read("hdifffiles.txt")
if hdiffbytes is not str:
# Typing
hdiffbytes: bytes
@@ -132,8 +162,8 @@ def apply_update_archive(
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
+ extract_files(archive, patch_files, game.cache)
+ reset_if_py7zr(archive) # For the next extraction
# Create new ThreadPoolExecutor for patching
patch_executor = concurrent.futures.ThreadPoolExecutor()
for job in patch_jobs:
@@ -141,7 +171,7 @@ def apply_update_archive(
patch_executor.shutdown(wait=True)
# Extract files from archive after we have filtered out the patch files
- archive.extract(game.path, files)
+ extract_files(archive, files, game.path)
# Close the archive
archive.close()
diff --git a/vollerei/zzz/launcher/game.py b/vollerei/zzz/launcher/game.py
index a9c6b67..e7e98c3 100644
--- a/vollerei/zzz/launcher/game.py
+++ b/vollerei/zzz/launcher/game.py
@@ -30,7 +30,7 @@ class Game(GameABC):
if not cache_path:
cache_path = paths.cache_path
cache_path = Path(cache_path)
- self.cache: Path = cache_path.joinpath("game/genshin/")
+ self.cache: Path = cache_path.joinpath("game/zzz/")
self.cache.mkdir(parents=True, exist_ok=True)
self._version_override: tuple[int, int, int] | None = None
self._channel_override: GameChannel | None = None
@@ -192,7 +192,7 @@ class Game(GameABC):
Gets the current installed game version.
Credits to An Anime Team for the code that does the magic:
- https://github.com/an-anime-team/anime-game-core/blob/main/src/games/genshin/game.rs#L52
+ https://github.com/an-anime-team/anime-game-core/blob/main/src/games/zzz/game.rs#L49
If the above method fails, it'll fallback to read the config.ini file
for the version, which is not recommended (as described in
@@ -224,25 +224,18 @@ class Game(GameABC):
for byte in f.read(10000):
match byte:
case 0:
+ if correct and len(version_bytes[0]) > 0 and len(version_bytes[1]) > 0 and len(version_bytes[2]) > 0:
+ found_version = tuple(
+ bytes_to_int(i) for i in version_bytes
+ )
+ return found_version
version_bytes = [[], [], []]
version_ptr = 0
correct = True
- case 46:
+ case b'.':
version_ptr += 1
if version_ptr > 2:
correct = False
- case 95:
- if (
- correct
- and len(version_bytes[0]) > 0
- and len(version_bytes[1]) > 0
- and len(version_bytes[2]) > 0
- ):
- return (
- bytes_to_int(version_bytes[0]),
- bytes_to_int(version_bytes[1]),
- bytes_to_int(version_bytes[2]),
- )
case _:
if correct and byte in b"0123456789":
version_bytes[version_ptr].append(byte)
@@ -276,9 +269,7 @@ class Game(GameABC):
raise GameNotInstalledError("Game is not installed.")
voicepacks = []
for child in (
- self.data_folder()
- .joinpath("StreamingAssets/Audio/Windows/Full/")
- .iterdir()
+ self.data_folder().joinpath("StreamingAssets/Audio/Windows/Full/").iterdir()
):
if child.resolve().is_dir():
try: