The canonical launcher runtime-key.json shape is
{ key_id: string, master_key_hex, sign_pubkey_hex }
but `m2pack export-runtime-key --format json` emits
{ version, mapping_name, key_id: int, master_key_hex, sign_public_key_hex }
because its JSON is really a dump of the Windows shared-memory struct.
Parsing the CLI output with the old strict deserializer throws
JsonException on key_id (int != string) and silently drops the public
key field (name mismatch), after which Validate() rejects the key as
not 64 hex chars and the m2pack release fails to boot with
"runtime master key with key_id=1 required for 'pack/root.m2p'".
Hit this tonight during the 2026.04.15-m2pack-v2 release and worked
around it by hand-writing runtime-key.json. Fix: parse into a
JsonElement and extract fields tolerantly — key_id accepts either a
JSON string or a JSON number (stringified), and the pubkey field is
looked up under both "sign_pubkey_hex" and "sign_public_key_hex".
Added a test covering the m2pack CLI shape end to end. Also kept the
malformed-input path on JsonSerializer.Deserialize so it still throws
JsonException (JsonDocument.Parse throws its internal subtype which
breaks Assert.Throws<JsonException>).
Tracked separately as metin-server/m2pack-secure#3 — the m2pack side
should also align its JSON to the canonical shape; this commit is the
client-side belt to the server-side suspenders.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the launcher is run from its bin/ dir during local dev, CWD
becomes the source tree and the orchestrator stages the client release
into src/Metin2Launcher/{pack,bgm,mark,config,.updates,...}. None of
that should ever land in git.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Headless Program.cs already passes `result.RuntimeKey` to
GameProcess.Launch, but the GUI Play command dropped it and spawned
Metin2.exe without any env vars. Any m2p-capable client then hit
"Invalid M2PACK_MASTER_KEY_HEX" and refused to load .m2p archives.
Cache the runtime key on the view model when StartUpdateAsync completes
and pass it through on Play. Matches the headless path and the
EnvVarKeyDelivery wiring already in main.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The default ~/.wine prefix lacks tahoma + d3dx9 + vcrun, which makes the
client run with invisible fonts and missing renderer DLLs — exactly what
Jan hit when running the launcher against a fresh install dir on Fedora.
If the parent shell already set WINEPREFIX, honor it. Otherwise fall
back to ~/metin/wine-metin, which is what
m2dev-client/scripts/setup-wine-prefix.sh prepares with the right
runtime deps. The fallback is guarded on the dir existing, so
deployments without that setup are a no-op rather than a broken prefix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two fixes addressing review feedback on the previous avalonia-gui PR round.
1. Platform filter was using host OS (`IsWindows() ? "windows" : "linux"`),
which dropped all `platform=windows` manifest entries whenever the
launcher ran on Linux. Since the client is always a Windows PE binary
launched through Wine on Linux hosts, the target platform we apply is
always `"windows"` regardless of host. Before this, the orchestrator
silently skipped Metin2.exe and the python314 / openssl / mingw runtime
DLLs on Linux, leaving an unbootable install dir.
2. Added `PruneStaleFiles` that walks clientRoot after every successful
update (both the normal apply path and the already-up-to-date
short-circuit) and deletes any file not in the manifest. Without it,
switching from a dirty release to a leaner one — e.g. dropping legacy
.pck after a .m2p migration — left orphaned files on disk and inflated
the install dir.
The keep set is `manifest.files ∪ {manifest.launcher.path}`. Per the
manifest spec in `m2dev-client/docs/update-manifest.md`, the top-level
launcher entry is privileged and never listed in files; it must
survive prune so a correctly-authored manifest does not delete the
updater itself (caught in Jakub's review of the previous round).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>