game: more refactoring
Signed-off-by: tretrauit <tretrauit@gmail.com>
This commit is contained in:
parent
680cf60720
commit
0140969e4b
592
worthless/cli.py
592
worthless/cli.py
|
@ -1,592 +1,2 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
import appdirs
|
|
||||||
from pathlib import Path
|
|
||||||
from worthless.launcher import Launcher
|
|
||||||
from worthless.game import Game as Installer
|
|
||||||
from worthless.patcher import Patcher
|
|
||||||
import worthless.constants as constants
|
|
||||||
|
|
||||||
|
|
||||||
class UI:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
gamedir: str,
|
|
||||||
noconfirm: bool,
|
|
||||||
tempdir: str | Path = None,
|
|
||||||
pre_download=False,
|
|
||||||
) -> None:
|
|
||||||
self._vo_version = None
|
|
||||||
self._noconfirm = noconfirm
|
|
||||||
self._gamedir = gamedir
|
|
||||||
self._launcher = Launcher(gamedir)
|
|
||||||
self._installer = Installer(gamedir, data_dir=tempdir)
|
|
||||||
self._patcher = Patcher(gamedir, data_dir=tempdir)
|
|
||||||
self._pre_download = pre_download
|
|
||||||
if self._pre_download:
|
|
||||||
print("Pre-download is enabled, use at your own risk!")
|
|
||||||
|
|
||||||
def _ask(self, question):
|
|
||||||
if self._noconfirm:
|
|
||||||
# Fake dialog
|
|
||||||
print(question + " [Y/n]:")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# random text
|
|
||||||
answer = "kurumi3"
|
|
||||||
while answer.lower() not in ["y", "n", ""]:
|
|
||||||
answer = input(question + " [Y/n]: ")
|
|
||||||
return answer.lower() == "y" or answer == ""
|
|
||||||
|
|
||||||
def override_game_version(self, version: str):
|
|
||||||
self._installer._version = version
|
|
||||||
|
|
||||||
def override_voiceover_version(self, version: str):
|
|
||||||
self._vo_version = version
|
|
||||||
|
|
||||||
async def get_game_version(self):
|
|
||||||
print(await self._installer.get_game_version())
|
|
||||||
|
|
||||||
async def block_telemetry(self):
|
|
||||||
print("Checking for available telemetry to block...")
|
|
||||||
try:
|
|
||||||
await self._patcher.block_telemetry()
|
|
||||||
except ValueError:
|
|
||||||
print("No telemetry to block.")
|
|
||||||
else:
|
|
||||||
print("Telemetry blocked.")
|
|
||||||
|
|
||||||
async def check_telemetry(self):
|
|
||||||
block_status = await self._patcher.is_telemetry_blocked()
|
|
||||||
if not block_status:
|
|
||||||
print("Telemetry is blocked.")
|
|
||||||
else:
|
|
||||||
print("Telemetry is not blocked, you need to block these hosts below.")
|
|
||||||
for hosts in block_status:
|
|
||||||
print(hosts)
|
|
||||||
|
|
||||||
async def _update_from_archive(self, filepath):
|
|
||||||
print("Reverting patches if patched...")
|
|
||||||
await self._patcher.revert_patch(True)
|
|
||||||
print("Updating game from archive (This may takes some time)...")
|
|
||||||
await self._installer.update_game(filepath)
|
|
||||||
self._installer.set_version_config()
|
|
||||||
|
|
||||||
async def _install_from_archive(self, filepath, force_reinstall):
|
|
||||||
print("Installing game from archive (This may takes some time)...")
|
|
||||||
print(filepath)
|
|
||||||
await self._installer.install_game(filepath, force_reinstall)
|
|
||||||
self._installer.set_version_config()
|
|
||||||
|
|
||||||
async def _apply_voiceover_from_archive(self, filepath):
|
|
||||||
print("Applying voiceover from archive (This may takes some time)...")
|
|
||||||
print("Voiceover archive:", filepath)
|
|
||||||
await self._installer.apply_voiceover(filepath)
|
|
||||||
|
|
||||||
async def install_voiceover_from_file(self, filepath):
|
|
||||||
print(
|
|
||||||
"Archive voiceover language: {} ({})".format(
|
|
||||||
await self._installer.get_voiceover_archive_language(filepath),
|
|
||||||
"Full archive"
|
|
||||||
if await 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
|
|
||||||
await self._apply_voiceover_from_archive(filepath)
|
|
||||||
print("Voiceover applied successfully.")
|
|
||||||
|
|
||||||
async def revert_patch(self):
|
|
||||||
print("Reverting patches...")
|
|
||||||
await self._patcher.revert_patch(True)
|
|
||||||
print("Patches reverted.")
|
|
||||||
|
|
||||||
async def patch_game(self, login_fix: bool = False):
|
|
||||||
print("NOTE: Hereby you are violating the game's Terms of Service!")
|
|
||||||
print("Do not patch the game if you don't know what you are doing!")
|
|
||||||
if not self._ask(
|
|
||||||
"Do you want to patch the game? (This will overwrite your game files!)"
|
|
||||||
):
|
|
||||||
print("Aborting patch process.")
|
|
||||||
return
|
|
||||||
await self.block_telemetry()
|
|
||||||
print("Updating patches...")
|
|
||||||
await self._patcher.download_patch()
|
|
||||||
print("Patching game...")
|
|
||||||
await self._patcher.apply_patch(login_fix)
|
|
||||||
print("Game patched.")
|
|
||||||
print("Please refrain from sharing this project to public, thank you.")
|
|
||||||
|
|
||||||
async def install_from_file(self, filepath):
|
|
||||||
gamever = await self._installer.get_game_version()
|
|
||||||
print(
|
|
||||||
"Archive game version:",
|
|
||||||
await self._installer.get_game_archive_version(filepath),
|
|
||||||
)
|
|
||||||
if gamever:
|
|
||||||
print(
|
|
||||||
"Current game installation detected. ({})".format(
|
|
||||||
await self._installer.get_game_version()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not self._ask("Do you want to update the game? ({})".format(filepath)):
|
|
||||||
print("Aborting update process.")
|
|
||||||
return
|
|
||||||
await self._update_from_archive(filepath)
|
|
||||||
print("Game updated successfully.")
|
|
||||||
else:
|
|
||||||
print("No game installation detected.")
|
|
||||||
if not self._ask("Do you want to install the game? ({})".format(filepath)):
|
|
||||||
print("Aborting installation process.")
|
|
||||||
return
|
|
||||||
await self._install_from_archive(filepath, False)
|
|
||||||
print("Game installed successfully.")
|
|
||||||
|
|
||||||
async def download_patch(self):
|
|
||||||
print("Downloading patches...")
|
|
||||||
await self._patcher.download_patch()
|
|
||||||
|
|
||||||
async def download_game(self):
|
|
||||||
print("Downloading full game (This will take a long time)...")
|
|
||||||
await self._installer.download_full_game(self._pre_download)
|
|
||||||
|
|
||||||
async def download_game_update(self):
|
|
||||||
print("Downloading game update (This will take a long time)...")
|
|
||||||
await self._installer.download_game_update(pre_download=self._pre_download)
|
|
||||||
|
|
||||||
async def download_voiceover(self, languages: str):
|
|
||||||
res_info = await self._launcher.get_resource_info()
|
|
||||||
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
|
|
||||||
print(
|
|
||||||
"Downloading voiceover pack for {} (This will take a long time)...".format(
|
|
||||||
lng
|
|
||||||
)
|
|
||||||
)
|
|
||||||
await self._installer.download_full_voiceover(
|
|
||||||
lng, pre_download=self._pre_download
|
|
||||||
)
|
|
||||||
|
|
||||||
async def download_voiceover_update(self, languages: str):
|
|
||||||
res_info = await self._launcher.get_resource_info()
|
|
||||||
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
|
|
||||||
print(
|
|
||||||
"Downloading voiceover update pack for {} (This will take a long time)...".format(
|
|
||||||
lng
|
|
||||||
)
|
|
||||||
)
|
|
||||||
await self._installer.download_voiceover_update(
|
|
||||||
lng, pre_download=self._pre_download
|
|
||||||
)
|
|
||||||
|
|
||||||
async def install_game(self, forced: bool = False):
|
|
||||||
res_info = await self._launcher.get_resource_info()
|
|
||||||
game = res_info.game
|
|
||||||
if self._pre_download:
|
|
||||||
game = res_info.pre_download_game
|
|
||||||
print("Latest game version: {}".format(game.latest.version))
|
|
||||||
if not self._ask("Do you want to install the game?"):
|
|
||||||
print("Aborting game installation process.")
|
|
||||||
return
|
|
||||||
await self.download_game()
|
|
||||||
print("Game archive:", game.latest.get_name())
|
|
||||||
await self._install_from_archive(
|
|
||||||
self._installer.temp_path.joinpath(game.latest.get_name()), forced
|
|
||||||
)
|
|
||||||
|
|
||||||
async def install_voiceover(self, languages: str):
|
|
||||||
res_info = await self._launcher.get_resource_info()
|
|
||||||
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.")
|
|
||||||
break
|
|
||||||
print("Downloading voiceover pack (This will take a long time)...")
|
|
||||||
await self._installer.download_full_voiceover(
|
|
||||||
lng, pre_download=self._pre_download
|
|
||||||
)
|
|
||||||
await self._apply_voiceover_from_archive(
|
|
||||||
self._installer.temp_path.joinpath(vo.get_name())
|
|
||||||
)
|
|
||||||
|
|
||||||
async def update_game(self):
|
|
||||||
game_ver = await self._installer.get_game_version()
|
|
||||||
if not game_ver:
|
|
||||||
await self.install_game()
|
|
||||||
return
|
|
||||||
print("Current game installation detected: {}".format(game_ver))
|
|
||||||
diff_archive = await self._installer.get_game_diff_archive()
|
|
||||||
res_info = await self._launcher.get_resource_info()
|
|
||||||
game = res_info.game
|
|
||||||
if self._pre_download:
|
|
||||||
game = res_info.pre_download_game
|
|
||||||
if not diff_archive:
|
|
||||||
print("No game updates available.")
|
|
||||||
return
|
|
||||||
print("Latest game version: {}".format(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)...")
|
|
||||||
await self._installer.download_game_update(pre_download=self._pre_download)
|
|
||||||
print("Installing game update...")
|
|
||||||
await self.install_from_file(
|
|
||||||
self._installer.temp_path.joinpath(diff_archive.get_name())
|
|
||||||
)
|
|
||||||
|
|
||||||
async def update_voiceover(self, languages: str | list):
|
|
||||||
if isinstance(languages, str):
|
|
||||||
languages = languages.split(" ")
|
|
||||||
game_ver = self._vo_version or await self._installer.get_game_version()
|
|
||||||
if not game_ver:
|
|
||||||
print("Couldn't detect current game installation, is game installed?")
|
|
||||||
return
|
|
||||||
installed_voiceovers = await self._installer.get_installed_voiceovers()
|
|
||||||
print(
|
|
||||||
f"Installed voiceovers: {None if installed_voiceovers == [] else ', '.join(installed_voiceovers)}"
|
|
||||||
)
|
|
||||||
for lng in languages:
|
|
||||||
if (
|
|
||||||
self._installer.voiceover_lang_translate(lng, "locale")
|
|
||||||
not in installed_voiceovers
|
|
||||||
):
|
|
||||||
await self.install_voiceover(lng)
|
|
||||||
continue
|
|
||||||
diff_archive = await self._installer.get_voiceover_diff_archive(
|
|
||||||
lng, pre_download=self._pre_download
|
|
||||||
)
|
|
||||||
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)...")
|
|
||||||
await self._installer.download_voiceover_update(
|
|
||||||
lng, pre_download=self._pre_download
|
|
||||||
)
|
|
||||||
print("Installing voiceover update for {}...".format(lng))
|
|
||||||
await self._apply_voiceover_from_archive(
|
|
||||||
self._installer.temp_path.joinpath(diff_archive.get_name())
|
|
||||||
)
|
|
||||||
|
|
||||||
async def update_game_voiceover(self, languages: str):
|
|
||||||
await self.update_game()
|
|
||||||
await self.update_voiceover(languages)
|
|
||||||
|
|
||||||
async def update_voiceover_all(self):
|
|
||||||
await self.update_voiceover(await self._installer.get_installed_voiceovers())
|
|
||||||
|
|
||||||
async def update_all(self):
|
|
||||||
await self.update_game()
|
|
||||||
await self.update_voiceover(await self._installer.get_installed_voiceovers())
|
|
||||||
|
|
||||||
async def verify_game(self):
|
|
||||||
game_ver = await self._installer.get_game_version()
|
|
||||||
if not game_ver:
|
|
||||||
print("Couldn't detect current game installation, is game installed?")
|
|
||||||
return
|
|
||||||
print("Verifying game contents... (This may takes a long time)")
|
|
||||||
failed_files = await self._installer.verify_game(ignore_mismatch=True)
|
|
||||||
if not failed_files:
|
|
||||||
print("All good.")
|
|
||||||
return
|
|
||||||
print("Some game files got corrupted (mismatch md5), uh oh.")
|
|
||||||
for file in failed_files:
|
|
||||||
print("{}: expected {}, actual {}".format(file[0], file[1], file[2]))
|
|
||||||
|
|
||||||
async def clear_cache(self):
|
|
||||||
if self._ask(
|
|
||||||
"Do you want to clear Installer cache (contains downloaded game files, etc)?"
|
|
||||||
):
|
|
||||||
await self._installer.clear_cache()
|
|
||||||
if self._ask(
|
|
||||||
"Do you want to clear Patcher cache (contains files used to patch)?"
|
|
||||||
):
|
|
||||||
await self._patcher.clear_cache()
|
|
||||||
|
|
||||||
|
|
||||||
async def main_async():
|
|
||||||
default_dirs = appdirs.AppDirs(constants.APP_NAME, constants.APP_AUTHOR)
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog="worthless", description="A worthless launcher written in Python."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-D",
|
|
||||||
"--dir",
|
|
||||||
action="store",
|
|
||||||
type=Path,
|
|
||||||
default=Path.cwd(),
|
|
||||||
help="Specify the game directory (default current working directory)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-W",
|
|
||||||
"--temporary-dir",
|
|
||||||
action="store",
|
|
||||||
type=Path,
|
|
||||||
default=None,
|
|
||||||
help="Specify the temporary directory (default {} and {})".format(
|
|
||||||
default_dirs.user_data_dir, default_dirs.user_cache_dir
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-S",
|
|
||||||
"--install",
|
|
||||||
action="store_true",
|
|
||||||
help="Install/update the game (if not already installed, else do nothing)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-U",
|
|
||||||
"--install-from-file",
|
|
||||||
action="store",
|
|
||||||
type=Path,
|
|
||||||
default=None,
|
|
||||||
help="Install the game from an archive (if not already installed, \
|
|
||||||
else update from archive)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Uv",
|
|
||||||
"--install-voiceover-from-file",
|
|
||||||
action="store",
|
|
||||||
type=Path,
|
|
||||||
default=None,
|
|
||||||
help="Install the voiceover from an archive (if not already installed, \
|
|
||||||
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(
|
|
||||||
"--login-fix",
|
|
||||||
action="store_true",
|
|
||||||
help="Patch the game to fix login issues (if not already patched, else do nothing)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Sy",
|
|
||||||
"--update",
|
|
||||||
action="store",
|
|
||||||
type=str,
|
|
||||||
default="",
|
|
||||||
help="Update the game and specified voiceover pack only (or install if not found)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Sw",
|
|
||||||
"--download-game",
|
|
||||||
action="store_true",
|
|
||||||
help="Download the full game to the temporary directory",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Swp",
|
|
||||||
"--download-patch",
|
|
||||||
action="store_true",
|
|
||||||
help="Download/Update the game patch to the temporary directory",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Swv",
|
|
||||||
"--download-voiceover",
|
|
||||||
action="store",
|
|
||||||
type=str,
|
|
||||||
help="Download the full voiceover to the temporary directory",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Syw",
|
|
||||||
"--download-game-update",
|
|
||||||
action="store",
|
|
||||||
type=str,
|
|
||||||
default="",
|
|
||||||
help="Download the game and the voiceover update to the temporary directory",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Sywv",
|
|
||||||
"--download-voiceover-update",
|
|
||||||
action="store",
|
|
||||||
type=str,
|
|
||||||
help="Download the voiceover update to the temporary directory",
|
|
||||||
)
|
|
||||||
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)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Scc",
|
|
||||||
"--clear-cache",
|
|
||||||
action="store_true",
|
|
||||||
help="Clear cache used by worthless",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Rs", "--remove", action="store_true", help="Remove the game (if installed)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Rp",
|
|
||||||
"--remove-patch",
|
|
||||||
action="store_true",
|
|
||||||
help="Revert the game patch (if patched)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-Rv",
|
|
||||||
"--remove-voiceover",
|
|
||||||
action="store_true",
|
|
||||||
help="Remove a Voiceover pack (if installed)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-V", "--verify", action="store_true", help="Verify the game installation"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--predownload",
|
|
||||||
action="store_true",
|
|
||||||
help="Download the game for the next update",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--get-game-version", action="store_true", help="Get the current game version"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--no-overseas", action="store_true", help="Don't use overseas server"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--check-telemetry",
|
|
||||||
action="store_true",
|
|
||||||
help="Check for the telemetry information",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--from-ver",
|
|
||||||
action="store",
|
|
||||||
help="Override the detected game version",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--from-vo-ver",
|
|
||||||
action="store",
|
|
||||||
help="Override the detected game version for voiceover " "detection",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--noconfirm",
|
|
||||||
action="store_true",
|
|
||||||
help="Do not ask any for confirmation. (Ignored in interactive mode)",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
if args.temporary_dir:
|
|
||||||
args.temporary_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
ui = UI(args.dir, args.noconfirm, args.temporary_dir, args.predownload)
|
|
||||||
|
|
||||||
if args.install and args.update:
|
|
||||||
raise ValueError("Cannot specify both --install and --update arguments.")
|
|
||||||
|
|
||||||
if args.install_from_file and args.update:
|
|
||||||
raise ValueError(
|
|
||||||
"Cannot specify both --install-from-file and --update arguments."
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.install_voiceover_from_file and args.update:
|
|
||||||
raise ValueError(
|
|
||||||
"Cannot specify both --install-voiceover-from-file and --update arguments."
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.install_from_file and args.install:
|
|
||||||
raise ValueError(
|
|
||||||
"Cannot specify both --install-from-file and --install arguments."
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.from_ver:
|
|
||||||
ui.override_game_version(args.from_ver)
|
|
||||||
|
|
||||||
if args.get_game_version:
|
|
||||||
await ui.get_game_version()
|
|
||||||
|
|
||||||
if args.check_telemetry:
|
|
||||||
await ui.check_telemetry()
|
|
||||||
|
|
||||||
if args.clear_cache:
|
|
||||||
await ui.clear_cache()
|
|
||||||
|
|
||||||
# Download
|
|
||||||
|
|
||||||
if args.download_game:
|
|
||||||
await ui.download_game()
|
|
||||||
|
|
||||||
if args.download_patch:
|
|
||||||
await ui.download_patch()
|
|
||||||
|
|
||||||
if args.download_voiceover:
|
|
||||||
await ui.download_voiceover(args.download_voiceover)
|
|
||||||
|
|
||||||
if args.download_game_update:
|
|
||||||
await ui.download_game_update()
|
|
||||||
|
|
||||||
if args.download_voiceover_update:
|
|
||||||
await ui.download_voiceover_update(args.download_voiceover_update)
|
|
||||||
|
|
||||||
# Install
|
|
||||||
|
|
||||||
if args.install:
|
|
||||||
await ui.install_game()
|
|
||||||
|
|
||||||
if args.install_from_file:
|
|
||||||
await ui.install_from_file(args.install_from_file)
|
|
||||||
|
|
||||||
if args.install_voiceover_from_file:
|
|
||||||
await ui.install_voiceover_from_file(args.install_voiceover_from_file)
|
|
||||||
|
|
||||||
# Update
|
|
||||||
|
|
||||||
if args.update_all:
|
|
||||||
await ui.update_all()
|
|
||||||
|
|
||||||
if args.update:
|
|
||||||
await ui.update_game_voiceover(args.update)
|
|
||||||
|
|
||||||
if args.update_voiceover:
|
|
||||||
await ui.update_voiceover(args.update_voiceover)
|
|
||||||
|
|
||||||
# Patch
|
|
||||||
|
|
||||||
if args.patch:
|
|
||||||
await ui.patch_game(args.login_fix)
|
|
||||||
|
|
||||||
if args.remove_patch:
|
|
||||||
await ui.revert_patch()
|
|
||||||
|
|
||||||
# Verify
|
|
||||||
|
|
||||||
if args.verify:
|
|
||||||
await ui.verify_game()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
asyncio.run(main_async())
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
from worthless.game.game import *
|
from . import gameenums as enums
|
||||||
from worthless.game.gamehelper import Helper
|
from ._game import *
|
||||||
from worthless.game import gameenums
|
from .gamehelper import Helper
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
import json
|
|
||||||
import hashlib
|
|
||||||
import logging
|
|
||||||
from os import PathLike
|
from os import PathLike
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from worthless import constants
|
from worthless import constants, launcher
|
||||||
from worthless.classes import installer
|
from worthless.classes import installer
|
||||||
from worthless.game.gameenums import (
|
from worthless.game.gameenums import (
|
||||||
Variant,
|
Variant,
|
||||||
|
@ -19,10 +19,9 @@ from worthless.game.gameenums import (
|
||||||
)
|
)
|
||||||
from worthless.game.hdiffpatch import HDiffPatch
|
from worthless.game.hdiffpatch import HDiffPatch
|
||||||
from worthless.game.launcherconfig import LauncherConfig
|
from worthless.game.launcherconfig import LauncherConfig
|
||||||
# Workaround for ImportError from partially initialized module
|
|
||||||
import worthless.launcher as launcher
|
|
||||||
|
|
||||||
_logger = logging.getLogger("worthless.Game")
|
_logger = logging.getLogger("worthless.Game")
|
||||||
|
_game_version_regex = re.compile(r"([1-9]+\.[0-9]+\.[0-9]+)_\d+_\d+")
|
||||||
|
|
||||||
|
|
||||||
def _calculate_md5(file: str | bytes | PathLike):
|
def _calculate_md5(file: str | bytes | PathLike):
|
||||||
|
@ -36,6 +35,26 @@ def _calculate_md5(file: str | bytes | PathLike):
|
||||||
return file_hash.hexdigest()
|
return file_hash.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def _read_version_from_game_file(globalgamemanagers: Path | bytes) -> str | None:
|
||||||
|
"""
|
||||||
|
Reads the version from the globalgamemanagers file. (Data/globalgamemanagers)
|
||||||
|
|
||||||
|
This function uses `An Anime Game Launcher` method to read the version:
|
||||||
|
https://github.com/an-anime-team/an-anime-game-launcher/blob/main/src/ts/Game.ts#L26
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Version as string "x.x.x" or None if not found.
|
||||||
|
"""
|
||||||
|
if isinstance(globalgamemanagers, Path):
|
||||||
|
data = globalgamemanagers.read_text(encoding="ascii", errors="ignore")
|
||||||
|
else:
|
||||||
|
data = globalgamemanagers.decode(encoding="ascii", errors="ignore")
|
||||||
|
result = _game_version_regex.search(data)
|
||||||
|
if not result:
|
||||||
|
return
|
||||||
|
return result.group(1)
|
||||||
|
|
||||||
|
|
||||||
def parse_pkg_version(pkg_version: str | bytes | PathLike) -> dict:
|
def parse_pkg_version(pkg_version: str | bytes | PathLike) -> dict:
|
||||||
"""
|
"""
|
||||||
Parse a pkg_version file
|
Parse a pkg_version file
|
||||||
|
@ -64,7 +83,7 @@ def voicepack_lang_translate(lang: str, base_language="game") -> str | None:
|
||||||
lang: Language to translate
|
lang: Language to translate
|
||||||
base_language: Base language type (game/locale/both)
|
base_language: Base language type (game/locale/both)
|
||||||
Returns:
|
Returns:
|
||||||
Translated language code such as `en-us` or English(US)
|
Translated language code such as "en-us" or "English(US)"
|
||||||
"""
|
"""
|
||||||
if base_language == "game" or base_language == "both":
|
if base_language == "game" or base_language == "both":
|
||||||
match lang.lower():
|
match lang.lower():
|
||||||
|
@ -86,8 +105,6 @@ def voicepack_lang_translate(lang: str, base_language="game") -> str | None:
|
||||||
return "Chinese"
|
return "Chinese"
|
||||||
case "ko-kr":
|
case "ko-kr":
|
||||||
return "Korean"
|
return "Korean"
|
||||||
# If nothing else matches
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def get_voicepack_archive_language(
|
def get_voicepack_archive_language(
|
||||||
|
@ -139,7 +156,7 @@ def verify_game_file(
|
||||||
game_dir: str | bytes | PathLike,
|
game_dir: str | bytes | PathLike,
|
||||||
pkg_version: dict[str, str] | str | bytes | PathLike,
|
pkg_version: dict[str, str] | str | bytes | PathLike,
|
||||||
ignore_mismatch=True,
|
ignore_mismatch=True,
|
||||||
):
|
) -> (str, str, str) | None:
|
||||||
"""
|
"""
|
||||||
Verifies a game file
|
Verifies a game file
|
||||||
|
|
||||||
|
@ -148,9 +165,9 @@ def verify_game_file(
|
||||||
to save resource and verify faster.
|
to save resource and verify faster.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file: The file to verify (e.g. GenshinImpact_Data/sharedassets0.assets.resS)
|
file: File to verify (e.g. GenshinImpact_Data/sharedassets0.assets.resS)
|
||||||
game_dir: The game directory
|
game_dir: Game directory
|
||||||
pkg_version: The pkg_version file to read MD5 from, if not specified it'll use
|
pkg_version: pkg_version file to read MD5 from, if not specified it'll use
|
||||||
the file from the current game installation
|
the file from the current game installation
|
||||||
ignore_mismatch: Do not raise exception if a file has mismatch MD5, instead
|
ignore_mismatch: Do not raise exception if a file has mismatch MD5, instead
|
||||||
return as a (file, expected md5, actual md5) tuple
|
return as a (file, expected md5, actual md5) tuple
|
||||||
|
@ -253,7 +270,6 @@ class Game:
|
||||||
self._game_config = LauncherConfig(
|
self._game_config = LauncherConfig(
|
||||||
self._game_config_file, game_version=self.version
|
self._game_config_file, game_version=self.version
|
||||||
)
|
)
|
||||||
self._game_version_re = re.compile(r"([1-9]+\.[0-9]+\.[0-9]+)_\d+_\d+")
|
|
||||||
|
|
||||||
def _read_version_from_config(self) -> str | None:
|
def _read_version_from_config(self) -> str | None:
|
||||||
"""
|
"""
|
||||||
|
@ -266,39 +282,20 @@ class Game:
|
||||||
raise FileNotFoundError(f"Config file {self._game_config_file} not found")
|
raise FileNotFoundError(f"Config file {self._game_config_file} not found")
|
||||||
return self._game_config.get("General", "game_version", fallback=None)
|
return self._game_config.get("General", "game_version", fallback=None)
|
||||||
|
|
||||||
def _read_version_from_game_file(
|
async def get_resource_info(self) -> installer.Resource:
|
||||||
self, globalgamemanagers: Path | bytes
|
|
||||||
) -> str | None:
|
|
||||||
"""
|
"""
|
||||||
Reads the version from the globalgamemanagers file. (Data/globalgamemanagers)
|
Alias for `launcher.get_resource_info()`
|
||||||
|
|
||||||
This function uses `An Anime Game Launcher` method to read the version:
|
|
||||||
https://github.com/an-anime-team/an-anime-game-launcher/blob/main/src/ts/Game.ts#L26
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Version as string "x.x.x" or None if not found.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(globalgamemanagers, Path):
|
return await self._launcher.get_resource_info()
|
||||||
data = globalgamemanagers.read_text(encoding="ascii", errors="ignore")
|
|
||||||
else:
|
|
||||||
data = globalgamemanagers.decode(encoding="ascii", errors="ignore")
|
|
||||||
result = self._game_version_re.search(data)
|
|
||||||
if not result:
|
|
||||||
return
|
|
||||||
return result.group(1)
|
|
||||||
|
|
||||||
async def _get_game_resource(self):
|
async def get_server_game_info(self, pre_download: bool = False) -> installer.Game:
|
||||||
game_resource = await self._launcher.get_resource_info()
|
game_resource = await self.get_resource_info()
|
||||||
if not game_resource:
|
|
||||||
raise ValueError("Could not fetch game resource")
|
|
||||||
return game_resource
|
|
||||||
|
|
||||||
async def _get_game(self, pre_download=False) -> installer.Game:
|
|
||||||
game_resource = await self._get_game_resource()
|
|
||||||
game = game_resource.game
|
game = game_resource.game
|
||||||
if pre_download:
|
if not pre_download:
|
||||||
game = game_resource.pre_download_game
|
return game
|
||||||
return game
|
game = game_resource.pre_download_game
|
||||||
|
if game is None:
|
||||||
|
raise RuntimeError("Pre-download version is not available.")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self) -> str | None:
|
def version(self) -> str | None:
|
||||||
|
@ -327,7 +324,7 @@ class Game:
|
||||||
return self._variant
|
return self._variant
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cache_path(self):
|
def cache_path(self) -> Path:
|
||||||
return self._cache_path
|
return self._cache_path
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -392,7 +389,7 @@ class Game:
|
||||||
if not game_archive.is_file():
|
if not game_archive.is_file():
|
||||||
raise FileNotFoundError(f"Game archive {game_archive} not found")
|
raise FileNotFoundError(f"Game archive {game_archive} not found")
|
||||||
with zipfile.ZipFile(game_archive, "r") as f:
|
with zipfile.ZipFile(game_archive, "r") as f:
|
||||||
return self._read_version_from_game_file(
|
return _read_version_from_game_file(
|
||||||
f.read(self.get_game_data_name() + "globalgamemanagers")
|
f.read(self.get_game_data_name() + "globalgamemanagers")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -426,7 +423,7 @@ class Game:
|
||||||
return self._read_version_from_config()
|
return self._read_version_from_config()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return
|
return
|
||||||
return self._read_version_from_game_file(globalgamemanagers)
|
return _read_version_from_game_file(globalgamemanagers)
|
||||||
|
|
||||||
def get_installed_voicepacks(self) -> list[str]:
|
def get_installed_voicepacks(self) -> list[str]:
|
||||||
"""
|
"""
|
||||||
|
@ -593,16 +590,17 @@ class Game:
|
||||||
self._extract_game_file(game_archive)
|
self._extract_game_file(game_archive)
|
||||||
self._version = self.get_current_game_version()
|
self._version = self.get_current_game_version()
|
||||||
self.set_version_config()
|
self.set_version_config()
|
||||||
|
call(InstallStatus.COMPLETED)
|
||||||
|
|
||||||
async def get_voicepack_diff_archive(
|
async def get_voicepack_diff_archive(
|
||||||
self, lang: str, from_version: str = None, pre_download=False
|
self, lang: str, from_version: str = None, pre_download=False
|
||||||
) -> installer.Diff:
|
) -> installer.Diff:
|
||||||
"""Gets a diff archive from `from_version` to the latest one
|
"""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 from_version is not specified, it will be taken from the current game version.
|
||||||
"""
|
"""
|
||||||
from_version = from_version if from_version else self.version
|
from_version = from_version if from_version else self.version
|
||||||
game = await self._get_game(pre_download=pre_download)
|
game = await self.get_server_game_info(pre_download=pre_download)
|
||||||
translated_lang = voicepack_lang_translate(lang)
|
translated_lang = voicepack_lang_translate(lang)
|
||||||
for v in game.diffs:
|
for v in game.diffs:
|
||||||
if v.version != from_version:
|
if v.version != from_version:
|
||||||
|
@ -616,10 +614,10 @@ class Game:
|
||||||
) -> installer.Diff:
|
) -> installer.Diff:
|
||||||
"""Gets a diff archive from `from_version` to the latest one
|
"""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 from_version is not specified, it will be taken from the current game version.
|
||||||
"""
|
"""
|
||||||
from_version = from_version if from_version else self.version
|
from_version = from_version if from_version else self.version
|
||||||
game = await self._get_game(pre_download=pre_download)
|
game = await self.get_server_game_info(pre_download=pre_download)
|
||||||
for v in game.diffs:
|
for v in game.diffs:
|
||||||
if v.version == from_version:
|
if v.version == from_version:
|
||||||
return v
|
return v
|
||||||
|
@ -691,6 +689,6 @@ class Game:
|
||||||
|
|
||||||
def clear_cache(self):
|
def clear_cache(self):
|
||||||
"""
|
"""
|
||||||
Clears the GameManager cache (e.g. downloaded game files)
|
Clears the cache (e.g. downloaded game files)
|
||||||
"""
|
"""
|
||||||
shutil.rmtree(self._cache_path, ignore_errors=True)
|
shutil.rmtree(self._cache_path, ignore_errors=True)
|
|
@ -1,34 +1,20 @@
|
||||||
from os import PathLike
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from worthless import helper
|
from worthless import helper
|
||||||
from worthless.game import Game, Variant
|
from worthless.game import Game
|
||||||
|
|
||||||
|
|
||||||
class Helper(Game):
|
class Helper:
|
||||||
"""
|
"""
|
||||||
Quick and dirty extra functions for Game
|
Quick and dirty extra functions for Game
|
||||||
|
|
||||||
Since this is quick and dirty, you are recommended to write your own method instead.
|
Since this is quick and dirty, you are recommended to write your own method instead.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
game: A worthless.game.Game instance, if not specified it'll initialize a new one
|
game: A worthless.game.Game instance
|
||||||
using super().__init__ and use the following arguments for that instance creation
|
|
||||||
game_dir: Game directory
|
|
||||||
variant: Game variant
|
|
||||||
cache_dir: Cache directory, if not specified it'll automatically detect your
|
|
||||||
system cache directory and use that.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, game: Game):
|
||||||
self,
|
|
||||||
game: Game = None,
|
|
||||||
game_dir: PathLike = None,
|
|
||||||
variant: Variant = None,
|
|
||||||
cache_dir: PathLike = None,
|
|
||||||
):
|
|
||||||
if not game:
|
|
||||||
super().__init__(game_dir, variant, cache_dir)
|
|
||||||
game = self
|
|
||||||
self._download_chunk = 8192
|
self._download_chunk = 8192
|
||||||
self._game = game
|
self._game = game
|
||||||
|
|
||||||
|
@ -41,8 +27,8 @@ class Helper(Game):
|
||||||
This function is a wrapper for helper.download_file
|
This function is a wrapper for helper.download_file
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file_url: The file url to download
|
file_url: File url to download
|
||||||
file_name: The file name to download into
|
file_name: File name to download into
|
||||||
Returns:
|
Returns:
|
||||||
A Path object containing downloaded file
|
A Path object containing downloaded file
|
||||||
"""
|
"""
|
||||||
|
@ -55,34 +41,41 @@ class Helper(Game):
|
||||||
chunks=self._download_chunk,
|
chunks=self._download_chunk,
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_download_chunk(self, chunk: int):
|
@property
|
||||||
|
def download_chunk(self):
|
||||||
|
return self._download_chunk
|
||||||
|
|
||||||
|
@download_chunk.setter
|
||||||
|
def download_chunk(self, chunk: int) -> None:
|
||||||
"""
|
"""
|
||||||
Sets the download chunk for the internal download function
|
Sets the download chunk for the internal download function
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chunk: The chunk to set into
|
chunk: Chunk to set into
|
||||||
"""
|
"""
|
||||||
self._download_chunk = chunk
|
self._download_chunk = chunk
|
||||||
|
|
||||||
async def download_full_game(self, pre_download=False) -> Path:
|
async def download_full_game(self, pre_download: bool = False) -> Path:
|
||||||
game = await self._game._get_game(pre_download)
|
game = await self._game.get_server_game_info(pre_download)
|
||||||
archive_name = game.latest.path.split("/")[-1]
|
archive_name = game.latest.path.split("/")[-1]
|
||||||
return await self._download_file(
|
return await self._download_file(
|
||||||
game.latest.path, archive_name, game.latest.size
|
game.latest.path, archive_name, game.latest.size
|
||||||
)
|
)
|
||||||
|
|
||||||
async def download_full_voicepack(self, language: str, pre_download=False) -> Path:
|
async def download_full_voicepack(
|
||||||
game = await self._game._get_game(pre_download)
|
self, language: str, pre_download: bool = False
|
||||||
|
) -> Path:
|
||||||
|
game = await self._game.get_server_game_info(pre_download)
|
||||||
translated_lang = self._game.voicepack_lang_translate(language)
|
translated_lang = self._game.voicepack_lang_translate(language)
|
||||||
for vo in game.latest.voice_packs:
|
for vo in game.latest.voice_packs:
|
||||||
if vo.language == translated_lang:
|
if vo.language == translated_lang:
|
||||||
return await self._download_file(vo.path, vo.get_name(), vo.size)
|
return await self._download_file(vo.path, vo.get_name(), vo.size)
|
||||||
|
|
||||||
async def download_game_update(
|
async def download_game_update(
|
||||||
self, from_version: str = None, pre_download=False
|
self, from_version: str = None, pre_download: bool = False
|
||||||
) -> Path:
|
) -> Path:
|
||||||
from_version = from_version if from_version else self.version
|
from_version = from_version if from_version else self._game.version
|
||||||
game = await self._game._get_game(pre_download=pre_download)
|
game = await self._game.get_server_game_info(pre_download=pre_download)
|
||||||
if self._game.version == game.latest.version:
|
if self._game.version == game.latest.version:
|
||||||
raise ValueError("Game is already up to date.")
|
raise ValueError("Game is already up to date.")
|
||||||
diff_archive = await self._game.get_game_diff_archive(
|
diff_archive = await self._game.get_game_diff_archive(
|
||||||
|
@ -97,9 +90,9 @@ class Helper(Game):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def download_voicepack_update(
|
async def download_voicepack_update(
|
||||||
self, language: str, from_version: str = None, pre_download=False
|
self, language: str, from_version: str = None, pre_download: bool = False
|
||||||
) -> Path:
|
) -> Path:
|
||||||
from_version = from_version if from_version else self.version
|
from_version = from_version if from_version else self._game.version
|
||||||
diff_archive = await self._game.get_voicepack_diff_archive(
|
diff_archive = await self._game.get_voicepack_diff_archive(
|
||||||
language, from_version, pre_download
|
language, from_version, pre_download
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import secrets
|
|
||||||
import math
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import aiohttp
|
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
|
import secrets
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
logger = logging.getLogger("worthless.helper")
|
import aiohttp
|
||||||
|
|
||||||
|
_logger = logging.getLogger("worthless.helper")
|
||||||
|
|
||||||
|
|
||||||
async def download_file(
|
async def download_file(
|
||||||
|
@ -13,28 +14,32 @@ async def download_file(
|
||||||
file_name: str,
|
file_name: str,
|
||||||
file_path: Path | str,
|
file_path: Path | str,
|
||||||
file_len: int = None,
|
file_len: int = None,
|
||||||
overwrite=False,
|
overwrite: bool = False,
|
||||||
chunks: int = None,
|
chunks: int = None,
|
||||||
threads_num: int = None,
|
threads_num: int = None,
|
||||||
) -> Path:
|
) -> Path:
|
||||||
"""
|
"""
|
||||||
Download file name to a file_path.
|
Download file name to file_path.
|
||||||
|
|
||||||
You should implement your own download method instead of using this.
|
You should implement your own download method instead of using this.
|
||||||
|
|
||||||
:param file_url: the url to download the file from
|
Args:
|
||||||
:param file_name: the file name to download into
|
file_url: Url to download the file from
|
||||||
:param file_path: the path to download file into
|
file_name: File name to download into
|
||||||
:param file_len: file length, to support threaded downloading
|
file_path: Path to download file into
|
||||||
:param overwrite: whether overwrite existing file or not
|
file_len: File length to support threaded downloading
|
||||||
:param chunks: chunks to write file into memory before writing to disk
|
overwrite: Whether overwrite existing file or not
|
||||||
:param threads_num: number of download threads
|
chunks: Chunks to write file into memory before writing to disk
|
||||||
:return: Downloaded file as a Path object
|
threads_num: Number of download threads
|
||||||
|
Return:
|
||||||
|
Downloaded file as a Path object
|
||||||
"""
|
"""
|
||||||
|
logger = _logger.getChild("download_file")
|
||||||
if not chunks:
|
if not chunks:
|
||||||
chunks = 8192
|
chunks = 8192
|
||||||
if not threads_num:
|
if not threads_num:
|
||||||
threads_num = 8
|
threads_num = 8
|
||||||
logger.debug("Download chunks {} with {} thread".format(chunks, threads_num))
|
logger.debug("Downloading chunks {} with {} thread".format(chunks, threads_num))
|
||||||
file_path = Path(file_path).joinpath(file_name)
|
file_path = Path(file_path).joinpath(file_name)
|
||||||
|
|
||||||
async def _download(
|
async def _download(
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import aiohttp
|
|
||||||
import locale
|
import locale
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from worthless import constants
|
from worthless import constants
|
||||||
from worthless.classes import launcher, installer
|
from worthless.classes import launcher, installer
|
||||||
from worthless.game.gameenums import Variant
|
from worthless.game.gameenums import Variant
|
||||||
|
@ -33,6 +35,8 @@ class Launcher:
|
||||||
"""Initialize the launcher API"""
|
"""Initialize the launcher API"""
|
||||||
if not variant:
|
if not variant:
|
||||||
variant = Variant.INTERNATIONAL
|
variant = Variant.INTERNATIONAL
|
||||||
|
if not language:
|
||||||
|
language = _get_system_language()
|
||||||
self._variant = variant
|
self._variant = variant
|
||||||
match variant:
|
match variant:
|
||||||
case Variant.INTERNATIONAL:
|
case Variant.INTERNATIONAL:
|
||||||
|
@ -41,11 +45,7 @@ class Launcher:
|
||||||
"key": "gcStgarh",
|
"key": "gcStgarh",
|
||||||
"launcher_id": "10",
|
"launcher_id": "10",
|
||||||
}
|
}
|
||||||
self._lang = (
|
self._lang = language.lower().replace("_", "-")
|
||||||
language.lower().replace("_", "-")
|
|
||||||
if language
|
|
||||||
else _get_system_language()
|
|
||||||
)
|
|
||||||
case Variant.CHINESE:
|
case Variant.CHINESE:
|
||||||
self._api = constants.LAUNCHER_API_URL_CN
|
self._api = constants.LAUNCHER_API_URL_CN
|
||||||
self._params = {
|
self._params = {
|
||||||
|
@ -54,7 +54,7 @@ class Launcher:
|
||||||
"channel_id": "1",
|
"channel_id": "1",
|
||||||
}
|
}
|
||||||
self._lang = (
|
self._lang = (
|
||||||
"zh-cn" # Use chinese language because this is chinese version
|
"zh-cn" # Uses Chinese language because this is Chinese version
|
||||||
)
|
)
|
||||||
case Variant.BILIBILI:
|
case Variant.BILIBILI:
|
||||||
self._api = constants.LAUNCHER_API_URL_CN
|
self._api = constants.LAUNCHER_API_URL_CN
|
||||||
|
@ -64,21 +64,23 @@ class Launcher:
|
||||||
"channel_id": "14",
|
"channel_id": "14",
|
||||||
}
|
}
|
||||||
self._lang = (
|
self._lang = (
|
||||||
"zh-cn" # Use chinese language because this is chinese version
|
"zh-cn" # Use Chinese language because this is Chinese version
|
||||||
)
|
)
|
||||||
|
# There is one for beta version too, but I don't have access to that version
|
||||||
self._session = aiohttp.ClientSession()
|
self._session = aiohttp.ClientSession()
|
||||||
|
|
||||||
async def _get(self, url, **kwargs) -> dict:
|
async def _get(self, url, **kwargs) -> dict:
|
||||||
# Workaround because miHoYo uses retcode for their API instead of HTTP status code
|
# Workaround because miHoYo uses retcode for their API instead of HTTP status code (always 200)
|
||||||
rsp = await self._session.get(url, **kwargs)
|
rsp = await self._session.get(url, **kwargs)
|
||||||
rsp_json = await rsp.json()
|
rsp_json = await rsp.json()
|
||||||
if rsp_json["retcode"] != 0:
|
if rsp_json["retcode"] != 0:
|
||||||
# TODO: Add more information to the error message
|
# TODO: Add more information to the error message
|
||||||
raise aiohttp.ClientResponseError(
|
raise aiohttp.ClientResponseError(
|
||||||
code=rsp_json["retcode"],
|
|
||||||
message=rsp_json["message"],
|
message=rsp_json["message"],
|
||||||
history=rsp.history,
|
history=rsp.history,
|
||||||
request_info=rsp.request_info,
|
request_info=rsp.request_info,
|
||||||
|
headers=rsp.headers,
|
||||||
|
status=rsp_json["retcode"],
|
||||||
)
|
)
|
||||||
return rsp_json
|
return rsp_json
|
||||||
|
|
||||||
|
@ -97,7 +99,9 @@ class Launcher:
|
||||||
|
|
||||||
@lang.setter
|
@lang.setter
|
||||||
def lang(self, language: str) -> None:
|
def lang(self, language: str) -> None:
|
||||||
"""Overrides system detected language with another language.
|
"""Sets current language to another language in xx-xx format.
|
||||||
|
|
||||||
|
This method will automatically convert language with xx_XX to xx-xx (e.g. en_US to en-us)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
language (str): Language to override with.
|
language (str): Language to override with.
|
||||||
|
@ -142,15 +146,3 @@ class Launcher:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await self._get_launcher_info(adv=False)
|
return await self._get_launcher_info(adv=False)
|
||||||
|
|
||||||
async def get_launcher_background_url(self) -> str:
|
|
||||||
"""Gets launcher background image url from the server.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Background image url.
|
|
||||||
Raises:
|
|
||||||
aiohttp.ClientResponseError: An error occurred while fetching the background image.
|
|
||||||
"""
|
|
||||||
|
|
||||||
rsp = await self.get_launcher_info()
|
|
||||||
return rsp.background.background
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import tarfile
|
|
||||||
import shutil
|
import shutil
|
||||||
import aiohttp
|
import tarfile
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from worthless import constants
|
from worthless import constants
|
||||||
from worthless.launcher import Launcher
|
|
||||||
from worthless.game import Game as Installer
|
from worthless.game import Game as Installer
|
||||||
|
from worthless.launcher import Launcher
|
||||||
|
|
||||||
match platform.system():
|
match platform.system():
|
||||||
case "Linux":
|
case "Linux":
|
||||||
|
@ -28,14 +29,14 @@ except ImportError:
|
||||||
class Patcher:
|
class Patcher:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
gamedir: Path | AsyncPath | str = AsyncPath.cwd(),
|
gamedir: Path | str = None,
|
||||||
data_dir: str | Path | AsyncPath = None,
|
data_dir: str | Path = None,
|
||||||
patch_url: str = None,
|
patch_url: str = None,
|
||||||
overseas=True,
|
overseas=True,
|
||||||
patch_provider="Krock",
|
patch_provider="Krock",
|
||||||
):
|
):
|
||||||
if isinstance(gamedir, str | Path):
|
if isinstance(gamedir, str | Path):
|
||||||
gamedir = AsyncPath(gamedir)
|
gamedir = Path(gamedir)
|
||||||
self._gamedir = gamedir
|
self._gamedir = gamedir
|
||||||
if not patch_url:
|
if not patch_url:
|
||||||
patch_url = constants.PATCH_LIST[patch_provider].replace(
|
patch_url = constants.PATCH_LIST[patch_provider].replace(
|
||||||
|
@ -44,13 +45,11 @@ class Patcher:
|
||||||
self._patch_url = patch_url
|
self._patch_url = patch_url
|
||||||
if not data_dir:
|
if not data_dir:
|
||||||
self._appdirs = constants.APPDIRS
|
self._appdirs = constants.APPDIRS
|
||||||
self._patch_path = AsyncPath(self._appdirs.user_data_dir).joinpath("Patch")
|
self._patch_path = Path(self._appdirs.user_data_dir).joinpath("Patch")
|
||||||
self._temp_path = AsyncPath(self._appdirs.user_cache_dir).joinpath(
|
self._temp_path = Path(self._appdirs.user_cache_dir).joinpath("Patcher")
|
||||||
"Patcher"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if isinstance(data_dir, str | Path):
|
if isinstance(data_dir, str | Path):
|
||||||
data_dir = AsyncPath(data_dir)
|
data_dir = Path(data_dir)
|
||||||
self._patch_path = data_dir.joinpath("Patch")
|
self._patch_path = data_dir.joinpath("Patch")
|
||||||
self._temp_path = data_dir.joinpath("Temp/Patcher")
|
self._temp_path = data_dir.joinpath("Temp/Patcher")
|
||||||
self._overseas = overseas
|
self._overseas = overseas
|
||||||
|
@ -275,20 +274,20 @@ class Patcher:
|
||||||
for file in disable_files:
|
for file in disable_files:
|
||||||
file_path = Path(self._gamedir.joinpath(file)).resolve()
|
file_path = Path(self._gamedir.joinpath(file)).resolve()
|
||||||
if file_path.exists():
|
if file_path.exists():
|
||||||
await AsyncPath(file_path).rename(str(file_path) + ".bak")
|
await Path(file_path).rename(str(file_path) + ".bak")
|
||||||
|
|
||||||
patch_jobs.append(disable_crashreporters())
|
patch_jobs.append(disable_crashreporters())
|
||||||
await asyncio.gather(*patch_jobs)
|
await asyncio.gather(*patch_jobs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _creation_date(file_path: str | Path | AsyncPath):
|
async def _creation_date(file_path: str | Path | Path):
|
||||||
"""
|
"""
|
||||||
Try to get the date that a file was created, falling back to when it was
|
Try to get the date that a file was created, falling back to when it was
|
||||||
last modified if that isn't possible.
|
last modified if that isn't possible.
|
||||||
See http://stackoverflow.com/a/39501288/1709587 for explanation.
|
See http://stackoverflow.com/a/39501288/1709587 for explanation.
|
||||||
"""
|
"""
|
||||||
if isinstance(file_path, str | Path):
|
if isinstance(file_path, str | Path):
|
||||||
file_path = AsyncPath(file_path)
|
file_path = Path(file_path)
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
return os.path.getctime(file_path)
|
return os.path.getctime(file_path)
|
||||||
else:
|
else:
|
||||||
|
@ -301,7 +300,7 @@ class Patcher:
|
||||||
return stat.st_mtime
|
return stat.st_mtime
|
||||||
|
|
||||||
async def _revert_file(
|
async def _revert_file(
|
||||||
self, original_file: str, base_file: AsyncPath, ignore_error=False
|
self, original_file: str, base_file: Path, ignore_error=False
|
||||||
):
|
):
|
||||||
original_path = Path(self._gamedir.joinpath(original_file + ".bak")).resolve()
|
original_path = Path(self._gamedir.joinpath(original_file + ".bak")).resolve()
|
||||||
target_file = Path(self._gamedir.joinpath(original_file)).resolve()
|
target_file = Path(self._gamedir.joinpath(original_file)).resolve()
|
||||||
|
@ -351,7 +350,7 @@ class Patcher:
|
||||||
|
|
||||||
files = get_files(("*.dxvk-cache", "*_d3d9.log", "*_d3d11.log", "*_dxgi.log"))
|
files = get_files(("*.dxvk-cache", "*_d3d9.log", "*_d3d11.log", "*_dxgi.log"))
|
||||||
for file in files:
|
for file in files:
|
||||||
revert_job.append(AsyncPath(file).unlink(missing_ok=True))
|
revert_job.append(Path(file).unlink(missing_ok=True))
|
||||||
|
|
||||||
await asyncio.gather(*revert_job)
|
await asyncio.gather(*revert_job)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue