mirror of
https://github.com/derrod/legendary.git
synced 2024-12-22 01:45:28 +00:00
[cli/utils/models] Add "verify-game" command to check game install
This commit is contained in:
parent
1622b415ea
commit
5b2ebada78
|
@ -17,8 +17,9 @@ from sys import exit, stdout
|
|||
from legendary import __version__, __codename__
|
||||
from legendary.core import LegendaryCore
|
||||
from legendary.models.exceptions import InvalidCredentialsError
|
||||
from legendary.models.game import SaveGameStatus
|
||||
from legendary.models.game import SaveGameStatus, VerifyResult
|
||||
from legendary.utils.custom_parser import AliasedSubParsersAction
|
||||
from legendary.utils.lfs import validate_files
|
||||
|
||||
# todo custom formatter for cli logger (clean info, highlighted error/warning)
|
||||
logging.basicConfig(
|
||||
|
@ -585,6 +586,47 @@ class LegendaryCLI:
|
|||
except Exception as e:
|
||||
logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.')
|
||||
|
||||
def verify_game(self, args):
|
||||
if not self.core.is_installed(args.app_name):
|
||||
logger.error(f'Game "{args.app_name}" is not installed')
|
||||
return
|
||||
|
||||
logger.info(f'Loading installed manifest for "{args.app_name}"')
|
||||
igame = self.core.get_installed_game(args.app_name)
|
||||
manifest_data, _ = self.core.get_installed_manifest(args.app_name)
|
||||
manifest = self.core.load_manfiest(manifest_data)
|
||||
|
||||
files = sorted(manifest.file_manifest_list.elements,
|
||||
key=lambda a: a.filename.lower())
|
||||
|
||||
# build list of hashes
|
||||
file_list = [(f.filename, f.sha_hash.hex()) for f in files]
|
||||
total = len(file_list)
|
||||
num = 0
|
||||
failed = []
|
||||
missing = []
|
||||
|
||||
for result, path in validate_files(igame.install_path, file_list):
|
||||
stdout.write(f'Verification progress: {num}/{total} ({num * 100 / total:.01f}%)\t\r')
|
||||
stdout.flush()
|
||||
num += 1
|
||||
|
||||
if result == VerifyResult.HASH_MATCH:
|
||||
continue
|
||||
elif result == VerifyResult.HASH_MISMATCH:
|
||||
logger.error(f'File does not match hash: "{path}"')
|
||||
failed.append(path)
|
||||
elif result == VerifyResult.FILE_MISSING:
|
||||
logger.error(f'File is missing: "{path}"')
|
||||
missing.append(path)
|
||||
|
||||
stdout.write(f'Verification progress: {num}/{total} ({num * 100 / total:.01f}%)\t\n')
|
||||
|
||||
if not missing and not failed:
|
||||
logger.info('Verification finished successfully.')
|
||||
else:
|
||||
logger.fatal(f'Verification failed, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=f'Legendary v{__version__} - "{__codename__}"')
|
||||
|
@ -611,6 +653,7 @@ def main():
|
|||
list_saves_parser = subparsers.add_parser('list-saves', help='List available cloud saves')
|
||||
download_saves_parser = subparsers.add_parser('download-saves', help='Download all cloud saves')
|
||||
sync_saves_parser = subparsers.add_parser('sync-saves', help='Sync cloud saves')
|
||||
verify_parser = subparsers.add_parser('verify-game', help='Verify a game\'s local files')
|
||||
|
||||
install_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>')
|
||||
uninstall_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>')
|
||||
|
@ -623,6 +666,7 @@ def main():
|
|||
help='Name of the app (optional)')
|
||||
sync_saves_parser.add_argument('app_name', nargs='?', metavar='<App Name>', default='',
|
||||
help='Name of the app (optional)')
|
||||
verify_parser.add_argument('app_name', help='Name of the app (optional)', metavar='<App Name>')
|
||||
|
||||
# importing only works on Windows right now
|
||||
if os.name == 'nt':
|
||||
|
@ -742,7 +786,7 @@ def main():
|
|||
|
||||
if args.subparser_name not in ('auth', 'list-games', 'list-installed', 'list-files',
|
||||
'launch', 'download', 'uninstall', 'install', 'update',
|
||||
'list-saves', 'download-saves', 'sync-saves'):
|
||||
'list-saves', 'download-saves', 'sync-saves', 'verify-game'):
|
||||
print(parser.format_help())
|
||||
|
||||
# Print the main help *and* the help for all of the subcommands. Thanks stackoverflow!
|
||||
|
@ -788,6 +832,8 @@ def main():
|
|||
cli.download_saves(args)
|
||||
elif args.subparser_name == 'sync-saves':
|
||||
cli.sync_saves(args)
|
||||
elif args.subparser_name == 'verify-game':
|
||||
cli.verify_game(args)
|
||||
except KeyboardInterrupt:
|
||||
logger.info('Command was aborted via KeyboardInterrupt, cleaning up...')
|
||||
|
||||
|
|
|
@ -129,3 +129,8 @@ class SaveGameStatus(Enum):
|
|||
SAME_AGE = 2
|
||||
NO_SAVE = 3
|
||||
|
||||
|
||||
class VerifyResult(Enum):
|
||||
HASH_MATCH = 0
|
||||
HASH_MISMATCH = 1
|
||||
FILE_MISSING = 2
|
||||
|
|
|
@ -5,7 +5,9 @@ import shutil
|
|||
import hashlib
|
||||
import logging
|
||||
|
||||
from typing import List
|
||||
from typing import List, Iterator
|
||||
|
||||
from legendary.models.game import VerifyResult
|
||||
|
||||
logger = logging.getLogger('LFS Utils')
|
||||
|
||||
|
@ -24,7 +26,7 @@ def delete_folder(path: str, recursive=True) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def validate_files(base_path: str, filelist: List[tuple], hash_type='sha1') -> list:
|
||||
def validate_files(base_path: str, filelist: List[tuple], hash_type='sha1') -> Iterator[tuple]:
|
||||
"""
|
||||
Validates the files in filelist in path against the provided hashes
|
||||
|
||||
|
@ -34,24 +36,18 @@ def validate_files(base_path: str, filelist: List[tuple], hash_type='sha1') -> l
|
|||
:return: list of files that failed hash check
|
||||
"""
|
||||
|
||||
failed = list()
|
||||
if not filelist:
|
||||
raise ValueError('No files to validate!')
|
||||
|
||||
if not os.path.exists(base_path):
|
||||
logger.error('Path does not exist!')
|
||||
failed.extend(i[0] for i in filelist)
|
||||
return failed
|
||||
|
||||
if not filelist:
|
||||
logger.info('No files to validate')
|
||||
return failed
|
||||
raise OSError('Path does not exist')
|
||||
|
||||
for file_path, file_hash in filelist:
|
||||
full_path = os.path.join(base_path, file_path)
|
||||
logger.debug(f'Checking "{file_path}"...')
|
||||
# logger.debug(f'Checking "{file_path}"...')
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
logger.warning(f'File "{full_path}" does not exist!')
|
||||
failed.append(file_path)
|
||||
yield VerifyResult.FILE_MISSING, file_path
|
||||
continue
|
||||
|
||||
with open(full_path, 'rb') as f:
|
||||
|
@ -60,10 +56,9 @@ def validate_files(base_path: str, filelist: List[tuple], hash_type='sha1') -> l
|
|||
real_file_hash.update(chunk)
|
||||
|
||||
if file_hash != real_file_hash.hexdigest():
|
||||
logger.error(f'Hash for "{full_path}" does not match!')
|
||||
failed.append(file_path)
|
||||
|
||||
return failed
|
||||
yield VerifyResult.HASH_MISMATCH, file_path
|
||||
else:
|
||||
yield VerifyResult.HASH_MATCH, file_path
|
||||
|
||||
|
||||
def clean_filename(filename):
|
||||
|
|
Loading…
Reference in a new issue