From 214eaa4ebb23cd8a6c44c9549691e346198905e8 Mon Sep 17 00:00:00 2001 From: Zash Date: Fri, 4 Apr 2025 22:04:15 +0200 Subject: [PATCH 1/4] Catch and log errors for failed EA games list parsing --- eadesktop_utils.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/eadesktop_utils.py b/eadesktop_utils.py index 747c4082..5720556e 100644 --- a/eadesktop_utils.py +++ b/eadesktop_utils.py @@ -2,13 +2,14 @@ import configparser import os +import sys import xml.etree.ElementTree as et from configparser import NoOptionError from pathlib import Path from typing import Dict -def find_games() -> Dict[str, Path]: +def find_games(errors: list[tuple[str, Exception]] | None = None) -> Dict[str, Path]: """ Find the list of EA Desktop games installed. @@ -37,7 +38,17 @@ def find_games() -> Dict[str, Path]: ini_content = "[mod_organizer]\n" + f.read() config = configparser.ConfigParser() - config.read_string(ini_content) + try: + config.read_string(ini_content) + except configparser.ParsingError as e: + error_message = ( + f'Failed to parse EA Desktop games list file "{user_ini}",\n' + " Try to run the launcher to recreate it." + ) + print(error_message, e, file=sys.stderr) + if errors is not None: + errors.append((error_message, e)) + return games try: install_path = Path(config.get("mod_organizer", "user.downloadinplacedir")) From bf786e459a3f457ba6d0dc1915cdb0f1042aee51 Mon Sep 17 00:00:00 2001 From: Zash Date: Fri, 4 Apr 2025 22:04:15 +0200 Subject: [PATCH 2/4] Extend error logging for Epic Games --- epic_utils.py | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/epic_utils.py b/epic_utils.py index c056252e..b7d34963 100644 --- a/epic_utils.py +++ b/epic_utils.py @@ -9,8 +9,12 @@ from collections.abc import Iterable from pathlib import Path +ErrorList = list[tuple[str, Exception]] -def find_epic_games() -> Iterable[tuple[str, Path]]: + +def find_epic_games( + errors: ErrorList | None = None, +) -> Iterable[tuple[str, Path]]: try: with winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, @@ -30,15 +34,23 @@ def find_epic_games() -> Iterable[tuple[str, Path]]: manifest_file_data["AppName"], Path(manifest_file_data["InstallLocation"]), ) - except (json.JSONDecodeError, KeyError): + except (json.JSONDecodeError, KeyError) as e: + error_message = ( + f'Unable to parse Epic Games manifest file: "{manifest_file_path}"\n' + " Try to run the launcher recreate it." + ) print( - "Unable to parse Epic Games manifest file", - manifest_file_path, + error_message, + e, file=sys.stderr, ) + if errors is not None: + errors.append((error_message, e)) -def find_legendary_games(config_path: str | None = None) -> Iterable[tuple[str, Path]]: +def find_legendary_games( + config_path: str | None = None, errors: ErrorList | None = None +) -> Iterable[tuple[str, Path]]: # Based on legendary source: # https://github.com/derrod/legendary/blob/master/legendary/lfs/lgndry.py if config_path := config_path or os.environ.get("XDG_CONFIG_HOME"): @@ -53,21 +65,33 @@ def find_legendary_games(config_path: str | None = None) -> Iterable[tuple[str, installed_games = json.load(installed_file) for game in installed_games.values(): yield game["app_name"], Path(game["install_path"]) - except (json.JSONDecodeError, AttributeError, KeyError): + except (json.JSONDecodeError, AttributeError, KeyError) as e: + error_message = ( + f'Unable to parse installed games from Legendary/Heroic launcher: "{installed_path}"\n' + " Try to run the launcher to recrated the file." + ) print( - "Unable to parse installed games from Legendary", - installed_path, + error_message, + e, file=sys.stderr, ) + if errors is not None: + errors.append((error_message, e)) -def find_heroic_games(): - return find_legendary_games(os.path.expandvars(r"%AppData%\heroic\legendaryConfig")) +def find_heroic_games(errors: ErrorList | None = None): + return find_legendary_games( + os.path.expandvars(r"%AppData%\heroic\legendaryConfig"), errors + ) -def find_games() -> dict[str, Path]: +def find_games(errors: ErrorList | None = None) -> dict[str, Path]: return dict( - itertools.chain(find_epic_games(), find_legendary_games(), find_heroic_games()) + itertools.chain( + find_epic_games(errors=errors), + find_legendary_games(errors=errors), + find_heroic_games(errors=errors), + ) ) From ebd6b7024e6a1a2a4c8c0cefc24464f9e3951f14 Mon Sep 17 00:00:00 2001 From: Zash Date: Fri, 4 Apr 2025 22:04:16 +0200 Subject: [PATCH 3/4] Add error message box for errors loading game list --- basic_game.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/basic_game.py b/basic_game.py index ae2c0c00..128cad58 100644 --- a/basic_game.py +++ b/basic_game.py @@ -8,6 +8,7 @@ import mobase from PyQt6.QtCore import QDir, QFileInfo, QStandardPaths from PyQt6.QtGui import QIcon +from PyQt6.QtWidgets import QMessageBox from .basic_features.basic_save_game_info import ( BasicGameSaveGame, @@ -380,11 +381,22 @@ def setup(): from .origin_utils import find_games as find_origin_games from .steam_utils import find_games as find_steam_games + errors: list[tuple[str, Exception]] = [] BasicGame.steam_games = find_steam_games() BasicGame.gog_games = find_gog_games() BasicGame.origin_games = find_origin_games() - BasicGame.epic_games = find_epic_games() - BasicGame.eadesktop_games = find_eadesktop_games() + BasicGame.epic_games = find_epic_games(errors) + BasicGame.eadesktop_games = find_eadesktop_games(errors) + + if errors: + QMessageBox.critical( + None, + "Errors loading game list", + ( + "The following errors occurred while loading the list of available games:\n" + f"\n- {'\n\n- '.join('\n '.join(str(e) for e in messageError) for messageError in errors)}" + ), + ) # File containing the plugin: _fromName: str From d2f1c7fbf90a125d3c41415a59cafd47113c12ba Mon Sep 17 00:00:00 2001 From: Zash Date: Fri, 4 Apr 2025 22:08:54 +0200 Subject: [PATCH 4/4] Fix lint error in game_sims4.py --- games/game_sims4.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/games/game_sims4.py b/games/game_sims4.py index ca342768..668a3f72 100644 --- a/games/game_sims4.py +++ b/games/game_sims4.py @@ -111,6 +111,8 @@ def fixOrValidateEntry( ValidationResult.FIXABLE, lambda: tree.move(entry, innerPath), ) + case _: + pass return walkReturn tree.walk(fixOrValidateEntry)