#!/usr/bin/python3 import argparse import asyncio import appdirs from pathlib import Path from worthless.launcher import Launcher from worthless.installer import Installer from worthless.patcher import Patcher import worthless.constants as constants class UI: def __init__(self, gamedir: str, noconfirm: bool, tempdir: str | Path = None) -> 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) def _ask(self, question): if self._noconfirm: # Fake dialog print(question + " [Y/n]:") return True answer = "" while answer.lower() not in ['y', 'n', '']: if answer != "": print("Invalid choice, please try again.") answer = input(question + " [Y/n]: ") return answer.lower() == 'y' or answer == '' def override_game_version(self, version: str): self._installer._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() async def download_game_update(self): print("Downloading game update (This will take a long time)...") await self._installer.download_game_update() 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) 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) async def install_game(self, forced: bool = False): res_info = await 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 await self.download_game() print("Game archive:", res_info.game.latest.get_name()) await self._install_from_archive(self._installer.temp_path.joinpath(res_info.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.") return print("Downloading voiceover pack (This will take a long time)...") await self._installer.download_full_voiceover(lng) await self._apply_voiceover_from_archive( self._installer.temp_path.joinpath(vo.get_name()) ) break 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() 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)...") await self._installer.download_game_update() print("Installing game update...") await self.install_from_file(self._installer.temp_path.joinpath(res_info.game.latest.name)) async def update_voiceover(self, languages: str | list): if isinstance(languages, str): languages = languages.split(" ") game_ver = 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) 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) 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_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(): 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("-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("-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("--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("--clear-cache", action="store_true", help="Clear cache used by worthless") parser.add_argument("--from-ver", action="store", help="Override the detected game version", 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) 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() # Download if args.download_game: await ui.download_game() 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() if args.clear_cache: await ui.clear_cache() if __name__ == "__main__": asyncio.run(main())