Add black

This commit is contained in:
PapaTutuWawa 2023-12-02 17:46:48 +01:00
parent d352e70956
commit b0d801f8a1
16 changed files with 199 additions and 88 deletions

View File

@ -9,6 +9,7 @@ from lmm.mods.bg3 import install_mod as bg3_install_mod
import click import click
def get_game_by_name(name: str) -> Optional[Game]: def get_game_by_name(name: str) -> Optional[Game]:
games = load_game_configs() games = load_game_configs()
for game in games: for game in games:
@ -16,12 +17,14 @@ def get_game_by_name(name: str) -> Optional[Game]:
return game return game
return None return None
@click.group() @click.group()
@click.pass_context @click.pass_context
def cli(ctx): def cli(ctx):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
click.echo("Invoked without a subcommand") click.echo("Invoked without a subcommand")
@cli.command() @cli.command()
@click.option("-g", "--game", required=True) @click.option("-g", "--game", required=True)
@click.option("-p", "--profile", required=True) @click.option("-p", "--profile", required=True)
@ -67,6 +70,7 @@ def launch(game: str, profile: str):
for mount in mounts: for mount in mounts:
mount.unmount() mount.unmount()
@cli.command() @cli.command()
@click.option("-g", "--game", required=True) @click.option("-g", "--game", required=True)
@click.option("-P", "--path", required=True) @click.option("-P", "--path", required=True)
@ -75,11 +79,12 @@ def install(game: str, path: str):
BG3: bg3_install_mod, BG3: bg3_install_mod,
}.get(game, None) }.get(game, None)
if install is None: if install is None:
click.echo(f"Unknown game \"{game}\"") click.echo(f'Unknown game "{game}"')
return return
install(Path(path)) install(Path(path))
@cli.command() @cli.command()
@click.option("-g", "--game") @click.option("-g", "--game")
def mods(game: Optional[str]): def mods(game: Optional[str]):
@ -87,7 +92,9 @@ def mods(game: Optional[str]):
games = [game] games = [game]
else: else:
games = [ games = [
item for item in os.listdir(LMM_GAMES_PATH) if (LMM_GAMES_PATH / item).is_dir() item
for item in os.listdir(LMM_GAMES_PATH)
if (LMM_GAMES_PATH / item).is_dir()
] ]
for idx, game_name in enumerate(games): for idx, game_name in enumerate(games):
@ -102,6 +109,7 @@ def mods(game: Optional[str]):
if idx < len(games) - 1: if idx < len(games) - 1:
click.echo() click.echo()
@cli.command @cli.command
def list(): def list():
games = load_game_configs() games = load_game_configs()
@ -130,8 +138,10 @@ def list():
if idx < len(games) - 1: if idx < len(games) - 1:
click.echo() click.echo()
def main(): def main():
cli() cli()
if __name__ == "__main__": if __name__ == "__main__":
cli() cli()

View File

@ -3,6 +3,7 @@ import subprocess
from typing import Optional from typing import Optional
from pathlib import Path from pathlib import Path
def run_cmd(args: list[str], cd: Optional[Path] = None) -> int: def run_cmd(args: list[str], cd: Optional[Path] = None) -> int:
cmdline = " ".join(args) cmdline = " ".join(args)
if cd is not None: if cd is not None:
@ -16,17 +17,20 @@ def run_cmd(args: list[str], cd: Optional[Path] = None) -> int:
os.chdir(cwd) os.chdir(cwd)
return ret return ret
def run_sudo_cmd(args: list[str], cd: Optional[Path] = None) -> int: def run_sudo_cmd(args: list[str], cd: Optional[Path] = None) -> int:
return run_cmd( return run_cmd(
[ [
"pkexec", "pkexec",
"--user", "root", "--user",
"root",
"--keep-cwd", "--keep-cwd",
*args, *args,
], ],
cd=cd, cd=cd,
) )
def run_cmd_shell(cmd: str) -> None: def run_cmd_shell(cmd: str) -> None:
print("Executing:", cmd) print("Executing:", cmd)
subprocess.run( subprocess.run(
@ -34,6 +38,7 @@ def run_cmd_shell(cmd: str) -> None:
shell=True, shell=True,
) )
def run_cmd_nonblocking(args: list[str]) -> subprocess.Popen: def run_cmd_nonblocking(args: list[str]) -> subprocess.Popen:
cmdline = " ".join(args) cmdline = " ".join(args)
print("Executing:", cmdline) print("Executing:", cmdline)

