[cli/core/models] Add support for importing already installed games

Fixes #10 though will need further improvement.
This commit is contained in:
derrod 2020-05-20 14:06:55 +02:00
parent 0d6bcf5950
commit 38f5bbd934
3 changed files with 98 additions and 3 deletions

View file

@ -389,6 +389,12 @@ 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):
if self.core.is_installed(args.app_name):
igame = self.core.get_installed_game(args.app_name)
if igame.needs_verification and not args.repair_mode:
logger.info('Game needs to be verified before updating, switching to repair mode...')
args.repair_mode = True
repair_file = None repair_file = None
if args.subparser_name == 'download': if args.subparser_name == 'download':
logger.info('Setting --no-install flag since "download" command was used') logger.info('Setting --no-install flag since "download" command was used')
@ -472,6 +478,11 @@ class LegendaryCLI:
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): if args.repair_mode and os.path.exists(repair_file):
igame = self.core.get_installed_game(game.app_name)
if igame.needs_verification:
igame.needs_verification = False
self.core.install_game(igame)
logger.debug('Removing repair file.') logger.debug('Removing repair file.')
os.remove(repair_file) os.remove(repair_file)
exit(0) exit(0)
@ -557,6 +568,11 @@ class LegendaryCLI:
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): if args.repair_mode and os.path.exists(repair_file):
igame = self.core.get_installed_game(game.app_name)
if igame.needs_verification:
igame.needs_verification = False
self.core.install_game(igame)
logger.debug('Removing repair file.') logger.debug('Removing repair file.')
os.remove(repair_file) os.remove(repair_file)
@ -670,6 +686,46 @@ class LegendaryCLI:
if print_command: if print_command:
logger.info(f'Run "legendary repair {args.app_name}" to repair your game installation.') logger.info(f'Run "legendary repair {args.app_name}" to repair your game installation.')
def import_game(self, args):
if not os.path.exists(args.app_path):
logger.error(f'Specified path "{args.app_path}" does not exist!')
exit(1)
if self.core.is_installed(args.app_name):
logger.error('Game is already installed!')
exit(0)
if not self.core.login():
logger.error('Log in failed!')
exit(1)
# do some basic checks
game = self.core.get_game(args.app_name, update_meta=True)
if not game:
logger.fatal(f'Did not find game "{args.app_name}" on account.')
exit(1)
# todo: if there is an Epic Games Launcher manifest in the install path use that instead
# get everything needed for import from core, then run additional checks.
manifest, igame = self.core.import_game(game, args.app_path)
# check if most files at least exist or if user might have specified the wrong directory
total = len(manifest.file_manifest_list.elements)
found = sum(os.path.exists(os.path.join(args.app_path, f.filename))
for f in manifest.file_manifest_list.elements)
if found != total:
ratio = found / total
if ratio < 0.95:
logger.fatal(f'{total-found}/{total} files are missing, cannot import.')
exit(1)
logger.warning('Some files are missing from the game installation, this may be due to newer updates.')
else:
logger.info('Game install appears to be complete.')
self.core.install_game(igame)
logger.info(f'NOTE: The game installation will have to be verified before it can be updated with legendary. '
f'Run "legendary repair {args.app_name}" to do so.')
logger.info('Game has been imported.')
def main(): def main():
parser = argparse.ArgumentParser(description=f'Legendary v{__version__} - "{__codename__}"') parser = argparse.ArgumentParser(description=f'Legendary v{__version__} - "{__codename__}"')
@ -697,6 +753,7 @@ def main():
download_saves_parser = subparsers.add_parser('download-saves', help='Download all cloud saves') download_saves_parser = subparsers.add_parser('download-saves', help='Download all cloud saves')
sync_saves_parser = subparsers.add_parser('sync-saves', help='Sync cloud saves') sync_saves_parser = subparsers.add_parser('sync-saves', help='Sync cloud saves')
verify_parser = subparsers.add_parser('verify-game', help='Verify a game\'s local files') verify_parser = subparsers.add_parser('verify-game', help='Verify a game\'s local files')
import_parser = subparsers.add_parser('import-game', help='Import an already installed game')
install_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>') install_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>')
uninstall_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>') uninstall_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>')
@ -709,7 +766,10 @@ def main():
help='Name of the app (optional)') help='Name of the app (optional)')
sync_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='', sync_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='',
help='Name of the app (optional)') help='Name of the app (optional)')
verify_parser.add_argument('app_name', help='Name of the app (optional)', metavar='<App Name>') verify_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>')
import_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>')
import_parser.add_argument('app_path', help='Path where the game is installed',
metavar='<Installation directory>')
# importing only works on Windows right now # importing only works on Windows right now
if os.name == 'nt': if os.name == 'nt':
@ -835,7 +895,7 @@ 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',
'repair', 'list-saves', 'download-saves', 'sync-saves', 'repair', 'list-saves', 'download-saves', 'sync-saves',
'verify-game'): 'verify-game', 'import-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!
@ -883,6 +943,8 @@ def main():
cli.sync_saves(args) cli.sync_saves(args)
elif args.subparser_name == 'verify-game': elif args.subparser_name == 'verify-game':
cli.verify_game(args) cli.verify_game(args)
elif args.subparser_name == 'import-game':
cli.import_game(args)
except KeyboardInterrupt: except KeyboardInterrupt:
logger.info('Command was aborted via KeyboardInterrupt, cleaning up...') logger.info('Command was aborted via KeyboardInterrupt, cleaning up...')

