[cli/core/lfs] Add support for mixing platforms

This commit is contained in:
derrod 2021-12-01 20:57:43 +01:00
parent ee3f9a3e07
commit f280d53496
3 changed files with 119 additions and 91 deletions

View file

@ -194,7 +194,7 @@ class LegendaryCLI:
logger.info('Getting game list... (this may take a while)') logger.info('Getting game list... (this may take a while)')
games, dlc_list = self.core.get_game_and_dlc_list( games, dlc_list = self.core.get_game_and_dlc_list(
platform_override=args.platform_override, skip_ue=not args.include_ue, platform=args.platform, skip_ue=not args.include_ue,
force_refresh=args.force_refresh force_refresh=args.force_refresh
) )
# Get information for games that cannot be installed through legendary (yet), such # Get information for games that cannot be installed through legendary (yet), such
@ -216,24 +216,24 @@ class LegendaryCLI:
writer = csv.writer(stdout, dialect='excel-tab' if args.tsv else 'excel', lineterminator='\n') writer = csv.writer(stdout, dialect='excel-tab' if args.tsv else 'excel', lineterminator='\n')
writer.writerow(['App name', 'App title', 'Version', 'Is DLC']) writer.writerow(['App name', 'App title', 'Version', 'Is DLC'])
for game in games: for game in games:
writer.writerow((game.app_name, game.app_title, game.app_version, False)) writer.writerow((game.app_name, game.app_title, game.app_version(args.platform), False))
for dlc in dlc_list[game.asset_info.catalog_item_id]: for dlc in dlc_list[game.asset_infos[args.platform].catalog_item_id]:
writer.writerow((dlc.app_name, dlc.app_title, dlc.app_version, True)) writer.writerow((dlc.app_name, dlc.app_title, dlc.app_version(args.platform), True))
return return
if args.json: if args.json:
_out = [] _out = []
for game in games: for game in games:
_j = vars(game) _j = vars(game)
_j['dlcs'] = [vars(dlc) for dlc in dlc_list[game.asset_info.catalog_item_id]] _j['dlcs'] = [vars(dlc) for dlc in dlc_list[game.asset_infos[args.platform].catalog_item_id]]
_out.append(_j) _out.append(_j)
return self._print_json(_out, args.pretty_json) return self._print_json(_out, args.pretty_json)
print('\nAvailable games:') print('\nAvailable games:')
for game in games: for game in games:
print(f' * {game.app_title.strip()} (App name: {game.app_name} | Version: {game.app_version})') print(f' * {game.app_title.strip()} (App name: {game.app_name} | Version: {game.app_version(args.platform)})')
if not game.app_version: if not game.app_version(args.platform):
_store = game.third_party_store _store = game.third_party_store
if _store == 'Origin': if _store == 'Origin':
print(f' - This game has to be activated, installed, and launched via Origin, use ' print(f' - This game has to be activated, installed, and launched via Origin, use '
@ -242,9 +242,9 @@ class LegendaryCLI:
print(f' ! This game has to be installed through third-party store ({_store}, not supported)') print(f' ! This game has to be installed through third-party store ({_store}, not supported)')
else: else:
print(f' ! No version information (unknown cause)') print(f' ! No version information (unknown cause)')
for dlc in dlc_list[game.asset_info.catalog_item_id]: for dlc in dlc_list[game.asset_infos[args.platform].catalog_item_id]:
print(f' + {dlc.app_title} (App name: {dlc.app_name} | Version: {dlc.app_version})') print(f' + {dlc.app_title} (App name: {dlc.app_name} | Version: {dlc.app_version(args.platform)})')
if not dlc.app_version: if not dlc.app_version(args.platform):
print(' ! This DLC is included in the game does not have to be downloaded separately') print(' ! This DLC is included in the game does not have to be downloaded separately')
print(f'\nTotal: {len(games)}') print(f'\nTotal: {len(games)}')
@ -263,7 +263,8 @@ class LegendaryCLI:
versions = dict() versions = dict()
for game in games: for game in games:
try: try:
versions[game.app_name] = self.core.get_asset(game.app_name).build_version print(f'{game.title} (App name: {game.app_name}, platform: {game.platform})')
versions[game.app_name] = self.core.get_asset(game.app_name, platform=game.platform).build_version
except ValueError: except ValueError:
logger.warning(f'Metadata for "{game.app_name}" is missing, the game may have been removed from ' logger.warning(f'Metadata for "{game.app_name}" is missing, the game may have been removed from '
f'your account or not be in legendary\'s database yet, try rerunning the command ' f'your account or not be in legendary\'s database yet, try rerunning the command '
@ -315,7 +316,7 @@ class LegendaryCLI:
print(f'\nTotal: {len(games)}') print(f'\nTotal: {len(games)}')
def list_files(self, args): def list_files(self, args):
if args.platform_override: if args.platform:
args.force_download = True args.force_download = True
if not args.override_manifest and not args.app_name: if not args.override_manifest and not args.app_name:
@ -340,7 +341,7 @@ class LegendaryCLI:
if not game: if not game:
logger.fatal(f'Could not fetch metadata for "{args.app_name}" (check spelling/account ownership)') logger.fatal(f'Could not fetch metadata for "{args.app_name}" (check spelling/account ownership)')
exit(1) exit(1)
manifest_data, _ = self.core.get_cdn_manifest(game, platform_override=args.platform_override) manifest_data, _ = self.core.get_cdn_manifest(game, platform=args.platform)
manifest = self.core.load_manifest(manifest_data) manifest = self.core.load_manifest(manifest_data)
files = sorted(manifest.file_manifest_list.elements, files = sorted(manifest.file_manifest_list.elements,
@ -678,6 +679,7 @@ class LegendaryCLI:
args.app_name = self._resolve_aliases(args.app_name) args.app_name = self._resolve_aliases(args.app_name)
if self.core.is_installed(args.app_name): if self.core.is_installed(args.app_name):
igame = self.core.get_installed_game(args.app_name) igame = self.core.get_installed_game(args.app_name)
args.platform = igame.platform
if igame.needs_verification and not args.repair_mode: if igame.needs_verification and not args.repair_mode:
logger.info('Game needs to be verified before updating, switching to repair mode...') logger.info('Game needs to be verified before updating, switching to repair mode...')
args.repair_mode = True args.repair_mode = True
@ -695,6 +697,10 @@ class LegendaryCLI:
logger.error('Login failed! Cannot continue with download process.') logger.error('Login failed! Cannot continue with download process.')
exit(1) exit(1)
# default to windows unless installed game or command line has overriden it
if not args.platform:
args.platform = 'Windows'
if args.file_prefix or args.file_exclude_prefix: if args.file_prefix or args.file_exclude_prefix:
args.no_install = True args.no_install = True
@ -703,9 +709,6 @@ class LegendaryCLI:
logger.error(f'Update requested for "{args.app_name}", but app not installed!') logger.error(f'Update requested for "{args.app_name}", but app not installed!')
exit(1) exit(1)
if args.platform_override:
args.no_install = True
game = self.core.get_game(args.app_name, update_meta=True) game = self.core.get_game(args.app_name, update_meta=True)
if not game: if not game:
@ -805,7 +808,7 @@ class LegendaryCLI:
override_manifest=args.override_manifest, override_manifest=args.override_manifest,
override_old_manifest=args.override_old_manifest, override_old_manifest=args.override_old_manifest,
override_base_url=args.override_base_url, override_base_url=args.override_base_url,
platform_override=args.platform_override, platform=args.platform,
file_prefix_filter=args.file_prefix, file_prefix_filter=args.file_prefix,
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,
@ -901,7 +904,8 @@ class LegendaryCLI:
if dlcs and not args.skip_dlcs: if dlcs and not args.skip_dlcs:
print('The following DLCs are available for this game:') print('The following DLCs are available for this game:')
for dlc in dlcs: for dlc in dlcs:
print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})') print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: '
f'{dlc.app_version(args.platform)})')
print('Manually installing DLCs works the same; just use the DLC app name instead.') print('Manually installing DLCs works the same; just use the DLC app name instead.')
install_dlcs = not args.skip_dlcs install_dlcs = not args.skip_dlcs
@ -1344,8 +1348,11 @@ class LegendaryCLI:
info_items = dict(game=list(), manifest=list(), install=list()) info_items = dict(game=list(), manifest=list(), install=list())
InfoItem = namedtuple('InfoItem', ['name', 'json_name', 'value', 'json_value']) InfoItem = namedtuple('InfoItem', ['name', 'json_name', 'value', 'json_value'])
game = self.core.get_game(app_name, update_meta=not args.offline) if self.core.is_installed(app_name):
if game and not self.core.asset_available(game): args.platform = self.core.get_installed_game(app_name).platform
game = self.core.get_game(app_name, update_meta=not args.offline, platform=args.platform)
if game and not self.core.asset_available(game, platform=args.platform):
logger.warning(f'Asset information for "{game.app_name}" is missing, the game may have been removed from ' logger.warning(f'Asset information for "{game.app_name}" is missing, the game may have been removed from '
f'your account or you may be logged in with a different account than the one used to build ' f'your account or you may be logged in with a different account than the one used to build '
f'legendary\'s metadata database.') f'legendary\'s metadata database.')
@ -1369,11 +1376,11 @@ class LegendaryCLI:
elif game: elif game:
entitlements = self.core.egs.get_user_entitlements() entitlements = self.core.egs.get_user_entitlements()
# get latest metadata and manifest # get latest metadata and manifest
if game.asset_info.catalog_item_id: if game.asset_infos[args.platform].catalog_item_id:
egl_meta = self.core.egs.get_game_info(game.asset_info.namespace, egl_meta = self.core.egs.get_game_info(game.asset_infos[args.platform].namespace,
game.asset_info.catalog_item_id) game.asset_infos[args.platform].catalog_item_id)
game.metadata = egl_meta game.metadata = egl_meta
manifest_data, _ = self.core.get_cdn_manifest(game) manifest_data, _ = self.core.get_cdn_manifest(game, args.platform)
else: else:
# Origin games do not have asset info, so fall back to info from metadata # Origin games do not have asset info, so fall back to info from metadata
egl_meta = self.core.egs.get_game_info(game.metadata['namespace'], egl_meta = self.core.egs.get_game_info(game.metadata['namespace'],
@ -1384,7 +1391,10 @@ class LegendaryCLI:
game_infos = info_items['game'] game_infos = info_items['game']
game_infos.append(InfoItem('App name', 'app_name', game.app_name, game.app_name)) game_infos.append(InfoItem('App name', 'app_name', game.app_name, game.app_name))
game_infos.append(InfoItem('Title', 'title', game.app_title, game.app_title)) game_infos.append(InfoItem('Title', 'title', game.app_title, game.app_title))
game_infos.append(InfoItem('Latest version', 'version', game.app_version, game.app_version)) game_infos.append(InfoItem('Latest version', 'version', game.app_version(args.platform),
game.app_version(args.platform)))
all_versions = {k: v.build_version for k,v in game.asset_infos.items()}
game_infos.append(InfoItem('All versions', 'platform_versions', all_versions, all_versions))
game_infos.append(InfoItem('Cloud saves supported', 'cloud_saves_supported', game_infos.append(InfoItem('Cloud saves supported', 'cloud_saves_supported',
game.supports_cloud_saves, game.supports_cloud_saves)) game.supports_cloud_saves, game.supports_cloud_saves))
if game.supports_cloud_saves: if game.supports_cloud_saves:
@ -1447,6 +1457,7 @@ class LegendaryCLI:
igame = self.core.get_installed_game(app_name) igame = self.core.get_installed_game(app_name)
if igame: if igame:
installation_info = info_items['install'] installation_info = info_items['install']
installation_info.append(InfoItem('Platform', 'platform', igame.platform, igame.platform))
installation_info.append(InfoItem('Version', 'version', igame.version, igame.version)) installation_info.append(InfoItem('Version', 'version', igame.version, igame.version))
disk_size_human = f'{igame.install_size / 1024 / 1024 / 1024:.02f} GiB' disk_size_human = f'{igame.install_size / 1024 / 1024 / 1024:.02f} GiB'
installation_info.append(InfoItem('Install size', 'disk_size', disk_size_human, installation_info.append(InfoItem('Install size', 'disk_size', disk_size_human,
@ -1586,6 +1597,10 @@ class LegendaryCLI:
print(f'- {item.name}:') print(f'- {item.name}:')
for list_item in item.value: for list_item in item.value:
print(' + ', list_item) print(' + ', list_item)
elif isinstance(item.value, dict):
print(f'- {item.name}:')
for k, v in item.value.items():
print(' + ', k, ':', v)
else: else:
print(f'- {item.name}: {item.value}') print(f'- {item.name}: {item.value}')
@ -1809,8 +1824,8 @@ def main():
help='Only update, do not do anything if specified app is not installed') help='Only update, do not do anything if specified app is not installed')
install_parser.add_argument('--dlm-debug', dest='dlm_debug', action='store_true', install_parser.add_argument('--dlm-debug', dest='dlm_debug', action='store_true',
help='Set download manager and worker processes\' loglevel to debug') help='Set download manager and worker processes\' loglevel to debug')
install_parser.add_argument('--platform', dest='platform_override', action='store', metavar='<Platform>', install_parser.add_argument('--platform', dest='platform', action='store', metavar='<Platform>',
type=str, help='Platform override for download (also sets --no-install)') type=str, help='Platform override for download')
install_parser.add_argument('--prefix', dest='file_prefix', action='append', metavar='<prefix>', install_parser.add_argument('--prefix', dest='file_prefix', action='append', metavar='<prefix>',
help='Only fetch files whose path starts with <prefix> (case insensitive)') help='Only fetch files whose path starts with <prefix> (case insensitive)')
install_parser.add_argument('--exclude', dest='file_exclude_prefix', action='append', metavar='<prefix>', install_parser.add_argument('--exclude', dest='file_exclude_prefix', action='append', metavar='<prefix>',
@ -1891,7 +1906,7 @@ def main():
launch_parser.add_argument('--no-wine', dest='no_wine', help=argparse.SUPPRESS, launch_parser.add_argument('--no-wine', dest='no_wine', help=argparse.SUPPRESS,
action='store_true', default=True) action='store_true', default=True)
list_parser.add_argument('--platform', dest='platform_override', action='store', metavar='<Platform>', list_parser.add_argument('--platform', dest='platform', action='store', metavar='<Platform>', default='Windows',
type=str, help='Override platform that games are shown for (e.g. Win32/Mac)') type=str, help='Override platform that games are shown for (e.g. Win32/Mac)')
list_parser.add_argument('--include-ue', dest='include_ue', action='store_true', list_parser.add_argument('--include-ue', dest='include_ue', action='store_true',
help='Also include Unreal Engine content (Engine/Marketplace) in list') help='Also include Unreal Engine content (Engine/Marketplace) in list')
@ -1916,8 +1931,8 @@ def main():
list_files_parser.add_argument('--force-download', dest='force_download', action='store_true', list_files_parser.add_argument('--force-download', dest='force_download', action='store_true',
help='Always download instead of using on-disk manifest') help='Always download instead of using on-disk manifest')
list_files_parser.add_argument('--platform', dest='platform_override', action='store', metavar='<Platform>', list_files_parser.add_argument('--platform', dest='platform', action='store', metavar='<Platform>',
type=str, help='Platform override for download (disables install)') type=str, help='Platform override for download', default='Windows')
list_files_parser.add_argument('--manifest', dest='override_manifest', action='store', metavar='<uri>', list_files_parser.add_argument('--manifest', dest='override_manifest', action='store', metavar='<uri>',
help='Manifest URL or path to use instead of the CDN one') help='Manifest URL or path to use instead of the CDN one')
list_files_parser.add_argument('--csv', dest='csv', action='store_true', help='Output in CSV format') list_files_parser.add_argument('--csv', dest='csv', action='store_true', help='Output in CSV format')
@ -1982,6 +1997,8 @@ def main():
help='Only print info available offline') help='Only print info available offline')
info_parser.add_argument('--json', dest='json', action='store_true', info_parser.add_argument('--json', dest='json', action='store_true',
help='Output information in JSON format') help='Output information in JSON format')
info_parser.add_argument('--platform', dest='platform', action='store', metavar='<Platform>',
type=str, help='Platform override for download', default='Windows')
args, extra = parser.parse_known_args() args, extra = parser.parse_known_args()

View file

@ -312,90 +312,93 @@ class LegendaryCore:
if _aliases_enabled and (force or not self.lgd.aliases): if _aliases_enabled and (force or not self.lgd.aliases):
self.lgd.generate_aliases() self.lgd.generate_aliases()
def get_assets(self, update_assets=False, platform_override=None) -> List[GameAsset]: def get_assets(self, update_assets=False, platform='Windows') -> List[GameAsset]:
# do not save and always fetch list when platform is overridden # do not save and always fetch list when platform is overridden
if platform_override:
return [GameAsset.from_egs_json(a) for a in
self.egs.get_game_assets(platform=platform_override)]
if not self.lgd.assets or update_assets: if not self.lgd.assets or update_assets:
# if not logged in, return empty list # if not logged in, return empty list
if not self.egs.user: if not self.egs.user:
return [] return []
self.lgd.assets = [GameAsset.from_egs_json(a) for a in self.egs.get_game_assets()]
return self.lgd.assets if self.lgd.assets:
assets = self.lgd.assets.copy()
else:
assets = dict()
def get_asset(self, app_name, update=False) -> GameAsset: assets.update({
if update: platform: [
self.get_assets(update_assets=True) GameAsset.from_egs_json(a) for a in
self.egs.get_game_assets(platform=platform)
]
})
self.lgd.assets = assets
return self.lgd.assets[platform]
def get_asset(self, app_name, platform='Windows', update=False) -> GameAsset:
if update or platform not in self.lgd.assets:
self.get_assets(update_assets=True, platform=platform)
try: try:
return next(i for i in self.lgd.assets if i.app_name == app_name) return next(i for i in self.lgd.assets[platform] if i.app_name == app_name)
except StopIteration: except StopIteration:
raise ValueError raise ValueError
def asset_valid(self, app_name) -> bool: def asset_valid(self, app_name) -> bool:
return any(i.app_name == app_name for i in self.lgd.assets) # EGL sync is only supported for Windows titles so this is fine
return any(i.app_name == app_name for i in self.lgd.assets['Windows'])
def asset_available(self, game: Game) -> bool: def asset_available(self, game: Game, platform='Windows') -> bool:
# Just say yes for Origin titles # Just say yes for Origin titles
if game.third_party_store: if game.third_party_store:
return True return True
try: try:
asset = self.get_asset(game.app_name) asset = self.get_asset(game.app_name, platform=platform)
return asset is not None return asset is not None
except ValueError: except ValueError:
return False return False
def get_game(self, app_name, update_meta=False) -> Game: def get_game(self, app_name, update_meta=False, platform='Windows') -> Game:
if update_meta: if update_meta:
self.get_game_list(True) self.get_game_list(True, platform=platform)
return self.lgd.get_game_meta(app_name) return self.lgd.get_game_meta(app_name)
def get_game_list(self, update_assets=True) -> List[Game]: def get_game_list(self, update_assets=True, platform='Windows') -> List[Game]:
return self.get_game_and_dlc_list(update_assets=update_assets)[0] return self.get_game_and_dlc_list(update_assets=update_assets, platform=platform)[0]
def get_game_and_dlc_list(self, update_assets=True, platform_override=None, def get_game_and_dlc_list(self, update_assets=True, platform='Windows',
force_refresh=False, skip_ue=True) -> (List[Game], Dict[str, List[Game]]): force_refresh=False, skip_ue=True) -> (List[Game], Dict[str, List[Game]]):
_ret = [] _ret = []
_dlc = defaultdict(list) _dlc = defaultdict(list)
meta_updated = False meta_updated = False
for ga in self.get_assets(update_assets=update_assets, for ga in self.get_assets(update_assets=update_assets, platform=platform):
platform_override=platform_override):
if ga.namespace == 'ue' and skip_ue: if ga.namespace == 'ue' and skip_ue:
continue continue
game = self.lgd.get_game_meta(ga.app_name) game = self.lgd.get_game_meta(ga.app_name)
if update_assets and (not game or force_refresh or if update_assets and (not game or force_refresh or
(game and game.app_version != ga.build_version and not platform_override)): (game and game.app_version(platform) != ga.build_version)):
if game and game.app_version != ga.build_version and not platform_override: if game and game.app_version(platform) != ga.build_version:
self.log.info(f'Updating meta for {game.app_name} due to build version mismatch') self.log.info(f'Updating meta for {game.app_name} due to build version mismatch')
eg_meta = self.egs.get_game_info(ga.namespace, ga.catalog_item_id) eg_meta = self.egs.get_game_info(ga.namespace, ga.catalog_item_id)
game = Game(app_name=ga.app_name, app_version=ga.build_version, game.asset_infos[platform] = ga
app_title=eg_meta['title'], asset_info=ga, metadata=eg_meta) game = Game(app_name=ga.app_name, app_title=eg_meta['title'], metadata=eg_meta,
asset_infos=game.asset_infos)
if not platform_override: meta_updated = True
meta_updated = True self.lgd.set_game_meta(game.app_name, game)
self.lgd.set_game_meta(game.app_name, game)
# replace asset info with the platform specific one if override is used
if platform_override:
game.app_version = ga.build_version
game.asset_info = ga
if game.is_dlc: if game.is_dlc:
_dlc[game.metadata['mainGameItem']['id']].append(game) _dlc[game.metadata['mainGameItem']['id']].append(game)
elif not any(i['path'] == 'mods' for i in game.metadata.get('categories', [])): elif not any(i['path'] == 'mods' for i in game.metadata.get('categories', [])):
_ret.append(game) _ret.append(game)
if not platform_override: self.update_aliases(force=meta_updated)
self.update_aliases(force=meta_updated) if meta_updated:
if meta_updated: self._prune_metadata()
self._prune_metadata()
return _ret, _dlc return _ret, _dlc
@ -440,8 +443,7 @@ class LegendaryCore:
game = self.lgd.get_game_meta(libitem['appName']) game = self.lgd.get_game_meta(libitem['appName'])
if not game or force_refresh: if not game or force_refresh:
eg_meta = self.egs.get_game_info(libitem['namespace'], libitem['catalogItemId']) eg_meta = self.egs.get_game_info(libitem['namespace'], libitem['catalogItemId'])
game = Game(app_name=libitem['appName'], app_version=None, game = Game(app_name=libitem['appName'], app_title=eg_meta['title'], metadata=eg_meta)
app_title=eg_meta['title'], asset_info=None, metadata=eg_meta)
self.lgd.set_game_meta(game.app_name, game) self.lgd.set_game_meta(game.app_name, game)
if game.is_dlc: if game.is_dlc:
@ -453,7 +455,7 @@ class LegendaryCore:
self.update_aliases(force=True) self.update_aliases(force=True)
return _ret, _dlc return _ret, _dlc
def get_dlc_for_game(self, app_name): def get_dlc_for_game(self, app_name, platform='Windows'):
game = self.get_game(app_name) game = self.get_game(app_name)
if not game: if not game:
self.log.warning(f'Metadata for {app_name} is missing!') self.log.warning(f'Metadata for {app_name} is missing!')
@ -462,8 +464,8 @@ class LegendaryCore:
if game.is_dlc: # dlc shouldn't have DLC if game.is_dlc: # dlc shouldn't have DLC
return [] return []
_, dlcs = self.get_game_and_dlc_list(update_assets=False) _, dlcs = self.get_game_and_dlc_list(update_assets=False, platform=platform)
return dlcs[game.asset_info.catalog_item_id] return dlcs[game.asset_infos['Windows'].catalog_item_id]
def get_installed_list(self, include_dlc=False) -> List[InstalledGame]: def get_installed_list(self, include_dlc=False) -> List[InstalledGame]:
if self.egl_sync_enabled: if self.egl_sync_enabled:
@ -539,6 +541,11 @@ class LegendaryCore:
install = self.lgd.get_installed_game(app_name) install = self.lgd.get_installed_game(app_name)
game = self.lgd.get_game_meta(app_name) game = self.lgd.get_game_meta(app_name)
# Disable wine for non-Windows executables (e.g. native macOS)
if not install.platform.startswith('Win'):
disable_wine = True
wine_pfx = wine_bin = None
if executable_override or (executable_override := self.lgd.config.get(app_name, 'override_exe', fallback=None)): if executable_override or (executable_override := self.lgd.config.get(app_name, 'override_exe', fallback=None)):
game_exe = executable_override.replace('\\', '/') game_exe = executable_override.replace('\\', '/')
exe_path = os.path.join(install.install_path, game_exe) exe_path = os.path.join(install.install_path, game_exe)
@ -584,10 +591,11 @@ class LegendaryCore:
if install.requires_ot and not offline: if install.requires_ot and not offline:
self.log.info('Getting ownership token.') self.log.info('Getting ownership token.')
ovt = self.egs.get_ownership_token(game.asset_info.namespace, ovt = self.egs.get_ownership_token(game.asset_infos['Windows'].namespace,
game.asset_info.catalog_item_id) game.asset_infos['Windows'].catalog_item_id)
ovt_path = os.path.join(self.lgd.get_tmp_path(), ovt_path = os.path.join(self.lgd.get_tmp_path(),
f'{game.asset_info.namespace}{game.asset_info.catalog_item_id}.ovt') f'{game.asset_infos["Windows"].namespace}'
f'{game.asset_infos["Windows"].catalog_item_id}.ovt')
with open(ovt_path, 'wb') as f: with open(ovt_path, 'wb') as f:
f.write(ovt) f.write(ovt)
params.egl_parameters.append(f'-epicovt={ovt_path}') params.egl_parameters.append(f'-epicovt={ovt_path}')
@ -975,10 +983,9 @@ class LegendaryCore:
old_bytes = self.lgd.load_manifest(app_name, igame.version) old_bytes = self.lgd.load_manifest(app_name, igame.version)
return old_bytes, igame.base_urls return old_bytes, igame.base_urls
def get_cdn_urls(self, game, platform_override=''): def get_cdn_urls(self, game, platform='Windows'):
platform = 'Windows' if not platform_override else platform_override m_api_r = self.egs.get_game_manifest(game.asset_infos[platform].namespace,
m_api_r = self.egs.get_game_manifest(game.asset_info.namespace, game.asset_infos[platform].catalog_item_id,
game.asset_info.catalog_item_id,
game.app_name, platform) game.app_name, platform)
# never seen this outside the launcher itself, but if it happens: PANIC! # never seen this outside the launcher itself, but if it happens: PANIC!
@ -1000,8 +1007,8 @@ class LegendaryCore:
return manifest_urls, base_urls return manifest_urls, base_urls
def get_cdn_manifest(self, game, platform_override=''): def get_cdn_manifest(self, game, platform='Windows'):
manifest_urls, base_urls = self.get_cdn_urls(game, platform_override) manifest_urls, base_urls = self.get_cdn_urls(game, platform)
self.log.debug(f'Downloading manifest from {manifest_urls[0]} ...') self.log.debug(f'Downloading manifest from {manifest_urls[0]} ...')
r = self.egs.unauth_session.get(manifest_urls[0]) r = self.egs.unauth_session.get(manifest_urls[0])
r.raise_for_status() r.raise_for_status()
@ -1036,7 +1043,7 @@ class LegendaryCore:
force: bool = False, disable_patching: bool = False, force: bool = False, disable_patching: bool = False,
game_folder: str = '', override_manifest: str = '', game_folder: str = '', override_manifest: str = '',
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: 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,
repair: bool = False, repair_use_latest: bool = False, repair: bool = False, repair_use_latest: bool = False,
@ -1069,7 +1076,7 @@ class LegendaryCore:
if _base_urls: if _base_urls:
base_urls = _base_urls base_urls = _base_urls
else: else:
new_manifest_data, base_urls = self.get_cdn_manifest(game, platform_override) new_manifest_data, base_urls = self.get_cdn_manifest(game, platform)
# overwrite base urls in metadata with current ones to avoid using old/dead CDNs # overwrite base urls in metadata with current ones to avoid using old/dead CDNs
game.base_urls = base_urls game.base_urls = base_urls
# save base urls to game metadata # save base urls to game metadata
@ -1203,6 +1210,8 @@ class LegendaryCore:
offline = game.metadata.get('customAttributes', {}).get('CanRunOffline', {}).get('value', 'true') offline = game.metadata.get('customAttributes', {}).get('CanRunOffline', {}).get('value', 'true')
ot = game.metadata.get('customAttributes', {}).get('OwnershipToken', {}).get('value', 'false') ot = game.metadata.get('customAttributes', {}).get('OwnershipToken', {}).get('value', 'false')
if file_install_tag is None:
file_install_tag = []
igame = InstalledGame(app_name=game.app_name, title=game.app_title, igame = InstalledGame(app_name=game.app_name, title=game.app_title,
version=new_manifest.meta.build_version, prereq_info=prereq, version=new_manifest.meta.build_version, prereq_info=prereq,
manifest_path=override_manifest, base_urls=base_urls, manifest_path=override_manifest, base_urls=base_urls,
@ -1210,7 +1219,8 @@ class LegendaryCore:
launch_parameters=new_manifest.meta.launch_command, launch_parameters=new_manifest.meta.launch_command,
can_run_offline=offline == 'true', requires_ot=ot == 'true', can_run_offline=offline == 'true', requires_ot=ot == 'true',
is_dlc=base_game is not None, install_size=anlres.install_size, is_dlc=base_game is not None, install_size=anlres.install_size,
egl_guid=egl_guid, install_tags=file_install_tag) egl_guid=egl_guid, install_tags=file_install_tag,
platform=platform)
return dlm, anlres, igame return dlm, anlres, igame
@ -1293,7 +1303,7 @@ class LegendaryCore:
return os.path.expanduser(self.lgd.config.get('Legendary', 'install_dir', fallback='~/legendary')) return os.path.expanduser(self.lgd.config.get('Legendary', 'install_dir', fallback='~/legendary'))
def install_game(self, installed_game: InstalledGame) -> dict: def install_game(self, installed_game: InstalledGame) -> dict:
if self.egl_sync_enabled and not installed_game.is_dlc: if self.egl_sync_enabled and not installed_game.is_dlc and installed_game.platform.startswith('Win'):
if not installed_game.egl_guid: if not installed_game.egl_guid:
installed_game.egl_guid = str(uuid4()).replace('-', '').upper() installed_game.egl_guid = str(uuid4()).replace('-', '').upper()
prereq = self._install_game(installed_game) prereq = self._install_game(installed_game)
@ -1426,7 +1436,8 @@ class LegendaryCore:
def egl_get_exportable(self): def egl_get_exportable(self):
if not self.egl.manifests: if not self.egl.manifests:
self.egl.read_manifests() self.egl.read_manifests()
return [g for g in self.get_installed_list() if g.app_name not in self.egl.manifests] return [g for g in self.get_installed_list() if
g.app_name not in self.egl.manifests and g.platform.startswith('Win')]
def egl_import(self, app_name): def egl_import(self, app_name):
if not self.asset_valid(app_name): if not self.asset_valid(app_name):
@ -1506,8 +1517,8 @@ class LegendaryCore:
mf.write(manifest_data) mf.write(manifest_data)
mancpn = dict(FormatVersion=0, AppName=app_name, mancpn = dict(FormatVersion=0, AppName=app_name,
CatalogItemId=lgd_game.asset_info.catalog_item_id, CatalogItemId=lgd_game.asset_infos['Windows'].catalog_item_id,
CatalogNamespace=lgd_game.asset_info.namespace) CatalogNamespace=lgd_game.asset_infos['Windows'].namespace)
with open(os.path.join(egstore_folder, f'{egl_game.installation_guid}.mancpn', ), 'w') as mcpnf: with open(os.path.join(egstore_folder, f'{egl_game.installation_guid}.mancpn', ), 'w') as mcpnf:
json.dump(mancpn, mcpnf, indent=4, sort_keys=True) json.dump(mancpn, mcpnf, indent=4, sort_keys=True)

View file

@ -177,8 +177,8 @@ class LGDLFS:
def assets(self): def assets(self):
if self._assets is None: if self._assets is None:
try: try:
self._assets = [GameAsset.from_json(a) for a in tmp = json.load(open(os.path.join(self.path, 'assets.json')))
json.load(open(os.path.join(self.path, 'assets.json')))] self._assets = {k: [GameAsset.from_json(j) for j in v] for k, v in tmp.items()}
except Exception as e: except Exception as e:
self.log.debug(f'Failed to load assets data: {e!r}') self.log.debug(f'Failed to load assets data: {e!r}')
return None return None
@ -191,7 +191,7 @@ class LGDLFS:
raise ValueError('Assets is none!') raise ValueError('Assets is none!')
self._assets = assets self._assets = assets
json.dump([a.__dict__ for a in self._assets], json.dump({platform: [a.__dict__ for a in assets] for platform, assets in self._assets.items()},
open(os.path.join(self.path, 'assets.json'), 'w'), open(os.path.join(self.path, 'assets.json'), 'w'),
indent=2, sort_keys=True) indent=2, sort_keys=True)