View File

@ -12,6 +12,7 @@ from lmm.runners.base import runner_from_config
GAME_NAME = "BaldursGate3" GAME_NAME = "BaldursGate3"
class BaldursGate3ModType(Enum): class BaldursGate3ModType(Enum):
# Override the game files # Override the game files
ROOT = 1 ROOT = 1
@ -19,6 +20,7 @@ class BaldursGate3ModType(Enum):
# Override the Paks in the compatdata prefix # Override the Paks in the compatdata prefix
PAK = 2 PAK = 2
@dataclass @dataclass
class BaldursGate3Mod: class BaldursGate3Mod:
# The directory name # The directory name
@ -36,7 +38,9 @@ class BaldursGate3Profile(Profile):
super().__init__(GAME_NAME, name, **kwargs) super().__init__(GAME_NAME, name, **kwargs)
self.mods = mods self.mods = mods
def get_mods(self, type: BaldursGate3ModType) -> Generator[BaldursGate3Mod, None, None]: def get_mods(
self, type: BaldursGate3ModType
) -> Generator[BaldursGate3Mod, None, None]:
"""Yields all mods of type @type""" """Yields all mods of type @type"""
for mod in self.mods: for mod in self.mods:
if not mod.type == type: if not mod.type == type:
@ -51,6 +55,7 @@ class BaldursGate3Profile(Profile):
def get_mod_names(self) -> list[str]: def get_mod_names(self) -> list[str]:
return [mod.name for mod in self.mods] return [mod.name for mod in self.mods]
class BaldursGate3Game(ProtonGame): class BaldursGate3Game(ProtonGame):
def __init__(self, profiles: list[BaldursGate3Profile], **kwargs): def __init__(self, profiles: list[BaldursGate3Profile], **kwargs):
super().__init__(GAME_NAME, "1086940", profiles, **kwargs) super().__init__(GAME_NAME, "1086940", profiles, **kwargs)
@ -65,7 +70,18 @@ class BaldursGate3Game(ProtonGame):
@property @property
def user_mods_path(self) -> Path: def user_mods_path(self) -> Path:
return self.compat_path / "pfx" / "drive_c" / "users" / "steamuser" / "AppData" / "Local" / "Larian Studios" / "Baldur's Gate 3" / "Mods" return (
self.compat_path
/ "pfx"
/ "drive_c"
/ "users"
/ "steamuser"
/ "AppData"
/ "Local"
/ "Larian Studios"
/ "Baldur's Gate 3"
/ "Mods"
)
def can_start(self) -> bool: def can_start(self) -> bool:
return self.user_mods_path.exists() and self.installation_path.exists() return self.user_mods_path.exists() and self.installation_path.exists()
@ -87,9 +103,7 @@ class BaldursGate3Game(ProtonGame):
if profile.has_mods_of_type(BaldursGate3ModType.ROOT): if profile.has_mods_of_type(BaldursGate3ModType.ROOT):
mods = list(profile.get_mods(BaldursGate3ModType.ROOT)) mods = list(profile.get_mods(BaldursGate3ModType.ROOT))
mod_paths = [ mod_paths = [LMM_GAMES_PATH / GAME_NAME / mod.name for mod in mods]
LMM_GAMES_PATH / GAME_NAME / mod.name for mod in mods
]
overlays.append( overlays.append(
OverlayFSMount( OverlayFSMount(
self.installation_path, self.installation_path,
@ -102,9 +116,7 @@ class BaldursGate3Game(ProtonGame):
print(f"Loaded root mods: {mod_names}") print(f"Loaded root mods: {mod_names}")
if profile.has_mods_of_type(BaldursGate3ModType.PAK): if profile.has_mods_of_type(BaldursGate3ModType.PAK):
mods = list(profile.get_mods(BaldursGate3ModType.PAK)) mods = list(profile.get_mods(BaldursGate3ModType.PAK))
mod_paths = [ mod_paths = [os.path.join("./", mod.name) for mod in mods]
os.path.join("./", mod.name) for mod in mods
]
# Merge all mods # Merge all mods
overlays.append( overlays.append(
@ -134,9 +146,11 @@ class BaldursGate3Game(ProtonGame):
{ {
"name": mod.name, "name": mod.name,
"type": mod.type.value, "type": mod.type.value,
} for mod in profile.mods }
] for mod in profile.mods
} for profile in self.profiles ],
}
for profile in self.profiles
] ]
} }
@ -149,7 +163,9 @@ class BaldursGate3Game(ProtonGame):
mods.append( mods.append(
BaldursGate3Mod( BaldursGate3Mod(
mod["name"], mod["name"],
BaldursGate3ModType.ROOT if mod["type"] == 1 else BaldursGate3ModType.PAK, BaldursGate3ModType.ROOT
if mod["type"] == 1
else BaldursGate3ModType.PAK,
), ),
) )

