diff --git a/legendary/core.py b/legendary/core.py index c723000..6c38a26 100644 --- a/legendary/core.py +++ b/legendary/core.py @@ -339,15 +339,26 @@ class LegendaryCore: else: return SaveGameStatus.REMOTE_NEWER, (dt_local, dt_remote) - def upload_save(self, app_name, save_dir, local_dt: datetime = None): + def upload_save(self, app_name, save_dir, local_dt: datetime = None, + disable_filtering: bool = False): game = self.lgd.get_game_meta(app_name) - save_path = game.metadata['customAttributes'].get('CloudSaveFolder', {}).get('value') + custom_attr = game.metadata['customAttributes'] + save_path = custom_attr.get('CloudSaveFolder', {}).get('value') + + include_f = exclude_f = None + if not disable_filtering: + # get file inclusion and exclusion filters if they exist + if _include := custom_attr.get('CloudIncludeList', {}).get('value', None) is not None: + include_f = _include.split(',') + if _exclude := custom_attr.get('CloudExcludeList', {}).get('value', None) is not None: + exclude_f = _exclude.split(',') + if not save_path: raise ValueError('Game does not support cloud saves') sgh = SaveGameHelper() files = sgh.package_savegame(save_dir, app_name, self.egs.user.get('account_id'), - save_path, local_dt) + save_path, include_f, exclude_f, local_dt) self.log.debug(f'Packed files: {str(files)}, creating cloud files...') resp = self.egs.create_game_cloud_saves(app_name, list(files.keys())) @@ -630,7 +641,6 @@ class LegendaryCore: @staticmethod def check_installation_conditions(analysis: AnalysisResult, install: InstalledGame) -> ConditionCheckResult: - # ToDo add more checks in the future results = ConditionCheckResult(failures=set(), warnings=set()) # if on linux, check for eac in the files diff --git a/legendary/utils/savegame_helper.py b/legendary/utils/savegame_helper.py index dd7bc17..fc900ad 100644 --- a/legendary/utils/savegame_helper.py +++ b/legendary/utils/savegame_helper.py @@ -2,6 +2,7 @@ import logging import os from datetime import datetime +from fnmatch import fnmatch from hashlib import sha1 from io import BytesIO from tempfile import TemporaryFile @@ -11,6 +12,26 @@ from legendary.models.manifest import \ Manifest, ManifestMeta, CDL, FML, CustomFields, FileManifest, ChunkPart, ChunkInfo +def _filename_matches(filename, patterns): + """ + Helper to determine if a filename matches the filter patterns + + :param filename: name of the file + :param patterns: list of patterns to match against + :return: + """ + + for pattern in patterns: + if pattern.endswith('/'): + # pat is a directory, check if path starts with it + if filename.startswith(pattern): + return True + elif fnmatch(filename, pattern): + return True + + return False + + class SaveGameHelper: def __init__(self): self.files = dict() @@ -32,12 +53,16 @@ class SaveGameHelper: def package_savegame(self, input_folder: str, app_name: str = '', epic_id: str = '', cloud_folder: str = '', + include_filter: list = None, + exclude_filter: list = None, manifest_dt: datetime = None): """ :param input_folder: Folder to be packaged into chunks/manifest :param app_name: App name for savegame being stored :param epic_id: Epic account ID :param cloud_folder: Folder the savegame resides in (based on game metadata) + :param include_filter: list of patterns for files to include (excludes all others) + :param exclude_filter: list of patterns for files to exclude (includes all others) :param manifest_dt: datetime for the manifest name (optional) :return: """ @@ -57,7 +82,17 @@ class SaveGameHelper: files = [] for _dir, _, _files in os.walk(input_folder): for _file in _files: - files.append(os.path.join(_dir, _file)) + _file_path = os.path.join(_dir, _file) + _file_path_rel = os.path.relpath(_file_path, input_folder).replace('\\', '/') + + if include_filter and not _filename_matches(_file_path_rel, include_filter): + self.log.debug(f'Excluding "{_file_path_rel}" (does not match include filter)') + continue + elif exclude_filter and _filename_matches(_file_path_rel, exclude_filter): + self.log.debug(f'Excluding "{_file_path_rel}" (does match exclude filter)') + continue + + files.append(_file_path) chunk_num = 0 cur_chunk = None