Something went wrong. Try again.
Steam Deck Doom modding setup automation for Doom Runner, UZDoom, Brutal Doom, and Project Brutality.
brutal-doom moddb python doom-runner linux-gaming project-brutality uzdoom doom steam-deck
Something went wrong. Try again.
6.2 kB · 161 lines
Python
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162"""Steam shortcut and Proton compatibility orchestration."""from __future__ import annotations
import dataclassesimport loggingimport shutilimport subprocessimport timefrom collections import OrderedDictfrom pathlib import Pathfrom typing import Callable, Optional, cast
from doomdeck.domain.models import Dirs, DoomDeckError, SteamInfofrom doomdeck.infrastructure.binary_vdf import BKV_OBJECT, BKVValue, BinaryVDFfrom doomdeck.infrastructure.files import atomic_write_bytes, atomic_write_text, backup_pathfrom doomdeck.infrastructure.processes import is_process_runningfrom doomdeck.infrastructure.steam_compat import compat_mapping_key, dumps_text_vdf, load_text_vdf, set_compat_tool_mappingfrom doomdeck.infrastructure.steam_shortcuts import get_bkv_str, load_shortcuts, shortcut_entries, upsert_shortcut
ProcessDetector = Callable[[list[str]], bool]SteamShutdown = Callable[[logging.Logger, bool], None]DOOMRUNNER_SHORTCUT_NAME = "Doom Runner"DOOMDECK_PRESET_SHORTCUT_PREFIX = "DoomDeck - "
@dataclasses.dataclass(frozen=True)class SteamShortcutSettings: dry_run: bool allow_steam_running: bool = False shutdown_steam: bool = False proton_compat_tool: str = ""
def doomrunner_proton_options_path(steam: SteamInfo, appid: int) -> Optional[Path]: if not steam.steam_root: return None return ( steam.steam_root / "steamapps" / "compatdata" / compat_mapping_key(appid) / "pfx" / "drive_c" / "users" / "steamuser" / "AppData" / "Roaming" / "DoomRunner" / "options.json" )
def shutdown_steam(logger: logging.Logger, dry_run: bool) -> None: logger.info("Request Steam shutdown") if dry_run: return candidates = [["steam", "-shutdown"], ["steam-runtime", "-shutdown"]] for cmd in candidates: if shutil.which(cmd[0]): subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(8) return logger.warning("Steam command not found; close Steam manually before modifying Steam shortcut and compatibility files")
def add_or_update_doomrunner_shortcut( shortcuts_path: Path, dirs: Dirs, settings: SteamShortcutSettings, logger: logging.Logger, *, process_detector: ProcessDetector = is_process_running, steam_shutdown: SteamShutdown = shutdown_steam,) -> int: return add_or_update_steam_shortcut( shortcuts_path, DOOMRUNNER_SHORTCUT_NAME, dirs.doomrunner / "DoomRunner.exe", dirs.doomrunner, dirs, settings, logger, process_detector=process_detector, steam_shutdown=steam_shutdown, remove_extra_doomdeck_shortcuts=True, )
def add_or_update_steam_shortcut( shortcuts_path: Path, appname: str, exe: Path, start_dir: Path, dirs: Dirs, settings: SteamShortcutSettings, logger: logging.Logger, launch_options: str = "", match_exe: bool = True, *, remove_extra_doomdeck_shortcuts: bool = False, process_detector: ProcessDetector = is_process_running, steam_shutdown: SteamShutdown = shutdown_steam,) -> int: if process_detector(["steamwebhelper", "steam -", "/steam"]): if settings.shutdown_steam: steam_shutdown(logger, settings.dry_run) elif not settings.allow_steam_running: raise DoomDeckError( "Steam appears to be running. Close Steam first, rerun with --shutdown-steam, " "or use --skip-steam-shortcut." ) else: logger.warning("Steam appears to be running; modifying Steam shortcut and compatibility files anyway because --allow-steam-running was set")
shortcuts_path.parent.mkdir(parents=True, exist_ok=True) if shortcuts_path.exists(): backup_path(shortcuts_path, dirs.backups, settings.dry_run, logger, label="shortcuts.vdf") root = load_shortcuts(shortcuts_path) result = upsert_shortcut(root, appname, exe, start_dir, tags=["Doom", "Tools"], launch_options=launch_options, match_exe=match_exe) if result.created: logger.info("Add Steam shortcut %s at index %s in %s", appname, result.key, shortcuts_path) else: logger.info("Update existing Steam shortcut %s in %s", appname, shortcuts_path) if remove_extra_doomdeck_shortcuts: removed = _remove_extra_doomdeck_shortcuts(root, keep_key=result.key) if removed: logger.info("Remove extra DoomDeck-managed Steam shortcuts: %s", ", ".join(removed)) atomic_write_bytes(shortcuts_path, BinaryVDF.dumps(root), settings.dry_run, logger) if settings.proton_compat_tool: add_or_update_steam_compat_mapping(shortcuts_path.parent / "localconfig.vdf", result.appid, settings.proton_compat_tool, dirs, settings, logger) return result.appid
def _remove_extra_doomdeck_shortcuts(root: OrderedDict[str, BKVValue], *, keep_key: str) -> tuple[str, ...]: shortcuts = shortcut_entries(root) removed: list[str] = [] for key, value in list(shortcuts.items()): if key == keep_key or value.type_code != BKV_OBJECT: continue entry = cast(OrderedDict[str, BKVValue], value.value) appname = get_bkv_str(entry, "appname", "AppName") if appname == DOOMRUNNER_SHORTCUT_NAME or appname.startswith(DOOMDECK_PRESET_SHORTCUT_PREFIX): del shortcuts[key] removed.append(appname or key) return tuple(removed)
def add_or_update_steam_compat_mapping( localconfig_path: Path, appid: int, compat_tool: str, dirs: Dirs, settings: SteamShortcutSettings, logger: logging.Logger,) -> None: if localconfig_path.exists(): backup_path(localconfig_path, dirs.backups, settings.dry_run, logger, label="localconfig.vdf") logger.info("Set Steam compatibility tool for appid %s to %s in %s", appid, compat_tool, localconfig_path) root = load_text_vdf(localconfig_path) set_compat_tool_mapping(root, appid, compat_tool) atomic_write_text(localconfig_path, dumps_text_vdf(root), settings.dry_run, logger)