View File

@ -9,9 +9,11 @@ from lmm.overlayfs import OverlayFSMount
from lmm.profile import Profile from lmm.profile import Profile
from lmm.steam import find_library_folder_for_game from lmm.steam import find_library_folder_for_game
class CannotStartReason: class CannotStartReason:
pass pass
@dataclass @dataclass
class PathNotExistingReason(CannotStartReason): class PathNotExistingReason(CannotStartReason):
# The path that is not existing # The path that is not existing
@ -20,6 +22,7 @@ class PathNotExistingReason(CannotStartReason):
def __str__(self) -> str: def __str__(self) -> str:
return f"Path {self.path} does not exist" return f"Path {self.path} does not exist"
class Game(abc.ABC): class Game(abc.ABC):
# The name of the game. # The name of the game.
name: str name: str
@ -30,7 +33,9 @@ class Game(abc.ABC):
# The default runner to use when launching the game. # The default runner to use when launching the game.
default_runner: Optional["Runner"] default_runner: Optional["Runner"]
def __init__(self, name: str, profiles: list[Profile], default_runner: Optional["Runner"]): def __init__(
self, name: str, profiles: list[Profile], default_runner: Optional["Runner"]
):
self.name = name self.name = name
self.profiles = profiles self.profiles = profiles
self.default_runner = default_runner self.default_runner = default_runner
@ -63,6 +68,7 @@ class Game(abc.ABC):
def wait(self): def wait(self):
return None return None
class ProtonGame(Game, abc.ABC): class ProtonGame(Game, abc.ABC):
# The appid of the game in Steam. # The appid of the game in Steam.
appid: str appid: str
@ -70,7 +76,13 @@ class ProtonGame(Game, abc.ABC):
# The PID of the game. # The PID of the game.
_pid: Optional[str] _pid: Optional[str]
def __init__(self, name: str, appid: str, profiles: list[Profile], default_runner: Optional["Runner"]): def __init__(
self,
name: str,
appid: str,
profiles: list[Profile],
default_runner: Optional["Runner"],
):
super().__init__(name, profiles, default_runner) super().__init__(name, profiles, default_runner)
self._pid = None self._pid = None
self.appid = appid self.appid = appid
@ -106,7 +118,9 @@ class ProtonGame(Game, abc.ABC):
# Workaround for games like Baldur's Gate 3 that have a relative path in the cmdline. # Workaround for games like Baldur's Gate 3 that have a relative path in the cmdline.
# Note that the cmdline contains null bytes that we have to remove. # Note that the cmdline contains null bytes that we have to remove.
abs_cmdline = os.path.abspath(cmdline).replace("\x00", "") abs_cmdline = os.path.abspath(cmdline).replace("\x00", "")
if cmdline.startswith(wine_exe_path) or abs_cmdline == str(self.game_executable): if cmdline.startswith(wine_exe_path) or abs_cmdline == str(
self.game_executable
):
self._pid = dir self._pid = dir
break break
except: except:

View File

