From 5e061d69468b6f2e7b53271fde27fac61376bb61 Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 16 Dec 2020 11:18:58 +0100 Subject: [PATCH] [core/utils] Add save path resolution on Linux If a wine prefix is specified in the config, attempt to find savegames in there. --- legendary/core.py | 39 +++++++++++++++++++++++++++------ legendary/utils/wine_helpers.py | 17 ++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 legendary/utils/wine_helpers.py diff --git a/legendary/core.py b/legendary/core.py index 4a5cc90..165cf28 100644 --- a/legendary/core.py +++ b/legendary/core.py @@ -32,6 +32,7 @@ from legendary.models.chunk import Chunk from legendary.utils.game_workarounds import is_opt_enabled from legendary.utils.savegame_helper import SaveGameHelper from legendary.utils.manifests import combine_manifests +from legendary.utils.wine_helpers import read_registry, get_shell_folders # ToDo: instead of true/false return values for success/failure actually raise an exception that the CLI/GUI @@ -412,19 +413,43 @@ class LegendaryCore: # the following variables are known: path_vars = { - '{appdata}': os.path.expandvars('%APPDATA%'), '{installdir}': igame.install_path, - '{userdir}': os.path.expandvars('%userprofile%/documents'), '{epicid}': self.lgd.userdata['account_id'] } - # the following variables are in the EGL binary but are not used by any of - # my games and I'm not sure where they actually point at: - # {UserProfile} (Probably %USERPROFILE%) - # {UserSavedGames} + + if os.name == 'nt': + path_vars.update({ + '{appdata}': os.path.expandvars('%APPDATA%'), + '{userdir}': os.path.expandvars('%userprofile%/documents'), + # '{userprofile}': os.path.expandvars('%userprofile%'), # possibly wrong + '{usersavedgames}': os.path.expandvars('%userprofile%/Saved Games') + }) + else: + # attempt to get WINE prefix from config + wine_pfx = self.lgd.config.get(app_name, 'wine_prefix', fallback=None) + if not wine_pfx: + wine_pfx = self.lgd.config.get(f'{app_name}.env', 'WINEPREFIX', fallback=None) + if not wine_pfx: + proton_pfx = self.lgd.config.get(f'{app_name}.env', 'STEAM_COMPAT_DATA_PATH', fallback=None) + if proton_pfx: + wine_pfx = f'{proton_pfx}/pfx' + + # if we have a prefix, read the `user.reg` file and get the proper paths. + if wine_pfx: + wine_reg = read_registry(wine_pfx) + wine_folders = get_shell_folders(wine_reg, wine_pfx) + # path_vars['{userprofile}'] = user_path + path_vars['{appdata}'] = wine_folders['AppData'] + # this maps to ~/Documents, but the name is locale-dependent so just resolve the symlink from WINE + path_vars['{userdir}'] = os.path.realpath(wine_folders['Personal']) + path_vars['{usersavedgames}'] = wine_folders['4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4'] + + # replace backslashes + save_path = save_path.replace('\\', '/') # these paths should always use a forward slash new_save_path = [path_vars.get(p.lower(), p) for p in save_path.split('/')] - return os.path.join(*new_save_path) + return os.path.realpath(os.path.join(*new_save_path)) def check_savegame_state(self, path: str, save: SaveGameFile) -> (SaveGameStatus, (datetime, datetime)): latest = 0 diff --git a/legendary/utils/wine_helpers.py b/legendary/utils/wine_helpers.py new file mode 100644 index 0000000..23c54ab --- /dev/null +++ b/legendary/utils/wine_helpers.py @@ -0,0 +1,17 @@ +import os +import configparser + + +def read_registry(wine_pfx): + reg = configparser.ConfigParser(comment_prefixes=(';', '#', '/', 'WINE'), allow_no_value=True) + reg.optionxform = str + reg.read(os.path.join(wine_pfx, 'user.reg')) + return reg + + +def get_shell_folders(registry, wine_pfx): + folders = dict() + for k, v in registry['Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\Shell Folders'].items(): + path_cleaned = v.strip('"').strip().replace('\\\\', '/').replace('C:/', '') + folders[k.strip('"').strip()] = os.path.join(wine_pfx, 'drive_c', path_cleaned) + return folders