Optimizations and performance boost to pre-loader for launcher and game client.
This commit is contained in:
@@ -44,15 +44,46 @@ PATCHED_V21_GAME_DLL_SHA256 = (
|
||||
RETIRED_POST_AUTH_GAME_DLL_SHA256 = (
|
||||
"93cd27284eb294753da1111c99bfc9689f82b6d4a59302aee9fdbd8cb52452dd"
|
||||
)
|
||||
RUNTIME_VIEW_CONFIGURATION_VERSION = 1
|
||||
RUNTIME_VIEW_CONFIGURATION_VERSION = 2
|
||||
|
||||
|
||||
class OverlayError(RuntimeError):
|
||||
"""Raised when a safe runtime view cannot be built."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimeFileIdentity:
|
||||
"""Cheap change detector for one previously hash-verified runtime input."""
|
||||
|
||||
name: str
|
||||
device: int
|
||||
inode: int
|
||||
mode: int
|
||||
size: int
|
||||
mtime_ns: int
|
||||
ctime_ns: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OverlayResult:
|
||||
source_game: str
|
||||
runtime_view: str
|
||||
source_scene_sha256: str
|
||||
runtime_scene_sha256: str
|
||||
scene_action: str
|
||||
binary_action: str
|
||||
game_exe_sha256: str
|
||||
game_dll_sha256: str
|
||||
input_identities: tuple[RuntimeFileIdentity, ...]
|
||||
runtime_scene_identity: RuntimeFileIdentity
|
||||
created_at: str
|
||||
links_created: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LegacyOverlayResult:
|
||||
"""Version-one runtime manifest retained only for a one-time fast migration."""
|
||||
|
||||
source_game: str
|
||||
runtime_view: str
|
||||
source_scene_sha256: str
|
||||
@@ -94,8 +125,9 @@ def inspect_runtime_compatibility(
|
||||
|
||||
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.
|
||||
view. Preparation reuses a prior authoritative result only while every input
|
||||
identity and runtime-view link remains unchanged; otherwise it repeats the
|
||||
full checks before launch.
|
||||
"""
|
||||
|
||||
requested_source = source_game.expanduser()
|
||||
@@ -105,6 +137,9 @@ def inspect_runtime_compatibility(
|
||||
try:
|
||||
source = requested_source.resolve(strict=True)
|
||||
_validate_game_tree(source)
|
||||
cached = _cached_runtime_compatibility(paths, source)
|
||||
if cached is not None:
|
||||
return cached
|
||||
source_scene = source / WHISPERING_VALE_RELATIVE
|
||||
inspection = inspect_wrapped_assbin(source_scene)
|
||||
source_scene_sha256 = inspection.file_sha256
|
||||
@@ -139,10 +174,14 @@ def prepare_runtime_view(
|
||||
source = source_game.expanduser().resolve(strict=True)
|
||||
_validate_game_tree(source)
|
||||
_validate_runtime_target(paths)
|
||||
game_save = _prepare_persistent_save(paths, source)
|
||||
reused = _reuse_runtime_view(paths, source, game_save)
|
||||
if reused is not None:
|
||||
return reused
|
||||
input_identities = _runtime_input_identities(paths, source)
|
||||
source_scene = source / WHISPERING_VALE_RELATIVE
|
||||
inspection = inspect_wrapped_assbin(source_scene)
|
||||
action = _scene_action(inspection)
|
||||
game_save = _prepare_persistent_save(paths, source)
|
||||
(
|
||||
binary_action,
|
||||
game_exe_source,
|
||||
@@ -152,7 +191,7 @@ def prepare_runtime_view(
|
||||
paths,
|
||||
source,
|
||||
)
|
||||
reused = _reuse_runtime_view(
|
||||
legacy_reuse = _reuse_legacy_runtime_view(
|
||||
paths,
|
||||
source,
|
||||
inspection,
|
||||
@@ -162,9 +201,10 @@ def prepare_runtime_view(
|
||||
game_exe_source,
|
||||
game_dll_source,
|
||||
source_binary_hashes,
|
||||
input_identities,
|
||||
)
|
||||
if reused is not None:
|
||||
return reused
|
||||
if legacy_reuse is not None:
|
||||
return legacy_reuse
|
||||
|
||||
build = paths.data / f".{paths.runtime_view.name}.build-{uuid.uuid4().hex}"
|
||||
backup = paths.data / f".{paths.runtime_view.name}.old-{uuid.uuid4().hex}"
|
||||
@@ -213,6 +253,10 @@ def prepare_runtime_view(
|
||||
validate_scene_normalization(normalization)
|
||||
else:
|
||||
shutil.copy2(source_scene, runtime_scene)
|
||||
if _runtime_input_identities(paths, source) != input_identities:
|
||||
raise OverlayError(
|
||||
"game files changed while the managed runtime view was being prepared"
|
||||
)
|
||||
|
||||
if paths.runtime_view.exists() or paths.runtime_view.is_symlink():
|
||||
paths.runtime_view.replace(backup)
|
||||
@@ -234,6 +278,11 @@ def prepare_runtime_view(
|
||||
binary_action=binary_action,
|
||||
game_exe_sha256=_sha256(paths.runtime_view / "Game.exe"),
|
||||
game_dll_sha256=_sha256(paths.runtime_view / "game.dll"),
|
||||
input_identities=input_identities,
|
||||
runtime_scene_identity=_runtime_file_identity(
|
||||
"runtime-scene",
|
||||
paths.runtime_view / WHISPERING_VALE_RELATIVE,
|
||||
),
|
||||
created_at=datetime.now().astimezone().isoformat(),
|
||||
links_created=links_created,
|
||||
)
|
||||
@@ -470,7 +519,99 @@ def _reject_save_symlinks(root: Path) -> None:
|
||||
raise OverlayError(f"persistent game save contains a symbolic link: {path}")
|
||||
|
||||
|
||||
def _reuse_runtime_view(
|
||||
def _runtime_file_identity(
|
||||
name: str,
|
||||
path: Path,
|
||||
*,
|
||||
follow_symlinks: bool = True,
|
||||
) -> RuntimeFileIdentity:
|
||||
state = path.stat(follow_symlinks=follow_symlinks)
|
||||
return RuntimeFileIdentity(
|
||||
name=name,
|
||||
device=state.st_dev,
|
||||
inode=state.st_ino,
|
||||
mode=state.st_mode,
|
||||
size=state.st_size,
|
||||
mtime_ns=state.st_mtime_ns,
|
||||
ctime_ns=state.st_ctime_ns,
|
||||
)
|
||||
|
||||
|
||||
def _runtime_input_identities(
|
||||
paths: AppPaths,
|
||||
source: Path,
|
||||
) -> tuple[RuntimeFileIdentity, ...]:
|
||||
"""Snapshot inputs whose hashes established the cached runtime profile.
|
||||
|
||||
Nanosecond mtime plus ctime/inode/device prevents ordinary edits, replacements,
|
||||
or timestamp restoration from bypassing the next authoritative hash check.
|
||||
"""
|
||||
|
||||
identities = [
|
||||
_runtime_file_identity("source-game-exe", source / "Game.exe"),
|
||||
_runtime_file_identity("source-game-dll", source / "game.dll"),
|
||||
_runtime_file_identity("source-scene", source / WHISPERING_VALE_RELATIVE),
|
||||
]
|
||||
cache = paths.compatibility_binaries
|
||||
if cache.exists() or cache.is_symlink():
|
||||
identities.append(
|
||||
_runtime_file_identity(
|
||||
"compatibility-cache",
|
||||
cache,
|
||||
follow_symlinks=False,
|
||||
)
|
||||
)
|
||||
if cache.is_dir():
|
||||
for name in COMPATIBILITY_BINARY_HASHES:
|
||||
path = cache / name
|
||||
if path.exists() or path.is_symlink():
|
||||
identities.append(
|
||||
_runtime_file_identity(
|
||||
f"compatibility-cache-{name.casefold()}",
|
||||
path,
|
||||
)
|
||||
)
|
||||
return tuple(identities)
|
||||
|
||||
|
||||
def _matching_cached_runtime_result(
|
||||
paths: AppPaths,
|
||||
source: Path,
|
||||
) -> OverlayResult | None:
|
||||
result = _read_overlay_result(paths.runtime_manifest)
|
||||
if result is None:
|
||||
return None
|
||||
if result.source_game != str(source):
|
||||
return None
|
||||
if result.runtime_view != str(paths.runtime_view):
|
||||
return None
|
||||
try:
|
||||
current_identities = _runtime_input_identities(paths, source)
|
||||
except OSError:
|
||||
return None
|
||||
if current_identities != result.input_identities:
|
||||
return None
|
||||
return result
|
||||
|
||||
|
||||
def _cached_runtime_compatibility(
|
||||
paths: AppPaths,
|
||||
source: Path,
|
||||
) -> RuntimeCompatibility | None:
|
||||
result = _matching_cached_runtime_result(paths, source)
|
||||
if result is None:
|
||||
return None
|
||||
return RuntimeCompatibility(
|
||||
compatible=True,
|
||||
source_game=str(source),
|
||||
source_scene_sha256=result.source_scene_sha256,
|
||||
scene_action=result.scene_action,
|
||||
binary_action=result.binary_action,
|
||||
reason=None,
|
||||
)
|
||||
|
||||
|
||||
def _reuse_legacy_runtime_view(
|
||||
paths: AppPaths,
|
||||
source: Path,
|
||||
inspection: SceneInspection,
|
||||
@@ -480,22 +621,82 @@ def _reuse_runtime_view(
|
||||
game_exe_source: Path,
|
||||
game_dll_source: Path,
|
||||
source_binary_hashes: Mapping[str, str],
|
||||
input_identities: tuple[RuntimeFileIdentity, ...],
|
||||
) -> OverlayResult | None:
|
||||
result = _read_overlay_result(paths.runtime_manifest)
|
||||
"""Upgrade an unchanged v1 view after its original hash checks pass once."""
|
||||
|
||||
legacy = _read_legacy_overlay_result(paths.runtime_manifest)
|
||||
if legacy is None:
|
||||
return None
|
||||
if legacy.source_game != str(source):
|
||||
return None
|
||||
if legacy.runtime_view != str(paths.runtime_view):
|
||||
return None
|
||||
if legacy.source_scene_sha256 != inspection.file_sha256:
|
||||
return None
|
||||
if legacy.scene_action != scene_action or legacy.binary_action != binary_action:
|
||||
return None
|
||||
if legacy.game_exe_sha256 != source_binary_hashes.get("Game.exe"):
|
||||
return None
|
||||
if legacy.game_dll_sha256 != source_binary_hashes.get("game.dll"):
|
||||
return None
|
||||
if not _runtime_layout_ready(
|
||||
paths,
|
||||
source,
|
||||
game_save,
|
||||
game_exe_source,
|
||||
game_dll_source,
|
||||
legacy.links_created,
|
||||
):
|
||||
return None
|
||||
runtime_scene = paths.runtime_view / WHISPERING_VALE_RELATIVE
|
||||
try:
|
||||
if _sha256(runtime_scene) != legacy.runtime_scene_sha256:
|
||||
return None
|
||||
if _runtime_input_identities(paths, source) != input_identities:
|
||||
raise OverlayError(
|
||||
"game files changed while the legacy runtime view was being verified"
|
||||
)
|
||||
runtime_scene_identity = _runtime_file_identity(
|
||||
"runtime-scene",
|
||||
runtime_scene,
|
||||
)
|
||||
except OSError:
|
||||
return None
|
||||
result = OverlayResult(
|
||||
source_game=legacy.source_game,
|
||||
runtime_view=legacy.runtime_view,
|
||||
source_scene_sha256=legacy.source_scene_sha256,
|
||||
runtime_scene_sha256=legacy.runtime_scene_sha256,
|
||||
scene_action=legacy.scene_action,
|
||||
binary_action=legacy.binary_action,
|
||||
game_exe_sha256=legacy.game_exe_sha256,
|
||||
game_dll_sha256=legacy.game_dll_sha256,
|
||||
input_identities=input_identities,
|
||||
runtime_scene_identity=runtime_scene_identity,
|
||||
created_at=legacy.created_at,
|
||||
links_created=legacy.links_created,
|
||||
)
|
||||
_write_json(
|
||||
paths.runtime_manifest,
|
||||
{
|
||||
"configuration_version": RUNTIME_VIEW_CONFIGURATION_VERSION,
|
||||
**asdict(result),
|
||||
},
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def _reuse_runtime_view(
|
||||
paths: AppPaths,
|
||||
source: Path,
|
||||
game_save: Path,
|
||||
) -> OverlayResult | None:
|
||||
result = _matching_cached_runtime_result(paths, source)
|
||||
if result is None:
|
||||
return None
|
||||
if result.source_game != str(source):
|
||||
return None
|
||||
if result.runtime_view != str(paths.runtime_view):
|
||||
return None
|
||||
if result.source_scene_sha256 != inspection.file_sha256:
|
||||
return None
|
||||
if result.scene_action != scene_action or result.binary_action != binary_action:
|
||||
return None
|
||||
if result.game_exe_sha256 != source_binary_hashes.get("Game.exe"):
|
||||
return None
|
||||
if result.game_dll_sha256 != source_binary_hashes.get("game.dll"):
|
||||
return None
|
||||
game_exe_source = source / "Game.exe"
|
||||
game_dll_source = source / "game.dll"
|
||||
if not _runtime_layout_ready(
|
||||
paths,
|
||||
source,
|
||||
@@ -507,7 +708,12 @@ def _reuse_runtime_view(
|
||||
return None
|
||||
runtime_scene = paths.runtime_view / WHISPERING_VALE_RELATIVE
|
||||
try:
|
||||
if _sha256(runtime_scene) != result.runtime_scene_sha256:
|
||||
if (
|
||||
_runtime_file_identity("runtime-scene", runtime_scene)
|
||||
!= result.runtime_scene_identity
|
||||
):
|
||||
return None
|
||||
if _runtime_input_identities(paths, source) != result.input_identities:
|
||||
return None
|
||||
except OSError:
|
||||
return None
|
||||
@@ -651,7 +857,101 @@ def _read_overlay_result(path: Path) -> OverlayResult | None:
|
||||
return None
|
||||
if links_created < 0:
|
||||
return None
|
||||
input_values = value.get("input_identities")
|
||||
if not isinstance(input_values, list) or not input_values:
|
||||
return None
|
||||
input_identities: list[RuntimeFileIdentity] = []
|
||||
for input_value in input_values:
|
||||
identity = _parse_runtime_file_identity(input_value)
|
||||
if identity is None:
|
||||
return None
|
||||
input_identities.append(identity)
|
||||
input_names = {identity.name for identity in input_identities}
|
||||
if len(input_names) != len(input_identities) or not {
|
||||
"source-game-exe",
|
||||
"source-game-dll",
|
||||
"source-scene",
|
||||
}.issubset(input_names):
|
||||
return None
|
||||
runtime_scene_identity = _parse_runtime_file_identity(
|
||||
value.get("runtime_scene_identity")
|
||||
)
|
||||
if (
|
||||
runtime_scene_identity is None
|
||||
or runtime_scene_identity.name != "runtime-scene"
|
||||
):
|
||||
return None
|
||||
return OverlayResult(
|
||||
source_game=value["source_game"],
|
||||
runtime_view=value["runtime_view"],
|
||||
source_scene_sha256=value["source_scene_sha256"],
|
||||
runtime_scene_sha256=value["runtime_scene_sha256"],
|
||||
scene_action=value["scene_action"],
|
||||
binary_action=value["binary_action"],
|
||||
game_exe_sha256=value["game_exe_sha256"],
|
||||
game_dll_sha256=value["game_dll_sha256"],
|
||||
input_identities=tuple(input_identities),
|
||||
runtime_scene_identity=runtime_scene_identity,
|
||||
created_at=value["created_at"],
|
||||
links_created=links_created,
|
||||
)
|
||||
|
||||
|
||||
def _parse_runtime_file_identity(value: object) -> RuntimeFileIdentity | None:
|
||||
if not isinstance(value, dict):
|
||||
return None
|
||||
name = value.get("name")
|
||||
if not isinstance(name, str) or not name:
|
||||
return None
|
||||
fields: dict[str, int] = {}
|
||||
for field in ("device", "inode", "mode", "size", "mtime_ns", "ctime_ns"):
|
||||
field_value = value.get(field)
|
||||
if (
|
||||
isinstance(field_value, bool)
|
||||
or not isinstance(field_value, int)
|
||||
or field_value < 0
|
||||
):
|
||||
return None
|
||||
fields[field] = field_value
|
||||
return RuntimeFileIdentity(
|
||||
name=name,
|
||||
device=fields["device"],
|
||||
inode=fields["inode"],
|
||||
mode=fields["mode"],
|
||||
size=fields["size"],
|
||||
mtime_ns=fields["mtime_ns"],
|
||||
ctime_ns=fields["ctime_ns"],
|
||||
)
|
||||
|
||||
|
||||
def _read_legacy_overlay_result(path: Path) -> LegacyOverlayResult | 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("configuration_version") != 1:
|
||||
return None
|
||||
string_fields = (
|
||||
"source_game",
|
||||
"runtime_view",
|
||||
"source_scene_sha256",
|
||||
"runtime_scene_sha256",
|
||||
"scene_action",
|
||||
"binary_action",
|
||||
"game_exe_sha256",
|
||||
"game_dll_sha256",
|
||||
"created_at",
|
||||
)
|
||||
if not all(isinstance(value.get(name), str) for name in string_fields):
|
||||
return None
|
||||
links_created = value.get("links_created")
|
||||
if (
|
||||
isinstance(links_created, bool)
|
||||
or not isinstance(links_created, int)
|
||||
or links_created < 0
|
||||
):
|
||||
return None
|
||||
return LegacyOverlayResult(
|
||||
source_game=value["source_game"],
|
||||
runtime_view=value["runtime_view"],
|
||||
source_scene_sha256=value["source_scene_sha256"],
|
||||
|
||||
Reference in New Issue
Block a user