feat: support launching games with uplay protocol (#750)

This commit is contained in:
Paweł Lidwin 2026-04-28 11:55:18 +02:00 committed by GitHub
parent aeb61d4eea
commit 46f8445a06
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 16 deletions

View file

@ -579,9 +579,9 @@ class LegendaryCLI:
args.reset = args.download = args.disable_version_check = False
self.crossover_setup(args)
if args.origin:
return self._launch_origin(args)
if args.origin or args.ubisoft:
return self._launch_third_party(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:
# override installed game with base title
@ -719,15 +719,15 @@ class LegendaryCLI:
f'{k}={v}' for k, v in params.environment.items())))
subprocess.Popen(full_params, cwd=params.working_directory, env=full_env)
def _launch_origin(self, args):
def _launch_third_party(self, args):
game = self.core.get_game(app_name=args.app_name)
if not game:
logger.error(f'Unknown game "{args.app_name}", run "legendary list-games --third-party" '
f'to fetch data for Origin titles before using this command.')
return
if not game.is_origin_game:
logger.error(f'The specified game is not an Origin title.')
if not game.is_origin_game and not game.is_ubisoft_game:
logger.error(f'The specified game is not an Origin or Ubisoft title.')
return
# login is not required to launch the game, but linking does require it.
@ -737,9 +737,10 @@ class LegendaryCLI:
logger.error('Login failed, cannot continue!')
exit(1)
origin_uri = self.core.get_origin_uri(args.app_name, args.offline)
uri = self.core.get_origin_uri(args.app_name, args.offline) if game.is_origin_game else self.core.get_ubisoft_uri(args.app_name, args.offline)
if args.json:
return self._print_json(dict(uri=origin_uri), args.pretty_json)
self._print_json(dict(uri=uri), args.pretty_json)
return
if os.name == 'nt':
cmd, wait_for_exit = self.core.get_pre_launch_command(args.app_name)
@ -747,7 +748,7 @@ class LegendaryCLI:
if args.dry_run:
if cmd:
logger.info(f'Pre-launch command: {cmd}')
logger.info(f'Origin URI: {origin_uri}')
logger.info(f'URI: {uri}')
else:
if cmd:
try:
@ -759,8 +760,8 @@ class LegendaryCLI:
except Exception as e:
logger.warning(f'Pre-launch command failed: {e!r}')
logger.debug(f'Opening Origin URI: {origin_uri}')
webbrowser.open(origin_uri)
logger.debug(f'Opening URI: {uri}')
webbrowser.open(uri)
return
# on linux, require users to specify at least the wine binary and prefix in config or command line
@ -791,17 +792,17 @@ class LegendaryCLI:
logger.info(f'Using CrossOver Bottle "{bottle_name}"')
if not command:
logger.error(f'In order to launch Origin correctly you must specify a prefix and wine binary or '
logger.error(f'In order to launch correctly you must specify a prefix and wine binary or '
f'wrapper in the configuration file or command line. See the README for details.')
return
# You cannot launch a URI without start.exe
command.append('start')
command.append(origin_uri)
command.append(uri)
if args.dry_run:
if cmd:
logger.info(f'Pre-launch command: {cmd}')
logger.info(f'Origin launch command: {shlex.join(command)}')
logger.info(f'Launch command: {shlex.join(command)}')
else:
if cmd:
try:
@ -813,7 +814,7 @@ class LegendaryCLI:
except Exception as e:
logger.warning(f'Pre-launch command failed: {e!r}')
logger.debug(f'Opening Origin URI with command: {shlex.join(command)}')
logger.debug(f'Opening URI with command: {shlex.join(command)}')
subprocess.Popen(command, env=full_env)
def install_game(self, args):
@ -2841,6 +2842,8 @@ def main():
help='Override executable to launch (relative path)')
launch_parser.add_argument('--origin', dest='origin', action='store_true',
help='Launch Origin to activate or run the game.')
launch_parser.add_argument('--ubisoft', dest='ubisoft', action='store_true',
help='Launch Ubisoft to install and run the game.')
launch_parser.add_argument('--json', dest='json', action='store_true',
help='Print launch information as JSON and exit')

View file

@ -829,6 +829,27 @@ class LegendaryCore:
parameters.extend(parse_qsl(extra_args))
return f'link2ea://launchgame/{app_name}?{urlencode(parameters)}'
def get_ubisoft_uri(self, app_name: str, offline: bool = False) -> str:
token = '0' if offline else self.egs.get_game_token()['code']
user_name = self.lgd.userdata['displayName']
account_id = self.lgd.userdata['account_id']
parameters = [
('AUTH_PASSWORD', token),
('AUTH_TYPE', 'exchangecode'),
('epicusername', user_name),
('epicuserid', account_id),
('epiclocale', self.language_code),
]
game = self.get_game(app_name)
game_id = game.metadata.get('customAttributes', {}).get('GameID', {}).get('value') or app_name
extra_args = game.metadata.get('customAttributes', {}).get('AdditionalCommandline', {}).get('value')
if extra_args:
parameters.extend(parse_qsl(extra_args))
return f'uplay://launch/{game_id}?{urlencode(parameters)}'
def get_save_games(self, app_name: str = ''):
savegames = self.egs.get_user_cloud_saves(app_name, manifests=not not app_name)

View file

@ -85,6 +85,10 @@ class Game:
def is_dlc(self):
return self.metadata and 'mainGameItem' in self.metadata
@property
def is_ubisoft_game(self) -> bool:
return self.third_party_store and self.third_party_store.lower() in ['ubisoftconnect']
@property
def is_origin_game(self) -> bool:
return self.third_party_store and self.third_party_store.lower() in ['origin', 'the ea app']
@ -93,7 +97,7 @@ class Game:
def third_party_store(self) -> Optional[str]:
if not self.metadata:
return None
return self.metadata.get('customAttributes', {}).get('ThirdPartyManagedApp', {}).get('value', None)
return self.metadata.get('customAttributes', {}).get('ThirdPartyManagedApp', {}).get('value', None) or self.metadata.get('customAttributes', {}).get('ThirdPartyManagedProvider', {}).get('value', None)
@property
def partner_link_type(self):