@ -9,6 +9,7 @@ from lmm.runners.base import runner_from_config
GAME_NAME = "ProjectDivaMegaMix" GAME_NAME = "ProjectDivaMegaMix"
class ProjectDivaMegaMixProfile(Profile): class ProjectDivaMegaMixProfile(Profile):
# The names of the directories the the mods directory. # The names of the directories the the mods directory.
mods: list[str] mods: list[str]
@ -21,19 +22,25 @@ class ProjectDivaMegaMixProfile(Profile):
def get_mod_names(self) -> list[str]: def get_mod_names(self) -> list[str]:
return self.mods return self.mods
class ProjectDivaMegaMixGame(ProtonGame): class ProjectDivaMegaMixGame(ProtonGame):
def __init__(self, profiles: list[ProjectDivaMegaMixProfile], **kwargs): def __init__(self, profiles: list[ProjectDivaMegaMixProfile], **kwargs):
super().__init__(GAME_NAME, "1761390", profiles, **kwargs) super().__init__(GAME_NAME, "1761390", profiles, **kwargs)
@property @property
def installation_path(self) -> Path: def installation_path(self) -> Path:
return self.steam_library / "steamapps/common/Hatsune Miku Project DIVA Mega Mix Plus" return (
self.steam_library
/ "steamapps/common/Hatsune Miku Project DIVA Mega Mix Plus"
)
@property @property
def game_executable(self) -> Path: def game_executable(self) -> Path:
return self.installation_path / "DivaMegaMix.exe" return self.installation_path / "DivaMegaMix.exe"
def prepare_overlays(self, profile: ProjectDivaMegaMixProfile) -> list[OverlayFSMount]: def prepare_overlays(
self, profile: ProjectDivaMegaMixProfile
) -> list[OverlayFSMount]:
return [ return [
OverlayFSMount( OverlayFSMount(
upper=self.installation_path, upper=self.installation_path,
@ -51,9 +58,11 @@ class ProjectDivaMegaMixGame(ProtonGame):
"mods": [ "mods": [
{ {
"name": mod, "name": mod,
} for mod in profile.mods }
] for mod in profile.mods
} for profile in self.profiles ],
}
for profile in self.profiles
] ]
} }
@ -61,9 +70,7 @@ class ProjectDivaMegaMixGame(ProtonGame):
def from_dict(cls, data: dict[str, Any]) -> "ProjectDivaMegaMixGame": def from_dict(cls, data: dict[str, Any]) -> "ProjectDivaMegaMixGame":
profiles = [] profiles = []
for profile in data["profiles"]: for profile in data["profiles"]:
mods = [ mods = [mod["name"] for mod in profile["mods"]]
mod["name"] for mod in profile["mods"]
]
if "runner" in profile: if "runner" in profile:
runner = runner_from_config(profile["runner"]) runner = runner_from_config(profile["runner"])

View File

@ -7,6 +7,7 @@ from lmm.games.game import ProtonGame
GAME_NAME = "ReadyOrNot" GAME_NAME = "ReadyOrNot"
class ReadyOrNotProfile(Profile): class ReadyOrNotProfile(Profile):
# Names of directories inside the game's mods directory. # Names of directories inside the game's mods directory.
mods: list[str] mods: list[str]
@ -18,6 +19,7 @@ class ReadyOrNotProfile(Profile):
def get_mod_names(self) -> list[str]: def get_mod_names(self) -> list[str]:
return self.mods return self.mods
class ReadyOrNotGame(ProtonGame): class ReadyOrNotGame(ProtonGame):
def __init__(self, profiles: list[ReadyOrNotProfile], **kwargs): def __init__(self, profiles: list[ReadyOrNotProfile], **kwargs):
super().__init__(GAME_NAME, "1144200", profiles, **kwargs) super().__init__(GAME_NAME, "1144200", profiles, **kwargs)
@ -55,9 +57,11 @@ class ReadyOrNotGame(ProtonGame):
"mods": [ "mods": [
{ {
"name": mod, "name": mod,
} for mod in profile.mods }
] for mod in profile.mods
} for profile in self.profiles ],
}
for profile in self.profiles
] ]
} }
@ -65,9 +69,7 @@ class ReadyOrNotGame(ProtonGame):
def from_dict(cls, data: dict[str, Any]) -> "ReadyOrNotGame": def from_dict(cls, data: dict[str, Any]) -> "ReadyOrNotGame":
profiles = [] profiles = []
for profile in data["profiles"]: for profile in data["profiles"]:
mods = [ mods = [mod["name"] for mod in profile["mods"]]
mod["name"] for mod in profile["mods"]
]
if "runner" in profile: if "runner" in profile:
runner = runner_from_config(profile["runner"]) runner = runner_from_config(profile["runner"])

View File

