More changes and fixes to use the new native world (game.dll) fixes.

This commit is contained in:
2026-08-28 20:34:31 +02:00
parent 7f6330a617
commit b344506071
7 changed files with 230 additions and 65 deletions

View File

@@ -19,11 +19,7 @@ from ptdiag.assbin import (
)
from .paths import AppPaths
from .v21_post_auth_reclaim import (
PATCHED_SHA256 as POST_AUTH_RECLAIM_GAME_DLL_SHA256,
PostAuthReclaimPatchError,
build_post_auth_reclaim_patch,
)
from .world_fix import V21_BINARY_HASHES
WHISPERING_VALE_RELATIVE = Path("game/maps/WhisperingVale.rovug")
@@ -39,10 +35,7 @@ COMPATIBILITY_BINARY_HASHES = {
"Game.exe": "3cc0980aac0f65b379ff340ec61750498fb9f620fb6c88aa66281e8ab27023a5",
"game.dll": "7ea85b19f452962a39c91f967cad22495f066b2d52e8ac37f4e234463d5c4f09",
}
CURRENT_V21_BINARY_HASHES = {
"Game.exe": "2bc3edd26c5f6d7ae9bbdc23967c5aefd374bdf934fcdfa2ed42c6442e550837",
"game.dll": "4b4e5c28e2bfdaa8d36c8341fad27becfd03faf63aa8ea874f5d43c5b1961868",
}
CURRENT_V21_BINARY_HASHES = V21_BINARY_HASHES
PATCHED_V21_GAME_DLL_SHA256 = (
"0be11b06ccf9be747e32b195c8d5980062f446d7d3a0a662ebfe0e602a01c538"
)
@@ -75,6 +68,67 @@ class CompatibilityCacheResult:
created_at: str
@dataclass(frozen=True)
class RuntimeCompatibility:
"""Read-only compatibility result for the managed game currently on disk."""
compatible: bool
source_game: str
source_scene_sha256: str | None
scene_action: str | None
binary_action: str | None
reason: str | None
def inspect_runtime_compatibility(
paths: AppPaths,
source_game: Path,
*,
use_world_fix: bool = True,
) -> RuntimeCompatibility:
"""Check whether ``prepare_runtime_view`` can recognize the installed release.
This preflight is intentionally read-only: it never normalizes a scene,
builds the verified runtime DLL, prepares save data, or replaces the runtime
view. The normal preparation path still performs every authoritative check
immediately before launch.
"""
requested_source = source_game.expanduser()
source_scene_sha256 = None
scene_action = None
binary_action = None
try:
source = requested_source.resolve(strict=True)
_validate_game_tree(source)
source_scene = source / WHISPERING_VALE_RELATIVE
inspection = inspect_wrapped_assbin(source_scene)
source_scene_sha256 = inspection.file_sha256
scene_action = _scene_action(inspection)
# Retain the keyword for callers from the 0.5.8 candidate. The runtime
# view is now always publisher-pristine; activation happens in memory.
_ = use_world_fix
binary_action = _inspect_runtime_binary_action(paths, source)
except (OSError, ValueError, OverlayError) as exc:
reason = str(exc).strip() or type(exc).__name__
return RuntimeCompatibility(
compatible=False,
source_game=str(requested_source.resolve(strict=False)),
source_scene_sha256=source_scene_sha256,
scene_action=scene_action,
binary_action=binary_action,
reason=reason,
)
return RuntimeCompatibility(
compatible=True,
source_game=str(source),
source_scene_sha256=source_scene_sha256,
scene_action=scene_action,
binary_action=binary_action,
reason=None,
)
def prepare_runtime_view(
paths: AppPaths,
source_game: Path,
@@ -90,10 +144,12 @@ def prepare_runtime_view(
inspection = inspect_wrapped_assbin(source_scene)
action = _scene_action(inspection)
game_save = _prepare_persistent_save(paths, source)
# ``use_world_fix`` is retained as a compatibility keyword. No code path may
# replace the publisher DLL; the post-login fix is selected by ``run_game``.
_ = use_world_fix
binary_action, game_exe_source, game_dll_source = _runtime_game_binary_sources(
paths,
source,
use_world_fix=use_world_fix,
)
build = paths.data / f".{paths.runtime_view.name}.build-{uuid.uuid4().hex}"
@@ -207,22 +263,14 @@ def cache_compatibility_binaries(
def select_binary_action(
cache_hashes: dict[str, str] | None,
source_hashes: dict[str, str],
runtime_patch_hash: str | None = None,
) -> str:
if runtime_patch_hash is not None:
if runtime_patch_hash != POST_AUTH_RECLAIM_GAME_DLL_SHA256:
raise OverlayError("post-authentication runtime patch has an unknown profile")
if cache_hashes is not None:
if cache_hashes != COMPATIBILITY_BINARY_HASHES:
raise OverlayError("compatibility cache has an unknown binary profile")
if source_hashes == COMPATIBILITY_BINARY_HASHES:
return "verified-source-pair"
if runtime_patch_hash is not None:
if source_hashes != CURRENT_V21_BINARY_HASHES:
raise OverlayError(
"post-authentication runtime patch does not match the installed client profile"
)
return "verified-v21-post-auth-world-fix"
if source_hashes == CURRENT_V21_BINARY_HASHES:
return "verified-v21-publisher-pair"
return "upstream-pair"
@@ -378,38 +426,30 @@ def _reject_save_symlinks(root: Path) -> None:
def _runtime_game_binary_sources(
paths: AppPaths,
source: Path,
*,
use_world_fix: bool = True,
) -> tuple[str, Path, Path]:
cache = paths.compatibility_binaries
cache_hashes = _binary_hashes(cache) if cache.is_dir() else None
source_hashes = _binary_hashes(source)
patch = paths.runtime_patch_dll
patch_hash = None
if source_hashes == CURRENT_V21_BINARY_HASHES and use_world_fix:
if patch.exists() or patch.is_symlink():
if not patch.is_file() or patch.is_symlink():
raise OverlayError(f"runtime patch cache is not a regular file: {patch}")
patch_hash = _sha256(patch)
else:
try:
result = build_post_auth_reclaim_patch(source / "game.dll", patch)
except PostAuthReclaimPatchError as exc:
raise OverlayError(
f"could not build the verified post-authentication world fix: {exc}"
) from exc
patch_hash = result.destination_sha256
payload = {"ok": True, **asdict(result)}
_write_json(patch.parent / "patch.json", payload)
_write_json(paths.runtime_patch_manifest, payload)
action = select_binary_action(cache_hashes, source_hashes, patch_hash)
action = select_binary_action(cache_hashes, source_hashes)
return (
action,
source / "Game.exe",
patch if action == "verified-v21-post-auth-world-fix" else source / "game.dll",
source / "game.dll",
)
def _inspect_runtime_binary_action(
paths: AppPaths,
source: Path,
) -> str:
"""Mirror publisher binary selection without writing any runtime cache."""
cache = paths.compatibility_binaries
cache_hashes = _binary_hashes(cache) if cache.is_dir() else None
source_hashes = _binary_hashes(source)
return select_binary_action(cache_hashes, source_hashes)
def _binary_hashes(directory: Path) -> dict[str, str]:
return {
name: _sha256(directory / name)