Add m2pack-secure release format support to the launcher without
removing the legacy JSON-blob path. Manifests now carry an optional
'format' field; a ReleaseFormatFactory dispatches to either
LegacyJsonBlobFormat (default, unchanged behaviour) or M2PackFormat
(new, treats .m2p files as opaque content-addressed blobs and loads
a runtime-key.json sidecar to hand off to the game via env vars).
Unknown formats fail closed.
Adds RuntimeKey model + EnvVarKeyDelivery (cross-platform) + a stub
SharedMemoryKeyDelivery for the future Windows path, an opt-in
ClientAppliedReporter for Morion2 telemetry with 5s timeout and
graceful fallback, plus 60 new tests (total 92) covering every branch.
adds docs/m2pack-integration.md covering the signature boundary,
runtime key env-var delivery, telemetry opt-in, backward compatibility
and expected on-disk layout. README gains a short "Release formats"
section pointing at the new doc, and CHANGELOG tracks the [Unreleased]
entries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adds ~60 new tests across RuntimeKey parsing, EnvVarKeyDelivery, the
legacy and m2pack formats, ReleaseFormatFactory dispatch, manifest
loader tolerance of unknown top-level fields, orchestrator wiring and
the ClientAppliedReporter (disabled-by-default, success, 5xx, timeout,
connection refused). The telemetry tests spin up an in-process
HttpListener helper — no new NuGet dependency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UpdateOrchestrator now resolves an IReleaseFormat from the verified
manifest and uses it to filter applicable files, run the post-apply
hook (which loads the m2pack runtime key when present) and drive the
opt-in client-applied telemetry ping. GameProcess.BuildStartInfo
accepts a RuntimeKey? and forwards it through EnvVarKeyDelivery onto
the child ProcessStartInfo, scoped to the child environment only.
Program.cs wires the reporter and threads the key from the update
result into the game launch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adds ClientAppliedReporter that fires a single bounded (5s) best-effort
POST after a successful update, carrying only release format and
version. the launcher config exposes TelemetryUrlTemplate defaulted to
empty string — when empty the reporter short-circuits and nothing goes
over the network.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adds IReleaseFormat with a legacy-json-blob implementation lifting the
existing per-file update behaviour, and an m2pack implementation that
loads runtime-key.json after apply. a central ReleaseFormatFactory
maps Manifest.EffectiveFormat onto concrete strategies and throws on
unknown values so a signed but unsupported format cannot silently
downgrade.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adds RuntimeKey DTO parsed from runtime-key.json, an IRuntimeKeyDelivery
strategy interface, and the env-var delivery used by the m2pack MVP
(M2PACK_MASTER_KEY_HEX / M2PACK_SIGN_PUBKEY_HEX / M2PACK_KEY_ID).
SharedMemoryKeyDelivery is a documented stub — will be wired once the
client-side receiver lands.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
introduces an optional top-level "format" field on the signed manifest,
defaulting to legacy-json-blob when absent so existing installs keep
parsing unchanged. follow-up commits wire the release format factory
and the m2pack strategy against this value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.