Support downloading game & voicepacks and install, bump to 1.1.0
In CLI too, also optimized code & added test for voiceover functions.
This commit is contained in:
parent
07ba17b576
commit
bb37e4554d
2
setup.py
2
setup.py
@ -9,7 +9,7 @@ README = (HERE / "README.md").read_text()
|
||||
|
||||
setup(
|
||||
name='worthless',
|
||||
version='1.0.0',
|
||||
version='1.1.0',
|
||||
packages=['worthless'],
|
||||
url='https://git.froggi.es/tretrauit/worthless-launcher',
|
||||
license='MIT License',
|
||||
|
@ -6,7 +6,7 @@ game_launcher = worthless.Launcher(overseas=False)
|
||||
game_installer = worthless.Installer(overseas=False)
|
||||
|
||||
|
||||
class LauncherOverseasTest(unittest.TestCase):
|
||||
class LauncherCNTest(unittest.TestCase):
|
||||
def test_get_version_info(self):
|
||||
version_info = asyncio.run(game_launcher.get_resource_info())
|
||||
print("get_resource_info test.")
|
||||
|
@ -35,13 +35,26 @@ class LauncherOverseasTest(unittest.TestCase):
|
||||
self.assertIsInstance(bg_url, str)
|
||||
self.assertTrue(bg_url)
|
||||
|
||||
def test_get_installer_diff(self):
|
||||
def test_get_installer_game_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)
|
||||
|
||||
def test_get_installer_voiceover_diff_one(self):
|
||||
game_diff = asyncio.run(game_installer.get_voiceover_diff_archive("en-us", "2.4.0"))
|
||||
print("get_voiceover_diff_archive test one (en-us)")
|
||||
print("get_voiceover_diff_archive: ", game_diff)
|
||||
print("raw: ", game_diff.raw)
|
||||
self.assertIsInstance(game_diff, installer.Voicepack)
|
||||
|
||||
def test_get_installer_voiceover_diff_two(self):
|
||||
game_diff = asyncio.run(game_installer.get_voiceover_diff_archive("en-us", "2.4.0"))
|
||||
print("get_voiceover_diff_archive test two (English(US))")
|
||||
print("get_voiceover_diff_archive: ", game_diff)
|
||||
print("raw: ", game_diff.raw)
|
||||
self.assertIsInstance(game_diff, installer.Voicepack)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
|
||||
import appdirs
|
||||
from pathlib import Path
|
||||
from worthless.launcher import Launcher
|
||||
@ -17,8 +19,11 @@ class UI:
|
||||
self._installer = Installer(gamedir, data_dir=tempdir)
|
||||
self._patcher = Patcher(gamedir)
|
||||
|
||||
@staticmethod
|
||||
def _ask(question):
|
||||
def _ask(self, question):
|
||||
if self._noconfirm:
|
||||
# Fake dialog
|
||||
print(question + " (y/n): y")
|
||||
return True
|
||||
answer = ""
|
||||
while answer.lower() not in ['y', 'n']:
|
||||
if answer != "":
|
||||
@ -76,16 +81,76 @@ class UI:
|
||||
self._install_from_archive(filepath)
|
||||
print("Game installed successfully.")
|
||||
|
||||
def install_game(self):
|
||||
# TODO
|
||||
raise NotImplementedError("Install game is not implemented.")
|
||||
def install_game(self, forced: bool = False):
|
||||
res_info = asyncio.run(self._launcher.get_resource_info())
|
||||
print("Latest game version: {}".format(res_info.game.latest.version))
|
||||
if not self._ask("Do you want to install the game?"):
|
||||
print("Aborting game installation process.")
|
||||
return
|
||||
print("Downloading full game (This will take a long time)...")
|
||||
asyncio.run(self._installer.download_full_game())
|
||||
print("Installing game...")
|
||||
self._install_from_archive(self._installer.temp_path.joinpath(res_info.game.latest.name))
|
||||
|
||||
def install_voiceover(self, languages: str):
|
||||
res_info = asyncio.run(self._launcher.get_resource_info())
|
||||
print("Latest game version: {}".format(res_info.game.latest.version))
|
||||
for lng in languages.split(" "):
|
||||
for vo in res_info.game.latest.voice_packs:
|
||||
if not self._installer.voiceover_lang_translate(lng) == vo.language:
|
||||
continue
|
||||
if not self._ask("Do you want to install this voiceover pack? ({})".format(lng)):
|
||||
print("Aborting voiceover installation process.")
|
||||
return
|
||||
print("Downloading voiceover pack (This will take a long time)...")
|
||||
asyncio.run(self._installer.download_full_voiceover(lng))
|
||||
print("Installing voiceover pack...")
|
||||
self._apply_voiceover_from_archive(self._installer.temp_path.joinpath(res_info.game.latest.name))
|
||||
break
|
||||
|
||||
def update_game(self):
|
||||
print("Checking for current game version...")
|
||||
# Call check_game_version()
|
||||
print("Updating game...")
|
||||
# Call update_game(fromver)
|
||||
raise NotImplementedError("Update game is not implemented.")
|
||||
game_ver = self._installer.get_game_version()
|
||||
if not game_ver:
|
||||
self.install_game()
|
||||
return
|
||||
print("Current game installation detected. ({})".format(game_ver))
|
||||
diff_archive = asyncio.run(self._installer.get_game_diff_archive())
|
||||
res_info = asyncio.run(self._launcher.get_resource_info())
|
||||
if not diff_archive:
|
||||
print("No game updates available.")
|
||||
return
|
||||
print("Latest game version: {}".format(res_info.game.latest.version))
|
||||
if not self._ask("Do you want to update the game?"):
|
||||
print("Aborting game update process.")
|
||||
return
|
||||
print("Downloading game update (This will take a long time)...")
|
||||
asyncio.run(self._installer.download_game_update())
|
||||
print("Installing game update...")
|
||||
self.install_from_file(self._installer.temp_path.joinpath(res_info.game.latest.name))
|
||||
|
||||
def update_voiceover(self, languages: str):
|
||||
game_ver = self._installer.get_game_version()
|
||||
if not game_ver:
|
||||
self.install_voiceover(languages)
|
||||
return
|
||||
print("Current game installation detected. ({})".format(game_ver))
|
||||
for lng in languages.split(" "):
|
||||
diff_archive = asyncio.run(self._installer.get_voiceover_diff_archive(lng))
|
||||
# res_info = asyncio.run(self._launcher.get_resource_info())
|
||||
if not diff_archive:
|
||||
print("No voiceover updates available for {}.".format(lng))
|
||||
continue
|
||||
if not self._ask("Do you want to update this voiceover? ({})".format(lng)):
|
||||
print("Aborting this voiceover language update process.")
|
||||
continue
|
||||
print("Downloading voiceover update (This may takes some time)...")
|
||||
asyncio.run(self._installer.download_voiceover_update(lng))
|
||||
print("Installing voiceover update for {}...".format(lng))
|
||||
self._apply_voiceover_from_archive(self._installer.temp_path.joinpath(diff_archive.name))
|
||||
|
||||
def update_game_voiceover(self, languages: str):
|
||||
self.update_game()
|
||||
self.update_voiceover(languages)
|
||||
|
||||
def interactive_ui(self):
|
||||
raise NotImplementedError()
|
||||
@ -109,9 +174,9 @@ def main():
|
||||
else update from archive)")
|
||||
parser.add_argument("-Sp", "--patch", action="store_true",
|
||||
help="Patch the game (if not already patched, else do nothing)")
|
||||
parser.add_argument("-Sy", "--update", action="store_true",
|
||||
parser.add_argument("-Sy", "--update", action="store", type=str,
|
||||
help="Update the game and specified voiceover pack only (or install if not found)")
|
||||
parser.add_argument("-Sv", "--update-voiceover", action="store_true",
|
||||
parser.add_argument("-Sv", "--update-voiceover", action="store", type=str,
|
||||
help="Update the voiceover pack only (or install if not found)")
|
||||
parser.add_argument("-Syu", "--update-all", action="store_true",
|
||||
help="Update the game and all installed voiceover packs (or install if not found)")
|
||||
@ -125,7 +190,7 @@ def main():
|
||||
args = parser.parse_args()
|
||||
interactive_mode = not args.install and not args.install_from_file and not args.patch and not args.update and not \
|
||||
args.remove and not args.remove_patch and not args.remove_voiceover and not args.get_game_version and not \
|
||||
args.install_voiceover_from_file
|
||||
args.install_voiceover_from_file and not args.update_voiceover
|
||||
if args.temporary_dir:
|
||||
args.temporary_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@ -150,7 +215,10 @@ def main():
|
||||
ui.install_game()
|
||||
|
||||
if args.update:
|
||||
ui.update_game()
|
||||
ui.update_game_voiceover(args.update)
|
||||
|
||||
if args.update_voiceover:
|
||||
ui.update_voiceover(args.update_voiceover)
|
||||
|
||||
if args.install_from_file:
|
||||
ui.install_from_file(args.install_from_file)
|
||||
|
@ -1,13 +1,14 @@
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import aiohttp
|
||||
import appdirs
|
||||
import zipfile
|
||||
import warnings
|
||||
import json
|
||||
from pathlib import Path
|
||||
from configparser import ConfigParser
|
||||
|
||||
from aiopath import AsyncPath
|
||||
from worthless import constants
|
||||
from worthless.launcher import Launcher
|
||||
|
||||
@ -75,11 +76,12 @@ class Installer:
|
||||
self._gamedir = gamedir
|
||||
if not data_dir:
|
||||
self._appdirs = appdirs.AppDirs(constants.APP_NAME, constants.APP_AUTHOR)
|
||||
self._temp_path = Path(self._appdirs.user_cache_dir).joinpath("Installer")
|
||||
self.temp_path = Path(self._appdirs.user_cache_dir).joinpath("Installer")
|
||||
else:
|
||||
if not isinstance(data_dir, Path):
|
||||
data_dir = Path(data_dir)
|
||||
self._temp_path = data_dir.joinpath("Temp/Installer/")
|
||||
self.temp_path = data_dir.joinpath("Temp/Installer/")
|
||||
self.temp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_file = self._gamedir.joinpath("config.ini")
|
||||
self._config_file = config_file.resolve()
|
||||
self._version = None
|
||||
@ -87,6 +89,33 @@ class Installer:
|
||||
self._launcher = Launcher(self._gamedir, overseas=self._overseas)
|
||||
self._version = self.get_game_version()
|
||||
|
||||
async def _download_file(self, file_url: str, file_name: str, file_len: int = None):
|
||||
"""
|
||||
Download file name to temporary directory,
|
||||
:param file_url:
|
||||
:param file_name:
|
||||
:return:
|
||||
"""
|
||||
params = {}
|
||||
file_path = AsyncPath(self.temp_path).joinpath(file_name)
|
||||
if file_path.exists():
|
||||
async with file_path.open("rb") as f:
|
||||
cur_len = len(await f.read())
|
||||
params |= {
|
||||
"Range": f"bytes={cur_len}-{file_len if file_len else ''}"
|
||||
}
|
||||
else:
|
||||
await file_path.touch()
|
||||
async with aiohttp.ClientSession() as session:
|
||||
rsp = await session.get(file_url, params=params, timeout=None)
|
||||
rsp.raise_for_status()
|
||||
while True:
|
||||
chunk = await rsp.content.read(8192)
|
||||
if not chunk:
|
||||
break
|
||||
async with file_path.open("ab") as f:
|
||||
await f.write(chunk)
|
||||
|
||||
def get_game_archive_version(self, game_archive: str | Path):
|
||||
if not game_archive.exists():
|
||||
raise FileNotFoundError(f"Game archive {game_archive} not found")
|
||||
@ -110,6 +139,7 @@ class Installer:
|
||||
return "zh-cn"
|
||||
case "Korean":
|
||||
return "ko-kr"
|
||||
return lang
|
||||
|
||||
@staticmethod
|
||||
def get_voiceover_archive_language(voiceover_archive: str | Path):
|
||||
@ -174,20 +204,53 @@ class Installer:
|
||||
|
||||
archive.extractall(self._gamedir, members=files)
|
||||
archive.close()
|
||||
# Update game version on local variable.
|
||||
self._version = self.get_game_version()
|
||||
|
||||
async def download_game_update(self):
|
||||
if self._version is None:
|
||||
raise ValueError("Game version not found, use install_game to install the game.")
|
||||
async def download_full_game(self):
|
||||
archive = await self._launcher.get_resource_info()
|
||||
if archive is None:
|
||||
raise RuntimeError("Failed to fetch game resource info.")
|
||||
if self._version == archive.game.latest.version:
|
||||
raise ValueError("Game is already up to date.")
|
||||
await self._download_file(archive.game.latest.path, archive.game.latest.name, archive.game.latest.size)
|
||||
|
||||
async def download_full_voiceover(self, language: str):
|
||||
archive = await self._launcher.get_resource_info()
|
||||
if archive is None:
|
||||
raise RuntimeError("Failed to fetch game resource info.")
|
||||
translated_lang = self.voiceover_lang_translate(language)
|
||||
for vo in archive.game.latest.voice_packs:
|
||||
if vo.language == translated_lang:
|
||||
await self._download_file(vo.path, vo.name, vo.size)
|
||||
|
||||
async def download_game_update(self, from_version: str = None):
|
||||
if not from_version:
|
||||
self._version = from_version
|
||||
if not from_version:
|
||||
raise ValueError("Game version not found")
|
||||
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()
|
||||
diff_archive = await self.get_game_diff_archive(from_version)
|
||||
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.")
|
||||
await self._download_file(diff_archive.path, diff_archive.name, diff_archive.size)
|
||||
|
||||
async def download_voiceover_update(self, language: str, from_version: str = None):
|
||||
if not from_version:
|
||||
self._version = from_version
|
||||
if not from_version:
|
||||
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.")
|
||||
diff_archive = await self.get_voiceover_diff_archive(language, from_version)
|
||||
if diff_archive is None:
|
||||
raise ValueError("Voiceover diff archive is not available for this version, please reinstall.")
|
||||
await self._download_file(diff_archive.path, diff_archive.name, diff_archive.size)
|
||||
|
||||
def uninstall_game(self):
|
||||
shutil.rmtree(self._gamedir)
|
||||
@ -211,6 +274,30 @@ class Installer:
|
||||
archive.extractall(self._gamedir)
|
||||
archive.close()
|
||||
|
||||
async def get_voiceover_diff_archive(self, lang: str, from_version: str = None):
|
||||
"""Gets a diff archive from `from_version` to the latest one
|
||||
|
||||
If from_version is not specified, it will be taken from the game version.
|
||||
"""
|
||||
if not from_version:
|
||||
if self._version:
|
||||
from_version = self._version
|
||||
else:
|
||||
from_version = self._version = self.get_game_version()
|
||||
if not from_version:
|
||||
raise ValueError("No game version found")
|
||||
game_resource = await self._launcher.get_resource_info()
|
||||
if not game_resource:
|
||||
raise ValueError("Could not fetch game resource")
|
||||
translated_lang = self.voiceover_lang_translate(lang)
|
||||
for v in game_resource.game.diffs:
|
||||
if v.version != from_version:
|
||||
continue
|
||||
for vo in v.voice_packs:
|
||||
if vo.language != translated_lang:
|
||||
continue
|
||||
return vo
|
||||
|
||||
async def get_game_diff_archive(self, from_version: str = None):
|
||||
"""Gets a diff archive from `from_version` to the latest one
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user