Files
pristontale-eu-linux/ptlinux/install_status.py
2026-08-30 11:24:02 +02:00

106 lines
2.9 KiB
Python

"""Read-only installation state used by the native launcher."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import json
from pathlib import Path
from .import_state import game_import_requires_verification
from .paths import AppPaths
REQUIRED_GAME_FILES = (
Path("Game.exe"),
Path("game.dll"),
Path("game.ini"),
Path("game/maps/WhisperingVale.rovug"),
)
class PrimaryAction(str, Enum):
"""Action presented by the launcher's large primary button."""
INSTALL = "INSTALL"
UPDATE = "UPDATE"
PLAY = "PLAY"
CHECKING = "CHECKING…"
LAUNCHER_UPDATE = "UPDATE LAUNCHER"
@dataclass(frozen=True)
class GameInstallationStatus:
"""Local game completeness and its relationship to the official depot."""
action: PrimaryAction
installed: bool
local_build: int | None
official_build: int | None
missing_files: tuple[Path, ...]
def inspect_game_installation(
paths: AppPaths,
official_build: int | None = None,
*,
compatibility_ready: bool | None = True,
) -> GameInstallationStatus:
"""Select the primary launcher action without modifying the installation.
A completed native sync is the verified source for the local depot build.
When the official build cannot be checked, a complete local game remains
playable so temporary network failures do not lock users out.
"""
if official_build is not None and (
isinstance(official_build, bool)
or not isinstance(official_build, int)
or official_build < 0
):
raise ValueError("official build must be a non-negative integer")
if compatibility_ready is not None and not isinstance(compatibility_ready, bool):
raise ValueError("compatibility readiness must be a boolean or null")
missing_files = tuple(
relative
for relative in REQUIRED_GAME_FILES
if not (paths.source_game / relative).is_file()
)
installed = not missing_files
local_build = _read_verified_local_build(paths.native_depot_state)
if not installed:
action = PrimaryAction.INSTALL
elif game_import_requires_verification(paths):
action = PrimaryAction.UPDATE
elif official_build is not None and (
local_build is None or local_build < official_build
):
action = PrimaryAction.UPDATE
elif compatibility_ready is None:
action = PrimaryAction.CHECKING
elif not compatibility_ready:
action = PrimaryAction.LAUNCHER_UPDATE
else:
action = PrimaryAction.PLAY
return GameInstallationStatus(
action=action,
installed=installed,
local_build=local_build,
official_build=official_build,
missing_files=missing_files,
)
def _read_verified_local_build(path: Path) -> int | None:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
return None
if not isinstance(value, dict) or value.get("dry_run") is not False:
return None
build = value.get("build")
if isinstance(build, bool) or not isinstance(build, int) or build < 0:
return None
return build