[cli/core/models] Refactor launch parameters and add --json option

Primarily intended to make it easier for third-party applications
(mainly Heroic) to handle launch options on their own by simply
taking the necessary information from legendary and ignoring
user-defined stuff.

Also useful for debugging.
This commit is contained in:
derrod 2021-09-28 04:04:15 +02:00
parent ee2432c443
commit 888d62a96d
3 changed files with 98 additions and 62 deletions

View file

@ -518,12 +518,12 @@ class LegendaryCLI:
logger.error('Game is out of date, please update or launch with update check skipping!')
exit(1)
params, cwd, env = self.core.get_launch_parameters(app_name=app_name, offline=args.offline,
extra_args=extra, user=args.user_name_override,
wine_bin=args.wine_bin, wine_pfx=args.wine_pfx,
language=args.language, wrapper=args.wrapper,
disable_wine=args.no_wine,
executable_override=args.executable_override)
params = self.core.get_launch_parameters(app_name=app_name, offline=args.offline,
extra_args=extra, user=args.user_name_override,
wine_bin=args.wine_bin, wine_pfx=args.wine_pfx,
language=args.language, wrapper=args.wrapper,
disable_wine=args.no_wine,
executable_override=args.executable_override)
if args.set_defaults:
self.core.lgd.config[app_name] = dict()
@ -546,19 +546,37 @@ class LegendaryCLI:
if args.wrapper:
self.core.lgd.config[app_name]['wrapper'] = args.wrapper
if args.json:
print(json.dumps(vars(params)))
return
full_params = list()
full_params.extend(params.launch_command)
full_params.append(os.path.join(params.game_directory, params.game_executable))
full_params.extend(params.game_parameters)
full_params.extend(params.egl_parameters)
full_params.extend(params.user_parameters)
env_overrides = []
if params.environment:
for env_var, env_value in params.environment.items():
if env_var in os.environ:
continue
env_overrides.append((env_var, env_value))
if args.dry_run:
logger.info(f'Not Launching {app_name} (dry run)')
logger.info(f'Launch parameters: {shlex.join(params)}')
logger.info(f'Working directory: {cwd}')
if env:
logger.info('Environment overrides:', env)
logger.info(f'Launch parameters: {shlex.join(full_params)}')
logger.info(f'Working directory: {params.working_directory}')
if env_overrides:
logger.info('Environment overrides: {}'.format(', '.join(f'{k}={v}' for k, v in env_overrides)))
else:
logger.info(f'Launching {app_name}...')
logger.debug(f'Launch parameters: {shlex.join(params)}')
logger.debug(f'Working directory: {cwd}')
if env:
logger.debug('Environment overrides:', env)
subprocess.Popen(params, cwd=cwd, env=env)
logger.debug(f'Launch parameters: {shlex.join(full_params)}')
logger.debug(f'Working directory: {params.working_directory}')
if env_overrides:
logger.debug('Environment overrides: {}'.format(', '.join(f'{k}={v}' for k, v in env_overrides)))
subprocess.Popen(full_params, cwd=params.working_directory, env=params.environment)
def launch_origin(self, args):
# login is not required to launch the game, but linking does require it.
@ -1325,6 +1343,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('--json', dest='json', action='store_true',
help='Print launch information as JSON and exit')
if os.name != 'nt':
launch_parser.add_argument('--wine', dest='wine_bin', action='store', metavar='<wine binary>',

View file

@ -438,10 +438,48 @@ class LegendaryCore:
wine_bin: str = None, wine_pfx: str = None,
language: str = None, wrapper: str = None,
disable_wine: bool = False,
executable_override: str = None) -> (list, str, dict):
executable_override: str = None) -> LaunchParameters:
install = self.lgd.get_installed_game(app_name)
game = self.lgd.get_game_meta(app_name)
if executable_override or (executable_override := self.lgd.config.get(app_name, 'override_exe', fallback=None)):
game_exe = executable_override.replace('\\', '/')
exe_path = os.path.join(install.install_path, game_exe)
if not os.path.exists(exe_path):
raise ValueError(f'Executable path is invalid: {exe_path}')
else:
game_exe = install.executable.replace('\\', '/').lstrip('/')
exe_path = os.path.join(install.install_path, game_exe)
working_dir = os.path.split(exe_path)[0]
params = LaunchParameters(game_executable=game_exe, game_directory=install.install_path,
working_directory=working_dir,
environment=self.get_app_environment(app_name, wine_pfx=wine_pfx))
if wrapper or (wrapper := self.lgd.config.get(app_name, 'wrapper',
fallback=self.lgd.config.get('default', 'wrapper',
fallback=None))):
params.launch_command.extend(shlex.split(wrapper))
if os.name != 'nt' and not disable_wine:
if not wine_bin:
# check if there's a default override
wine_bin = self.lgd.config.get('default', 'wine_executable', fallback='wine')
# check if there's a game specific override
wine_bin = self.lgd.config.get(app_name, 'wine_executable', fallback=wine_bin)
if not self.lgd.config.getboolean(app_name, 'no_wine',
fallback=self.lgd.config.get('default', 'no_wine', fallback=False)):
params.launch_command.append(wine_bin)
if install.launch_parameters:
try:
params.game_parameters.extend(shlex.split(install.launch_parameters, posix=False))
except ValueError as e:
self.log.warning(f'Parsing predefined launch parameters failed with: {e!r}, '
f'input: {install.launch_parameters}')
game_token = ''
if not offline:
self.log.info('Getting authentication token...')
@ -454,45 +492,7 @@ class LegendaryCore:
if user:
user_name = user
if executable_override or (executable_override := self.lgd.config.get(app_name, 'override_exe', fallback=None)):
game_exe = os.path.join(install.install_path,
executable_override.replace('\\', '/'))
if not os.path.exists(game_exe):
raise ValueError(f'Executable path is invalid: {game_exe}')
else:
game_exe = os.path.join(install.install_path,
install.executable.replace('\\', '/').lstrip('/'))
working_dir = os.path.split(game_exe)[0]
params = []
if wrapper or (wrapper := self.lgd.config.get(app_name, 'wrapper',
fallback=self.lgd.config.get('default', 'wrapper',
fallback=None))):
params.extend(shlex.split(wrapper))
if os.name != 'nt' and not disable_wine:
if not wine_bin:
# check if there's a default override
wine_bin = self.lgd.config.get('default', 'wine_executable', fallback='wine')
# check if there's a game specific override
wine_bin = self.lgd.config.get(app_name, 'wine_executable', fallback=wine_bin)
if not self.lgd.config.getboolean(app_name, 'no_wine',
fallback=self.lgd.config.get('default', 'no_wine', fallback=False)):
params.append(wine_bin)
params.append(game_exe)
if install.launch_parameters:
try:
params.extend(shlex.split(install.launch_parameters, posix=False))
except ValueError as e:
self.log.warning(f'Parsing predefined launch parameters failed with: {e!r}, '
f'input: {install.launch_parameters}')
params.extend([
params.egl_parameters.extend([
'-AUTH_LOGIN=unused',
f'-AUTH_PASSWORD={game_token}',
'-AUTH_TYPE=exchangecode',
@ -507,13 +507,13 @@ class LegendaryCore:
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.egl_parameters.append(f'-epicovt={ovt_path}')
language_code = self.lgd.config.get(app_name, 'language', fallback=language)
if not language_code: # fall back to system or config language
language_code = self.language_code
params.extend([
params.egl_parameters.extend([
'-EpicPortal',
f'-epicusername={user_name}',
f'-epicuserid={account_id}',
@ -521,13 +521,12 @@ class LegendaryCore:
])
if extra_args:
params.extend(extra_args)
params.user_parameters.extend(extra_args)
if config_args := self.lgd.config.get(app_name, 'start_params', fallback=None):
params.extend(shlex.split(config_args.strip()))
params.user_parameters.extend(shlex.split(config_args.strip()))
env = self.get_app_environment(app_name, wine_pfx=wine_pfx)
return params, working_dir, env
return params
def get_origin_uri(self, app_name: str, offline: bool = False) -> str:
if offline:

View file

@ -1,8 +1,8 @@
# coding: utf-8
from dataclasses import dataclass, field
from enum import Enum
class GameAsset:
def __init__(self):
self.app_name = ''
@ -144,3 +144,20 @@ class VerifyResult(Enum):
HASH_MISMATCH = 1
FILE_MISSING = 2
OTHER_ERROR = 3
@dataclass
class LaunchParameters:
# game-supplied parameters
game_parameters: list = field(default_factory=list)
game_executable: str = ''
game_directory: str = ''
# EGL parameters (auth, ovt, etc.)
egl_parameters: list = field(default_factory=list)
# command line before executable (WINE, gamemode, etc.)
launch_command: list = field(default_factory=list)
# working directory for launched process
working_directory: str = ''
# user and environment supplied options
user_parameters: list = field(default_factory=list)
environment: dict = field(default_factory=dict)