Fixed issues with encodings and wine / proton compatibilities.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
from dataclasses import asdict, dataclass, replace
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import json
|
||||
@@ -14,6 +14,13 @@ import time
|
||||
from typing import Callable, Protocol
|
||||
|
||||
from . import __version__
|
||||
from .command import CommandRunner
|
||||
from .identity_probe import (
|
||||
IDENTITY_PROBE_PRIVACY,
|
||||
IdentityProbeResult,
|
||||
probe_client_identity,
|
||||
)
|
||||
from .login_trace import LOGIN_TRACE_DISABLED_REASON, LOGIN_TRACE_ENABLED
|
||||
from .overlay import (
|
||||
COMPATIBILITY_BINARY_HASHES,
|
||||
CURRENT_V21_BINARY_HASHES,
|
||||
@@ -43,6 +50,36 @@ DEFAULT_LOGIN_TIMEOUT_SECONDS = 5.0
|
||||
VERIFIED_GAME_PROFILES = frozenset(
|
||||
{"v21.75.83", "verified-compatibility"}
|
||||
)
|
||||
SERVER_LOGIN_RESPONSE_OWNERSHIP = (
|
||||
"Banned Code 1 and Banned Code 3 are server-provided login response text; "
|
||||
"neither string exists in the verified V21.75.83 Game.exe/game.dll pair"
|
||||
)
|
||||
CLIENT_IDENTITY_INPUTS = (
|
||||
"Win32_Processor",
|
||||
"Win32_BIOS",
|
||||
"Win32_BaseBoard",
|
||||
"Win32_VideoController",
|
||||
"network-adapter identity",
|
||||
"C: volume information",
|
||||
"computer name",
|
||||
)
|
||||
PUBLISHER_COMPARISON_COMMAND = "pristontale-eu wine-update"
|
||||
RUNTIME_COMPONENT_PRIVACY = (
|
||||
"Only SHA-256 hashes of public runtime executables and DLLs plus expected DLL-"
|
||||
"override flags are reported. Registry contents and machine identifiers are not "
|
||||
"hashed or returned."
|
||||
)
|
||||
PREFIX_COMPONENT_PATHS = (
|
||||
"drive_c/windows/syswow64/d3d9.dll",
|
||||
"drive_c/windows/syswow64/d3dx9_43.dll",
|
||||
"drive_c/windows/syswow64/d3dcompiler_43.dll",
|
||||
"drive_c/windows/syswow64/iphlpapi.dll",
|
||||
"drive_c/windows/syswow64/kernel32.dll",
|
||||
"drive_c/windows/syswow64/ole32.dll",
|
||||
"drive_c/windows/syswow64/oleaut32.dll",
|
||||
"drive_c/windows/syswow64/wbemprox.dll",
|
||||
"drive_c/windows/syswow64/wbem/wbemprox.dll",
|
||||
)
|
||||
|
||||
|
||||
class _Closable(Protocol):
|
||||
@@ -65,6 +102,20 @@ class LoginServerProbe:
|
||||
error: str | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimeComponentFingerprint:
|
||||
privacy: str
|
||||
runtime_wrapper_sha256: str | None
|
||||
proton_script_sha256: str | None
|
||||
proton_version_sha256: str | None
|
||||
proton_toolmanifest_sha256: str | None
|
||||
proton_wine_sha256: str | None
|
||||
proton_wineserver_sha256: str | None
|
||||
prefix_dll_sha256: dict[str, str | None]
|
||||
registry_overrides: dict[str, bool]
|
||||
runtime_marker_matches_selection: bool | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LoginDiagnosticReport:
|
||||
preflight_ok: bool
|
||||
@@ -76,6 +127,16 @@ class LoginDiagnosticReport:
|
||||
kernel_release: str
|
||||
login_server: LoginServerProbe
|
||||
authentication_tested: bool
|
||||
login_response_ownership: str
|
||||
client_identity_inputs: tuple[str, ...]
|
||||
client_identity_values_collected: bool
|
||||
client_identity_shape_collected: bool
|
||||
client_identity_ready: bool | None
|
||||
client_identity_privacy: str
|
||||
client_identity_probe: IdentityProbeResult | None
|
||||
login_trace_enabled: bool
|
||||
login_trace_disabled_reason: str | None
|
||||
publisher_comparison_command: str
|
||||
install_directory: str
|
||||
game_exe: str
|
||||
game_exe_sha256: str | None
|
||||
@@ -93,9 +154,13 @@ class LoginDiagnosticReport:
|
||||
world_fix_helper_ready: bool
|
||||
world_fix_enabled: bool
|
||||
world_fix_disabled_reason: str | None
|
||||
runtime_configured: str
|
||||
runtime_requested: str
|
||||
runtime_override_applied: bool
|
||||
runtime_kind: str | None
|
||||
runtime_path: str | None
|
||||
runtime_error: str | None
|
||||
runtime_components: RuntimeComponentFingerprint | None
|
||||
proton_sync_configured: str
|
||||
proton_sync_effective: str | None
|
||||
prefix: str | None
|
||||
@@ -148,6 +213,8 @@ def inspect_login_environment(
|
||||
*,
|
||||
timeout_seconds: float = DEFAULT_LOGIN_TIMEOUT_SECONDS,
|
||||
connector: LoginConnector = _create_login_connection,
|
||||
runner: CommandRunner | None = None,
|
||||
runtime_override: str | None = None,
|
||||
) -> LoginDiagnosticReport:
|
||||
"""Inspect the installed client and its network route without authenticating."""
|
||||
|
||||
@@ -155,7 +222,9 @@ def inspect_login_environment(
|
||||
timeout_seconds=timeout_seconds,
|
||||
connector=connector,
|
||||
)
|
||||
selection, runtime_error = _runtime_selection(settings)
|
||||
runtime_requested = runtime_override or settings.runtime
|
||||
diagnostic_settings = replace(settings, runtime=runtime_requested)
|
||||
selection, runtime_error = _runtime_selection(diagnostic_settings)
|
||||
distribution_id = detect_distribution_id() or "unknown"
|
||||
kernel_release = platform.release()
|
||||
proton_sync_effective = (
|
||||
@@ -192,6 +261,16 @@ def inspect_login_environment(
|
||||
prefix = _runtime_prefix(paths, selection)
|
||||
prefix_ready = _prefix_ready(prefix)
|
||||
directx_ready = native_d3d_ready(prefix) if prefix is not None else False
|
||||
runtime_components = (
|
||||
inspect_runtime_components(paths, selection, prefix)
|
||||
if selection is not None and prefix is not None
|
||||
else None
|
||||
)
|
||||
identity_probe = (
|
||||
probe_client_identity(paths, selection, runner)
|
||||
if selection is not None and prefix_ready and runner is not None
|
||||
else None
|
||||
)
|
||||
latest_log = latest_native_game_log(paths, settings)
|
||||
warnings = _diagnostic_warnings(
|
||||
probe,
|
||||
@@ -201,6 +280,12 @@ def inspect_login_environment(
|
||||
directx_ready,
|
||||
latest_log,
|
||||
runtime_matches_managed_source,
|
||||
identity_probe,
|
||||
)
|
||||
identity_preflight_ok = (
|
||||
True
|
||||
if runner is None
|
||||
else identity_probe is not None and identity_probe.ready is True
|
||||
)
|
||||
return LoginDiagnosticReport(
|
||||
preflight_ok=(
|
||||
@@ -210,6 +295,7 @@ def inspect_login_environment(
|
||||
and selection is not None
|
||||
and prefix_ready
|
||||
and directx_ready
|
||||
and identity_preflight_ok
|
||||
),
|
||||
checked_at=datetime.now().astimezone().isoformat(),
|
||||
launcher_version=__version__,
|
||||
@@ -219,6 +305,22 @@ def inspect_login_environment(
|
||||
kernel_release=kernel_release,
|
||||
login_server=probe,
|
||||
authentication_tested=False,
|
||||
login_response_ownership=SERVER_LOGIN_RESPONSE_OWNERSHIP,
|
||||
client_identity_inputs=CLIENT_IDENTITY_INPUTS,
|
||||
client_identity_values_collected=False,
|
||||
client_identity_shape_collected=(
|
||||
identity_probe is not None and identity_probe.report is not None
|
||||
),
|
||||
client_identity_ready=(
|
||||
identity_probe.ready if identity_probe is not None else None
|
||||
),
|
||||
client_identity_privacy=IDENTITY_PROBE_PRIVACY,
|
||||
client_identity_probe=identity_probe,
|
||||
login_trace_enabled=LOGIN_TRACE_ENABLED,
|
||||
login_trace_disabled_reason=(
|
||||
None if LOGIN_TRACE_ENABLED else LOGIN_TRACE_DISABLED_REASON
|
||||
),
|
||||
publisher_comparison_command=PUBLISHER_COMPARISON_COMMAND,
|
||||
install_directory=str(paths.data),
|
||||
game_exe=str(game_exe),
|
||||
game_exe_sha256=game_exe_sha256,
|
||||
@@ -238,9 +340,13 @@ def inspect_login_environment(
|
||||
world_fix_disabled_reason=(
|
||||
None if WORLD_FIX_ENABLED else WORLD_FIX_DISABLED_REASON
|
||||
),
|
||||
runtime_configured=settings.runtime,
|
||||
runtime_requested=runtime_requested,
|
||||
runtime_override_applied=runtime_override is not None,
|
||||
runtime_kind=selection.kind if selection is not None else None,
|
||||
runtime_path=_runtime_path(selection),
|
||||
runtime_error=runtime_error,
|
||||
runtime_components=runtime_components,
|
||||
proton_sync_configured=settings.proton_sync,
|
||||
proton_sync_effective=proton_sync_effective,
|
||||
prefix=str(prefix) if prefix is not None else None,
|
||||
@@ -252,6 +358,85 @@ def inspect_login_environment(
|
||||
)
|
||||
|
||||
|
||||
def inspect_runtime_components(
|
||||
paths: AppPaths,
|
||||
selection: RuntimeSelection,
|
||||
prefix: Path,
|
||||
) -> RuntimeComponentFingerprint:
|
||||
"""Fingerprint non-private runtime components for side-by-side reports."""
|
||||
|
||||
proton_script_sha256: str | None = None
|
||||
proton_version_sha256: str | None = None
|
||||
proton_toolmanifest_sha256: str | None = None
|
||||
proton_wine_sha256: str | None = None
|
||||
proton_wineserver_sha256: str | None = None
|
||||
if selection.proton_path:
|
||||
proton = Path(selection.proton_path)
|
||||
proton_script_sha256 = _sha256_if_file(proton)
|
||||
proton_version_sha256 = _sha256_if_file(proton.parent / "version")
|
||||
proton_toolmanifest_sha256 = _sha256_if_file(
|
||||
proton.parent / "toolmanifest.vdf"
|
||||
)
|
||||
for directory in (proton.parent / "files/bin", proton.parent / "dist/bin"):
|
||||
wine = directory / "wine"
|
||||
wineserver = directory / "wineserver"
|
||||
if wine.is_file() and wineserver.is_file():
|
||||
proton_wine_sha256 = _sha256_if_file(wine)
|
||||
proton_wineserver_sha256 = _sha256_if_file(wineserver)
|
||||
break
|
||||
prefix_dll_sha256 = {
|
||||
relative: _sha256_if_file(prefix / relative)
|
||||
for relative in PREFIX_COMPONENT_PATHS
|
||||
}
|
||||
runtime_marker = _read_json_object(paths.game_runtime_marker)
|
||||
marker_matches = (
|
||||
runtime_marker.get("runtime") == asdict(selection)
|
||||
if runtime_marker
|
||||
else None
|
||||
)
|
||||
return RuntimeComponentFingerprint(
|
||||
privacy=RUNTIME_COMPONENT_PRIVACY,
|
||||
runtime_wrapper_sha256=_sha256_if_file(Path(selection.executable)),
|
||||
proton_script_sha256=proton_script_sha256,
|
||||
proton_version_sha256=proton_version_sha256,
|
||||
proton_toolmanifest_sha256=proton_toolmanifest_sha256,
|
||||
proton_wine_sha256=proton_wine_sha256,
|
||||
proton_wineserver_sha256=proton_wineserver_sha256,
|
||||
prefix_dll_sha256=prefix_dll_sha256,
|
||||
registry_overrides=_runtime_registry_override_flags(prefix),
|
||||
runtime_marker_matches_selection=marker_matches,
|
||||
)
|
||||
|
||||
|
||||
def _runtime_registry_override_flags(prefix: Path) -> dict[str, bool]:
|
||||
try:
|
||||
registry = (prefix / "user.reg").read_text(
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
).casefold()
|
||||
except OSError:
|
||||
registry = ""
|
||||
return {
|
||||
"d3d9_native_builtin": '"d3d9"="native,builtin"' in registry,
|
||||
"d3dx9_43_native": _native_override_present(registry, "d3dx9_43"),
|
||||
"d3dcompiler_43_native": _native_override_present(
|
||||
registry,
|
||||
"d3dcompiler_43",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _native_override_present(registry: str, name: str) -> bool:
|
||||
return any(
|
||||
entry in registry
|
||||
for entry in (
|
||||
f'"{name}"="native,builtin"',
|
||||
f'"{name}"="native"',
|
||||
f'"*{name}"="native"',
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def classify_game_profile(
|
||||
game_exe_sha256: str | None,
|
||||
game_dll_sha256: str | None,
|
||||
@@ -354,6 +539,7 @@ def _diagnostic_warnings(
|
||||
directx_ready: bool,
|
||||
latest_log: Path | None,
|
||||
runtime_matches_managed_source: bool,
|
||||
identity_probe: IdentityProbeResult | None,
|
||||
) -> tuple[str, ...]:
|
||||
warnings: list[str] = []
|
||||
if not probe.reachable:
|
||||
@@ -392,6 +578,8 @@ def _diagnostic_warnings(
|
||||
warnings.append("The native DirectX helper DLLs are not ready; run play to repair them.")
|
||||
if latest_log is None:
|
||||
warnings.append("No native game log was found yet.")
|
||||
if identity_probe is not None:
|
||||
warnings.extend(identity_probe.warnings)
|
||||
return tuple(warnings)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user