From 3ade39a5062296eb57c3b5efeafcbc65521df80c Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Mon, 27 Apr 2026 16:31:47 +0300 Subject: [PATCH 1/6] chore: code golf --- rare/components/tabs/settings/widgets/proton.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rare/components/tabs/settings/widgets/proton.py b/rare/components/tabs/settings/widgets/proton.py index 47410a571..3230c55e9 100644 --- a/rare/components/tabs/settings/widgets/proton.py +++ b/rare/components/tabs/settings/widgets/proton.py @@ -150,8 +150,9 @@ def _on_tool_changed(self, index: int): steam_environ = steam.get_steam_environment(steam_tool, self.compat_edit.text()) library_paths = steam_environ.get('STEAM_COMPAT_LIBRARY_PATHS', '') if self.app_name != 'default' and (install_path := self.rcore.get_game(self.app_name).install_path): + game_library = os.path.dirname(install_path) library_paths = ( - ':'.join([library_paths, os.path.dirname(install_path)]) if library_paths else os.path.dirname(install_path) + ':'.join([library_paths, game_library]) if library_paths else game_library ) # https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/-/blob/main/docs/steam-compat-tool-interface.md#non-steam-games steam_environ['STEAM_COMPAT_INSTALL_PATH'] = install_path From 191d329abbd58e2921d756618cbc957aa243a34f Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:56:13 +0300 Subject: [PATCH 2/6] Rare: treat Ubisoft games as third party games do not try to download their outdated assets offered by EGL, rather launch Ubisoft Connect similar to EA App and let it handle the installation --- rare/commands/launcher/__init__.py | 2 +- rare/commands/launcher/lgd_helper.py | 22 +++++++----- .../tabs/library/details/__init__.py | 6 ++-- .../components/tabs/library/details/compat.py | 6 +++- .../tabs/library/details/details.py | 6 ++-- .../tabs/library/widgets/game_widget.py | 9 +++-- .../tabs/library/widgets/list_game_widget.py | 6 +++- rare/models/game.py | 34 ++++++++----------- rare/models/game_slim.py | 17 +++++----- rare/shared/rare_core.py | 14 ++++---- rare/shared/workers/wine_resolver.py | 2 +- 11 files changed, 68 insertions(+), 56 deletions(-) diff --git a/rare/commands/launcher/__init__.py b/rare/commands/launcher/__init__.py index a87a194f1..c35e20927 100644 --- a/rare/commands/launcher/__init__.py +++ b/rare/commands/launcher/__init__.py @@ -335,7 +335,7 @@ def launch_game(self, params: LaunchParams): self.stop() return - if platform.system() == 'Windows' and params.is_origin_game: + if platform.system() == 'Windows' and params.is_third_party_game: # executable is a protocol link (link2ea://launchgame/...) QDesktopServices.openUrl(QUrl(params.executable)) self.stop() # stop because it is not a subprocess diff --git a/rare/commands/launcher/lgd_helper.py b/rare/commands/launcher/lgd_helper.py index 4b5bc39f3..428dccff1 100644 --- a/rare/commands/launcher/lgd_helper.py +++ b/rare/commands/launcher/lgd_helper.py @@ -52,28 +52,32 @@ class LaunchParams: environment: dict[str, str] = field(default_factory=dict) pre_launch_command: str = '' pre_launch_wait: bool = False - is_origin_game: bool = False # only for windows to launch as url + is_third_party_game: bool = False # only for windows to launch as url def __bool__(self): return bool(self.executable) -def get_origin_params(rgame: RareGameSlim, init: InitParams, launch: LaunchParams) -> LaunchParams: +def get_third_party_params(rgame: RareGameSlim, init: InitParams, launch: LaunchParams) -> LaunchParams: core = rgame.core app_name = rgame.app_name - origin_uri = core.get_origin_uri(app_name, init.offline) + uri = ( + core.get_origin_uri(app_name, init.offline) + if rgame.is_origin + else core.get_ubisoft_uri(app_name, init.offline) + ) if platform.system() == 'Windows': - command = [origin_uri] + command = [uri] else: command = core.get_app_launch_command(app_name) if not os.path.exists(command[0]) and shutil.which(command[0]) is None: return launch - command.append(origin_uri) + command.append(uri) exe, args, env = prepare_process(command, core.get_app_environment(app_name)) - launch.is_origin_game = True + launch.is_third_party_game = True launch.executable = exe launch.arguments = args launch.environment = env @@ -132,7 +136,7 @@ def get_launch_params(rgame: RareGameSlim, init: InitParams = None) -> LaunchPar if not rgame.game: raise GameArgsError(f'Could not find metadata for {rgame.app_title}') - if rgame.is_origin: + if rgame.is_third_party: init.offline = False else: if not rgame.is_installed: @@ -143,8 +147,8 @@ def get_launch_params(rgame: RareGameSlim, init: InitParams = None) -> LaunchPar if not os.path.exists(rgame.install_path): raise GameArgsError('Game path does not exist') - if rgame.is_origin: - resp = get_origin_params(rgame, init, resp) + if rgame.is_third_party: + resp = get_third_party_params(rgame, init, resp) else: resp = get_game_params(rgame, init, resp) diff --git a/rare/components/tabs/library/details/__init__.py b/rare/components/tabs/library/details/__init__.py index 2cef232d1..dce33cfc7 100644 --- a/rare/components/tabs/library/details/__init__.py +++ b/rare/components/tabs/library/details/__init__.py @@ -65,14 +65,14 @@ def update_game(self, rgame: RareGame): self.details_tab.update_game(rgame) self.game_settings_tab.load_settings(rgame) - self.game_settings_tab.launch.setEnabled(rgame.is_installed or rgame.is_origin) + self.game_settings_tab.launch.setEnabled(rgame.is_installed or rgame.is_third_party) if pf.system() != 'Windows': self.compat_settings_tab.load_settings(rgame) - self.compat_settings_tab.setEnabled(rgame.is_installed or rgame.is_origin) + self.compat_settings_tab.setEnabled(rgame.is_installed or rgame.is_third_party) self.environ_tab.load_settings(rgame) - self.environ_tab.setEnabled(rgame.is_installed or rgame.is_origin) + self.environ_tab.setEnabled(rgame.is_installed or rgame.is_third_party) self.dlcs_tab.update_dlcs(rgame) self.dlcs_tab.setEnabled(rgame.is_installed and bool(rgame.owned_dlcs)) diff --git a/rare/components/tabs/library/details/compat.py b/rare/components/tabs/library/details/compat.py index 9a6012ca9..cc4670301 100644 --- a/rare/components/tabs/library/details/compat.py +++ b/rare/components/tabs/library/details/compat.py @@ -44,7 +44,11 @@ def load_settings(self, app_name: str): def _get_compat_path(self, compat_location: ProtonSettings.CompatLocation): folder_name = 'default' - local_folder_name = self.rcore.get_game(self.app_name).folder_name + rgame = self.rcore.get_game(self.app_name) + if rgame.is_third_party: + local_folder_name = "Origin" if rgame.is_origin else "Ubisoft" + else: + local_folder_name = rgame.folder_name if compat_location == ProtonSettings.CompatLocation.NONE: if wine_prefix_dir(local_folder_name).joinpath('system.reg').is_file(): compat_location = ProtonSettings.CompatLocation.ISOLATED diff --git a/rare/components/tabs/library/details/details.py b/rare/components/tabs/library/details/details.py index ef63856bb..babd81bb4 100644 --- a/rare/components/tabs/library/details/details.py +++ b/rare/components/tabs/library/details/details.py @@ -432,8 +432,10 @@ def update_game(self, rgame: RareGame): self.ui.app_name.setText(rgame.app_name) self.ui.dev.setText(rgame.developer) - if rgame.is_non_asset: - self.ui.install_button.setText(self.tr('Link/Launch')) + if rgame.is_non_asset or rgame.is_third_party: + self.ui.install_button.setText( + self.tr('Launch in EA App') if rgame.is_origin else self.tr('Launch in Ubisoft Connect') + ) self.ui.actions_stack.setCurrentWidget(self.ui.uninstalled_page) else: self.ui.install_button.setText(self.tr('Install')) diff --git a/rare/components/tabs/library/widgets/game_widget.py b/rare/components/tabs/library/widgets/game_widget.py index 995831470..0aaa9dfc7 100644 --- a/rare/components/tabs/library/widgets/game_widget.py +++ b/rare/components/tabs/library/widgets/game_widget.py @@ -92,7 +92,8 @@ def __init__(self, rgame: RareGame, parent=None): 'can_launch': self.tr('Launch game'), 'is_foreign': self.tr('Launch offline'), 'has_update': self.tr('Launch without version check'), - 'is_origin': self.tr('Launch/Link'), + 'is_origin': self.tr('Launch in EA App'), + 'is_ubisoft': self.tr('Launch in Ubisoft Connect'), 'not_can_launch': self.tr("Can't launch"), } @@ -169,7 +170,7 @@ def update_actions(self): for action in self.actions(): self.removeAction(action) - if self.rgame.is_installed or self.rgame.is_origin: + if self.rgame.is_installed or self.rgame.is_third_party: self.addAction(self.launch_action) else: self.addAction(self.install_action) @@ -194,7 +195,7 @@ def update_actions(self): self.addAction(self.steam_shortcut_action) self.addAction(self.reload_action) - if self.rgame.is_installed and not self.rgame.is_origin: + if self.rgame.is_installed and not self.rgame.is_third_party: self.addAction(self.uninstall_action) def eventFilter(self, a0: QObject, a1: QEvent) -> bool: @@ -216,6 +217,8 @@ def eventFilter(self, a0: QObject, a1: QEvent) -> bool: self.ui.tooltip_label.setText(self.hover_strings['not_can_launch']) elif self.rgame.is_origin: self.ui.tooltip_label.setText(self.hover_strings['is_origin']) + elif self.rgame.is_ubisoft: + self.ui.tooltip_label.setText(self.hover_strings['is_ubisoft']) elif self.rgame.has_update: self.ui.tooltip_label.setText(self.hover_strings['has_update']) elif self.rgame.is_foreign and self.rgame.can_run_offline: diff --git a/rare/components/tabs/library/widgets/list_game_widget.py b/rare/components/tabs/library/widgets/list_game_widget.py index 8a197d76b..645594c22 100644 --- a/rare/components/tabs/library/widgets/list_game_widget.py +++ b/rare/components/tabs/library/widgets/list_game_widget.py @@ -38,7 +38,11 @@ def __init__(self, rgame: RareGame, parent=None): self.ui.launch_btn.setEnabled(self.rgame.can_launch) - self.ui.launch_btn.setText(self.tr('Launch') if not self.rgame.is_origin else self.tr('Link/Play')) + if not self.rgame.is_third_party: + self.ui.launch_btn.setText(self.tr('Launch')) + else: + self.ui.launch_btn.setText(self.tr('Launch in EA App') if self.rgame.is_origin else self.tr('Launch in Ubisoft Connect')) + self.ui.developer_label.setText(self.rgame.developer) # self.version_label.setVisible(self.is_installed) if self.rgame.igame: diff --git a/rare/models/game.py b/rare/models/game.py index eb2cd9ef1..f483c55b0 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -91,8 +91,8 @@ def __init__( game: Game, ): super(RareGame, self).__init__(settings, legendary_core, game) - self.__origin_install_path: str | None = None - self.__origin_install_size: int | None = None + self._third_party_install_path: str | None = None + self._third_party_install_size: int | None = None self.image_manager = image_manager @@ -251,15 +251,15 @@ def install_size(self) -> int: @return int The size of the installation """ - if self.is_origin: - return self.__origin_install_size if self.__origin_install_size is not None else 0 + if self.is_third_party: + return self._third_party_install_size if self._third_party_install_size is not None else 0 return self.igame.install_size if self.igame is not None else 0 @property def install_path(self) -> str | None: - if self.is_origin: + if self.is_third_party: # TODO Linux is also C:\\... - return self.__origin_install_path + return self._third_party_install_path return super(RareGame, self).install_path @install_path.setter @@ -267,8 +267,8 @@ def install_path(self, path: str) -> None: if self.igame: self.igame.install_path = path self.store_igame() - elif self.is_origin: - self.__origin_install_path = path + elif self.is_third_party: + self._third_party_install_path = path @property def remote_version(self) -> str: @@ -314,7 +314,7 @@ def is_installed(self) -> bool: @return bool If the game should be considered installed """ - return (self.igame is not None) or (self.is_origin and self.__origin_install_path is not None) + return (self.igame is not None) or self.is_ubisoft or self.is_origin def set_installed(self, installed: bool) -> None: """! @@ -450,18 +450,14 @@ def is_non_asset(self) -> bool: @return bool If the game doesn't have assets """ - # Asset infos are usually None, but there was a bug, that it was an empty GameAsset class - return not self.game.asset_infos or not next(iter(self.game.asset_infos.values())).app_name + + return not self.game.asset_infos or not next(iter(self.game.asset_infos.values())).app_name or self.is_third_party @property def is_android_only(self) -> bool: return self.is_non_asset and self.is_android - @property - def is_ubisoft(self) -> bool: - return self.game.partner_link_type == 'ubisoft' - @property def folder_name(self) -> str: return ( @@ -632,9 +628,9 @@ def tags(self, tags: tuple[str, ...]) -> None: self.logger.debug(f'Saving tags for {self.game.app_title}: {self.metadata.tags}') self.__save_metadata() - def set_origin_attributes(self, path: str, size: int = 0) -> None: - self.__origin_install_path = path - self.__origin_install_size = size + def set_third_party_attributes(self, path: str, size: int = 0) -> None: + self._third_party_install_path = path + self._third_party_install_size = size if self.install_path and self.install_size: self.signals.game.installed.emit(self.app_name) else: @@ -643,7 +639,7 @@ def set_origin_attributes(self, path: str, size: int = 0) -> None: @property def can_launch(self) -> bool: - if self.is_idle and self.is_origin: + if self.is_idle and self.is_third_party: return True if self.is_installed: if (not self.is_idle) or self.needs_verification: diff --git a/rare/models/game_slim.py b/rare/models/game_slim.py index f97b07498..f2a49fa37 100644 --- a/rare/models/game_slim.py +++ b/rare/models/game_slim.py @@ -160,16 +160,15 @@ def is_win32(self) -> bool: @property def is_origin(self) -> bool: - """! - @brief Property to report if a Game is an Origin game + return bool(self.game.is_origin_game) - Legendary and by extenstion Rare can't launch Origin games directly, - it just launches the Origin client and thus requires a bit of a special - handling to let the user know. + @property + def is_ubisoft(self) -> bool: + return bool(self.game.is_ubisoft_game) - @return bool If the game is an Origin game - """ - return self.game.third_party_store in {'Origin', 'the EA app'} + @property + def is_third_party(self) -> bool: + return self.is_origin or self.is_ubisoft @property def is_android(self) -> bool: @@ -230,7 +229,7 @@ def __init__(self, settings: RareAppSettings, legendary_core: LegendaryCore, gam @property def is_installed(self) -> bool: - if self.is_origin: + if self.is_third_party: return True return self.igame is not None diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index d42e02c75..b42c24517 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -342,7 +342,7 @@ def __add_games_and_dlcs(self, games: list[Game], dlcs_dict: dict[str, list]) -> # lk: since loading has to know about the game's state, # validate installation just by trying to add each RareGame # TODO: this should probably be moved into RareGame - if rgame.is_installed and not (rgame.is_dlc or rgame.is_non_asset): + if rgame.is_installed and not rgame.is_third_party and not (rgame.is_dlc or rgame.is_non_asset): try: self.__validate_install(rgame) except FileNotFoundError as e: @@ -435,14 +435,14 @@ def fetch_saves(self): saves_worker = QRunnable.create(self.__fetch_saves) QThreadPool.globalInstance().start(saves_worker) - def resolve_origin(self) -> None: + def resolve_third_party(self) -> None: origin_worker = OriginWineWorker(self.__core, list(self.origin_games)) QThreadPool.globalInstance().start(origin_worker) def __post_init(self) -> None: if not self.__args.offline: self.fetch_saves() - self.resolve_origin() + self.resolve_third_party() @property def game_tags(self) -> tuple[str, ...]: @@ -460,19 +460,19 @@ def games_and_dlcs(self) -> Iterator[RareGame]: @property def games(self) -> Iterator[RareGame]: - return self.__filter_games(lambda game: not game.is_dlc or game.is_launchable_addon) + return self.__filter_games(lambda rgame: not rgame.is_dlc or rgame.is_launchable_addon) @property def installed_games(self) -> Iterator[RareGame]: - return self.__filter_games(lambda game: game.is_installed and not game.is_dlc) + return self.__filter_games(lambda rgame: rgame.is_installed and not rgame.is_dlc) @property def origin_games(self) -> Iterator[RareGame]: - return self.__filter_games(lambda game: game.is_origin and not game.is_dlc) + return self.__filter_games(lambda rgame: rgame.is_origin and not rgame.is_dlc) @property def ubisoft_games(self) -> Iterator[RareGame]: - return self.__filter_games(lambda game: game.is_ubisoft and not game.is_dlc) + return self.__filter_games(lambda rgame: rgame.is_ubisoft and not rgame.is_dlc) @property def game_list(self) -> Iterator[Game]: diff --git a/rare/shared/workers/wine_resolver.py b/rare/shared/workers/wine_resolver.py index 6ffb3ddd4..81dc63279 100644 --- a/rare/shared/workers/wine_resolver.py +++ b/rare/shared/workers/wine_resolver.py @@ -180,7 +180,7 @@ def run_real(self) -> None: if install_dir: if os.path.isdir(install_dir): install_size = path_size(install_dir) - rgame.set_origin_attributes(install_dir, install_size) + rgame.set_third_party_attributes(install_dir, install_size) self.logger.info( "Origin game '%s' (%s, %s)", rgame.app_title, From 0a93f5e272219b4ed37a2cbd3fb22556197b6a6b Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:58:35 +0300 Subject: [PATCH 3/6] project: update legendary --- misc/requirements.in | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements.in b/misc/requirements.in index bc5c91944..7dc2a304c 100644 --- a/misc/requirements.in +++ b/misc/requirements.in @@ -4,7 +4,7 @@ setuptools-scm requests < 3.0 PySide6-Essentials >= 6.8.1 QtAwesome -legendary-gl @ https://github.com/RareDevs/legendary/archive/rare-next.zip +legendary-gl @ https://github.com/RareDevs/legendary/archive/c5e3e909fdc2db518fc42dc3dbba302860d8550b.zip orjson vdf @ https://github.com/solsticegamestudios/vdf/archive/be1f7220238022f8b29fe747f0b643f280bfdb6e.zip pywin32 ; platform_system == "Windows" diff --git a/pyproject.toml b/pyproject.toml index 9d3688a3d..6b63f2634 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ "requests < 3.0", "PySide6-Essentials >= 6.8.1", "QtAwesome", - "legendary-gl @ git+https://github.com/RareDevs/legendary@rare-next", + "legendary-gl @ git+https://github.com/RareDevs/legendary@c5e3e909fdc2db518fc42dc3dbba302860d8550b", "orjson", "vdf @ https://github.com/solsticegamestudios/vdf/archive/be1f7220238022f8b29fe747f0b643f280bfdb6e.zip", "pywin32 ; platform_system == 'Windows'", From 46e835d4fa787a5a105a1d12971317e0a6ae0a1d Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 3 May 2026 15:34:25 +0300 Subject: [PATCH 4/6] chore: add comment field in dekstop entries --- rare/utils/paths.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rare/utils/paths.py b/rare/utils/paths.py index 9dae704f6..ee0fd1756 100644 --- a/rare/utils/paths.py +++ b/rare/utils/paths.py @@ -350,6 +350,7 @@ def create_desktop_link(app_name: str, app_title: str = '', link_name: str = '', desktop_file.write( '[Desktop Entry]\n' f'Name={app_title}\n' + f'Comment={app_title} (Rare)\n' 'Type=Application\n' 'Categories=Game;\n' f'Icon={icon_path}\n' From c75c3554f00b531435e4c17a1fac7aaf943e27ec Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 3 May 2026 16:50:00 +0300 Subject: [PATCH 5/6] RareApp: use native dialogs in Snap --- rare/widgets/rare_app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rare/widgets/rare_app.py b/rare/widgets/rare_app.py index bdbb92501..d9a917dd1 100644 --- a/rare/widgets/rare_app.py +++ b/rare/widgets/rare_app.py @@ -76,7 +76,8 @@ def __init__(self, args: Namespace, log_file: str): self.logger = logging.getLogger(type(self).__name__) self._hook = RareAppException(self) self.setQuitOnLastWindowClosed(False) - self.setAttribute(Qt.ApplicationAttribute.AA_DontUseNativeDialogs, True) + use_qt_dialogs = not os.environ.get('SNAP') + self.setAttribute(Qt.ApplicationAttribute.AA_DontUseNativeDialogs, use_qt_dialogs) self.setDesktopFileName('rare') self.setApplicationName('Rare') From 40dd502b99a34ffd4149c647b5eb9a6b15a6eaf1 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 3 May 2026 16:53:01 +0300 Subject: [PATCH 6/6] project: update legendary --- misc/requirements.in | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/requirements.in b/misc/requirements.in index 7dc2a304c..8367ea3de 100644 --- a/misc/requirements.in +++ b/misc/requirements.in @@ -4,7 +4,7 @@ setuptools-scm requests < 3.0 PySide6-Essentials >= 6.8.1 QtAwesome -legendary-gl @ https://github.com/RareDevs/legendary/archive/c5e3e909fdc2db518fc42dc3dbba302860d8550b.zip +legendary-gl @ https://github.com/RareDevs/legendary/archive/207b859ae4476ae77466d3c922efaba90e093f12.zip orjson vdf @ https://github.com/solsticegamestudios/vdf/archive/be1f7220238022f8b29fe747f0b643f280bfdb6e.zip pywin32 ; platform_system == "Windows" diff --git a/pyproject.toml b/pyproject.toml index 6b63f2634..a4810106a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ "requests < 3.0", "PySide6-Essentials >= 6.8.1", "QtAwesome", - "legendary-gl @ git+https://github.com/RareDevs/legendary@c5e3e909fdc2db518fc42dc3dbba302860d8550b", + "legendary-gl @ git+https://github.com/RareDevs/legendary@207b859ae4476ae77466d3c922efaba90e093f12", "orjson", "vdf @ https://github.com/solsticegamestudios/vdf/archive/be1f7220238022f8b29fe747f0b643f280bfdb6e.zip", "pywin32 ; platform_system == 'Windows'",