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:
parent
b1a9223c19
commit
ef24ad43ca
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 = []
|
||||
|
Loading…
Reference in New Issue
Block a user