linux-mod-manager/lmm/cli.py

167 lines
4.2 KiB
Python

import os
from typing import Optional
import time
from lmm.config import LMMConfig
from lmm.const import LMM_GAMES_PATH
from lmm.games.config import load_game_configs
from lmm.games.game import Game
from lmm.games.bg3 import GAME_NAME as BG3
from lmm.mods.bg3 import install_mod as bg3_install_mod
import click
def get_game_by_name(name: str) -> Optional[Game]:
games = load_game_configs()
for game in games:
if game.name == name:
return game
return None
@click.group()
@click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
click.echo("Invoked without a subcommand")
@cli.command()
@click.option("-g", "--game", required=True)
@click.option("-p", "--profile", required=True)
def launch(game: str, profile: str):
game = get_game_by_name(game)
if game is None:
click.echo("Game not found")
return
profile = game.get_profile_by_name(profile)
if profile is None:
click.echo("Profile not found")
return
# Check if the game can start.
if not game.can_start():
click.echo("Cannot start game:")
for issue in game.cannot_start_reasons():
click.echo(issue)
return
# Check if we have a runner available.
runner = None
if profile.runner is not None:
runner = profile.runner
elif game.default_runner is not None:
runner = game.default_runner
if runner is None:
click.echo("Cannot launch profile as no runner or default runner is configured")
return
# Prepare mounts
mounts_ok = True
mounts = game.prepare_overlays(profile)
for mount in mounts:
result = mount.mount()
if result is None:
click.echo("Mount failed")
mounts_ok = False
break
print(f"Mounted overlayfs at {result}")
# Launch the game
if mounts_ok:
runner.run(game)
game.wait()
# Unmount (with retries)
failed = False
for retry in range(5):
failed = any(not mount.unmount() for mount in mounts)
if not failed:
break
print(f"Unmounting failed ({retry +1 }/5). Waiting 3s...")
time.sleep(3)
if failed:
print("Unmounting failed!")
@cli.command()
@click.option("-g", "--game", required=True)
@click.option("-P", "--path", required=True)
def install(game: str, path: str):
install = {
BG3: bg3_install_mod,
}.get(game, None)
if install is None:
click.echo(f'Unknown game "{game}"')
return
install(Path(path))
@cli.command()
@click.option("-g", "--game")
def mods(game: Optional[str]):
if game is not None:
games = [game]
else:
games = [
item
for item in os.listdir(LMM_GAMES_PATH)
if (LMM_GAMES_PATH / item).is_dir()
]
for idx, game_name in enumerate(games):
click.echo(f"Installed mods for {game_name}:")
for item in os.listdir(LMM_GAMES_PATH / game_name):
path = LMM_GAMES_PATH / game_name / item
if not path.is_dir():
continue
click.echo(f"- {item}")
if idx < len(games) - 1:
click.echo()
@cli.command
def list():
games = load_game_configs()
for idx, game in enumerate(games):
if game.default_runner is not None:
default_runner_name = game.default_runner.__class__.__name__
else:
default_runner_name = "None"
click.echo(f"- {game.name}")
click.echo(f" {game.installation_path}")
click.echo(f" Can start: {game.can_start()}")
click.echo(f" Default runner: {default_runner_name}")
click.echo(" Profiles:")
for profile in game.profiles:
mods = ", ".join(profile.get_mod_names())
if profile.runner is not None:
runner_name = profile.runner.__class__.__name__
else:
runner_name = "None"
click.echo(f" - {profile.name}")
click.echo(f" Runner: {runner_name}")
click.echo(f" Mods: {mods}")
if idx < len(games) - 1:
click.echo()
def main():
LMMConfig.init()
cli()
if __name__ == "__main__":
cli()