1
0
Fork 0
mirror of synced 2024-06-02 18:54:41 +12:00
Rare/rare/utils/legendary_utils.py

195 lines
7.8 KiB
Python

import os
import platform
import shutil
from logging import getLogger
from sys import stdout
from PyQt5.QtCore import QProcess, QProcessEnvironment, QThread, pyqtSignal
from PyQt5.QtWidgets import QMessageBox
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import VerifyResult
from custom_legendary.utils.lfs import validate_files
logger = getLogger("Legendary Utils")
def launch_game(core, app_name: str, offline: bool = False, skip_version_check: bool = False):
game = core.get_installed_game(app_name)
if not game:
print("Game not found")
return None
if game.is_dlc:
print("Game is dlc")
return None
if not os.path.exists(game.install_path):
print("Game doesn't exist")
return None
if not offline:
if not skip_version_check and not core.is_noupdate_game(app_name):
# check updates
try:
latest = core.get_asset(app_name, update=True)
except ValueError:
print("Metadata doesn't exist")
return None
if latest.build_version != game.version:
print("Please update game")
return None
params, cwd, env = core.get_launch_parameters(app_name=app_name, offline=offline)
process = QProcess()
process.setProcessChannelMode(QProcess.MergedChannels)
process.setWorkingDirectory(cwd)
environment = QProcessEnvironment()
for e in env:
environment.insert(e, env[e])
process.setProcessEnvironment(environment)
return process, params
def uninstall(app_name: str, core: LegendaryCore, options=None):
if not options:
options = {"keep_files": False}
igame = core.get_installed_game(app_name)
# remove shortcuts link
if platform.system() == "Linux":
if os.path.exists(os.path.expanduser(f"~/Desktop/{igame.title}.desktop")):
os.remove(os.path.expanduser(f"~/Desktop/{igame.title}.desktop"))
if os.path.exists(os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop")):
os.remove(os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop"))
elif platform.system() == "Windows":
if os.path.exists(os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk")):
os.remove(os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk"))
elif os.path.exists(
os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk")):
os.remove(os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk"))
try:
# Remove DLC first so directory is empty when game uninstall runs
dlcs = core.get_dlc_for_game(app_name)
for dlc in dlcs:
if (idlc := core.get_installed_game(dlc.app_name)) is not None:
logger.info(f'Uninstalling DLC "{dlc.app_name}"...')
core.uninstall_game(idlc, delete_files=not options["keep_files"])
logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...')
core.uninstall_game(igame, delete_files=not options["keep_files"], delete_root_directory=True)
logger.info('Game has been uninstalled.')
if not options["keep_files"]:
shutil.rmtree(igame.install_path)
except Exception as e:
logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.')
logger.info("Removing sections in config file")
if core.lgd.config.has_section(app_name):
core.lgd.config.remove_section(app_name)
if core.lgd.config.has_section(app_name + ".env"):
core.lgd.config.remove_section(app_name + ".env")
core.lgd.save_config()
class VerifyThread(QThread):
status = pyqtSignal(tuple)
summary = pyqtSignal(tuple)
def __init__(self, core, app_name):
super(VerifyThread, self).__init__()
self.core, self.app_name = core, app_name
def run(self):
if not self.core.is_installed(self.app_name):
logger.error(f'Game "{self.app_name}" is not installed')
return
logger.info(f'Loading installed manifest for "{self.app_name}"')
igame = self.core.get_installed_game(self.app_name)
manifest_data, _ = self.core.get_installed_manifest(self.app_name)
manifest = self.core.load_manifest(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]
self.total = len(file_list)
self.num = 0
failed = []
missing = []
logger.info(f'Verifying "{igame.title}" version "{manifest.meta.build_version}"')
repair_file = []
try:
for result, path, result_hash in validate_files(igame.install_path, file_list):
self.status.emit((self.num, self.total, self.app_name))
self.num += 1
if result == VerifyResult.HASH_MATCH:
repair_file.append(f'{result_hash}:{path}')
continue
elif result == VerifyResult.HASH_MISMATCH:
logger.error(f'File does not match hash: "{path}"')
repair_file.append(f'{result_hash}:{path}')
failed.append(path)
elif result == VerifyResult.FILE_MISSING:
logger.error(f'File is missing: "{path}"')
missing.append(path)
else:
logger.error(f'Other failure (see log), treating file as missing: "{path}"')
missing.append(path)
except OSError as e:
QMessageBox.warning(None, "Error", self.tr("Path does not exist"))
logger.error(str(e))
except ValueError as e:
QMessageBox.warning(None, "Error", self.tr("No files to validate"))
logger.error(str(e))
stdout.write(f'Verification progress: {self.num}/{self.total} ({self.num * 100 / self.total:.01f}%)\t\n')
# always write repair file, even if all match
if repair_file:
repair_filename = os.path.join(self.core.lgd.get_tmp_path(), f'{self.app_name}.repair')
with open(repair_filename, 'w') as f:
f.write('\n'.join(repair_file))
logger.debug(f'Written repair file to "{repair_filename}"')
if not missing and not failed:
logger.info('Verification finished successfully.')
self.summary.emit((0, 0, self.app_name))
else:
logger.error(f'Verification finished, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
self.summary.emit((len(failed), len(missing), self.app_name))
def import_game(core: LegendaryCore, app_name: str, path: str):
logger.info("Import " + app_name)
game = core.get_game(app_name)
manifest, igame = core.import_game(game, path)
exe_path = os.path.join(path, manifest.meta.launch_exe.lstrip('/'))
total = len(manifest.file_manifest_list.elements)
found = sum(os.path.exists(os.path.join(path, f.filename))
for f in manifest.file_manifest_list.elements)
ratio = found / total
if not os.path.exists(exe_path):
logger.error(f"Game {game.app_title} failed to import")
return False
if ratio < 0.95:
logger.error(
"Game files are missing. It may be not the lates version ore it is corrupt")
return False
core.install_game(igame)
if igame.needs_verification:
logger.info(logger.info(
f'NOTE: The game installation will have to be verified before it can be updated '
f'with legendary. Run "legendary repair {app_name}" to do so.'))
logger.info("Successfully imported Game: " + game.app_title)
return True