Replace the flat light theme with a dark palette built for a fantasy MMO
feel. Banner uses a crimson linear gradient and wider letter-spacing on
the title, the body is #0F0F12 with a #17171B left column, the Play
button is a red gradient with hover state and a disabled rust tone.
Status foreground defaults to white and turns a soft red only on
signature failure. The news panel gains an empty-state placeholder with
a diamond glyph so a missing manifest doesn't look like a bug.
Localization strings for the empty state switch from 'Žádné novinky.' to
'Zatím žádné novinky' / 'No news yet' to read more like a UI state than
an error. The view model exposes IsNewsEmpty / IsNewsPresent so the
XAML can toggle between the placeholder and the real news body without
touching the flush timer.
Add Avalonia GUI to the launcher with a proper windowed flow, progress bar,
news panel, settings dialog, Czech/English localization, and a refactored
UpdateOrchestrator with progress events. Keeps the headless --nogui path
working. Fixes the Linux-native game launch path to spawn wine explicitly so
the child inherits WINEPREFIX.
The extracted orchestrator and the new GUI view model didn't call
Velopack's self-update path, leaving that behaviour only in the headless
--nogui branch. Wire the same TrySelfUpdateAsync helper into the GUI
orchestration so the launcher keeps itself current regardless of which
mode the user runs.
Program.Main now starts the Avalonia desktop lifetime by default and
falls back to a headless orchestrator run that logs progress and launches
the game when --nogui is passed. README documents both modes and the new
launcher-settings.json store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds the Avalonia App, MainWindow (900x560 fixed, dark-red banner, left
status/progress column, right news panel) and a modal SettingsDialog with
language + dev-mode override. MainWindowViewModel binds to the orchestrator
through IProgress, throttles UI updates to ~10 Hz via a DispatcherTimer
flush, drives state-specific status text and Play-button gating, fetches
previous-manifest notes best-effort, and forces a red status banner with
Play disabled on a SignatureFailed result.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lifts the manifest fetch / verify / diff / download / apply pipeline out
of Program.cs into a reusable UpdateOrchestrator that emits typed
LauncherState progress events through IProgress<UpdateProgress>. The
existing logic, error handling, and signature-failure semantics are
preserved verbatim; the orchestrator just drives a sink instead of the
console so the GUI and the headless --nogui path can share one pipeline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds Avalonia 11 + CommunityToolkit.Mvvm package references (no UI code
yet), a flat-key Loc resource loader with embedded cs.json/en.json, the
LauncherSettings JSON store with dev-mode-gated manifest URL override,
and an optional IProgress<long> bytesProgress hook on BlobDownloader so
the upcoming GUI can drive a progress bar from per-blob byte counts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the MVP placeholder Ed25519 public key with the real one that
corresponds to ~/.config/metin/launcher-signing-key on the release
machine. Update README to document where the private half lives and
how rotation works. Private key is not committed anywhere; the only
code path that touches it is scripts/sign-manifest.py in m2dev-client.
Defence-in-depth against a buggy or crafted manifest that points
outside the client directory. Path resolution is centralised in
PathSafety.ResolveInside so the orchestration code cannot forget the
check; the applier still sees already-validated absolute paths.
Rejected cases: parent traversal, mixed traversal, absolute paths,
sibling-root lexical escapes. Required files that fail the check
abort the update; optional files are skipped and logged.
The previous flow threw InvalidOperationException on signature
failure, which was caught by the outer catch(Exception) and silently
fell through to the offline launch branch — the exact bypass the
design doc forbids ('server is lying' is more dangerous than 'server
is down'). Introduce ManifestSignatureException and handle it in a
dedicated catch above the generic fallback, exiting with code 2
without launching the game.