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 unittest
|
||||||
import asyncio
|
import asyncio
|
||||||
import worthless
|
import worthless
|
||||||
from worthless.classes import launcher
|
from worthless.classes import launcher, installer
|
||||||
client = worthless.Launcher(overseas=False)
|
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):
|
def test_get_version_info(self):
|
||||||
version_info = asyncio.run(client.get_version_info())
|
version_info = asyncio.run(game_launcher.get_resource_info())
|
||||||
print("get_version_info test.")
|
print("get_resource_info test.")
|
||||||
print("get_version_info: ", version_info)
|
print("get_resource_info: ", version_info)
|
||||||
self.assertIsInstance(version_info, dict)
|
print("raw: ", version_info.raw)
|
||||||
|
self.assertIsInstance(version_info, installer.Resource)
|
||||||
|
|
||||||
def test_get_launcher_info(self):
|
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 test.")
|
||||||
print("get_launcher_info: ", launcher_info)
|
print("get_launcher_info: ", launcher_info)
|
||||||
print("raw: ", launcher_info.raw)
|
print("raw: ", launcher_info.raw)
|
||||||
self.assertIsInstance(launcher_info, launcher.Info)
|
self.assertIsInstance(launcher_info, launcher.Info)
|
||||||
|
|
||||||
def test_get_launcher_full_info(self):
|
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 test.")
|
||||||
print("get_launcher_full_info: ", launcher_info)
|
print("get_launcher_full_info: ", launcher_info)
|
||||||
print("raw: ", launcher_info.raw)
|
print("raw: ", launcher_info.raw)
|
||||||
self.assertIsInstance(launcher_info, launcher.Info)
|
self.assertIsInstance(launcher_info, launcher.Info)
|
||||||
|
|
||||||
def test_get_launcher_background_url(self):
|
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 test.")
|
||||||
print("get_launcher_background_url: ", bg_url)
|
print("get_launcher_background_url: ", bg_url)
|
||||||
self.assertIsInstance(bg_url, str)
|
self.assertIsInstance(bg_url, str)
|
||||||
self.assertTrue(bg_url)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -32,14 +32,17 @@ class UI:
|
|||||||
def _update_from_archive(self, filepath):
|
def _update_from_archive(self, filepath):
|
||||||
print("Reverting patches if patched...")
|
print("Reverting patches if patched...")
|
||||||
self._patcher.revert_patch(True)
|
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)
|
self._installer.update_game(filepath)
|
||||||
|
|
||||||
def _apply_voiceover_from_archive(self, 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)
|
self._installer.apply_voiceover(filepath)
|
||||||
|
|
||||||
def install_voiceover_from_file(self, 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)):
|
if not self._ask("Do you want to apply this voiceover pack? ({})".format(filepath)):
|
||||||
print("Aborting apply process.")
|
print("Aborting apply process.")
|
||||||
return
|
return
|
||||||
@ -55,8 +58,8 @@ class UI:
|
|||||||
gamever = self._installer.get_game_version()
|
gamever = self._installer.get_game_version()
|
||||||
if gamever:
|
if gamever:
|
||||||
print("Current game installation detected. ({})".format(self._installer.get_game_version()))
|
print("Current game installation detected. ({})".format(self._installer.get_game_version()))
|
||||||
print("Archive game version: " + self._installer.get_archive_version(filepath))
|
print("Archive game version: " + self._installer.get_game_archive_version(filepath))
|
||||||
if not self._ask("Do you want to update the game? (from {})".format(filepath)):
|
if not self._ask("Do you want to update the game? ({})".format(filepath)):
|
||||||
print("Aborting update process.")
|
print("Aborting update process.")
|
||||||
return
|
return
|
||||||
self._update_from_archive(filepath)
|
self._update_from_archive(filepath)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
|
|
||||||
import appdirs
|
import appdirs
|
||||||
import zipfile
|
import zipfile
|
||||||
|
import warnings
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
|
||||||
@ -8,26 +12,36 @@ from worthless import constants
|
|||||||
from worthless.launcher import Launcher
|
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:
|
class Installer:
|
||||||
def _read_version_from_config(self):
|
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():
|
if not self._config_file.exists():
|
||||||
raise FileNotFoundError(f"Config file {self._config_file} not found")
|
raise FileNotFoundError(f"Config file {self._config_file} not found")
|
||||||
cfg = ConfigParser()
|
cfg = ConfigParser()
|
||||||
cfg.read(str(self._config_file))
|
cfg.read(str(self._config_file))
|
||||||
return cfg.get("General", "game_version")
|
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):
|
def get_game_data_name(self):
|
||||||
if self._overseas:
|
if self._overseas:
|
||||||
return "GenshinImpact_Data/"
|
return "GenshinImpact_Data/"
|
||||||
@ -37,15 +51,23 @@ class Installer:
|
|||||||
def get_game_data_path(self):
|
def get_game_data_path(self):
|
||||||
return self._gamedir.joinpath(self.get_game_data_name())
|
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):
|
def get_game_version(self):
|
||||||
if self._config_file.exists():
|
globalgamemanagers = self.get_game_data_path().joinpath("./globalgamemanagers")
|
||||||
return self._read_version_from_config()
|
if not globalgamemanagers.exists():
|
||||||
else:
|
return
|
||||||
globalgamemanagers = self.get_game_data_path().joinpath("./globalgamemanagers")
|
return self.read_version_from_game_file(globalgamemanagers)
|
||||||
if not globalgamemanagers.exists():
|
|
||||||
return
|
def get_installed_voiceovers(self):
|
||||||
return read_version_from_game_file(globalgamemanagers)
|
"""
|
||||||
|
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):
|
def __init__(self, gamedir: str | Path = Path.cwd(), overseas: bool = True, data_dir: str | Path = None):
|
||||||
if isinstance(gamedir, str):
|
if isinstance(gamedir, str):
|
||||||
@ -65,17 +87,60 @@ class Installer:
|
|||||||
self._launcher = Launcher(self._gamedir, overseas=self._overseas)
|
self._launcher = Launcher(self._gamedir, overseas=self._overseas)
|
||||||
self._version = self.get_game_version()
|
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():
|
if not game_archive.exists():
|
||||||
raise FileNotFoundError(f"Game archive {game_archive} not found")
|
raise FileNotFoundError(f"Game archive {game_archive} not found")
|
||||||
archive = zipfile.ZipFile(game_archive, 'r')
|
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):
|
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():
|
if not self.get_game_data_path().exists():
|
||||||
raise FileNotFoundError(f"Game not found in {self._gamedir}")
|
raise FileNotFoundError(f"Game not found in {self._gamedir}")
|
||||||
if isinstance(voiceover_archive, str):
|
if isinstance(voiceover_archive, str):
|
||||||
game_archive = Path(voiceover_archive).resolve()
|
voiceover_archive = Path(voiceover_archive).resolve()
|
||||||
if not voiceover_archive.exists():
|
if not voiceover_archive.exists():
|
||||||
raise FileNotFoundError(f"Voiceover archive {voiceover_archive} not found")
|
raise FileNotFoundError(f"Voiceover archive {voiceover_archive} not found")
|
||||||
archive = zipfile.ZipFile(voiceover_archive, 'r')
|
archive = zipfile.ZipFile(voiceover_archive, 'r')
|
||||||
@ -90,6 +155,15 @@ class Installer:
|
|||||||
if not game_archive.exists():
|
if not game_archive.exists():
|
||||||
raise FileNotFoundError(f"Update archive {game_archive} not found")
|
raise FileNotFoundError(f"Update archive {game_archive} not found")
|
||||||
archive = zipfile.ZipFile(game_archive, 'r')
|
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")
|
deletefiles = archive.read("deletefiles.txt").decode().split("\n")
|
||||||
for file in deletefiles:
|
for file in deletefiles:
|
||||||
current_game_file = self._gamedir.joinpath(file)
|
current_game_file = self._gamedir.joinpath(file)
|
||||||
@ -97,28 +171,45 @@ class Installer:
|
|||||||
continue
|
continue
|
||||||
if current_game_file.is_file():
|
if current_game_file.is_file():
|
||||||
current_game_file.unlink(missing_ok=True)
|
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
|
"""Installs the game to the current directory
|
||||||
|
|
||||||
If `from_version` is not specified, it will be taken from the game version.
|
If `force_reinstall` is True, the game will be uninstalled then reinstalled.
|
||||||
If `to_version` is not specified, it will be taken from the game version.
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Not implemented yet")
|
if self.get_game_data_path().exists():
|
||||||
# if not force:
|
if not force_reinstall:
|
||||||
# if self._temp_path.exists():
|
raise ValueError(f"Game is already installed in {self._gamedir}")
|
||||||
# raise FileExistsError(f"Directory {self._temp_path} already exists")
|
self.uninstall_game()
|
||||||
# self._temp_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
# self._launcher.set_temp_path(self._temp_path)
|
self._gamedir.mkdir(parents=True, exist_ok=True)
|
||||||
# await self._launcher.download_game_diff_archive(from_version, to_version)
|
if isinstance(game_archive, str):
|
||||||
# await self._launcher.extract_game_diff_archive()
|
game_archive = Path(game_archive).resolve()
|
||||||
# await self._launcher.install_game_diff_archive()
|
if not game_archive.exists():
|
||||||
# self._launcher.set_temp_path(None)
|
raise FileNotFoundError(f"Install archive {game_archive} not found")
|
||||||
# self._temp_path.rmdir()
|
archive = zipfile.ZipFile(game_archive, 'r')
|
||||||
|
archive.extractall(self._gamedir)
|
||||||
|
archive.close()
|
||||||
|
|
||||||
async def get_game_diff_archive(self, from_version: str = None):
|
async def get_game_diff_archive(self, from_version: str = None):
|
||||||
"""Gets a diff archive from `from_version` to the latest one
|
"""Gets a diff archive from `from_version` to the latest one
|
||||||
|
@ -127,8 +127,8 @@ class Patcher:
|
|||||||
]
|
]
|
||||||
for file in revert_files:
|
for file in revert_files:
|
||||||
self._revert_file(file, game_exec, ignore_errors)
|
self._revert_file(file, game_exec, ignore_errors)
|
||||||
self._gamedir.joinpath("launcher.bat").unlink(missing_ok=True)
|
for file in ["launcher.bat", "mhyprot2_running.reg"]:
|
||||||
self._gamedir.joinpath("mhyprot2_running.reg").unlink(missing_ok=True)
|
self._gamedir.joinpath(file).unlink(missing_ok=True)
|
||||||
|
|
||||||
def get_files(extensions):
|
def get_files(extensions):
|
||||||
all_files = []
|
all_files = []
|
||||||
|
Loading…
Reference in New Issue
Block a user