Skip to content

Commit

Permalink
feat: support deployment-id (#6)
Browse files Browse the repository at this point in the history
* feat: support deployment-id

* improv: handle the case when the game's sidecar is added

* improv: add sidecar_config to info command

* tech: use json dump instead

---------

Co-authored-by: Mathis Dröge <[email protected]>
  • Loading branch information
imLinguin and CommandMC authored Aug 13, 2024
1 parent 4f5798a commit 87434ff
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 15 deletions.
26 changes: 21 additions & 5 deletions legendary/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ def list_files(self, args):
if not game:
logger.fatal(f'Could not fetch metadata for "{args.app_name}" (check spelling/account ownership)')
exit(1)
manifest_data, _ = self.core.get_cdn_manifest(game, platform=args.platform)
manifest_data, _, _ = self.core.get_cdn_manifest(game, platform=args.platform)

manifest = self.core.load_manifest(manifest_data)
files = sorted(manifest.file_manifest_list.elements,
Expand Down Expand Up @@ -580,7 +580,8 @@ def launch_game(self, args, extra):
return self._launch_origin(args)

igame = self.core.get_installed_game(app_name)
if (not igame or not igame.executable) and (game := self.core.get_game(app_name)) is not None:
game = self.core.get_game(app_name)
if (not igame or not igame.executable) and game is not None:
# override installed game with base title
if game.is_launchable_addon:
addon_app_name = app_name
Expand Down Expand Up @@ -623,6 +624,18 @@ def launch_game(self, args, extra):
if latest.build_version != igame.version:
logger.error('Game is out of date, please update or launch with update check skipping!')
exit(1)

try:
game_sidecar = igame.sidecar or dict()
if game_sidecar.get('rvn', 0) != latest.sidecar_rvn:
logger.info('Updating sidecar conifg...')
_, _, _, new_sidecar = self.core.get_cdn_urls(game, igame.platform)
igame.sidecar = new_sidecar
self.core.lgd.set_installed_game(app_name, igame)
self.core.egl_export(app_name)
except Exception as err:
logger.error(f'Failed to update sidecar - {err}')


params = self.core.get_launch_parameters(app_name=app_name, offline=args.offline,
extra_args=extra, user=args.user_name_override,
Expand Down Expand Up @@ -1232,7 +1245,7 @@ def verify_game(self, args, print_command=True, repair_mode=False, repair_online

logger.warning('No manifest could be loaded, the file may be missing. Downloading the latest manifest.')
game = self.core.get_game(args.app_name, platform=igame.platform)
manifest_data, _ = self.core.get_cdn_manifest(game, igame.platform)
manifest_data, _, _ = self.core.get_cdn_manifest(game, igame.platform)
else:
logger.critical(f'Manifest appears to be missing! To repair, run "legendary repair '
f'{args.app_name} --repair-and-update", this will however redownload all files '
Expand Down Expand Up @@ -1644,10 +1657,11 @@ def info(self, args):

manifest_data = None
entitlements = None
sidecar = None
# load installed manifest or URI
if args.offline or manifest_uri:
if app_name and self.core.is_installed(app_name):
manifest_data, _ = self.core.get_installed_manifest(app_name)
manifest_data, _, sidecar = self.core.get_installed_manifest(app_name)
elif manifest_uri and manifest_uri.startswith('http'):
r = self.core.egs.unauth_session.get(manifest_uri)
r.raise_for_status()
Expand All @@ -1663,7 +1677,7 @@ def info(self, args):
game.metadata = egl_meta
# Get manifest if asset exists for current platform
if args.platform in game.asset_infos:
manifest_data, _ = self.core.get_cdn_manifest(game, args.platform)
manifest_data, _, sidecar = self.core.get_cdn_manifest(game, args.platform)

if game:
game_infos = info_items['game']
Expand Down Expand Up @@ -1893,6 +1907,8 @@ def info(self, args):
manifest_info.append(InfoItem('Download size by install tag', 'tag_download_size',
tag_download_size_human or 'N/A', tag_download_size))

manifest_info.append(InfoItem('Sidecar Config', 'sidecar_config', sidecar, sidecar))

if not args.json:
def print_info_item(item: InfoItem):
if item.value is None:
Expand Down
45 changes: 35 additions & 10 deletions legendary/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,15 @@ def get_launch_parameters(self, app_name: str, offline: bool = False,
f'-epicsandboxid={game.namespace}'
])

if install.sidecar and 'config' in install.sidecar:
try:
config = json.loads(install.sidecar['config'])
dep_id = config.get('deploymentId')
if dep_id:
params.egl_parameters.append(f'-epicdeploymentid={dep_id}')
except Exception:
self.log.warning("Failed to parse sidecar config")

if extra_args:
params.user_parameters.extend(extra_args)

Expand Down Expand Up @@ -1220,7 +1229,7 @@ def load_manifest(data: bytes) -> Manifest:
def get_installed_manifest(self, app_name):
igame = self._get_installed_game(app_name)
old_bytes = self.lgd.load_manifest(app_name, igame.version, igame.platform)
return old_bytes, igame.base_urls
return old_bytes, igame.base_urls, igame.sidecar

def get_cdn_urls(self, game, platform='Windows'):
m_api_r = self.egs.get_game_manifest(game.namespace, game.catalog_item_id,
Expand All @@ -1244,10 +1253,12 @@ def get_cdn_urls(self, game, platform='Windows'):
else:
manifest_urls.append(manifest['uri'])

return manifest_urls, base_urls, manifest_hash
sidecar = m_api_r['elements'][0].get('sidecar')

return manifest_urls, base_urls, manifest_hash, sidecar

def get_cdn_manifest(self, game, platform='Windows', disable_https=False):
manifest_urls, base_urls, manifest_hash = self.get_cdn_urls(game, platform)
manifest_urls, base_urls, manifest_hash, sidecar = self.get_cdn_urls(game, platform)
if not manifest_urls:
raise ValueError('No manifest URLs returned by API')

Expand Down Expand Up @@ -1275,7 +1286,7 @@ def get_cdn_manifest(self, game, platform='Windows', disable_https=False):
if sha1(manifest_bytes).hexdigest() != manifest_hash:
raise ValueError('Manifest sha hash mismatch!')

return manifest_bytes, base_urls
return manifest_bytes, base_urls, sidecar

def get_uri_manifest(self, uri):
if uri.startswith('http'):
Expand Down Expand Up @@ -1312,14 +1323,15 @@ def prepare_download(self, game: Game, base_game: Game = None, base_path: str =
disable_https: bool = False, bind_ip: str = None) -> (DLManager, AnalysisResult, ManifestMeta):
# load old manifest
old_manifest = None
sidecar = None

# load old manifest if we have one
if override_old_manifest:
self.log.info(f'Overriding old manifest with "{override_old_manifest}"')
old_bytes, _ = self.get_uri_manifest(override_old_manifest)
old_manifest = self.load_manifest(old_bytes)
elif not disable_patching and not force and self.is_installed(game.app_name):
old_bytes, _base_urls = self.get_installed_manifest(game.app_name)
old_bytes, _base_urls, sidecar = self.get_installed_manifest(game.app_name)
if _base_urls and not game.base_urls:
game.base_urls = _base_urls

Expand All @@ -1341,7 +1353,7 @@ def prepare_download(self, game: Game, base_game: Game = None, base_path: str =
if _base_urls:
base_urls = _base_urls
else:
new_manifest_data, base_urls = self.get_cdn_manifest(game, platform, disable_https=disable_https)
new_manifest_data, base_urls, sidecar = self.get_cdn_manifest(game, platform, disable_https=disable_https)
# overwrite base urls in metadata with current ones to avoid using old/dead CDNs
game.base_urls = base_urls
# save base urls to game metadata
Expand Down Expand Up @@ -1517,7 +1529,7 @@ def prepare_download(self, game: Game, base_game: Game = None, base_path: str =
can_run_offline=offline == 'true', requires_ot=ot == 'true',
is_dlc=base_game is not None, install_size=anlres.install_size,
egl_guid=egl_guid, install_tags=file_install_tag,
platform=platform, uninstaller=uninstaller)
platform=platform, uninstaller=uninstaller, sidecar=sidecar)

return dlm, anlres, igame

Expand Down Expand Up @@ -1699,6 +1711,7 @@ def prereq_installed(self, app_name):
def import_game(self, game: Game, app_path: str, egl_guid='', platform='Windows') -> (Manifest, InstalledGame):
needs_verify = True
manifest_data = None
sidecar = None

# check if the game is from an EGL installation, load manifest if possible
if not game.is_dlc and os.path.exists(os.path.join(app_path, '.egstore')):
Expand Down Expand Up @@ -1734,7 +1747,7 @@ def import_game(self, game: Game, app_path: str, egl_guid='', platform='Windows'

if not manifest_data:
self.log.info(f'Downloading latest manifest for "{game.app_name}"')
manifest_data, base_urls = self.get_cdn_manifest(game)
manifest_data, base_urls, sidecar = self.get_cdn_manifest(game)
if not game.base_urls:
game.base_urls = base_urls
self.lgd.set_game_meta(game.app_name, game)
Expand All @@ -1760,7 +1773,7 @@ def import_game(self, game: Game, app_path: str, egl_guid='', platform='Windows'
executable=new_manifest.meta.launch_exe, can_run_offline=offline == 'true',
launch_parameters=new_manifest.meta.launch_command, requires_ot=ot == 'true',
needs_verification=needs_verify, install_size=install_size, egl_guid=egl_guid,
platform=platform)
platform=platform, sidecar=sidecar)

return new_manifest, igame

Expand Down Expand Up @@ -1860,6 +1873,18 @@ def egl_export(self, app_name):
with open(os.path.join(egstore_folder, f'{egl_game.installation_guid}.manifest', ), 'wb') as mf:
mf.write(manifest_data)

if lgd_igame.sidecar and 'config' in lgd_igame.sidecar:
# EGL seems to change keys to Pascal
sidecar_conf = lgd_igame.sidecar['config']
json_config = json.loads(sidecar_conf)
new_config = dict()
# Make config PascalCase
for key in json_config:
new_config[key.capitalize()] = json_config[key]

with open(os.path.join(egstore_folder, f'{lgd_game.app_name}appconfig.json', ), 'w') as ac:
json.dump(new_config, ac)

mancpn = dict(FormatVersion=0, AppName=app_name,
CatalogItemId=lgd_game.catalog_item_id,
CatalogNamespace=lgd_game.namespace)
Expand Down Expand Up @@ -2011,7 +2036,7 @@ def prepare_overlay_install(self, path=None):
if not self.logged_in:
self.egs.start_session(client_credentials=True)

_manifest, base_urls = self.get_cdn_manifest(EOSOverlayApp)
_manifest, base_urls, _ = self.get_cdn_manifest(EOSOverlayApp)
manifest = self.load_manifest(_manifest)

if igame := self.lgd.get_overlay_install_info():
Expand Down
5 changes: 5 additions & 0 deletions legendary/models/egl.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def __init__(self):
self.can_run_offline = None
self.is_incomplete_install = None
self.needs_validation = None
self.sidecar_config_revision = 0

self.remainder = dict()

Expand Down Expand Up @@ -98,6 +99,7 @@ def from_json(cls, json: dict):
tmp.can_run_offline = json.pop('bCanRunOffline', True)
tmp.is_incomplete_install = json.pop('bIsIncompleteInstall', False)
tmp.needs_validation = json.pop('bNeedsValidation', False)
tmp.sidecar_config_revision = json.pop('SidecarConfigRevision', 0)
tmp.remainder = json.copy()
return tmp

Expand Down Expand Up @@ -125,6 +127,7 @@ def to_json(self) -> dict:
out['bCanRunOffline'] = self.can_run_offline
out['bIsIncompleteInstall'] = self.is_incomplete_install
out['bNeedsValidation'] = self.needs_validation
out['SidecarConfigRevision'] = self.sidecar_config_revision
return out

@classmethod
Expand All @@ -151,6 +154,8 @@ def from_lgd_game(cls, game: Game, igame: InstalledGame):
tmp.can_run_offline = igame.can_run_offline
tmp.is_incomplete_install = False
tmp.needs_validation = igame.needs_verification
if igame.sidecar:
tmp.sidecar_config_revision = igame.sidecar.get('rvn', 0)
return tmp

def to_lgd_igame(self) -> InstalledGame:
Expand Down
5 changes: 5 additions & 0 deletions legendary/models/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class GameAsset:
label_name: str = ''
namespace: str = ''
metadata: Dict = field(default_factory=dict)
sidecar_rvn: int = 0

@classmethod
def from_egs_json(cls, json):
Expand All @@ -29,6 +30,7 @@ def from_egs_json(cls, json):
tmp.label_name = json.get('labelName', '')
tmp.namespace = json.get('namespace', '')
tmp.metadata = json.get('metadata', {})
tmp.sidecar_rvn = json.get('sidecarRvn', 0)
return tmp

@classmethod
Expand All @@ -41,6 +43,7 @@ def from_json(cls, json):
tmp.label_name = json.get('label_name', '')
tmp.namespace = json.get('namespace', '')
tmp.metadata = json.get('metadata', {})
tmp.sidecar_rvn = json.get('sidecarRvn', 0)
return tmp


Expand Down Expand Up @@ -168,6 +171,7 @@ class InstalledGame:
uninstaller: Optional[Dict] = None
requires_ot: bool = False
save_path: Optional[str] = None
sidecar: Optional[dict] = None

@classmethod
def from_json(cls, json):
Expand All @@ -194,6 +198,7 @@ def from_json(cls, json):
tmp.install_size = json.get('install_size', 0)
tmp.egl_guid = json.get('egl_guid', '')
tmp.install_tags = json.get('install_tags', [])
tmp.sidecar = json.get('sidecar', None)
return tmp


Expand Down

0 comments on commit 87434ff

Please sign in to comment.