[cli/core/models] Add basic support for DLCs

This commit is contained in:
derrod 2020-04-18 02:11:58 +02:00
parent 0031e5908a
commit 662f6e7bd0
3 changed files with 93 additions and 16 deletions

View file

@ -151,12 +151,14 @@ def main():
if not core.login():
logger.error('Login failed, cannot continue!')
exit(1)
logger.info('Getting game list...')
games = core.get_game_list()
logger.info('Getting game list... (this may take a while)')
games, dlc_list = core.get_game_and_dlc_list()
print('\nAvailable games:')
for game in sorted(games, key=lambda x: x.app_title):
print(f' * {game.app_title} (App name: {game.app_name}, version: {game.app_version})')
for dlc in sorted(dlc_list[game.asset_info.catalog_item_id], key=lambda d: d.app_title):
print(f' + {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})')
print(f'\nTotal: {len(games)}')
@ -185,6 +187,10 @@ def main():
logger.error(f'Game {app_name} is not currently installed!')
exit(1)
if core.is_dlc(app_name):
logger.error(f'{app_name} is DLC; please launch the base game instead!')
exit(1)
if not args.offline and not core.is_offline_game(app_name):
logger.info('Logging in...')
if not core.login():
@ -233,9 +239,23 @@ def main():
logger.fatal(f'Could not find "{target_app}" in list of available games, did you type the name correctly?')
exit(1)
if game.is_dlc:
logger.info('Install candidate is DLC')
app_name = game.metadata['mainGameItem']['releaseInfo'][0]['appId']
base_game = core.get_game(app_name)
# check if base_game is actually installed
if not core.get_installed_game(app_name):
# download mode doesn't care about whether or not something's installed
if args.install or args.update:
logger.fatal(f'Base game "{app_name}" is not installed!')
exit(1)
else:
base_game = None
# todo use status queue to print progress from CLI
dlm, analysis, igame = core.prepare_download(game=game, base_path=args.base_path, force=args.force,
max_shm=args.shared_memory, max_workers=args.max_workers,
dlm, analysis, igame = core.prepare_download(game=game, base_game=base_game, base_path=args.base_path,
force=args.force, max_shm=args.shared_memory,
max_workers=args.max_workers,
disable_patching=args.disable_patching,
override_manifest=args.override_manifest,
override_base_url=args.override_base_url)
@ -284,6 +304,15 @@ def main():
end_t = time.time()
if args.install or args.update:
postinstall = core.install_game(igame)
dlcs = core.get_dlc_for_game(game.app_name)
if dlcs:
print('The following DLCs are available for this game:')
for dlc in dlcs:
print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})')
print('Installing DLCs works the same as the main game, just use the DLC app name instead.')
print('Automatic installation of DLC is currently not supported.')
if postinstall:
logger.info('This game lists the following prequisites to be installed:')
logger.info(f'{postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}')
@ -307,10 +336,21 @@ def main():
igame = core.get_installed_game(target_app)
if not igame:
logger.error(f'Game {target_app} not installed, cannot uninstall!')
if igame.is_dlc:
logger.error('Uninstalling DLC is not supported.')
exit(1)
try:
logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...')
core.uninstall_game(igame)
dlcs = core.get_dlc_for_game(igame.app_name)
for dlc in dlcs:
idlc = core.get_installed_game(dlc.app_name)
if core.is_installed(dlc.app_name):
logger.info(f'Uninstalling DLC "{dlc.app_name}"...')
core.uninstall_game(idlc, delete_files=False)
logger.info('Game has been uninstalled.')
except Exception as e:
logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.')

View file

