Skip to content

Commit 80f2b32

Browse files
Merge pull request #727 from loathingKernel/develop
Add option to use EpicGamesLauncher.exe shim for Rockstar Launcher
2 parents b586e5b + dc71855 commit 80f2b32

12 files changed

Lines changed: 145 additions & 87 deletions

File tree

rare/commands/launcher/lgd_helper.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,19 +109,18 @@ def get_game_params(rgame: RareGameSlim, init: InitParams, launch: LaunchParams)
109109
full_params = []
110110
full_params.extend(params.launch_command)
111111
if 'LEGENDARY_WRAPPER_EXE' in params.environment:
112-
lgd_wrapper = params.environment.pop('LEGENDARY_WRAPPER_EXE').strip()
113-
if os.path.isfile(lgd_wrapper):
112+
lgd_wrapper = params.environment.pop('LEGENDARY_WRAPPER_EXE', '').strip()
113+
if lgd_wrapper and os.path.isfile(lgd_wrapper):
114114
full_params.append(lgd_wrapper)
115115
full_params.append(os.path.join(params.game_directory, params.game_executable))
116116
full_params.extend(params.game_parameters)
117117
full_params.extend(params.egl_parameters)
118118
full_params.extend(params.user_parameters)
119119

120-
exe, init, env = prepare_process(full_params, params.environment)
121-
122-
launch.executable = exe
123-
launch.arguments = init
124-
launch.environment = env
120+
_exe, _init, _env = prepare_process(full_params, params.environment)
121+
launch.executable = _exe
122+
launch.arguments = _init
123+
launch.environment = _env
125124
launch.working_directory = params.working_directory
126125

127126
return launch

