Added voiceover language info & archive type and install_game function
Available through Installer.get_voiceover_archive_type and Installer.get_voiceover_archive_language Also some other optimizations including not extracting unneeded files from diff archive, deprecate _read_version_from_config function, and added install_game, uninstall_game, voiceover_lang_translate, get_installed_voiceovers
This commit is contained in:
@ -1,6 +1,10 @@
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import appdirs
|
||||
import zipfile
|
||||
import warnings
|
||||
import json
|
||||
from pathlib import Path
|
||||
from configparser import ConfigParser
|
||||
|
||||
@ -8,26 +12,36 @@ from worthless import constants
|
||||
from worthless.launcher import Launcher
|
||||
|
||||
|
||||
def read_version_from_game_file(globalgamemanagers: Path | bytes):
|
||||
if isinstance(globalgamemanagers, Path):
|
||||
with globalgamemanagers.open("rb") as f:
|
||||
data = f.read().decode("ascii", errors="ignore")
|
||||
else:
|
||||
data = globalgamemanagers.decode("ascii", errors="ignore")
|
||||
result = re.search(r"([1-9]+\.[0-9]+\.[0-9]+)_[\d]+_[\d]+", data)
|
||||
if not result:
|
||||
raise ValueError("Could not find version in game file")
|
||||
return result.group(1)
|
||||
|
||||
|
||||
class Installer:
|
||||
def _read_version_from_config(self):
|
||||
warnings.warn("This function is not reliable as upgrading game version from worthless\
|
||||
doesn't write the config.", DeprecationWarning)
|
||||
if not self._config_file.exists():
|
||||
raise FileNotFoundError(f"Config file {self._config_file} not found")
|
||||
cfg = ConfigParser()
|
||||
cfg.read(str(self._config_file))
|
||||
return cfg.get("General", "game_version")
|
||||
|
||||
@staticmethod
|
||||
def read_version_from_game_file(globalgamemanagers: Path | bytes):
|
||||
"""
|
||||
Reads the version from the globalgamemanagers file. (Data/globalgamemanagers)
|
||||
|
||||
Uses `An Anime Game Launcher` method to read the version:
|
||||
https://gitlab.com/KRypt0n_/an-anime-game-launcher/-/blob/main/src/ts/Game.ts#L26
|
||||
|
||||
:return: Game version (ex 1.0.0)
|
||||
"""
|
||||
if isinstance(globalgamemanagers, Path):
|
||||
with globalgamemanagers.open("rb") as f:
|
||||
data = f.read().decode("ascii", errors="ignore")
|
||||
else:
|
||||
data = globalgamemanagers.decode("ascii", errors="ignore")
|
||||
result = re.search(r"([1-9]+\.[0-9]+\.[0-9]+)_[\d]+_[\d]+", data)
|
||||
if not result:
|
||||
raise ValueError("Could not find version in game file")
|
||||
return result.group(1)
|
||||
|
||||
def get_game_data_name(self):
|
||||
if self._overseas:
|
||||
return "GenshinImpact_Data/"
|
||||
@ -37,15 +51,23 @@ class Installer:
|
||||
def get_game_data_path(self):
|
||||
return self._gamedir.joinpath(self.get_game_data_name())
|
||||
|
||||
# https://gitlab.com/KRypt0n_/an-anime-game-launcher/-/blob/main/src/ts/Game.ts#L26
|
||||
def get_game_version(self):
|
||||
if self._config_file.exists():
|
||||
return self._read_version_from_config()
|
||||
else:
|
||||
globalgamemanagers = self.get_game_data_path().joinpath("./globalgamemanagers")
|
||||
if not globalgamemanagers.exists():
|
||||
return
|
||||
return read_version_from_game_file(globalgamemanagers)
|
||||
globalgamemanagers = self.get_game_data_path().joinpath("./globalgamemanagers")
|
||||
if not globalgamemanagers.exists():
|
||||
return
|
||||
return self.read_version_from_game_file(globalgamemanagers)
|
||||
|
||||
def get_installed_voiceovers(self):
|
||||
"""
|
||||
Returns a list of installed voiceovers.
|
||||
|
||||
:return: List of installed voiceovers
|
||||
"""
|
||||
voiceovers = []
|
||||
for file in self.get_game_data_path().joinpath("StreamingAssets/Audio/GeneratedSoundBanks/Windows").iterdir():
|
||||
if file.is_dir():
|
||||
voiceovers.append(file.name)
|
||||
return voiceovers
|
||||
|
||||
def __init__(self, gamedir: str | Path = Path.cwd(), overseas: bool = True, data_dir: str | Path = None):
|
||||
if isinstance(gamedir, str):
|
||||
@ -65,17 +87,60 @@ class Installer:
|
||||
self._launcher = Launcher(self._gamedir, overseas=self._overseas)
|
||||
self._version = self.get_game_version()
|
||||
|
||||
def get_archive_version(self, game_archive: str | Path):
|
||||
def get_game_archive_version(self, game_archive: str | Path):
|
||||
if not game_archive.exists():
|
||||
raise FileNotFoundError(f"Game archive {game_archive} not found")
|
||||
archive = zipfile.ZipFile(game_archive, 'r')
|
||||
return read_version_from_game_file(archive.read(self.get_game_data_name() + "globalgamemanagers"))
|
||||
return self.read_version_from_game_file(archive.read(self.get_game_data_name() + "globalgamemanagers"))
|
||||
|
||||
@staticmethod
|
||||
def voiceover_lang_translate(lang: str):
|
||||
"""
|
||||
Translates the voiceover language to the language code used by the game.
|
||||
|
||||
:param lang: Language to translate
|
||||
:return: Language code
|
||||
"""
|
||||
match lang:
|
||||
case "English(US)":
|
||||
return "en-us"
|
||||
case "Japanese":
|
||||
return "ja-jp"
|
||||
case "Chinese":
|
||||
return "zh-cn"
|
||||
case "Korean":
|
||||
return "ko-kr"
|
||||
|
||||
@staticmethod
|
||||
def get_voiceover_archive_language(voiceover_archive: str | Path):
|
||||
if isinstance(voiceover_archive, str):
|
||||
voiceover_archive = Path(voiceover_archive).resolve()
|
||||
if not voiceover_archive.exists():
|
||||
raise FileNotFoundError(f"Voiceover archive {voiceover_archive} not found")
|
||||
archive = zipfile.ZipFile(voiceover_archive, 'r')
|
||||
archive_path = zipfile.Path(archive)
|
||||
for file in archive_path.iterdir():
|
||||
if file.name.endswith("_pkg_version"):
|
||||
return file.name.split("_")[1]
|
||||
|
||||
def get_voiceover_archive_type(self, voiceover_archive: str | Path):
|
||||
vo_lang = self.get_voiceover_archive_language(voiceover_archive)
|
||||
archive = zipfile.ZipFile(voiceover_archive, 'r')
|
||||
archive_path = zipfile.Path(archive)
|
||||
files = archive.read("Audio_{}_pkg_version".format(vo_lang)).decode().split("\n")
|
||||
for file in files:
|
||||
if file.strip() and not archive_path.joinpath(json.loads(file)["remoteName"]).exists():
|
||||
return False
|
||||
return True
|
||||
|
||||
def apply_voiceover(self, voiceover_archive: str | Path):
|
||||
# Since Voiceover packages are unclear about diff package or full package
|
||||
# we will try to extract the voiceover package and apply it to the game
|
||||
# making this function universal for both cases
|
||||
if not self.get_game_data_path().exists():
|
||||
raise FileNotFoundError(f"Game not found in {self._gamedir}")
|
||||
if isinstance(voiceover_archive, str):
|
||||
game_archive = Path(voiceover_archive).resolve()
|
||||
voiceover_archive = Path(voiceover_archive).resolve()
|
||||
if not voiceover_archive.exists():
|
||||
raise FileNotFoundError(f"Voiceover archive {voiceover_archive} not found")
|
||||
archive = zipfile.ZipFile(voiceover_archive, 'r')
|
||||
@ -90,6 +155,15 @@ class Installer:
|
||||
if not game_archive.exists():
|
||||
raise FileNotFoundError(f"Update archive {game_archive} not found")
|
||||
archive = zipfile.ZipFile(game_archive, 'r')
|
||||
files = archive.namelist()
|
||||
# Don't extract these files (they're useless and if the game isn't patched then it'll
|
||||
# raise 31-4xxx error ingame)
|
||||
for file in ["deletefiles.txt", "hdifffiles.txt"]:
|
||||
try:
|
||||
files.remove(file)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
deletefiles = archive.read("deletefiles.txt").decode().split("\n")
|
||||
for file in deletefiles:
|
||||
current_game_file = self._gamedir.joinpath(file)
|
||||
@ -97,28 +171,45 @@ class Installer:
|
||||
continue
|
||||
if current_game_file.is_file():
|
||||
current_game_file.unlink(missing_ok=True)
|
||||
archive.extractall(self._gamedir)
|
||||
archive.close()
|
||||
self._gamedir.joinpath("deletefiles.txt").unlink(missing_ok=True)
|
||||
self._gamedir.joinpath("hdifffiles.txt").unlink(missing_ok=True)
|
||||
|
||||
async def install_game(self, force_reinstall: bool = False):
|
||||
archive.extractall(self._gamedir, members=files)
|
||||
archive.close()
|
||||
|
||||
async def download_game_update(self):
|
||||
if self._version is None:
|
||||
raise ValueError("Game version not found, use install_game to install the game.")
|
||||
version_info = await self._launcher.get_resource_info()
|
||||
if version_info is None:
|
||||
raise RuntimeError("Failed to fetch game resource info.")
|
||||
if self._version == version_info.game.latest.version:
|
||||
raise ValueError("Game is already up to date.")
|
||||
diff_archive = self.get_game_diff_archive()
|
||||
if diff_archive is None:
|
||||
raise ValueError("Game diff archive is not available for this version, please reinstall.")
|
||||
# TODO: Download the diff archive
|
||||
raise NotImplementedError("Downloading game diff archive is not implemented yet.")
|
||||
|
||||
def uninstall_game(self):
|
||||
shutil.rmtree(self._gamedir)
|
||||
|
||||
def install_game(self, game_archive: str | Path, force_reinstall: bool = False):
|
||||
"""Installs the game to the current directory
|
||||
|
||||
If `from_version` is not specified, it will be taken from the game version.
|
||||
If `to_version` is not specified, it will be taken from the game version.
|
||||
If `force_reinstall` is True, the game will be uninstalled then reinstalled.
|
||||
"""
|
||||
raise NotImplementedError("Not implemented yet")
|
||||
# if not force:
|
||||
# if self._temp_path.exists():
|
||||
# raise FileExistsError(f"Directory {self._temp_path} already exists")
|
||||
# self._temp_path.mkdir(parents=True, exist_ok=True)
|
||||
# self._launcher.set_temp_path(self._temp_path)
|
||||
# await self._launcher.download_game_diff_archive(from_version, to_version)
|
||||
# await self._launcher.extract_game_diff_archive()
|
||||
# await self._launcher.install_game_diff_archive()
|
||||
# self._launcher.set_temp_path(None)
|
||||
# self._temp_path.rmdir()
|
||||
if self.get_game_data_path().exists():
|
||||
if not force_reinstall:
|
||||
raise ValueError(f"Game is already installed in {self._gamedir}")
|
||||
self.uninstall_game()
|
||||
|
||||
self._gamedir.mkdir(parents=True, exist_ok=True)
|
||||
if isinstance(game_archive, str):
|
||||
game_archive = Path(game_archive).resolve()
|
||||
if not game_archive.exists():
|
||||
raise FileNotFoundError(f"Install archive {game_archive} not found")
|
||||
archive = zipfile.ZipFile(game_archive, 'r')
|
||||
archive.extractall(self._gamedir)
|
||||
archive.close()
|
||||
|
||||
async def get_game_diff_archive(self, from_version: str = None):
|
||||
"""Gets a diff archive from `from_version` to the latest one
|
||||
|
||||
Reference in New Issue
Block a user