More changes and fixes to use the new native world (game.dll) fixes.
This commit is contained in:
@@ -4,4 +4,4 @@ from .paths import AppPaths
|
||||
|
||||
__all__ = ["AppPaths", "__version__"]
|
||||
|
||||
__version__ = "0.5.8"
|
||||
__version__ = "0.5.9"
|
||||
|
||||
@@ -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(
|
||||
"--publisher-dll",
|
||||
"--no-world-fix",
|
||||
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("--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}")
|
||||
if 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:
|
||||
print(f"Runtime: {report.runtime_kind} ({report.runtime_path or 'unknown path'})")
|
||||
else:
|
||||
@@ -626,11 +635,7 @@ def _play_locked(
|
||||
overlay = None
|
||||
_print_operation_status(as_json, 1, 4, "Preparing the managed game view…")
|
||||
if not dry_run:
|
||||
overlay = prepare_runtime_view(
|
||||
paths,
|
||||
source,
|
||||
use_world_fix=not publisher_dll,
|
||||
)
|
||||
overlay = prepare_runtime_view(paths, source)
|
||||
_print_operation_status(as_json, 2, 4, "Selecting the configured game runtime…")
|
||||
selection = select_runtime(settings)
|
||||
runner = _runner(paths, "play", dry_run)
|
||||
@@ -652,6 +657,7 @@ def _play_locked(
|
||||
selection,
|
||||
runner,
|
||||
use_gamebox=not no_box,
|
||||
use_world_fix=not publisher_dll,
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
current_game_log = _current_game_log(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -182,10 +182,14 @@ class AppPaths:
|
||||
|
||||
@property
|
||||
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"
|
||||
|
||||
@property
|
||||
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"
|
||||
|
||||
@property
|
||||
|
||||
@@ -16,6 +16,7 @@ from .command import CommandOutcome, CommandRunner, OutputLineFilter
|
||||
from .gamebox import build_gamebox_command
|
||||
from .paths import AppPaths
|
||||
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"
|
||||
@@ -250,6 +251,7 @@ def run_game(
|
||||
runner: CommandRunner,
|
||||
*,
|
||||
use_gamebox: bool = True,
|
||||
use_world_fix: bool = False,
|
||||
timeout_seconds: float = 12 * 60 * 60,
|
||||
check: bool = True,
|
||||
extra_environment: Mapping[str, str] | None = None,
|
||||
@@ -257,21 +259,30 @@ def run_game(
|
||||
) -> CommandOutcome:
|
||||
if not paths.runtime_view.is_dir() and not runner.dry_run:
|
||||
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":
|
||||
game_command = [
|
||||
selection.executable,
|
||||
str(paths.runtime_view / "Game.exe"),
|
||||
"PTEU_REBORN",
|
||||
str(launch_executable),
|
||||
]
|
||||
if world_fix_helper is None:
|
||||
game_command.append("PTEU_REBORN")
|
||||
environment = _umu_environment(paths)
|
||||
elif selection.kind == "proton7-container":
|
||||
if not selection.proton_path:
|
||||
raise RuntimeErrorPT("Proton script path is missing")
|
||||
game_command = [
|
||||
selection.executable,
|
||||
str(paths.runtime_view / "Game.exe"),
|
||||
"PTEU_REBORN",
|
||||
str(launch_executable),
|
||||
]
|
||||
if world_fix_helper is None:
|
||||
game_command.append("PTEU_REBORN")
|
||||
environment = _umu_proton7_environment(
|
||||
paths,
|
||||
selection,
|
||||
@@ -286,9 +297,10 @@ def run_game(
|
||||
"ptlinux.proton_compat",
|
||||
selection.proton_path,
|
||||
"runinprefix",
|
||||
WINDOWS_GAME_EXE,
|
||||
"PTEU_REBORN",
|
||||
str(world_fix_helper) if world_fix_helper is not None else WINDOWS_GAME_EXE,
|
||||
]
|
||||
if world_fix_helper is None:
|
||||
game_command.append("PTEU_REBORN")
|
||||
environment = _proton_environment(
|
||||
paths,
|
||||
selection,
|
||||
|
||||
@@ -637,6 +637,7 @@ def _validate_release_directory(release: Path) -> str:
|
||||
Path("ptlinux/cli.py"),
|
||||
Path("ptlinux/self_update.py"),
|
||||
Path("ptlinux") / PUBLIC_KEY_FILENAME,
|
||||
Path("ptlinux/native/pt-world-fix.exe"),
|
||||
Path("ptdiag/__init__.py"),
|
||||
Path("ptdiag/assbin.py"),
|
||||
Path("ptdiag/logs.py"),
|
||||
|
||||
122
ptlinux/ui.py
122
ptlinux/ui.py
@@ -29,6 +29,7 @@ from .news import (
|
||||
fetch_news_image,
|
||||
fetch_official_logo,
|
||||
)
|
||||
from .overlay import RuntimeCompatibility, inspect_runtime_compatibility
|
||||
from .paths import AppPaths
|
||||
from .settings import Settings, ensure_settings, save_settings
|
||||
from .self_update import LauncherUpdateCheck, check_for_launcher_update
|
||||
@@ -568,9 +569,12 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
operation_stage = 0
|
||||
operation_stage_total = 0
|
||||
operation_message = ""
|
||||
operation_refreshes_compatibility = False
|
||||
last_elapsed_second = -1
|
||||
official_snapshot: DepotSnapshot | None = None
|
||||
launcher_update_check: LauncherUpdateCheck | None = None
|
||||
runtime_compatibility: RuntimeCompatibility | None = None
|
||||
compatibility_check_generation = 0
|
||||
buttons = (
|
||||
play_button,
|
||||
update_button,
|
||||
@@ -753,18 +757,21 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
*,
|
||||
show_preloader: bool = False,
|
||||
restart_on_success: bool = False,
|
||||
game_files_may_change: bool = False,
|
||||
) -> None:
|
||||
nonlocal operation_active
|
||||
nonlocal operation_started_at
|
||||
nonlocal operation_stage
|
||||
nonlocal operation_stage_total
|
||||
nonlocal operation_message
|
||||
nonlocal operation_refreshes_compatibility
|
||||
nonlocal last_elapsed_second
|
||||
operation_active = True
|
||||
operation_started_at = time.monotonic()
|
||||
operation_stage = 0
|
||||
operation_stage_total = 0
|
||||
operation_message = ""
|
||||
operation_refreshes_compatibility = game_files_may_change
|
||||
last_elapsed_second = -1
|
||||
progress_updates.clear()
|
||||
transfer_rate.reset()
|
||||
@@ -1068,8 +1075,21 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
installation = inspect_game_installation(
|
||||
paths,
|
||||
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)
|
||||
if not operation_active:
|
||||
play_button.configure(
|
||||
state=(
|
||||
"disabled"
|
||||
if installation.action is PrimaryAction.CHECKING
|
||||
else "normal"
|
||||
),
|
||||
)
|
||||
if not update_activity or operation_active:
|
||||
return installation
|
||||
if installation.action is PrimaryAction.INSTALL:
|
||||
@@ -1089,11 +1109,42 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
f"Installed depot {installation.local_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:
|
||||
status.set("Ready")
|
||||
progress_text.set("Click Play to start your journey")
|
||||
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 broadcast_worker() -> None:
|
||||
events.put(("broadcast", fetch_latest_broadcast(paths.broadcast_cache)))
|
||||
@@ -1127,8 +1178,10 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
).start()
|
||||
|
||||
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_refreshes_compatibility = False
|
||||
operation_started_at = None
|
||||
progress_updates.clear()
|
||||
transfer_rate.reset()
|
||||
@@ -1138,13 +1191,16 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
root.deiconify()
|
||||
root.lift()
|
||||
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:
|
||||
status.set(f"Operation failed (exit {returncode})")
|
||||
progress_text.set("See the activity log for details.")
|
||||
|
||||
def poll_events() -> None:
|
||||
nonlocal launcher_update_check
|
||||
nonlocal launcher_update_check, runtime_compatibility
|
||||
processed = 0
|
||||
try:
|
||||
while processed < EVENT_BATCH_SIZE:
|
||||
@@ -1192,6 +1248,29 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
install_news_image(value)
|
||||
elif kind == "logo_image":
|
||||
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):
|
||||
version.set(
|
||||
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]:
|
||||
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
|
||||
if check is None:
|
||||
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()
|
||||
return
|
||||
if not check.available:
|
||||
messagebox.showinfo(
|
||||
"Launcher update",
|
||||
check.reason,
|
||||
parent=root,
|
||||
)
|
||||
if required_reason is None:
|
||||
messagebox.showinfo(
|
||||
"Launcher update",
|
||||
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
|
||||
if not check.supported:
|
||||
messagebox.showwarning(
|
||||
@@ -1287,11 +1377,19 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
run_operation(
|
||||
"Installing official game files…",
|
||||
command_for("update"),
|
||||
game_files_may_change=True,
|
||||
)
|
||||
else:
|
||||
elif installation.action is PrimaryAction.UPDATE:
|
||||
run_operation(
|
||||
"Synchronizing official game files…",
|
||||
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)
|
||||
@@ -1299,12 +1397,14 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
command=lambda: run_operation(
|
||||
"Synchronizing official game files…",
|
||||
command_for("update"),
|
||||
game_files_may_change=True,
|
||||
)
|
||||
)
|
||||
repair_button.configure(
|
||||
command=lambda: run_operation(
|
||||
"Checking every game file against the official depot…",
|
||||
command_for("update", "--no-import", "--repair"),
|
||||
game_files_may_change=True,
|
||||
)
|
||||
)
|
||||
doctor_button.configure(
|
||||
@@ -1603,6 +1703,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
||||
run_operation(
|
||||
"Importing the existing game into the managed installation…",
|
||||
command_for("import-game", source),
|
||||
game_files_may_change=True,
|
||||
)
|
||||
|
||||
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_canvas.bind("<Configure>", resize_news_content)
|
||||
refresh_primary_action()
|
||||
refresh_runtime_compatibility()
|
||||
poll_events()
|
||||
load_official_logo()
|
||||
refresh_remote_content()
|
||||
|
||||
Reference in New Issue
Block a user