From 9a8ebf302762dbe54c93c5806f3476e38cb464ff Mon Sep 17 00:00:00 2001 From: Jeroen Ruigrok van der Werven Date: Mon, 11 Apr 2022 21:35:21 +0200 Subject: [PATCH] Add EA Desktop support Add EA Desktop IDs for Dragon Age: Origins and Dragon Age 2. --- README.md | 8 ++++--- basic_game.py | 24 ++++++++++++++++++-- eadesktop_utils.py | 56 ++++++++++++++++++++++++++++++++++++++++++++++ games/game_da2.py | 3 ++- games/game_dao.py | 3 ++- 5 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 eadesktop_utils.py diff --git a/README.md b/README.md index 961ed8d..f31ce7f 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,7 @@ class Witcher3Game(BasicGame): | GameGogId | GOG ID of the game (Optional) | `gogAPPId` | `List[str]` or `str` or `int` | | GameOriginManifestIds | Origin Manifest ID of the game (Optional) | `originManifestIds` | `List[str]` or `str` | | GameOriginWatcherExecutables | Executables to watch for Origin DRM (Optional) | `originWatcherExecutables` | `List[str]` or `str` | +| GameEaDesktopId | EA Desktop ID of the game (Optional) | `eaDesktopContentId` | `List[str]` or `str` or `int` | You can use the following variables for `str`: @@ -166,9 +167,9 @@ You can use the following variables for `str`: The meta-plugin provides some useful extra feature: -1. **Automatic Steam, GOG, and Origin game detection:** If you provide Steam, GOG, or Origin IDs for the game (via - `GameSteamId`, `GameGogId`, or `GameOriginManifestIds`), the game will be listed in the list of available games when creating a new - MO2 instance (if the game is installed via Steam or GOG). +1. **Automatic Steam, GOG, and Origin game detection:** If you provide Steam, GOG, Origin or EA Desktop IDs for the game (via + `GameSteamId`, `GameGogId`, `GameOriginManifestIds`, or `GameEaDesktopId`), the game will be listed in the list of available games when creating a new + MO2 instance (if the game is installed via Steam, GOG, Origin or EA Desktop). 2. **Basic save game preview:** If you use the Python version, and if you can easily obtain a picture (file) for any saves, you can provide basic save-game preview by using the `BasicGameSaveGameInfo`. See [games/game_witcher3.py](games/game_witcher3.py) for more details. @@ -178,3 +179,4 @@ Game IDs can be found here: - For Steam on [Steam Database](https://steamdb.info/) - For GOG on [GOG Database](https://www.gogdb.org/) - For Origin from C:\ProgramData\Origin\LocalContent (.mfst files) +- For EA Desktop from `\\__Installer\installerdata.xml` diff --git a/basic_game.py b/basic_game.py index 375af49..c460fe9 100644 --- a/basic_game.py +++ b/basic_game.py @@ -205,6 +205,7 @@ class BasicGameMappings: gogAPPId: BasicGameOptionsMapping[str] originManifestIds: BasicGameOptionsMapping[str] originWatcherExecutables: BasicGameMapping[List[str]] + eaDesktopContentId: BasicGameOptionsMapping[str] @staticmethod def _default_documents_directory(game): @@ -316,7 +317,9 @@ class BasicGameMappings: apply_fn=lambda s: [s] if isinstance(s, str) else s, default=lambda g: [], ) - + self.eaDesktopContentId = BasicGameOptionsMapping( + game, "GameEaDesktopId", "eaDesktopContentId", default=lambda g: "", apply_fn=ids_apply + ) class BasicGame(mobase.IPluginGame): @@ -328,16 +331,19 @@ class BasicGame(mobase.IPluginGame): steam_games: Dict[str, Path] gog_games: Dict[str, Path] origin_games: Dict[str, Path] + eadesktop_games: Dict[str, Path] @staticmethod def setup(): from .gog_utils import find_games as find_gog_games from .steam_utils import find_games as find_steam_games from .origin_utils import find_games as find_origin_games + from .eadesktop_utils import find_games as find_eadesktop_games BasicGame.steam_games = find_steam_games() BasicGame.gog_games = find_gog_games() BasicGame.origin_games = find_origin_games() + BasicGame.eadesktop_games = find_eadesktop_games() # File containing the plugin: _fromName: str @@ -372,6 +378,9 @@ class BasicGame(mobase.IPluginGame): def is_origin(self) -> bool: return self._mappings.originManifestIds.has_value() + def is_eadesktop(self) -> bool: + return self._mappings.eaDesktopContentId.has_value() + # IPlugin interface: def init(self, organizer: mobase.IOrganizer) -> bool: @@ -434,6 +443,11 @@ class BasicGame(mobase.IPluginGame): self.setGamePath(BasicGame.origin_games[origin_manifest_id]) return + for eadesktop_content_id in self._mappings.eaDesktopContentId.get(): + if eadesktop_content_id in BasicGame.eadesktop_games: + self.setGamePath(BasicGame.eadesktop_games[eadesktop_content_id]) + return + def gameName(self) -> str: return self._mappings.gameName.get() @@ -463,6 +477,9 @@ class BasicGame(mobase.IPluginGame): def gogAPPId(self) -> str: return self._mappings.gogAPPId.current() + def eaDesktopContentId(self) -> str: + return self._mappings.eaDesktopContentId.current() + def binaryName(self) -> str: return self._mappings.binaryName.get() @@ -560,7 +577,7 @@ class BasicGame(mobase.IPluginGame): path = Path(path) - # Check if we have a matching steam ID or GOG id and set the index accordingly: + # Check if we have a matching steam, GOG, Origin or EA Desktop id and set the index accordingly: for steamid, steampath in BasicGame.steam_games.items(): if steampath == path: self._mappings.steamAPPId.set_value(steamid) @@ -570,6 +587,9 @@ class BasicGame(mobase.IPluginGame): for originid, originpath in BasicGame.origin_games.items(): if originpath == path: self._mappings.originManifestIds.set_value(originid) + for eadesktopid, eadesktoppath in BasicGame.eadesktop_games.items(): + if eadesktoppath == path: + self._mappings.eaDesktopContentId.set_value(eadesktopid) def documentsDirectory(self) -> QDir: return self._mappings.documentsDirectory.get() diff --git a/eadesktop_utils.py b/eadesktop_utils.py new file mode 100644 index 0000000..74ed1bb --- /dev/null +++ b/eadesktop_utils.py @@ -0,0 +1,56 @@ +# -*- encoding: utf-8 -*- + +import configparser +import os +import xml.etree.ElementTree as et +from pathlib import Path +from typing import Dict, List + + +def find_games() -> Dict[str, Path]: + """ + Find the list of EA Desktop games installed. + + Returns: + A mapping from EA Desktop content IDs to install locations for available + EA Desktop games. + """ + games: Dict[str, Path] = {} + + local_app_data_path = os.path.expandvars("%LocalAppData%") + ea_desktop_settings_path = Path(local_app_data_path).joinpath("Electronic Arts", "EA Desktop") + + if not ea_desktop_settings_path.exists: + return games + + user_ini, *_ = list(ea_desktop_settings_path.glob("user_*.ini")) + + # The INI file in its current form has no section headers. + # So we wrangle the input to add it all under a fake section. + with open(user_ini) as f: + ini_content = "[mod_organizer]\n" + f.read() + + config = configparser.ConfigParser() + config.read_string(ini_content) + + install_path = Path(config.get("mod_organizer", "user.downloadinplacedir")) + + for game_dir in install_path.iterdir(): + installer_file = game_dir.joinpath("__Installer", "installerdata.xml") + xml_tree = et.parse(installer_file) + root = xml_tree.getroot() + + # For all manifest files the following XPath expression returns the + # numeric ID. There are, in some cases, also name IDs but we do not + # consider these. + content_id = root.find(".//contentIDs/contentID[1]") + game_id = content_id.text + games[game_id] = game_dir + + return games + + +if __name__ == "__main__": + games = find_games() + for k, v in games.items(): + print("Found game with id {} at {}.".format(k, v)) diff --git a/games/game_da2.py b/games/game_da2.py index b4954b4..2aa691c 100644 --- a/games/game_da2.py +++ b/games/game_da2.py @@ -17,10 +17,11 @@ class DA2Game(BasicGame): GameSteamId = 1238040 GameOriginManifestIds = ["OFB-EAST:59474", "DR:201797000"] GameOriginWatcherExecutables = ("DragonAge2.exe",) + GameEaDesktopId = [70784, 1002980] def version(self): # Don't forget to import mobase! - return mobase.VersionInfo(1, 0, 0, mobase.ReleaseType.final) + return mobase.VersionInfo(1, 0, 1, mobase.ReleaseType.final) def savesDirectory(self): return QDir(self.documentsDirectory().absoluteFilePath("gamesaves")) diff --git a/games/game_dao.py b/games/game_dao.py index 6755da0..f7d835b 100644 --- a/games/game_dao.py +++ b/games/game_dao.py @@ -10,7 +10,7 @@ class DAOriginsGame(BasicGame): Name = "Dragon Age Origins Support Plugin" Author = "Patchier" - Version = "1.1.0" + Version = "1.1.1" GameName = "Dragon Age: Origins" GameShortName = "dragonage" @@ -20,6 +20,7 @@ class DAOriginsGame(BasicGame): GameSaveExtension = "das" GameSteamId = [17450, 47810] GameGogId = 1949616134 + GameEaDesktopId = [70377, 70843] def init(self, organizer: mobase.IOrganizer): super().init(organizer)