View file

@ -754,6 +754,36 @@ class LegendaryCore:
igame.prereq_info['installed'] = True igame.prereq_info['installed'] = True
self.lgd.set_installed_game(app_name, igame) self.lgd.set_installed_game(app_name, igame)
def import_game(self, game: Game, app_path: str) -> (Manifest, InstalledGame):
self.log.info(f'Downloading latest manifest for "{game.app_name}"')
manifest_data, base_urls = self.get_cdn_manifest(game)
if not game.base_urls:
game.base_urls = base_urls
self.lgd.set_game_meta(game.app_name, game)
# parse and save manifest to disk for verification step of import
new_manifest = self.load_manfiest(manifest_data)
self.lgd.save_manifest(game.app_name, manifest_data)
self.lgd.save_manifest(game.app_name, manifest_data,
version=new_manifest.meta.build_version)
prereq = None
if new_manifest.meta.prereq_ids:
prereq = dict(ids=new_manifest.meta.prereq_ids, name=new_manifest.meta.prereq_name,
path=new_manifest.meta.prereq_path, args=new_manifest.meta.prereq_args)
offline = game.metadata.get('customAttributes', {}).get('CanRunOffline', {}).get('value', 'true')
ot = game.metadata.get('customAttributes', {}).get('OwnershipToken', {}).get('value', 'false')
igame = InstalledGame(app_name=game.app_name, title=game.app_title, prereq_info=prereq, base_urls=base_urls,
install_path=app_path, version=new_manifest.meta.build_version, is_dlc=game.is_dlc,
executable=new_manifest.meta.launch_exe, can_run_offline=offline == 'true',
launch_parameters=new_manifest.meta.launch_command, requires_ot=ot == 'true',
needs_verification=True)
return new_manifest, igame
def exit(self): def exit(self):
""" """
Do cleanup, config saving, and exit. Do cleanup, config saving, and exit.

View file

@ -78,7 +78,8 @@ class Game:
class InstalledGame: class InstalledGame:
def __init__(self, app_name='', title='', version='', manifest_path='', base_urls=None, def __init__(self, app_name='', title='', version='', manifest_path='', base_urls=None,
install_path='', executable='', launch_parameters='', prereq_info=None, install_path='', executable='', launch_parameters='', prereq_info=None,
can_run_offline=False, requires_ot=False, is_dlc=False, save_path=None): can_run_offline=False, requires_ot=False, is_dlc=False, save_path=None,
needs_verification=False):
self.app_name = app_name self.app_name = app_name
self.title = title self.title = title
self.version = version self.version = version
@ -93,6 +94,7 @@ class InstalledGame:
self.requires_ot = requires_ot self.requires_ot = requires_ot
self.is_dlc = is_dlc self.is_dlc = is_dlc
self.save_path = save_path self.save_path = save_path
self.needs_verification = needs_verification
@classmethod @classmethod
def from_json(cls, json): def from_json(cls, json):
@ -112,6 +114,7 @@ class InstalledGame:
tmp.requires_ot = json.get('requires_ot', False) tmp.requires_ot = json.get('requires_ot', False)
tmp.is_dlc = json.get('is_dlc', False) tmp.is_dlc = json.get('is_dlc', False)
tmp.save_path = json.get('save_path', None) tmp.save_path = json.get('save_path', None)
tmp.needs_verification = json.get('needs_verification', None)
return tmp return tmp