mirror of
https://github.com/derrod/legendary.git
synced 2024-12-22 17:55:27 +00:00
parent
8ab63f0665
commit
c904bbfa19
|
@ -389,9 +389,14 @@ class LegendaryCLI:
|
||||||
subprocess.Popen(params, cwd=cwd, env=env)
|
subprocess.Popen(params, cwd=cwd, env=env)
|
||||||
|
|
||||||
def install_game(self, args):
|
def install_game(self, args):
|
||||||
|
repair_file = None
|
||||||
if args.subparser_name == 'download':
|
if args.subparser_name == 'download':
|
||||||
logger.info('The "download" command will be changed to set the --no-install command by default '
|
logger.info('Setting --no-install flag since "download" command was used')
|
||||||
'in the future, please adjust install scripts etc. to use "install" instead.')
|
args.no_install = True
|
||||||
|
elif args.subparser_name == 'repair' or args.repair_mode:
|
||||||
|
args.repair_mode = True
|
||||||
|
args.no_install = True
|
||||||
|
repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{args.app_name}.repair')
|
||||||
|
|
||||||
if not self.core.login():
|
if not self.core.login():
|
||||||
logger.error('Login failed! Cannot continue with download process.')
|
logger.error('Login failed! Cannot continue with download process.')
|
||||||
|
@ -428,6 +433,23 @@ class LegendaryCLI:
|
||||||
else:
|
else:
|
||||||
base_game = None
|
base_game = None
|
||||||
|
|
||||||
|
if args.repair_mode:
|
||||||
|
if not self.core.is_installed(game.app_name):
|
||||||
|
logger.error(f'Game "{game.app_title}" ({game.app_name}) is not installed!')
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
if not os.path.exists(repair_file):
|
||||||
|
logger.info('Game has not been verified yet.')
|
||||||
|
if not args.yes:
|
||||||
|
choice = input(f'Verify "{game.app_name}" now (answer "no" will abort repair)? [Y/n]: ')
|
||||||
|
if choice and choice.lower()[0] != 'y':
|
||||||
|
print('Aborting...')
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
self.verify_game(args, print_command=False)
|
||||||
|
else:
|
||||||
|
logger.info(f'Using existing repair file: {repair_file}')
|
||||||
|
|
||||||
logger.info('Preparing download...')
|
logger.info('Preparing download...')
|
||||||
# todo use status queue to print progress from CLI
|
# todo use status queue to print progress from CLI
|
||||||
# This has become a little ridiculous hasn't it?
|
# This has become a little ridiculous hasn't it?
|
||||||
|
@ -443,11 +465,15 @@ class LegendaryCLI:
|
||||||
file_exclude_filter=args.file_exclude_prefix,
|
file_exclude_filter=args.file_exclude_prefix,
|
||||||
file_install_tag=args.install_tag,
|
file_install_tag=args.install_tag,
|
||||||
dl_optimizations=args.order_opt,
|
dl_optimizations=args.order_opt,
|
||||||
dl_timeout=args.dl_timeout)
|
dl_timeout=args.dl_timeout,
|
||||||
|
repair=args.repair_mode)
|
||||||
|
|
||||||
# game is either up to date or hasn't changed, so we have nothing to do
|
# game is either up to date or hasn't changed, so we have nothing to do
|
||||||
if not analysis.dl_size:
|
if not analysis.dl_size:
|
||||||
logger.info('Download size is 0, the game is either already up to date or has not changed. Exiting...')
|
logger.info('Download size is 0, the game is either already up to date or has not changed. Exiting...')
|
||||||
|
if args.repair_mode and os.path.exists(repair_file):
|
||||||
|
logger.debug('Removing repair file.')
|
||||||
|
os.remove(repair_file)
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
logger.info(f'Install size: {analysis.install_size / 1024 / 1024:.02f} MiB')
|
logger.info(f'Install size: {analysis.install_size / 1024 / 1024:.02f} MiB')
|
||||||
|
@ -530,6 +556,10 @@ class LegendaryCLI:
|
||||||
logger.info('This game supports cloud saves, syncing is handled by the "sync-saves" command.')
|
logger.info('This game supports cloud saves, syncing is handled by the "sync-saves" command.')
|
||||||
logger.info(f'To download saves for this game run "legendary sync-saves {args.app_name}"')
|
logger.info(f'To download saves for this game run "legendary sync-saves {args.app_name}"')
|
||||||
|
|
||||||
|
if args.repair_mode and os.path.exists(repair_file):
|
||||||
|
logger.debug('Removing repair file.')
|
||||||
|
os.remove(repair_file)
|
||||||
|
|
||||||
logger.info(f'Finished installation process in {end_t - start_t:.02f} seconds.')
|
logger.info(f'Finished installation process in {end_t - start_t:.02f} seconds.')
|
||||||
|
|
||||||
def _handle_postinstall(self, postinstall, igame, yes=False):
|
def _handle_postinstall(self, postinstall, igame, yes=False):
|
||||||
|
@ -586,7 +616,7 @@ class LegendaryCLI:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.')
|
logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.')
|
||||||
|
|
||||||
def verify_game(self, args):
|
def verify_game(self, args, print_command=True):
|
||||||
if not self.core.is_installed(args.app_name):
|
if not self.core.is_installed(args.app_name):
|
||||||
logger.error(f'Game "{args.app_name}" is not installed')
|
logger.error(f'Game "{args.app_name}" is not installed')
|
||||||
return
|
return
|
||||||
|
@ -606,15 +636,19 @@ class LegendaryCLI:
|
||||||
failed = []
|
failed = []
|
||||||
missing = []
|
missing = []
|
||||||
|
|
||||||
for result, path in validate_files(igame.install_path, file_list):
|
logger.info(f'Verifying "{igame.title}" version "{manifest.meta.build_version}"')
|
||||||
|
repair_file = []
|
||||||
|
for result, path, result_hash in validate_files(igame.install_path, file_list):
|
||||||
stdout.write(f'Verification progress: {num}/{total} ({num * 100 / total:.01f}%)\t\r')
|
stdout.write(f'Verification progress: {num}/{total} ({num * 100 / total:.01f}%)\t\r')
|
||||||
stdout.flush()
|
stdout.flush()
|
||||||
num += 1
|
num += 1
|
||||||
|
|
||||||
if result == VerifyResult.HASH_MATCH:
|
if result == VerifyResult.HASH_MATCH:
|
||||||
|
repair_file.append(f'{result_hash}:{path}')
|
||||||
continue
|
continue
|
||||||
elif result == VerifyResult.HASH_MISMATCH:
|
elif result == VerifyResult.HASH_MISMATCH:
|
||||||
logger.error(f'File does not match hash: "{path}"')
|
logger.error(f'File does not match hash: "{path}"')
|
||||||
|
repair_file.append(f'{result_hash}:{path}')
|
||||||
failed.append(path)
|
failed.append(path)
|
||||||
elif result == VerifyResult.FILE_MISSING:
|
elif result == VerifyResult.FILE_MISSING:
|
||||||
logger.error(f'File is missing: "{path}"')
|
logger.error(f'File is missing: "{path}"')
|
||||||
|
@ -622,10 +656,19 @@ class LegendaryCLI:
|
||||||
|
|
||||||
stdout.write(f'Verification progress: {num}/{total} ({num * 100 / total:.01f}%)\t\n')
|
stdout.write(f'Verification progress: {num}/{total} ({num * 100 / total:.01f}%)\t\n')
|
||||||
|
|
||||||
|
# always write repair file, even if all match
|
||||||
|
if repair_file:
|
||||||
|
repair_filename = os.path.join(self.core.lgd.get_tmp_path(), f'{args.app_name}.repair')
|
||||||
|
with open(repair_filename, 'w') as f:
|
||||||
|
f.write('\n'.join(repair_file))
|
||||||
|
logger.debug(f'Written repair file to "{repair_filename}"')
|
||||||
|
|
||||||
if not missing and not failed:
|
if not missing and not failed:
|
||||||
logger.info('Verification finished successfully.')
|
logger.info('Verification finished successfully.')
|
||||||
else:
|
else:
|
||||||
logger.fatal(f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
|
logger.error(f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
|
||||||
|
if print_command:
|
||||||
|
logger.info(f'Run "legendary repair {args.app_name}" to repair your game installation.')
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -641,7 +684,7 @@ def main():
|
||||||
subparsers = parser.add_subparsers(title='Commands', dest='subparser_name')
|
subparsers = parser.add_subparsers(title='Commands', dest='subparser_name')
|
||||||
auth_parser = subparsers.add_parser('auth', help='Authenticate with EPIC')
|
auth_parser = subparsers.add_parser('auth', help='Authenticate with EPIC')
|
||||||
install_parser = subparsers.add_parser('install', help='Download a game',
|
install_parser = subparsers.add_parser('install', help='Download a game',
|
||||||
aliases=('download', 'update'),
|
aliases=('download', 'update', 'repair'),
|
||||||
usage='%(prog)s <App Name> [options]',
|
usage='%(prog)s <App Name> [options]',
|
||||||
description='Aliases: download, update')
|
description='Aliases: download, update')
|
||||||
uninstall_parser = subparsers.add_parser('uninstall', help='Uninstall (delete) a game')
|
uninstall_parser = subparsers.add_parser('uninstall', help='Uninstall (delete) a game')
|
||||||
|
@ -715,6 +758,8 @@ def main():
|
||||||
help='Connection timeout for downloader (default: 10 seconds)')
|
help='Connection timeout for downloader (default: 10 seconds)')
|
||||||
install_parser.add_argument('--save-path', dest='save_path', action='store', metavar='<path>',
|
install_parser.add_argument('--save-path', dest='save_path', action='store', metavar='<path>',
|
||||||
help='Set save game path during install.')
|
help='Set save game path during install.')
|
||||||
|
install_parser.add_argument('--repair', dest='repair_mode', action='store_true',
|
||||||
|
help='Repair already installed game by downloading corrupted/missing files')
|
||||||
|
|
||||||
launch_parser.add_argument('--offline', dest='offline', action='store_true',
|
launch_parser.add_argument('--offline', dest='offline', action='store_true',
|
||||||
default=False, help='Skip login and launch game without online authentication')
|
default=False, help='Skip login and launch game without online authentication')
|
||||||
|
@ -786,7 +831,8 @@ def main():
|
||||||
|
|
||||||
if args.subparser_name not in ('auth', 'list-games', 'list-installed', 'list-files',
|
if args.subparser_name not in ('auth', 'list-games', 'list-installed', 'list-files',
|
||||||
'launch', 'download', 'uninstall', 'install', 'update',
|
'launch', 'download', 'uninstall', 'install', 'update',
|
||||||
'list-saves', 'download-saves', 'sync-saves', 'verify-game'):
|
'repair', 'list-saves', 'download-saves', 'sync-saves',
|
||||||
|
'verify-game'):
|
||||||
print(parser.format_help())
|
print(parser.format_help())
|
||||||
|
|
||||||
# Print the main help *and* the help for all of the subcommands. Thanks stackoverflow!
|
# Print the main help *and* the help for all of the subcommands. Thanks stackoverflow!
|
||||||
|
@ -820,7 +866,7 @@ def main():
|
||||||
cli.list_installed(args)
|
cli.list_installed(args)
|
||||||
elif args.subparser_name == 'launch':
|
elif args.subparser_name == 'launch':
|
||||||
cli.launch_game(args, extra)
|
cli.launch_game(args, extra)
|
||||||
elif args.subparser_name in ('download', 'install', 'update'):
|
elif args.subparser_name in ('download', 'install', 'update', 'repair'):
|
||||||
cli.install_game(args)
|
cli.install_game(args)
|
||||||
elif args.subparser_name == 'uninstall':
|
elif args.subparser_name == 'uninstall':
|
||||||
cli.uninstall_game(args)
|
cli.uninstall_game(args)
|
||||||
|
|
|
@ -570,8 +570,8 @@ class LegendaryCore:
|
||||||
override_old_manifest: str = '', override_base_url: str = '',
|
override_old_manifest: str = '', override_base_url: str = '',
|
||||||
platform_override: str = '', file_prefix_filter: list = None,
|
platform_override: str = '', file_prefix_filter: list = None,
|
||||||
file_exclude_filter: list = None, file_install_tag: list = None,
|
file_exclude_filter: list = None, file_install_tag: list = None,
|
||||||
dl_optimizations: bool = False, dl_timeout: int = 10
|
dl_optimizations: bool = False, dl_timeout: int = 10,
|
||||||
) -> (DLManager, AnalysisResult, ManifestMeta):
|
repair: bool = False) -> (DLManager, AnalysisResult, ManifestMeta):
|
||||||
# load old manifest
|
# load old manifest
|
||||||
old_manifest = None
|
old_manifest = None
|
||||||
|
|
||||||
|
@ -637,8 +637,15 @@ class LegendaryCore:
|
||||||
|
|
||||||
self.log.info(f'Install path: {install_path}')
|
self.log.info(f'Install path: {install_path}')
|
||||||
|
|
||||||
if not force:
|
if repair:
|
||||||
filename = clean_filename(f'{game.app_name}_{new_manifest.meta.build_version}.resume')
|
# use installed manifest for repairs, do not update to latest version (for now)
|
||||||
|
new_manifest = old_manifest
|
||||||
|
old_manifest = None
|
||||||
|
filename = clean_filename(f'{game.app_name}.repair')
|
||||||
|
resume_file = os.path.join(self.lgd.get_tmp_path(), filename)
|
||||||
|
force = False
|
||||||
|
elif not force:
|
||||||
|
filename = clean_filename(f'{game.app_name}.resume')
|
||||||
resume_file = os.path.join(self.lgd.get_tmp_path(), filename)
|
resume_file = os.path.join(self.lgd.get_tmp_path(), filename)
|
||||||
else:
|
else:
|
||||||
resume_file = None
|
resume_file = None
|
||||||
|
|
Loading…
Reference in a new issue