2023-12-02 16:45:16 +00:00
|
|
|
import os
|
|
|
|
from typing import Optional
|
2023-12-22 00:17:23 +00:00
|
|
|
import time
|
2023-12-02 16:45:16 +00:00
|
|
|
|
2023-12-03 18:30:57 +00:00
|
|
|
from lmm.config import LMMConfig
|
2023-12-02 16:45:16 +00:00
|
|
|
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
|
|
|
|
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
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
|
|
|
|
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
@click.group()
|
|
|
|
@click.pass_context
|
|
|
|
def cli(ctx):
|
|
|
|
if ctx.invoked_subcommand is None:
|
|
|
|
click.echo("Invoked without a subcommand")
|
|
|
|
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
@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
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
profile = game.get_profile_by_name(profile)
|
|
|
|
if profile is None:
|
|
|
|
click.echo("Profile not found")
|
|
|
|
return
|
|
|
|
|
2023-12-02 20:51:08 +00:00
|
|
|
# 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.
|
2023-12-02 16:46:48 +00:00
|
|
|
runner = None
|
2023-12-02 16:45:16 +00:00
|
|
|
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}")
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
# Launch the game
|
|
|
|
if mounts_ok:
|
2023-12-02 20:49:00 +00:00
|
|
|
runner.run(game)
|
|
|
|
game.wait()
|
2023-12-02 16:45:16 +00:00
|
|
|
|
2023-12-21 23:43:01 +00:00
|
|
|
# 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!")
|
2023-12-02 16:45:16 +00:00
|
|
|
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
@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:
|
2023-12-02 16:46:48 +00:00
|
|
|
click.echo(f'Unknown game "{game}"')
|
2023-12-02 16:45:16 +00:00
|
|
|
return
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
install(Path(path))
|
|
|
|
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
@cli.command()
|
|
|
|
@click.option("-g", "--game")
|
|
|
|
def mods(game: Optional[str]):
|
|
|
|
if game is not None:
|
|
|
|
games = [game]
|
|
|
|
else:
|
|
|
|
games = [
|
2023-12-02 16:46:48 +00:00
|
|
|
item
|
|
|
|
for item in os.listdir(LMM_GAMES_PATH)
|
|
|
|
if (LMM_GAMES_PATH / item).is_dir()
|
2023-12-02 16:45:16 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
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}")
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
if idx < len(games) - 1:
|
|
|
|
click.echo()
|
|
|
|
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
@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}")
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
if idx < len(games) - 1:
|
|
|
|
click.echo()
|
|
|
|
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
def main():
|
2023-12-03 18:30:57 +00:00
|
|
|
LMMConfig.init()
|
2023-12-02 16:45:16 +00:00
|
|
|
cli()
|
|
|
|
|
2023-12-02 16:46:48 +00:00
|
|
|
|
2023-12-02 16:45:16 +00:00
|
|
|
if __name__ == "__main__":
|
2023-12-02 16:46:48 +00:00
|
|
|
cli()
|