@ -5,6 +5,7 @@ import zipfile
from lmm.const import LMM_GAMES_PATH from lmm.const import LMM_GAMES_PATH
from lmm.games.bg3 import GAME_NAME from lmm.games.bg3 import GAME_NAME
def install_mod(mod: Path): def install_mod(mod: Path):
match mod.suffix: match mod.suffix:
case ".zip": case ".zip":
@ -12,6 +13,7 @@ def install_mod(mod: Path):
case _: case _:
print("Unknown mod") print("Unknown mod")
def install_zip_mod(mod: Path): def install_zip_mod(mod: Path):
# Inspect the archive to see if it's a PAK mod # Inspect the archive to see if it's a PAK mod
with zipfile.ZipFile(mod, "r") as f: with zipfile.ZipFile(mod, "r") as f:

View File

@ -4,6 +4,7 @@ from typing import Optional
from lmm.cmd import run_sudo_cmd from lmm.cmd import run_sudo_cmd
def compute_workdirs_location(mount: Path) -> Path: def compute_workdirs_location(mount: Path) -> Path:
with open("/proc/mounts", "r") as f: with open("/proc/mounts", "r") as f:
mounts = f.read().split("\n") mounts = f.read().split("\n")
@ -35,7 +36,13 @@ class OverlayFSMount:
cd: Optional[Path] cd: Optional[Path]
def __init__(self, upper: Optional[Path], lower: list[Path | str], mount: Path, cd: Optional[Path] = None): def __init__(
self,
upper: Optional[Path],
lower: list[Path | str],
mount: Path,
cd: Optional[Path] = None,
):
self.upper = upper self.upper = upper
self.lower = lower self.lower = lower
self.mount_path = mount self.mount_path = mount
@ -58,9 +65,7 @@ class OverlayFSMount:
return Path(self.workdir.name) return Path(self.workdir.name)
def mount(self) -> Optional[Path]: def mount(self) -> Optional[Path]:
lower = ":".join([ lower = ":".join([str(l) for l in self.lower])
str(l) for l in self.lower
])
options = f"lowerdir={lower}" options = f"lowerdir={lower}"
if self.upper is not None: if self.upper is not None:
workdir = self.create_workdir() workdir = self.create_workdir()
@ -69,8 +74,11 @@ class OverlayFSMount:
result = run_sudo_cmd( result = run_sudo_cmd(
[ [
"mount", "mount",
"-t", "overlay", "overlay", "-t",
"-o", options, "overlay",
"overlay",
"-o",
options,
str(self.mount_path), str(self.mount_path),
], ],
cd=self.cd, cd=self.cd,
@ -87,10 +95,12 @@ class OverlayFSMount:
print("Unmounting...") print("Unmounting...")
# Remove the mount # Remove the mount
run_sudo_cmd([ run_sudo_cmd(
[
"umount", "umount",
str(self.mount_path), str(self.mount_path),
]) ]
)
# Remove the temporary workdir # Remove the temporary workdir
if self.workdir is not None: if self.workdir is not None:

View File

@ -3,6 +3,7 @@ from typing import Optional
from lmm.const import LMM_GAMES_PATH from lmm.const import LMM_GAMES_PATH
class Profile: class Profile:
# The name of the game. # The name of the game.
game: str game: str

View File

@ -7,6 +7,7 @@ from lmm.games.game import Game, ProtonGame
from lmm.runners.runner import Runner from lmm.runners.runner import Runner
from lmm.runners.steam import SteamFlatpakAppIdRunner from lmm.runners.steam import SteamFlatpakAppIdRunner
def runner_from_config(data: dict[str, Any]) -> Optional[Runner]: def runner_from_config(data: dict[str, Any]) -> Optional[Runner]:
match data["type"]: match data["type"]:
case "steam.flatpak": case "steam.flatpak":

View File

@ -2,6 +2,7 @@ import abc
from lmm.games.game import Game from lmm.games.game import Game
class Runner(abc.ABC): class Runner(abc.ABC):
# Type identifier of the runner. # Type identifier of the runner.
runner_type: str runner_type: str

View File

@ -4,10 +4,12 @@ from pathlib import Path
from lmm.runners.runner import Runner from lmm.runners.runner import Runner
from lmm.games.game import Game from lmm.games.game import Game
class SteamRuntime(abc.ABC): class SteamRuntime(abc.ABC):
def get_run_script(self) -> Path: def get_run_script(self) -> Path:
raise NotImplementedError() raise NotImplementedError()
class SteamSniperRuntime(SteamRuntime): class SteamSniperRuntime(SteamRuntime):
library: Path library: Path
@ -15,7 +17,13 @@ class SteamSniperRuntime(SteamRuntime):
self.library = library self.library = library
def get_run_script(self) -> Path: def get_run_script(self) -> Path:
return self.library / "steamapps" / "common" / "SteamLinuxRuntime_sniper" / "run-in-sniper" return (
self.library
/ "steamapps"
/ "common"
/ "SteamLinuxRuntime_sniper"
/ "run-in-sniper"
)
class SteamSoldierRuntime(SteamRuntime): class SteamSoldierRuntime(SteamRuntime):
@ -25,7 +33,14 @@ class SteamSoldierRuntime(SteamRuntime):
self.library = library self.library = library
def get_run_script(self) -> Path: def get_run_script(self) -> Path:
return self.library / "steamapps" / "common" / "SteamLinuxRuntime_soldier" / "run-in-soldier" return (
self.library
/ "steamapps"
/ "common"
/ "SteamLinuxRuntime_soldier"
/ "run-in-soldier"
)
class SteamFlatpakAppIdRunner(Runner): class SteamFlatpakAppIdRunner(Runner):
def __init__(self): def __init__(self):
@ -34,12 +49,15 @@ class SteamFlatpakAppIdRunner(Runner):
def run(self, game: Game): def run(self, game: Game):
assert isinstance(game, ProtonGame) assert isinstance(game, ProtonGame)
run_cmd_nonblocking([ run_cmd_nonblocking(
[
"flatpak", "flatpak",
"run", "run",
"com.valvesoftware.Steam", "com.valvesoftware.Steam",
f"steam://launch/{game.appid}/", f"steam://launch/{game.appid}/",
]) ]
)
class SteamFlatpakProtonRunner(Runner): class SteamFlatpakProtonRunner(Runner):
# Path to the Proton prefix # Path to the Proton prefix
@ -55,7 +73,14 @@ class SteamFlatpakProtonRunner(Runner):
# "waitforexitandrun" | "run" # "waitforexitandrun" | "run"
launchmode: str launchmode: str
def __init__(self, compat_data: Path, runtime: SteamRuntime, proton_version: str, game: str, launchmode: str = "waitforexitandrun"): def __init__(
self,
compat_data: Path,
runtime: SteamRuntime,
proton_version: str,
game: str,
launchmode: str = "waitforexitandrun",
):
self.compat_data = compat_data self.compat_data = compat_data
self.runtime = runtime self.runtime = runtime
self.game = game self.game = game
@ -81,7 +106,13 @@ class SteamFlatpakProtonRunner(Runner):
@property @property
def _proton_path(self) -> Path: def _proton_path(self) -> Path:
return Path.home() / "data" / "Steam" / "compatibilitytools.d" / self.proton_version return (
Path.home()
/ "data"
/ "Steam"
/ "compatibilitytools.d"
/ self.proton_version
)
def _build_launch_command(self) -> str: def _build_launch_command(self) -> str:
command = [ command = [
@ -94,7 +125,9 @@ class SteamFlatpakProtonRunner(Runner):
return commandline return commandline
def run(self): def run(self):
run_cmd_shell(" ".join([ run_cmd_shell(
" ".join(
[
"flatpak", "flatpak",
"run", "run",
"--command=bash", "--command=bash",
@ -102,4 +135,6 @@ class SteamFlatpakProtonRunner(Runner):
"com.valvesoftware.Steam", "com.valvesoftware.Steam",
"-c", "-c",
f'"{self._build_launch_command()}"', f'"{self._build_launch_command()}"',
])) ]
)
)

View File

@ -7,6 +7,7 @@ STEAM_PATHS = [
Path.home() / ".var/app/com.valvesoftware.Steam/data/Steam", Path.home() / ".var/app/com.valvesoftware.Steam/data/Steam",
] ]
def find_library_folder_for_game(appid: str) -> Optional[Path]: def find_library_folder_for_game(appid: str) -> Optional[Path]:
for steam_path in STEAM_PATHS: for steam_path in STEAM_PATHS:
if not steam_path.exists(): if not steam_path.exists():

View File

@ -7,5 +7,11 @@ dependencies = [
"pyyaml" "pyyaml"
] ]
[project.optional-dependencies]
dev = [
"black",
"pylint"
]
[project.scripts] [project.scripts]
lmm = "lmm.cli:main" lmm = "lmm.cli:main"