@ -8,10 +8,11 @@ import shlex
import shutil
from base64 import b64decode
from collections import defaultdict
from datetime import datetime
from random import choice as randchoice
from requests.exceptions import HTTPError
from typing import List
from typing import List, Dict
from legendary.api.egs import EPCAPI
from legendary.downloader.manager import DLManager
@ -141,7 +142,11 @@ class LegendaryCore:
return self.lgd.get_game_meta(app_name)
def get_game_list(self, update_assets=True) -> List[Game]:
return self.get_game_and_dlc_list(update_assets=update_assets)[0]
def get_game_and_dlc_list(self, update_assets=True) -> (List[Game], Dict[str, Game]):
_ret = []
_dlc = defaultdict(list)
for ga in self.get_assets(update_assets=update_assets):
if ga.namespace == 'ue': # skip UE demo content
@ -156,12 +161,24 @@ class LegendaryCore:
game = Game(app_name=ga.app_name, app_version=ga.build_version,
app_title=eg_meta['title'], asset_info=ga, metadata=eg_meta)
self.lgd.set_game_meta(game.app_name, game)
_ret.append(game)
return _ret
if game.is_dlc:
_dlc[game.metadata['mainGameItem']['id']].append(game)
else:
_ret.append(game)
return _ret, _dlc
def get_dlc_for_game(self, app_name):
game = self.get_game(app_name)
_, dlcs = self.get_game_and_dlc_list(update_assets=False)
return dlcs[game.asset_info.catalog_item_id]
def get_installed_list(self) -> List[InstalledGame]:
return self.lgd.get_installed_list()
return [g for g in self.lgd.get_installed_list() if not g.is_dlc]
def get_installed_dlc_list(self) -> List[InstalledGame]:
return [g for g in self.lgd.get_installed_list() if g.is_dlc]
def get_installed_game(self, app_name) -> InstalledGame:
return self.lgd.get_installed_game(app_name)
@ -254,6 +271,12 @@ class LegendaryCore:
def is_installed(self, app_name: str) -> bool:
return self.lgd.get_installed_game(app_name) is not None
def is_dlc(self, app_name: str) -> bool:
meta = self.lgd.get_game_meta(app_name)
if not meta:
raise ValueError('Game unknown!')
return meta.is_dlc
@staticmethod
def load_manfiest(data: bytes) -> Manifest:
if data[0:1] == b'{':
@ -261,7 +284,7 @@ class LegendaryCore:
else:
return Manifest.read_all(data)
def prepare_download(self, game: Game, base_path: str = '',
def prepare_download(self, game: Game, base_game: Game = None, base_path: str = '',
max_shm: int = 0, max_workers: int = 0, force: bool = False,
disable_patching: bool = False, override_manifest: str = '',
override_base_url: str = '') -> (DLManager, AnalysisResult, ManifestMeta):
@ -323,10 +346,17 @@ class LegendaryCore:
if not base_path:
base_path = self.get_default_install_dir()
install_path = os.path.join(
base_path,
game.metadata.get('customAttributes', {}).get('FolderName', {}).get('value', game.app_name)
)
if game.is_dlc:
install_path = os.path.join(
base_path,
base_game.metadata.get('customAttributes', {}).get('FolderName', {}).get('value', game.app_name)
)
else:
install_path = os.path.join(
base_path,
game.metadata.get('customAttributes', {}).get('FolderName', {}).get('value', game.app_name)
)
if not os.path.exists(install_path):
os.makedirs(install_path)
@ -362,7 +392,8 @@ class LegendaryCore:
prereq_info=prereq, manifest_path=override_manifest, base_urls=base_urls,
install_path=install_path, executable=new_manifest.meta.launch_exe,
launch_parameters=new_manifest.meta.launch_command,
can_run_offline=offline == 'true', requires_ot=ot == 'true')
can_run_offline=offline == 'true', requires_ot=ot == 'true',
is_dlc=base_game is not None)
return dlm, anlres, igame

View file

@ -47,6 +47,10 @@ class Game:
self.app_title = app_title
self.base_urls = [] # base urls for download, only really used when cached manifest is current
@property
def is_dlc(self):
return self.metadata and 'mainGameItem' in self.metadata
@classmethod
def from_json(cls, json):
tmp = cls()
@ -69,7 +73,7 @@ class Game:
class InstalledGame:
def __init__(self, app_name='', title='', version='', manifest_path='', base_urls=None,
install_path='', executable='', launch_parameters='', prereq_info=None,
can_run_offline=False, requires_ot=False):
can_run_offline=False, requires_ot=False, is_dlc=False):
self.app_name = app_name
self.title = title
self.version = version
@ -82,6 +86,7 @@ class InstalledGame:
self.prereq_info = prereq_info
self.can_run_offline = can_run_offline
self.requires_ot = requires_ot
self.is_dlc = is_dlc
@classmethod
def from_json(cls, json):
@ -97,6 +102,7 @@ class InstalledGame:
tmp.launch_parameters = json.get('launch_parameters', '')
tmp.prereq_info = json.get('prereq_info', None)
tmp.can_run_offline = json.get('can_run_offline', True)
tmp.can_run_offline = json.get('can_run_offline', False)
tmp.requires_ot = json.get('requires_ot', False)
tmp.is_dlc = json.get('is_dlc', False)
return tmp