[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.
This commit is contained in:
derrod 2020-04-17 23:54:49 +02:00
parent ce28ce2914
commit 5eb51dfd11
4 changed files with 38 additions and 4 deletions

View file

@ -19,6 +19,7 @@ class EPCAPI:
_launcher_host = 'launcher-public-service-prod06.ol.epicgames.com' _launcher_host = 'launcher-public-service-prod06.ol.epicgames.com'
_entitlements_host = 'entitlement-public-service-prod08.ol.epicgames.com' _entitlements_host = 'entitlement-public-service-prod08.ol.epicgames.com'
_catalog_host = 'catalog-public-service-prod06.ol.epicgames.com' _catalog_host = 'catalog-public-service-prod06.ol.epicgames.com'
_ecommerce_host = 'ecommerceintegration-public-service-ecomprod02.ol.epicgames.com'
def __init__(self): def __init__(self):
self.session = requests.session() self.session = requests.session()
@ -71,6 +72,14 @@ class EPCAPI:
r.raise_for_status() r.raise_for_status()
return r.json() 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): def get_game_assets(self):
r = self.session.get(f'https://{self._launcher_host}/launcher/api/public/assets/Windows', r = self.session.get(f'https://{self._launcher_host}/launcher/api/public/assets/Windows',
params=dict(label='Live')) params=dict(label='Live'))

View file

@ -320,5 +320,5 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
multiprocessing.freeze_support() multiprocessing.freeze_support() # required for pyinstaller
main() main()

View file

@ -162,11 +162,14 @@ class LegendaryCore:
def get_launch_parameters(self, app_name: str, offline: bool = False, def get_launch_parameters(self, app_name: str, offline: bool = False,
user: str = None, extra_args: list = None) -> (list, str, dict): user: str = None, extra_args: list = None) -> (list, str, dict):
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_token = '' game_token = ''
if not offline: if not offline:
self.log.info('Getting authentication token...') self.log.info('Getting authentication token...')
game_token = self.egs.get_game_token()['code'] 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'] user_name = self.lgd.userdata['displayName']
account_id = self.lgd.userdata['account_id'] account_id = self.lgd.userdata['account_id']
@ -191,7 +194,19 @@ class LegendaryCore:
f'-AUTH_PASSWORD={game_token}', f'-AUTH_PASSWORD={game_token}',
'-AUTH_TYPE=exchangecode', '-AUTH_TYPE=exchangecode',
f'-epicapp={app_name}', 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', '-EpicPortal',
f'-epicusername={user_name}', f'-epicusername={user_name}',
f'-epicuserid={account_id}', f'-epicuserid={account_id}',
@ -333,10 +348,14 @@ class LegendaryCore:
prereq = dict(ids=new_manifest.meta.prereq_ids, name=new_manifest.meta.prereq_name, 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) 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, 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, prereq_info=prereq, manifest_path=override_manifest, base_urls=base_urls,
install_path=install_path, executable=new_manifest.meta.launch_exe, 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 return dlm, anlres, igame

View file

@ -68,7 +68,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):
self.app_name = app_name self.app_name = app_name
self.title = title self.title = title
self.version = version self.version = version
@ -79,6 +80,8 @@ class InstalledGame:
self.executable = executable self.executable = executable
self.launch_parameters = launch_parameters self.launch_parameters = launch_parameters
self.prereq_info = prereq_info self.prereq_info = prereq_info
self.can_run_offline = can_run_offline
self.requires_ot = requires_ot
@classmethod @classmethod
def from_json(cls, json): def from_json(cls, json):
@ -93,4 +96,7 @@ class InstalledGame:
tmp.executable = json.get('executable', '') tmp.executable = json.get('executable', '')
tmp.launch_parameters = json.get('launch_parameters', '') tmp.launch_parameters = json.get('launch_parameters', '')
tmp.prereq_info = json.get('prereq_info', None) 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 return tmp