rare/components/dialogs/install/dialog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ def __init__(self, settings: RareAppSettings, rgame: 'RareGame', options: Instal
9999

100100
self.install_dir_edit.setDisabled(rgame.is_installed or rgame.is_dlc)
101101
self.ui.install_dir_label.setDisabled(rgame.is_installed or rgame.is_dlc)
102-
self.ui.shortcut_label.setDisabled(rgame.is_installed or rgame.is_dlc)
103-
self.ui.shortcut_check.setDisabled(rgame.is_installed or rgame.is_dlc)
102+
self.ui.shortcut_label.setDisabled(rgame.is_installed or rgame.is_dlc or 'WINEUSERNAME' in os.environ)
103+
self.ui.shortcut_check.setDisabled(rgame.is_installed or rgame.is_dlc or 'WINEUSERNAME' in os.environ)
104104
self.ui.shortcut_check.setChecked(not rgame.is_installed and self.settings.get_value(app_settings.create_shortcut))
105105
self.ui.shortcut_check.checkStateChanged.connect(self._on_option_changed_no_reload)
106106

rare/components/tabs/library/details/game.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from rare.models.settings import RareAppSettings
1414
from rare.shared import RareCore
1515
from rare.utils import config_helper as config
16+
from rare.utils.wrapper_exe import wrapper_path
1617
from rare.widgets.indicator_edit import IndicatorReasonsCommon, PathEdit
1718

1819
logger = getLogger('LocalGameSettings')
@@ -34,13 +35,13 @@ def __init__(self, rcore: RareCore, parent=None):
3435
self.skip_update_combo.addItem(self.tr('Default'), None)
3536
self.skip_update_combo.addItem(self.tr('No'), 'false')
3637
self.skip_update_combo.addItem(self.tr('Yes'), 'true')
37-
self.skip_update_combo.currentIndexChanged.connect(self.__skip_update_changed)
38+
self.skip_update_combo.currentIndexChanged.connect(self._skip_update_changed)
3839

3940
self.offline_combo = QComboBox(self)
4041
self.offline_combo.addItem(self.tr('Default'), None)
4142
self.offline_combo.addItem(self.tr('No'), 'false')
4243
self.offline_combo.addItem(self.tr('Yes'), 'true')
43-
self.offline_combo.currentIndexChanged.connect(self.__offline_changed)
44+
self.offline_combo.currentIndexChanged.connect(self._offline_changed)
4445

4546
self.override_exe_name_filters: tuple[str, ...] = (
4647
'*.exe',
@@ -54,14 +55,14 @@ def __init__(self, rcore: RareCore, parent=None):
5455
file_mode=QFileDialog.FileMode.ExistingFile,
5556
name_filters=self.override_exe_name_filters,
5657
placeholder=self.tr('Relative path to the replacement executable'),
57-
edit_func=self.__override_exe_edit_callback,
58-
save_func=self.__override_exe_save_callback,
58+
edit_func=self._override_exe_edit_callback,
59+
save_func=self._override_exe_save_callback,
5960
parent=self,
6061
)
6162

6263
self.launch_params_edit = QLineEdit(self)
6364
self.launch_params_edit.setPlaceholderText(self.tr('Game specific command line arguments'))
64-
self.launch_params_edit.textChanged.connect(self.__launch_params_changed)
65+
self.launch_params_edit.textChanged.connect(self._launch_params_changed)
6566

6667
self.main_layout.insertRow(0, self.tr('Skip update check'), self.skip_update_combo)
6768
self.main_layout.insertRow(1, self.tr('Offline mode'), self.offline_combo)
@@ -94,11 +95,11 @@ def showEvent(self, a0: QShowEvent):
9495
return super().showEvent(a0)
9596

9697
@Slot(int)
97-
def __skip_update_changed(self, index):
98+
def _skip_update_changed(self, index):
9899
data = self.skip_update_combo.itemData(index, Qt.ItemDataRole.UserRole)
99100
config.adjust_option(self.app_name, 'skip_update_check', data)
100101

101-
def __override_exe_edit_callback(self, path: str) -> tuple[bool, str, int]:
102+
def _override_exe_edit_callback(self, path: str) -> tuple[bool, str, int]:
102103
if not path or self.igame is None:
103104
return True, path, IndicatorReasonsCommon.VALID
104105
if not os.path.isabs(path):
@@ -116,17 +117,25 @@ def __override_exe_edit_callback(self, path: str) -> tuple[bool, str, int]:
116117
path = os.path.relpath(path, self.igame.install_path)
117118
return True, path, IndicatorReasonsCommon.VALID
118119

119-
def __override_exe_save_callback(self, path: str):
120+
def _override_exe_save_callback(self, path: str):
120121
config.adjust_option(self.app_name, 'override_exe', path)
121122

122123
@Slot(int)
123-
def __offline_changed(self, index):
124+
def _offline_changed(self, index):
124125
data = self.skip_update_combo.itemData(index, Qt.ItemDataRole.UserRole)
125126
config.adjust_option(self.app_name, 'offline', data)
126127

127-
def __launch_params_changed(self, value) -> None:
128+
def _launch_params_changed(self, value) -> None:
128129
config.adjust_option(self.app_name, 'start_params', value)
129130

131+
@Slot(Qt.CheckState)
132+
def _lgd_wrapper_check_changed(self, state: Qt.CheckState):
133+
_wrapper = str(wrapper_path())
134+
config.adjust_envvar_with_global(
135+
self.app_name, 'LEGENDARY_WRAPPER_EXE', _wrapper if state == Qt.CheckState.Checked else ''
136+
)
137+
self.environ_changed.emit('LEGENDARY_WRAPPER_EXE')
138+
130139
def load_settings(self, rgame: RareGame):
131140
self.game = rgame.game
132141
self.igame = rgame.igame

rare/components/tabs/settings/game.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from PySide6.QtCore import Qt
1+
from PySide6.QtCore import Qt, Signal
22
from PySide6.QtGui import QHideEvent
33
from PySide6.QtWidgets import QVBoxLayout, QWidget
44

@@ -12,6 +12,9 @@
1212

1313

1414
class GameSettingsBase(QWidget, SideTabContents):
15+
# str: option key
16+
environ_changed: Signal = Signal(str)
17+
1518
def __init__(
1619
self,
1720
settings: RareAppSettings,
@@ -26,6 +29,7 @@ def __init__(
2629
self.app_name: str = 'default'
2730

2831
self.launch = launch_widget(rcore, self)
32+
self.launch.environ_changed.connect(self.environ_changed)
2933

3034
self.main_layout = QVBoxLayout(self)
3135
self.main_layout.addWidget(self.launch, stretch=0)

rare/components/tabs/settings/widgets/launch.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import shutil
44
from typing import TypeVar
55

6-
from PySide6.QtCore import Qt, Slot
6+
from PySide6.QtCore import Qt, Signal, Slot
77
from PySide6.QtGui import QShowEvent
88
from PySide6.QtWidgets import (
99
QCheckBox,
@@ -16,12 +16,16 @@
1616

1717
import rare.utils.config_helper as config
1818
from rare.shared import RareCore
19+
from rare.utils.wrapper_exe import wrapper_path
1920
from rare.widgets.indicator_edit import IndicatorReasonsCommon, PathEdit
2021

2122
from .wrappers import WrapperSettings
2223

2324

2425
class LaunchSettingsBase(QGroupBox):
26+
# str: option key
27+
environ_changed: Signal = Signal(str)
28+
2529
def __init__(self, rcore: RareCore, wrapper_widget: type[WrapperSettings], parent=None):
2630
super(LaunchSettingsBase, self).__init__(parent=parent)
2731
self.setTitle(self.tr('Launch'))
@@ -33,21 +37,21 @@ def __init__(self, rcore: RareCore, wrapper_widget: type[WrapperSettings], paren
3337
path='',
3438
placeholder=self.tr('Path to a script or program to run before the game'),
3539
file_mode=QFileDialog.FileMode.ExistingFile,
36-
edit_func=self.__prelaunch_cmd_edit_callback,
37-
save_func=self.__prelaunch_cmd_save_callback,
40+
edit_func=self._prelaunch_cmd_edit_callback,
41+
save_func=self._prelaunch_cmd_save_callback,
3842
)
3943

4044
self.prelaunch_args = QLineEdit('')
4145
self.prelaunch_args.setPlaceholderText(self.tr('Arguments to the script or program to run before the game'))
4246
self.prelaunch_args.setToolTip(self.prelaunch_args.placeholderText())
43-
self.prelaunch_args.textChanged.connect(self.__prelaunch_changed)
47+
self.prelaunch_args.textChanged.connect(self._prelaunch_changed)
4448

4549
font = self.font()
4650
font.setItalic(True)
4751

4852
self.prelaunch_check = QCheckBox(self.tr('Wait for the pre-launch command to finish before launching the game'))
4953
self.prelaunch_check.setFont(font)
50-
self.prelaunch_check.checkStateChanged.connect(self.__prelauch_check_changed)
54+
self.prelaunch_check.checkStateChanged.connect(self._prelauch_check_changed)
5155

5256
prelaunch_layout = QVBoxLayout()
5357
prelaunch_layout.addWidget(self.prelaunch_cmd)
@@ -61,6 +65,13 @@ def __init__(self, rcore: RareCore, wrapper_widget: type[WrapperSettings], paren
6165

6266
self.wrappers_widget = wrapper_widget(rcore, self)
6367

68+
self.lgd_wrapper = QCheckBox(
69+
self.tr('Use "EpicGamesLauncher.exe" shim for compatibility with third-party launchers (Rockstar etc.)')
70+
)
71+
self.lgd_wrapper.setFont(font)
72+
self.lgd_wrapper.checkStateChanged.connect(self._lgd_wrapper_check_changed)
73+
74+
self.main_layout.addRow(self.tr('Use fake EGL'), self.lgd_wrapper)
6475
self.main_layout.addRow(self.tr('Wrappers'), self.wrappers_widget)
6576
self.main_layout.addRow(self.tr('Pre-launch'), prelaunch_layout)
6677

@@ -77,31 +88,36 @@ def showEvent(self, a0: QShowEvent):
7788
self.prelaunch_check.setChecked(wait)
7889
self.prelaunch_check.setEnabled(bool(command))
7990

91+
wrapper = config.get_envvar_with_global(self.app_name, 'LEGENDARY_WRAPPER_EXE', fallback=False)
92+
93+
self.lgd_wrapper.setEnabled(wrapper_path().exists())
94+
self.lgd_wrapper.setChecked(wrapper_path().exists() and bool(wrapper) and os.path.exists(wrapper))
95+
8096
return super().showEvent(a0)
8197

8298
@Slot()
8399
def tool_enabled(self):
84100
self.wrappers_widget.update_state()
85101

86102
@staticmethod
87-
def __prelaunch_cmd_edit_callback(text: str) -> tuple[bool, str, int]:
103+
def _prelaunch_cmd_edit_callback(text: str) -> tuple[bool, str, int]:
88104
if not text.strip():
89105
return True, text, IndicatorReasonsCommon.UNDEFINED
90106
if not os.path.isfile(text) and not shutil.which(text):
91107
return False, text, IndicatorReasonsCommon.FILE_NOT_EXISTS
92108
else:
93109
return True, text, IndicatorReasonsCommon.VALID
94110

95-
def __prelaunch_cmd_save_callback(self, text):
111+
def _prelaunch_cmd_save_callback(self, text):
96112
self.prelaunch_check.setEnabled(bool(text))
97-
self.__prelaunch_changed()
113+
self._prelaunch_changed()
98114

99115
@Slot(Qt.CheckState)
100-
def __prelauch_check_changed(self, state: Qt.CheckState):
116+
def _prelauch_check_changed(self, state: Qt.CheckState):
101117
config.set_boolean(self.app_name, 'pre_launch_wait', state != Qt.CheckState.Unchecked)
102118

103119
@Slot()
104-
def __prelaunch_changed(self):
120+
def _prelaunch_changed(self):
105121
command = self.prelaunch_cmd.text().strip()
106122
if not command:
107123
config.adjust_option(self.app_name, 'pre_launch_command', command)
@@ -111,5 +127,11 @@ def __prelaunch_changed(self):
111127
arguments = self.prelaunch_args.text().strip()
112128
config.adjust_option(self.app_name, 'pre_launch_command', ' '.join([command, arguments]))
113129

130+
@Slot(Qt.CheckState)
131+
def _lgd_wrapper_check_changed(self, state: Qt.CheckState):
132+
_wrapper = str(wrapper_path())
133+
config.adjust_envvar(self.app_name, 'LEGENDARY_WRAPPER_EXE', _wrapper if state == Qt.CheckState.Checked else '')
134+
self.environ_changed.emit('LEGENDARY_WRAPPER_EXE')
135+
114136

115137
LaunchSettingsType = TypeVar('LaunchSettingsType', bound=LaunchSettingsBase)

rare/models/settings.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import locale
2+
import os
23
import platform as pf
34
from argparse import Namespace
45
from typing import Any, Optional
@@ -59,7 +60,9 @@ class Settings(Namespace):
5960
discord_rpc_os = Setting(key='discord_rpc_os', default=True, dtype=bool)
6061

6162
local_shader_cache = Setting(key='local_shader_cache', default=False, dtype=bool)
62-
create_shortcut = Setting(key='create_shortcut', default=pf.system() == 'Windows', dtype=bool)
63+
create_shortcut = Setting(
64+
key='create_shortcut', default=pf.system() == 'Windows' and 'WINEUSERNAME' not in os.environ, dtype=bool
65+
)
6366

6467

6568
app_settings = Settings()

rare/shared/rare_core.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from collections.abc import Callable, Iterable, Iterator
66
from itertools import chain
77
from logging import getLogger
8-
from typing import Optional
98

109
from legendary.lfs.eos import EOSOverlayApp
1110
from legendary.models.game import Game, SaveGameFile
@@ -46,19 +45,19 @@ class RareCore(QObject):
4645
# completed_entitlements = Signal()
4746

4847
# lk: special case class attribute, this has to be here
49-
__instance: Optional['RareCore'] = None
48+
__instance: 'RareCore' = None
5049

5150
def __init__(self, settings: RareAppSettings, args: Namespace):
5251
if self.__instance is not None:
5352
raise RuntimeError('RareCore already initialized')
5453
super(RareCore, self).__init__()
5554
self.logger = getLogger(type(self).__name__)
5655
self.__settings = settings
57-
self.__args: Namespace | None = None
58-
self.__signals: GlobalSignals | None = None
59-
self.__core: LegendaryCore | None = None
60-
self.__image_manager: ImageManager | None = None
61-
self.__wrappers: Wrappers | None = None
56+
self.__args: Namespace = None
57+
self.__signals: GlobalSignals = None
58+
self.__core: LegendaryCore = None
59+
self.__image_manager: ImageManager = None
60+
self.__wrappers: Wrappers = None
6261

6362
self.__start_time = time.perf_counter()
6463

@@ -84,7 +83,7 @@ def __init__(self, settings: RareAppSettings, args: Namespace):
8483
self.__fetch_progress: int = 0
8584
self.__fetched_games_dlcs: bool = False
8685
self.__fetched_entitlements: bool = False
87-
self.__fetched_steamappids: bool = False
86+
self.__fetched_runtimeassets: bool = False
8887

8988
RareCore.__instance = self
9089

@@ -196,12 +195,14 @@ def check_config(option: str, accepted: set = None) -> bool:
196195
self.__core.lgd.config.set('Legendary', 'default_platform', self.__core.default_platform)
197196
if not check_config('install_dir'):
198197
self.__core.lgd.config.set('Legendary', 'install_dir', self.__core.get_default_install_dir())
198+
os.makedirs(self.__core.get_default_install_dir(), exist_ok=True)
199199
if not check_config('mac_install_dir'):
200200
self.__core.lgd.config.set(
201201
'Legendary',
202202
'mac_install_dir',
203203
self.__core.get_default_install_dir(self.__core.default_platform),
204204
)
205+
os.makedirs(self.__core.get_default_install_dir(self.__core.default_platform), exist_ok=True)
205206

206207
# Always set these options
207208
# Avoid implicitly falling back to Windows games on macOS
@@ -320,7 +321,7 @@ def __filter_games(self, condition: Callable[[RareGame], bool]) -> Iterator[Rare
320321
return filter(condition, self.__library.values())
321322

322323
def __create_or_update_rgame(self, game: Game) -> RareGame:
323-
if rgame := self.__library.get(game.app_name, False):
324+
if rgame := self.__library.get(game.app_name):
324325
self.logger.warning(f'{rgame.app_name} already present in {type(self).__name__}')
325326
self.logger.info(f'Updating Game for {rgame.app_name}')
326327
rgame.update_rgame()
@@ -333,7 +334,7 @@ def __add_games_and_dlcs(self, games: list[Game], dlcs_dict: dict[str, list]) ->
333334
length = len(games)
334335
for idx, game in enumerate(games):
335336
rgame = self.__create_or_update_rgame(game)
336-
if game_dlcs := dlcs_dict.get(rgame.game.catalog_item_id, False):
337+
if game_dlcs := dlcs_dict.get(rgame.game.catalog_item_id):
337338
for dlc in game_dlcs:
338339
rdlc = self.__create_or_update_rgame(dlc)
339340
if rdlc not in rgame.owned_dlcs:
@@ -366,8 +367,8 @@ def __on_fetch_result(self, result: tuple, result_type: int):
366367
self.__core.lgd.entitlements = result
367368
self.__fetched_entitlements = True
368369

369-
if result_type == FetchWorker.Result.EXTRAS:
370-
self.__fetched_steamappids = True
370+
if result_type == FetchWorker.Result.RUNTIMEASSETS:
371+
self.__fetched_runtimeassets = True
371372

372373
self.logger.info('Acquired data from %s worker', FetchWorker.Result(result_type).name)
373374

@@ -376,7 +377,7 @@ def __on_fetch_result(self, result: tuple, result_type: int):
376377
{
377378
self.__fetched_games_dlcs,
378379
self.__fetched_entitlements,
379-
self.__fetched_steamappids,
380+
self.__fetched_runtimeassets,
380381
}
381382
):
382383
return

0 commit comments

Comments
 (0)