From 5eb51dfd11265b169520cda0ba85679179aed8ca Mon Sep 17 00:00:00 2001 From: derrod Date: Fri, 17 Apr 2020 23:54:49 +0200 Subject: [PATCH] [api/core/models] Add support for "Ownership Token" Ownership verification tokens appear to be part of EPIC's DRM scheme, they're basically just a JSON file with a token in it that's downloaded from their API before launching. This fixes launching games such as Just Cause 4. --- legendary/api/egs.py | 9 +++++++++ legendary/cli.py | 2 +- legendary/core.py | 23 +++++++++++++++++++++-- legendary/models/game.py | 8 +++++++- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/legendary/api/egs.py b/legendary/api/egs.py index 79e4117..c904d4b 100644 --- a/legendary/api/egs.py +++ b/legendary/api/egs.py @@ -19,6 +19,7 @@ class EPCAPI: _launcher_host = 'launcher-public-service-prod06.ol.epicgames.com' _entitlements_host = 'entitlement-public-service-prod08.ol.epicgames.com' _catalog_host = 'catalog-public-service-prod06.ol.epicgames.com' + _ecommerce_host = 'ecommerceintegration-public-service-ecomprod02.ol.epicgames.com' def __init__(self): self.session = requests.session() @@ -71,6 +72,14 @@ class EPCAPI: r.raise_for_status() return r.json() + def get_ownership_token(self, namespace, catalog_item_id): + user_id = self.user.get('account_id') + r = self.session.post(f'https://{self._ecommerce_host}/ecommerceintegration/api/public/' + f'platforms/EPIC/identities/{user_id}/ownershipToken', + data=dict(nsCatalogItemId=f'{namespace}:{catalog_item_id}')) + r.raise_for_status() + return r.content + def get_game_assets(self): r = self.session.get(f'https://{self._launcher_host}/launcher/api/public/assets/Windows', params=dict(label='Live')) diff --git a/legendary/cli.py b/legendary/cli.py index 54492da..01aaa48 100644 --- a/legendary/cli.py +++ b/legendary/cli.py @@ -320,5 +320,5 @@ def main(): if __name__ == '__main__': - multiprocessing.freeze_support() + multiprocessing.freeze_support() # required for pyinstaller main() diff --git a/legendary/core.py b/legendary/core.py index 0c708c0..f4bb3d1 100644 --- a/legendary/core.py +++ b/legendary/core.py @@ -162,11 +162,14 @@ class LegendaryCore: def get_launch_parameters(self, app_name: str, offline: bool = False, user: str = None, extra_args: list = None) -> (list, str, dict): install = self.lgd.get_installed_game(app_name) + game = self.lgd.get_game_meta(app_name) game_token = '' if not offline: self.log.info('Getting authentication token...') game_token = self.egs.get_game_token()['code'] + elif not install.can_run_offline: + self.log.warning('Game is not approved for offline use and may not work correctly.') user_name = self.lgd.userdata['displayName'] account_id = self.lgd.userdata['account_id'] @@ -191,7 +194,19 @@ class LegendaryCore: f'-AUTH_PASSWORD={game_token}', '-AUTH_TYPE=exchangecode', f'-epicapp={app_name}', - '-epicenv=Prod', + '-epicenv=Prod']) + + if install.requires_ot and not offline: + self.log.info('Getting ownership token.') + ovt = self.egs.get_ownership_token(game.asset_info.namespace, + game.asset_info.catalog_item_id) + ovt_path = os.path.join(self.lgd.get_tmp_path(), + f'{game.asset_info.namespace}{game.asset_info.catalog_item_id}.ovt') + with open(ovt_path, 'wb') as f: + f.write(ovt) + params.append(f'-epicovt={ovt_path}') + + params.extend([ '-EpicPortal', f'-epicusername={user_name}', f'-epicuserid={account_id}', @@ -333,10 +348,14 @@ class LegendaryCore: 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, version=game.app_version, prereq_info=prereq, manifest_path=override_manifest, base_urls=base_urls, install_path=install_path, executable=new_manifest.meta.launch_exe, - launch_parameters=new_manifest.meta.launch_command) + launch_parameters=new_manifest.meta.launch_command, + can_run_offline=offline == 'true', requires_ot=ot == 'true') return dlm, anlres, igame diff --git a/legendary/models/game.py b/legendary/models/game.py index 50cf425..f8a594d 100644 --- a/legendary/models/game.py +++ b/legendary/models/game.py @@ -68,7 +68,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): + install_path='', executable='', launch_parameters='', prereq_info=None, + can_run_offline=False, requires_ot=False): self.app_name = app_name self.title = title self.version = version @@ -79,6 +80,8 @@ class InstalledGame: self.executable = executable self.launch_parameters = launch_parameters self.prereq_info = prereq_info + self.can_run_offline = can_run_offline + self.requires_ot = requires_ot @classmethod def from_json(cls, json): @@ -93,4 +96,7 @@ class InstalledGame: tmp.executable = json.get('executable', '') tmp.launch_parameters = json.get('launch_parameters', '') tmp.prereq_info = json.get('prereq_info', None) + + tmp.can_run_offline = json.get('can_run_offline', True) + tmp.requires_ot = json.get('requires_ot', False) return tmp