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

@@ -4,4 +4,4 @@ from .paths import AppPaths
__all__ = ["AppPaths", "__version__"] __all__ = ["AppPaths", "__version__"]
__version__ = "0.5.8" __version__ = "0.5.9"

View File

@@ -139,8 +139,13 @@ def build_parser() -> argparse.ArgumentParser:
play.add_argument("--no-box", action="store_true", help="launch without X11 window styling") play.add_argument("--no-box", action="store_true", help="launch without X11 window styling")
play.add_argument( play.add_argument(
"--publisher-dll", "--publisher-dll",
"--no-world-fix",
action="store_true", action="store_true",
help="diagnostic fallback: disable the post-authentication world fix", dest="publisher_dll",
help=(
"diagnostic fallback: disable the in-memory post-login world fix "
"(the publisher DLL is always used)"
),
) )
play.add_argument("--dry-run", action="store_true") play.add_argument("--dry-run", action="store_true")
play.add_argument("--skip-runtime-setup", action="store_true") play.add_argument("--skip-runtime-setup", action="store_true")
@@ -348,6 +353,10 @@ def _login_diagnostics(paths: AppPaths, timeout_seconds: float, as_json: bool) -
print(f"game.dll: {report.game_dll}") print(f"game.dll: {report.game_dll}")
if report.game_dll_sha256: if report.game_dll_sha256:
print(f"DLL SHA-256: {report.game_dll_sha256}") print(f"DLL SHA-256: {report.game_dll_sha256}")
world_fix_status = "ready" if report.world_fix_helper_ready else "not ready"
print(f"World fix: {world_fix_status} ({report.world_fix_helper})")
if report.world_fix_helper_sha256:
print(f"Helper SHA: {report.world_fix_helper_sha256}")
if report.runtime_kind: if report.runtime_kind:
print(f"Runtime: {report.runtime_kind} ({report.runtime_path or 'unknown path'})") print(f"Runtime: {report.runtime_kind} ({report.runtime_path or 'unknown path'})")
else: else:
@@ -626,11 +635,7 @@ def _play_locked(
overlay = None overlay = None
_print_operation_status(as_json, 1, 4, "Preparing the managed game view…") _print_operation_status(as_json, 1, 4, "Preparing the managed game view…")
if not dry_run: if not dry_run:
overlay = prepare_runtime_view( overlay = prepare_runtime_view(paths, source)
paths,
source,
use_world_fix=not publisher_dll,
)
_print_operation_status(as_json, 2, 4, "Selecting the configured game runtime…") _print_operation_status(as_json, 2, 4, "Selecting the configured game runtime…")
selection = select_runtime(settings) selection = select_runtime(settings)
runner = _runner(paths, "play", dry_run) runner = _runner(paths, "play", dry_run)
@@ -652,6 +657,7 @@ def _play_locked(
selection, selection,
runner, runner,
use_gamebox=not no_box, use_gamebox=not no_box,
use_world_fix=not publisher_dll,
) )
except KeyboardInterrupt: except KeyboardInterrupt:
current_game_log = _current_game_log( current_game_log = _current_game_log(

View File

@@ -19,11 +19,7 @@ from ptdiag.assbin import (
) )
from .paths import AppPaths from .paths import AppPaths
from .v21_post_auth_reclaim import ( from .world_fix import V21_BINARY_HASHES
PATCHED_SHA256 as POST_AUTH_RECLAIM_GAME_DLL_SHA256,
PostAuthReclaimPatchError,
build_post_auth_reclaim_patch,
)
WHISPERING_VALE_RELATIVE = Path("game/maps/WhisperingVale.rovug") WHISPERING_VALE_RELATIVE = Path("game/maps/WhisperingVale.rovug")
@@ -39,10 +35,7 @@ COMPATIBILITY_BINARY_HASHES = {
"Game.exe": "3cc0980aac0f65b379ff340ec61750498fb9f620fb6c88aa66281e8ab27023a5", "Game.exe": "3cc0980aac0f65b379ff340ec61750498fb9f620fb6c88aa66281e8ab27023a5",
"game.dll": "7ea85b19f452962a39c91f967cad22495f066b2d52e8ac37f4e234463d5c4f09", "game.dll": "7ea85b19f452962a39c91f967cad22495f066b2d52e8ac37f4e234463d5c4f09",
} }
CURRENT_V21_BINARY_HASHES = { CURRENT_V21_BINARY_HASHES = V21_BINARY_HASHES
"Game.exe": "2bc3edd26c5f6d7ae9bbdc23967c5aefd374bdf934fcdfa2ed42c6442e550837",
"game.dll": "4b4e5c28e2bfdaa8d36c8341fad27becfd03faf63aa8ea874f5d43c5b1961868",
}
PATCHED_V21_GAME_DLL_SHA256 = ( PATCHED_V21_GAME_DLL_SHA256 = (
"0be11b06ccf9be747e32b195c8d5980062f446d7d3a0a662ebfe0e602a01c538" "0be11b06ccf9be747e32b195c8d5980062f446d7d3a0a662ebfe0e602a01c538"
) )
@@ -75,6 +68,67 @@ class CompatibilityCacheResult:
created_at: str 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( def prepare_runtime_view(
paths: AppPaths, paths: AppPaths,
source_game: Path, source_game: Path,
@@ -90,10 +144,12 @@ def prepare_runtime_view(
inspection = inspect_wrapped_assbin(source_scene) inspection = inspect_wrapped_assbin(source_scene)
action = _scene_action(inspection) action = _scene_action(inspection)
game_save = _prepare_persistent_save(paths, source) 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( binary_action, game_exe_source, game_dll_source = _runtime_game_binary_sources(
paths, paths,
source, source,
use_world_fix=use_world_fix,
) )
build = paths.data / f".{paths.runtime_view.name}.build-{uuid.uuid4().hex}" build = paths.data / f".{paths.runtime_view.name}.build-{uuid.uuid4().hex}"
@@ -207,22 +263,14 @@ def cache_compatibility_binaries(
def select_binary_action( def select_binary_action(
cache_hashes: dict[str, str] | None, cache_hashes: dict[str, str] | None,
source_hashes: dict[str, str], source_hashes: dict[str, str],
runtime_patch_hash: str | None = None,
) -> str: ) -> 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 is not None:
if cache_hashes != COMPATIBILITY_BINARY_HASHES: if cache_hashes != COMPATIBILITY_BINARY_HASHES:
raise OverlayError("compatibility cache has an unknown binary profile") raise OverlayError("compatibility cache has an unknown binary profile")
if source_hashes == COMPATIBILITY_BINARY_HASHES: if source_hashes == COMPATIBILITY_BINARY_HASHES:
return "verified-source-pair" return "verified-source-pair"
if runtime_patch_hash is not None: if source_hashes == CURRENT_V21_BINARY_HASHES:
if source_hashes != CURRENT_V21_BINARY_HASHES: return "verified-v21-publisher-pair"
raise OverlayError(
"post-authentication runtime patch does not match the installed client profile"
)
return "verified-v21-post-auth-world-fix"
return "upstream-pair" return "upstream-pair"
@@ -378,38 +426,30 @@ def _reject_save_symlinks(root: Path) -> None:
def _runtime_game_binary_sources( def _runtime_game_binary_sources(
paths: AppPaths, paths: AppPaths,
source: Path, source: Path,
*,
use_world_fix: bool = True,
) -> tuple[str, Path, Path]: ) -> tuple[str, Path, Path]:
cache = paths.compatibility_binaries cache = paths.compatibility_binaries
cache_hashes = _binary_hashes(cache) if cache.is_dir() else None cache_hashes = _binary_hashes(cache) if cache.is_dir() else None
source_hashes = _binary_hashes(source) source_hashes = _binary_hashes(source)
patch = paths.runtime_patch_dll action = select_binary_action(cache_hashes, source_hashes)
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)
return ( return (
action, action,
source / "Game.exe", 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]: def _binary_hashes(directory: Path) -> dict[str, str]:
return { return {
name: _sha256(directory / name) name: _sha256(directory / name)

View File

@@ -182,10 +182,14 @@ class AppPaths:
@property @property
def runtime_patch_directory(self) -> Path: def runtime_patch_directory(self) -> Path:
"""Retired 0.5.8 disk-patch cache; never selected for play."""
return self.data / "runtime-patches" / "v21-post-auth-reclaim" return self.data / "runtime-patches" / "v21-post-auth-reclaim"
@property @property
def runtime_patch_dll(self) -> Path: def runtime_patch_dll(self) -> Path:
"""Retired 0.5.8 DLL retained only so diagnostics can identify it."""
return self.runtime_patch_directory / "game.dll" return self.runtime_patch_directory / "game.dll"
@property @property

View File

@@ -16,6 +16,7 @@ from .command import CommandOutcome, CommandRunner, OutputLineFilter
from .gamebox import build_gamebox_command from .gamebox import build_gamebox_command
from .paths import AppPaths from .paths import AppPaths
from .settings import Settings from .settings import Settings
from .world_fix import WorldFixError, resolve_world_fix_helper
WINDOWS_GAME_EXE = r"C:\Program Files (x86)\Pristontale EU\Game.exe" WINDOWS_GAME_EXE = r"C:\Program Files (x86)\Pristontale EU\Game.exe"
@@ -250,6 +251,7 @@ def run_game(
runner: CommandRunner, runner: CommandRunner,
*, *,
use_gamebox: bool = True, use_gamebox: bool = True,
use_world_fix: bool = False,
timeout_seconds: float = 12 * 60 * 60, timeout_seconds: float = 12 * 60 * 60,
check: bool = True, check: bool = True,
extra_environment: Mapping[str, str] | None = None, extra_environment: Mapping[str, str] | None = None,
@@ -257,21 +259,30 @@ def run_game(
) -> CommandOutcome: ) -> CommandOutcome:
if not paths.runtime_view.is_dir() and not runner.dry_run: if not paths.runtime_view.is_dir() and not runner.dry_run:
raise RuntimeErrorPT("runtime view is missing; prepare the game first") raise RuntimeErrorPT("runtime view is missing; prepare the game first")
world_fix_helper = None
if use_world_fix and not runner.dry_run:
try:
world_fix_helper = resolve_world_fix_helper(paths.runtime_view)
except WorldFixError as exc:
raise RuntimeErrorPT(str(exc)) from exc
launch_executable = world_fix_helper or (paths.runtime_view / "Game.exe")
if selection.kind == "umu": if selection.kind == "umu":
game_command = [ game_command = [
selection.executable, selection.executable,
str(paths.runtime_view / "Game.exe"), str(launch_executable),
"PTEU_REBORN",
] ]
if world_fix_helper is None:
game_command.append("PTEU_REBORN")
environment = _umu_environment(paths) environment = _umu_environment(paths)
elif selection.kind == "proton7-container": elif selection.kind == "proton7-container":
if not selection.proton_path: if not selection.proton_path:
raise RuntimeErrorPT("Proton script path is missing") raise RuntimeErrorPT("Proton script path is missing")
game_command = [ game_command = [
selection.executable, selection.executable,
str(paths.runtime_view / "Game.exe"), str(launch_executable),
"PTEU_REBORN",
] ]
if world_fix_helper is None:
game_command.append("PTEU_REBORN")
environment = _umu_proton7_environment( environment = _umu_proton7_environment(
paths, paths,
selection, selection,
@@ -286,9 +297,10 @@ def run_game(
"ptlinux.proton_compat", "ptlinux.proton_compat",
selection.proton_path, selection.proton_path,
"runinprefix", "runinprefix",
WINDOWS_GAME_EXE, str(world_fix_helper) if world_fix_helper is not None else WINDOWS_GAME_EXE,
"PTEU_REBORN",
] ]
if world_fix_helper is None:
game_command.append("PTEU_REBORN")
environment = _proton_environment( environment = _proton_environment(
paths, paths,
selection, selection,

View File

@@ -637,6 +637,7 @@ def _validate_release_directory(release: Path) -> str:
Path("ptlinux/cli.py"), Path("ptlinux/cli.py"),
Path("ptlinux/self_update.py"), Path("ptlinux/self_update.py"),
Path("ptlinux") / PUBLIC_KEY_FILENAME, Path("ptlinux") / PUBLIC_KEY_FILENAME,
Path("ptlinux/native/pt-world-fix.exe"),
Path("ptdiag/__init__.py"), Path("ptdiag/__init__.py"),
Path("ptdiag/assbin.py"), Path("ptdiag/assbin.py"),
Path("ptdiag/logs.py"), Path("ptdiag/logs.py"),

View File

@@ -29,6 +29,7 @@ from .news import (
fetch_news_image, fetch_news_image,
fetch_official_logo, fetch_official_logo,
) )
from .overlay import RuntimeCompatibility, inspect_runtime_compatibility
from .paths import AppPaths from .paths import AppPaths
from .settings import Settings, ensure_settings, save_settings from .settings import Settings, ensure_settings, save_settings
from .self_update import LauncherUpdateCheck, check_for_launcher_update from .self_update import LauncherUpdateCheck, check_for_launcher_update
@@ -568,9 +569,12 @@ def run_dashboard(paths: AppPaths) -> None:
operation_stage = 0 operation_stage = 0
operation_stage_total = 0 operation_stage_total = 0
operation_message = "" operation_message = ""
operation_refreshes_compatibility = False
last_elapsed_second = -1 last_elapsed_second = -1
official_snapshot: DepotSnapshot | None = None official_snapshot: DepotSnapshot | None = None
launcher_update_check: LauncherUpdateCheck | None = None launcher_update_check: LauncherUpdateCheck | None = None
runtime_compatibility: RuntimeCompatibility | None = None
compatibility_check_generation = 0
buttons = ( buttons = (
play_button, play_button,
update_button, update_button,
@@ -753,18 +757,21 @@ def run_dashboard(paths: AppPaths) -> None:
*, *,
show_preloader: bool = False, show_preloader: bool = False,
restart_on_success: bool = False, restart_on_success: bool = False,
game_files_may_change: bool = False,
) -> None: ) -> None:
nonlocal operation_active nonlocal operation_active
nonlocal operation_started_at nonlocal operation_started_at
nonlocal operation_stage nonlocal operation_stage
nonlocal operation_stage_total nonlocal operation_stage_total
nonlocal operation_message nonlocal operation_message
nonlocal operation_refreshes_compatibility
nonlocal last_elapsed_second nonlocal last_elapsed_second
operation_active = True operation_active = True
operation_started_at = time.monotonic() operation_started_at = time.monotonic()
operation_stage = 0 operation_stage = 0
operation_stage_total = 0 operation_stage_total = 0
operation_message = "" operation_message = ""
operation_refreshes_compatibility = game_files_may_change
last_elapsed_second = -1 last_elapsed_second = -1
progress_updates.clear() progress_updates.clear()
transfer_rate.reset() transfer_rate.reset()
@@ -1068,8 +1075,21 @@ def run_dashboard(paths: AppPaths) -> None:
installation = inspect_game_installation( installation = inspect_game_installation(
paths, paths,
official_snapshot.build if official_snapshot is not None else None, official_snapshot.build if official_snapshot is not None else None,
compatibility_ready=(
runtime_compatibility.compatible
if runtime_compatibility is not None
else None
),
) )
play_button.configure(text=installation.action.value) play_button.configure(text=installation.action.value)
if not operation_active:
play_button.configure(
state=(
"disabled"
if installation.action is PrimaryAction.CHECKING
else "normal"
),
)
if not update_activity or operation_active: if not update_activity or operation_active:
return installation return installation
if installation.action is PrimaryAction.INSTALL: if installation.action is PrimaryAction.INSTALL:
@@ -1089,11 +1109,42 @@ def run_dashboard(paths: AppPaths) -> None:
f"Installed depot {installation.local_build} · " f"Installed depot {installation.local_build} · "
f"official depot {official_build}" f"official depot {official_build}"
) )
elif installation.action is PrimaryAction.CHECKING:
status.set("Checking game compatibility…")
progress_text.set("The installed release is being inspected without modifying it.")
elif installation.action is PrimaryAction.LAUNCHER_UPDATE:
status.set("Launcher compatibility update required.")
progress_text.set("This installed game release is not recognized by the launcher.")
else: else:
status.set("Ready") status.set("Ready")
progress_text.set("Click Play to start your journey") progress_text.set("Click Play to start your journey")
return installation return installation
def refresh_runtime_compatibility(*, update_activity: bool = True) -> None:
nonlocal compatibility_check_generation, runtime_compatibility
compatibility_check_generation += 1
check_generation = compatibility_check_generation
runtime_compatibility = None
local_status = inspect_game_installation(paths)
refresh_primary_action(update_activity=update_activity)
if not local_status.installed:
return
def worker() -> None:
result = inspect_runtime_compatibility(paths, paths.source_game)
events.put(
(
"compatibility",
(check_generation, result, update_activity),
)
)
threading.Thread(
target=worker,
name="ptlinux-compatibility",
daemon=True,
).start()
def refresh_remote_content() -> None: def refresh_remote_content() -> None:
def broadcast_worker() -> None: def broadcast_worker() -> None:
events.put(("broadcast", fetch_latest_broadcast(paths.broadcast_cache))) events.put(("broadcast", fetch_latest_broadcast(paths.broadcast_cache)))
@@ -1127,8 +1178,10 @@ def run_dashboard(paths: AppPaths) -> None:
).start() ).start()
def finish_operation(returncode: int) -> None: def finish_operation(returncode: int) -> None:
nonlocal operation_active, operation_started_at nonlocal operation_active, operation_refreshes_compatibility, operation_started_at
refresh_compatibility = operation_refreshes_compatibility
operation_active = False operation_active = False
operation_refreshes_compatibility = False
operation_started_at = None operation_started_at = None
progress_updates.clear() progress_updates.clear()
transfer_rate.reset() transfer_rate.reset()
@@ -1138,13 +1191,16 @@ def run_dashboard(paths: AppPaths) -> None:
root.deiconify() root.deiconify()
root.lift() root.lift()
set_busy(False) set_busy(False)
refresh_primary_action(update_activity=returncode == 0) if refresh_compatibility:
refresh_runtime_compatibility(update_activity=returncode == 0)
else:
refresh_primary_action(update_activity=returncode == 0)
if returncode != 0: if returncode != 0:
status.set(f"Operation failed (exit {returncode})") status.set(f"Operation failed (exit {returncode})")
progress_text.set("See the activity log for details.") progress_text.set("See the activity log for details.")
def poll_events() -> None: def poll_events() -> None:
nonlocal launcher_update_check nonlocal launcher_update_check, runtime_compatibility
processed = 0 processed = 0
try: try:
while processed < EVENT_BATCH_SIZE: while processed < EVENT_BATCH_SIZE:
@@ -1192,6 +1248,29 @@ def run_dashboard(paths: AppPaths) -> None:
install_news_image(value) install_news_image(value)
elif kind == "logo_image": elif kind == "logo_image":
install_official_logo(value) install_official_logo(value)
elif (
kind == "compatibility"
and isinstance(value, tuple)
and len(value) == 3
and isinstance(value[0], int)
and isinstance(value[1], RuntimeCompatibility)
and isinstance(value[2], bool)
):
check_generation, checked, update_activity = value
if check_generation == compatibility_check_generation:
runtime_compatibility = checked
if checked.compatible:
append_log(
"Compatibility check passed: "
f"scene={checked.scene_action}, "
f"binaries={checked.binary_action}"
)
else:
append_log(
"Launcher compatibility update required: "
f"{checked.reason or 'unknown release profile'}"
)
refresh_primary_action(update_activity=update_activity)
elif kind == "version" and isinstance(value, DepotSnapshot): elif kind == "version" and isinstance(value, DepotSnapshot):
version.set( version.set(
f"Launcher {__version__} · {value.version} · depot {value.build}" f"Launcher {__version__} · {value.version} · depot {value.build}"
@@ -1241,18 +1320,29 @@ def run_dashboard(paths: AppPaths) -> None:
def command_for(*arguments: str) -> list[str]: def command_for(*arguments: str) -> list[str]:
return [sys.executable, "-m", "ptlinux", *arguments] return [sys.executable, "-m", "ptlinux", *arguments]
def run_launcher_update() -> None: def run_launcher_update(required_reason: str | None = None) -> None:
check = launcher_update_check check = launcher_update_check
if check is None: if check is None:
launcher_update_button.configure(text="CHECKING…", state="disabled") launcher_update_button.configure(text="CHECKING…", state="disabled")
if required_reason is not None:
status.set("Checking for a compatible launcher update…")
progress_text.set("Play remains disabled until this release is supported.")
refresh_remote_content() refresh_remote_content()
return return
if not check.available: if not check.available:
messagebox.showinfo( if required_reason is None:
"Launcher update", messagebox.showinfo(
check.reason, "Launcher update",
parent=root, check.reason,
) parent=root,
)
else:
messagebox.showwarning(
"Compatibility update required",
"The installed game needs a launcher compatibility update:\n\n"
f"{required_reason}\n\n{check.reason}",
parent=root,
)
return return
if not check.supported: if not check.supported:
messagebox.showwarning( messagebox.showwarning(
@@ -1287,11 +1377,19 @@ def run_dashboard(paths: AppPaths) -> None:
run_operation( run_operation(
"Installing official game files…", "Installing official game files…",
command_for("update"), command_for("update"),
game_files_may_change=True,
) )
else: elif installation.action is PrimaryAction.UPDATE:
run_operation( run_operation(
"Synchronizing official game files…", "Synchronizing official game files…",
command_for("update"), command_for("update"),
game_files_may_change=True,
)
elif installation.action is PrimaryAction.LAUNCHER_UPDATE:
run_launcher_update(
runtime_compatibility.reason
if runtime_compatibility is not None
else "The installed release profile is not recognized.",
) )
play_button.configure(command=run_primary_action) play_button.configure(command=run_primary_action)
@@ -1299,12 +1397,14 @@ def run_dashboard(paths: AppPaths) -> None:
command=lambda: run_operation( command=lambda: run_operation(
"Synchronizing official game files…", "Synchronizing official game files…",
command_for("update"), command_for("update"),
game_files_may_change=True,
) )
) )
repair_button.configure( repair_button.configure(
command=lambda: run_operation( command=lambda: run_operation(
"Checking every game file against the official depot…", "Checking every game file against the official depot…",
command_for("update", "--no-import", "--repair"), command_for("update", "--no-import", "--repair"),
game_files_may_change=True,
) )
) )
doctor_button.configure( doctor_button.configure(
@@ -1603,6 +1703,7 @@ def run_dashboard(paths: AppPaths) -> None:
run_operation( run_operation(
"Importing the existing game into the managed installation…", "Importing the existing game into the managed installation…",
command_for("import-game", source), command_for("import-game", source),
game_files_may_change=True,
) )
import_button.configure(command=choose_import_source) import_button.configure(command=choose_import_source)
@@ -1619,6 +1720,7 @@ def run_dashboard(paths: AppPaths) -> None:
news_content.bind("<Configure>", update_news_viewport) news_content.bind("<Configure>", update_news_viewport)
news_canvas.bind("<Configure>", resize_news_content) news_canvas.bind("<Configure>", resize_news_content)
refresh_primary_action() refresh_primary_action()
refresh_runtime_compatibility()
poll_events() poll_events()
load_official_logo() load_official_logo()
refresh_remote_content() refresh_remote_content()