"""Read-only filesystem and hash verification helpers.""" from __future__ import annotations from dataclasses import asdict, dataclass from hashlib import sha256 from pathlib import Path from typing import Mapping KNOWN_GAME_HASHES: dict[str, str] = { "Game.exe": "3cc0980aac0f65b379ff340ec61750498fb9f620fb6c88aa66281e8ab27023a5", "game.dll": "7ea85b19f452962a39c91f967cad22495f066b2d52e8ac37f4e234463d5c4f09", "gamese.exe": "c3443e50a93c6c778eff456122c351e5c7d2c1ceaea50e5a650bffe846a05c87", "gamese.dll": "0ae6822f5f347f04bd2b0dc947f0286089c711c84703e110f6f8fc6e0d626131", } @dataclass(frozen=True) class HashVerification: path: Path expected_sha256: str actual_sha256: str | None ok: bool error: str | None = None def to_dict(self) -> dict[str, str | bool | None]: data = asdict(self) data["path"] = str(self.path) return data def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str: if chunk_size <= 0: raise ValueError("chunk_size must be positive") digest = sha256() with path.open("rb") as handle: for chunk in iter(lambda: handle.read(chunk_size), b""): digest.update(chunk) return digest.hexdigest() def verify_file_hash(path: Path, expected_sha256: str) -> HashVerification: expected = expected_sha256.casefold() try: actual = sha256_file(path) except (OSError, ValueError) as exc: return HashVerification(path, expected, None, False, str(exc)) return HashVerification(path, expected, actual, actual == expected) def verify_hashes(expected: Mapping[Path, str]) -> list[HashVerification]: return [verify_file_hash(path, digest) for path, digest in expected.items()] def verify_known_game_hashes(live_game: Path) -> list[HashVerification]: expected = {live_game / name: digest for name, digest in KNOWN_GAME_HASHES.items()} return verify_hashes(expected)