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:
tretrauit 2022-02-17 22:02:08 +07:00
parent b1a9223c19
commit ef24ad43ca
No known key found for this signature in database
GPG Key ID: 862760FF1903319E
4 changed files with 160 additions and 57 deletions

View File

@ -1,38 +1,47 @@
import unittest
import asyncio
import worthless
from worthless.classes import launcher
client = worthless.Launcher(overseas=False)
from worthless.classes import launcher, installer
game_launcher = worthless.Launcher(overseas=False)
game_installer = worthless.Installer(overseas=False)
class LauncherCNTest(unittest.TestCase):
class LauncherOverseasTest(unittest.TestCase):
def test_get_version_info(self):
version_info = asyncio.run(client.get_version_info())
print("get_version_info test.")
print("get_version_info: ", version_info)
self.assertIsInstance(version_info, dict)
version_info = asyncio.run(game_launcher.get_resource_info())
print("get_resource_info test.")
print("get_resource_info: ", version_info)
print("raw: ", version_info.raw)
self.assertIsInstance(version_info, installer.Resource)
def test_get_launcher_info(self):
launcher_info = asyncio.run(client.get_launcher_info())
launcher_info = asyncio.run(game_launcher.get_launcher_info())
print("get_launcher_info test.")
print("get_launcher_info: ", launcher_info)
print("raw: ", launcher_info.raw)
self.assertIsInstance(launcher_info, launcher.Info)
def test_get_launcher_full_info(self):
launcher_info = asyncio.run(client.get_launcher_full_info())
launcher_info = asyncio.run(game_launcher.get_launcher_full_info())
print("get_launcher_full_info test.")
print("get_launcher_full_info: ", launcher_info)
print("raw: ", launcher_info.raw)
self.assertIsInstance(launcher_info, launcher.Info)
def test_get_launcher_background_url(self):
bg_url = asyncio.run(client.get_launcher_background_url())
bg_url = asyncio.run(game_launcher.get_launcher_background_url())
print("get_launcher_background_url test.")
print("get_launcher_background_url: ", bg_url)
self.assertIsInstance(bg_url, str)
self.assertTrue(bg_url)
def test_get_installer_diff(self):
game_diff = asyncio.run(game_installer.get_game_diff_archive("2.4.0"))
print("get_game_diff_archive test.")
print("get_game_diff_archive: ", game_diff)
print("raw: ", game_diff.raw)
self.assertIsInstance(game_diff, installer.Diff)
if __name__ == '__main__':
unittest.main()

View File

@ -32,14 +32,17 @@ class UI:
def _update_from_archive(self, filepath):
print("Reverting patches if patched...")
self._patcher.revert_patch(True)
print("Updating game from archive...")
print("Updating game from archive (this may takes some time)...")
self._installer.update_game(filepath)
def _apply_voiceover_from_archive(self, filepath):
print("Applying voiceover from archive...")
print("Applying voiceover from archive (this may takes some time)...")
self._installer.apply_voiceover(filepath)
def install_voiceover_from_file(self, filepath):
print("Archive voiceover language: {} ({})".format(
self._installer.get_voiceover_archive_language(filepath),
"Full archive" if self._installer.get_voiceover_archive_type(filepath) else "Update archive"))
if not self._ask("Do you want to apply this voiceover pack? ({})".format(filepath)):
print("Aborting apply process.")
return
@ -55,8 +58,8 @@ class UI:
gamever = self._installer.get_game_version()
if gamever:
print("Current game installation detected. ({})".format(self._installer.get_game_version()))
print("Archive game version: " + self._installer.get_archive_version(filepath))
if not self._ask("Do you want to update the game? (from {})".format(filepath)):
print("Archive game version: " + self._installer.get_game_archive_version(filepath))
if not self._ask("Do you want to update the game? ({})".format(filepath)):
print("Aborting update process.")
return
self._update_from_archive(filepath)

View File

@ -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,7 +12,26 @@ from worthless import constants
from worthless.launcher import Launcher
def read_version_from_game_file(globalgamemanagers: Path | bytes):
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")
@ -19,15 +42,6 @@ def read_version_from_game_file(globalgamemanagers: Path | bytes):
raise ValueError("Could not find version in game file")
return result.group(1)
class Installer:
def _read_version_from_config(self):
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")
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)
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

View File

@ -127,8 +127,8 @@ class Patcher:
]
for file in revert_files:
self._revert_file(file, game_exec, ignore_errors)
self._gamedir.joinpath("launcher.bat").unlink(missing_ok=True)
self._gamedir.joinpath("mhyprot2_running.reg").unlink(missing_ok=True)
for file in ["launcher.bat", "mhyprot2_running.reg"]:
self._gamedir.joinpath(file).unlink(missing_ok=True)
def get_files(extensions):
all_files = []