[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)
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
if args.subparser_name == 'download':
logger.info('Setting --no-install flag since "download" command was used')
@ -472,6 +478,11 @@ class LegendaryCLI:
if not analysis.dl_size:
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):
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.')
os.remove(repair_file)
exit(0)
@ -557,6 +568,11 @@ class LegendaryCLI:
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):
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.')
os.remove(repair_file)
@ -670,6 +686,46 @@ class LegendaryCLI:
if print_command:
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():
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')
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')
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>')
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)')
sync_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='',
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
if os.name == 'nt':
@ -835,7 +895,7 @@ def main():
if args.subparser_name not in ('auth', 'list-games', 'list-installed', 'list-files',
'launch', 'download', 'uninstall', 'install', 'update',
'repair', 'list-saves', 'download-saves', 'sync-saves',
'verify-game'):
'verify-game', 'import-game'):
print(parser.format_help())
# Print the main help *and* the help for all of the subcommands. Thanks stackoverflow!
@ -883,6 +943,8 @@ def main():
cli.sync_saves(args)
elif args.subparser_name == 'verify-game':
cli.verify_game(args)
elif args.subparser_name == 'import-game':
cli.import_game(args)
except KeyboardInterrupt:
logger.info('Command was aborted via KeyboardInterrupt, cleaning up...')

View file

@ -754,6 +754,36 @@ class LegendaryCore:
igame.prereq_info['installed'] = True
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):
"""
Do cleanup, config saving, and exit.

View file

@ -78,7 +78,8 @@ class Game:
class InstalledGame:
def __init__(self, app_name='', title='', version='', manifest_path='', base_urls=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.title = title
self.version = version
@ -93,6 +94,7 @@ class InstalledGame:
self.requires_ot = requires_ot
self.is_dlc = is_dlc
self.save_path = save_path
self.needs_verification = needs_verification
@classmethod
def from_json(cls, json):
@ -112,6 +114,7 @@ class InstalledGame:
tmp.requires_ot = json.get('requires_ot', False)
tmp.is_dlc = json.get('is_dlc', False)
tmp.save_path = json.get('save_path', None)
tmp.needs_verification = json.get('needs_verification', None)
return tmp