release: separate Metin2Launcher.exe (updater) from Metin2.exe (game client) per manifest spec #12

Open
opened 2026-04-15 12:24:12 +02:00 by jann · 1 comment
Member

The release pipeline this week conflated two distinct entities that the manifest spec keeps separate:

  • Metin2Launcher.exe — the self-updating updater (what the launcher top-level field of the manifest is meant to describe, privileged, replaced via rename-before-replace, never in files)
  • Metin2.exe — the game client (a regular content file that belongs in files like any other blob)

My release wrapper was passing --launcher Metin2.exe to make-manifest.py. That did what it was asked: put Metin2.exe in the top-level launcher field. The orchestrator then never downloaded it (it only iterates files), so I patched around that by also injecting Metin2.exe back into files post-hoc and re-signing. Result: Metin2.exe ended up in both places with a double role, and the release tree had no real Metin2Launcher.exe at all.

This is architecturally wrong. Jakub flagged it in the PR #1 review of jann/metin-launcher. See spec: docs/update-manifest.md.

What a correct m2pack release tree should look like

  • Metin2Launcher.exe — the .NET Avalonia updater binary, in the tree at root, referenced from the top-level launcher field of the manifest, not in files
  • Metin2.exe — the game client, in the tree at root, listed in files like any other file
  • *.dll, *.pyd, python314.zip, python314._pth — in files
  • pack/*.m2p — in files
  • bgm/*, mark/*, config/* — in files
  • runtime-key.json — in files (the M2PackFormat.OnApplied hook in the launcher loads it after apply)

What's needed

  1. Release tree builder must ship Metin2Launcher.exe alongside Metin2.exe. Velopack produces it; it is the binary that self-updates and spawns the game. Today the launcher is built separately (jann/metin-launcher bin dir) and is not part of the release tree.

  2. make-release.sh / build-release-tree.sh (see #11) should copy Metin2Launcher.exe from the launcher build output into the release tree root before make-manifest.py runs.

  3. make-manifest.py --launcher should then point at Metin2Launcher.exe, not Metin2.exe. No post-process patch needed.

  4. Orchestrator prune must preserve manifest.launcher.path — already fixed in jann/metin-launcher PR #1 round 2.

  5. Orchestrator should probably also apply the launcher file using rename-before-replace semantics per spec, so the updater can update itself without a second Velopack feed. Right now the launcher assumes Velopack for self-update (VelopackFeedUrl) and the manifest launcher field is effectively unused. Pick one path and document it. This is a bigger design decision and doesn't block the immediate fix.

The release pipeline this week conflated two distinct entities that the manifest spec keeps separate: - **`Metin2Launcher.exe`** — the self-updating updater (what the `launcher` top-level field of the manifest is meant to describe, privileged, replaced via rename-before-replace, never in `files`) - **`Metin2.exe`** — the game client (a regular content file that belongs in `files` like any other blob) My release wrapper was passing `--launcher Metin2.exe` to `make-manifest.py`. That did what it was asked: put `Metin2.exe` in the top-level `launcher` field. The orchestrator then never downloaded it (it only iterates `files`), so I patched around that by also injecting `Metin2.exe` back into `files` post-hoc and re-signing. Result: `Metin2.exe` ended up in both places with a double role, and the release tree had no real `Metin2Launcher.exe` at all. This is architecturally wrong. Jakub flagged it in the PR #1 review of jann/metin-launcher. See spec: `docs/update-manifest.md`. ## What a correct m2pack release tree should look like - `Metin2Launcher.exe` — the .NET Avalonia updater binary, in the tree at root, referenced from the top-level `launcher` field of the manifest, not in `files` - `Metin2.exe` — the game client, in the tree at root, listed in `files` like any other file - `*.dll`, `*.pyd`, `python314.zip`, `python314._pth` — in `files` - `pack/*.m2p` — in `files` - `bgm/*`, `mark/*`, `config/*` — in `files` - `runtime-key.json` — in `files` (the `M2PackFormat.OnApplied` hook in the launcher loads it after apply) ## What's needed 1. **Release tree builder must ship `Metin2Launcher.exe`** alongside `Metin2.exe`. Velopack produces it; it is the binary that self-updates and spawns the game. Today the launcher is built separately (`jann/metin-launcher` bin dir) and is not part of the release tree. 2. **`make-release.sh` / `build-release-tree.sh`** (see #11) should copy `Metin2Launcher.exe` from the launcher build output into the release tree root before `make-manifest.py` runs. 3. **`make-manifest.py --launcher`** should then point at `Metin2Launcher.exe`, not `Metin2.exe`. No post-process patch needed. 4. **Orchestrator prune** must preserve `manifest.launcher.path` — already fixed in jann/metin-launcher PR #1 round 2. 5. **Orchestrator should probably also apply the launcher file** using rename-before-replace semantics per spec, so the updater can update itself without a second Velopack feed. Right now the launcher assumes Velopack for self-update (`VelopackFeedUrl`) and the manifest `launcher` field is effectively unused. Pick one path and document it. This is a bigger design decision and doesn't block the immediate fix. ## Related - jann/metin-launcher PR #1 — prune fix, runtime key GUI wiring - metin-server/m2dev-client#11 — reproducible release tree builder (will be the natural place to put the Metin2Launcher.exe copy step) - metin-server/m2dev-client#9 — closed as misframed; this issue supersedes it
Author
Member

Keep-partially checklist for release-v2/client/pack

Produced from a full m2pack diff sweep of every .m2p in the release tree against its source asset dir (m2dev-client/assets/<Name>/) using the canonical master key from .secrets/m2pack-secure/2026-04-14/. Reports under /home/mt2.jakubkadlec.dev/work/release-v2/diff-reports/<Name>.json on the VPS.

Aggregate result: 91/91 packs diffed without tool error. 90/91 are byte-identical to source. 1 pack has content divergence.

Category A — KEEP AS-IS (89 packs, clean diff)

All 49 of my mass-migrated .m2p files (the .pck-only groups I rebuilt this morning) diff clean against their source asset dirs — zero added, zero removed, zero changed, every file path in the archive matches the source with byte-identical content. Same for 40 of Jakub's 42 pre-existing .m2p files. These are good.

  • 49 from my batch: indoordeviltower1, indoormonkeydungeon1..3, indoorspiderdungeon1, metin2_patch_5th_armor, metin2_patch_6th_armor, metin2_patch_bleudmetin, metin2_patch_cash_costume, metin2_patch_christmas_dungeon, metin2_patch_dawndungeon, metin2_patch_event, metin2_patch_event_dungeon, metin2_patch_fishing2, metin2_patch_flame, metin2_patch_flame_dungeon, metin2_patch_flower_hunting, metin2_patch_guild_dungeon, metin2_patch_halloween, metin2_patch_halloween2, metin2_patch_honor_dungeon, metin2_patch_ice, metin2_patch_kingdomquest, metin2_patch_melhountain, metin2_patch_mining, metin2_patch_newyear, metin2_patch_pet1..2, metin2_patch_porcelain, metin2_patch_ramadan_costume, metin2_patch_season1..2, metin2_patch_snow, metin2_patch_snow_dungeon, metin2_patch_snowyquest, metin2_patch_spiderdungeon, metin2_patch_thursday_dungeon, metin2_patch_tree, metin2_patch_vegasnight, metin2_patch_winter, metin2_patch_woods, outdoora1..4, outdoorb2, outdoorc2, outdoorguild1..3, outdoormilgyo1, outdoort1..4, outdoortrent, outdoortrent02, outdoorwedding, property2, season1..2, terrain2, tree2, uiloading, uiscript, zone2
  • 40 from Jakub's batch: effect, etc (uppercase), item, icon, locale, monster, npc, outdoor, outdoorA1..3, outdoorB1/B3, outdoorC1/C3, outdoorsnow1, patch1..2, pc, pc2, property, root, season3_eu, sound, sound_m, terrain, textureset, tree, zone

Keep decision: ship as-is. Strong evidence they're not lossy relative to source.

Residual gap: diff doesn't cover runtime behavior. In-game walkthrough still needed before production.

Category B — REBUILD REQUIRED (1 pack)

  • sound2.m2p: diff shows 2 removed + 1 changed paths.
    • Missing from archive: sound/monster2/outlaw/fall.wav, sound/pc2/assassin/bow/attack1.wav
    • Content mismatch: sound/pc2/assassin/dualhand_sword/combo_07.mss
    • Likely cause: Jakub's commit 5e79fc27 Fix missing dualhand combo 7 sound reference updated the asset source but sound2.m2p was built before that fix. Stale artifact.
    • Action: m2pack build --input assets/sound2 --output pack/sound2.m2p --key master.key --sign-secret-key signing.key --key-id 1. Unblocks once done; full .m2p content would be current.

Category C — UNKNOWN / NEEDS HUMAN CALL (0 packs)

No packs in this category after diff. Nothing else flagged.

Category D — DROP (dropped during cleanup, informational)

19 case-collision duplicates were dropped earlier tonight (lowercase-first stems whose uppercase-first sibling already existed in Jakub's batch): effect, etc, monster, npc, outdoor, outdoora1..3, outdoorb1/b3, outdoorc1/c3, outdoorsnow1, pc, property, sound, terrain, tree, zone. Their uppercase-first counterparts (in Category A) are what survives.

Gates still required before production release

These are the remaining steps from m2pack-secure/docs/migration.md that I skipped and which this diff does not cover:

  1. Runtime scenario gatevalidate_runtime_gate.py needs the assets/ source tree, not the release artifact, so it has to run against the m2dev-client canonical source against a live client boot. Not run tonight.
  2. In-game walkthrough — login, map transitions, skill use, item interactions per the migration doc's "runtime validation" section. Requires a human + working test account.
  3. strict-known-issues gate against m2pack-secure/known_issues/runtime_known_issues.json — tells you if any baseline regressions have landed. Needs the source tree integration.

Recommendation for tonight: Keep Category A, rebuild sound2 (Category B), then do not promote to root until at least the runtime walkthrough passes. Root stays on 2026.04.15-clean as safe fallback.

## Keep-partially checklist for `release-v2/client/pack` Produced from a full `m2pack diff` sweep of every `.m2p` in the release tree against its source asset dir (`m2dev-client/assets/<Name>/`) using the canonical master key from `.secrets/m2pack-secure/2026-04-14/`. Reports under `/home/mt2.jakubkadlec.dev/work/release-v2/diff-reports/<Name>.json` on the VPS. **Aggregate result:** 91/91 packs diffed without tool error. 90/91 are byte-identical to source. 1 pack has content divergence. ### Category A — KEEP AS-IS (89 packs, clean diff) All 49 of my mass-migrated `.m2p` files (the `.pck`-only groups I rebuilt this morning) diff **clean** against their source asset dirs — zero `added`, zero `removed`, zero `changed`, every file path in the archive matches the source with byte-identical content. Same for 40 of Jakub's 42 pre-existing `.m2p` files. These are good. - 49 from my batch: `indoordeviltower1`, `indoormonkeydungeon1..3`, `indoorspiderdungeon1`, `metin2_patch_5th_armor`, `metin2_patch_6th_armor`, `metin2_patch_bleudmetin`, `metin2_patch_cash_costume`, `metin2_patch_christmas_dungeon`, `metin2_patch_dawndungeon`, `metin2_patch_event`, `metin2_patch_event_dungeon`, `metin2_patch_fishing2`, `metin2_patch_flame`, `metin2_patch_flame_dungeon`, `metin2_patch_flower_hunting`, `metin2_patch_guild_dungeon`, `metin2_patch_halloween`, `metin2_patch_halloween2`, `metin2_patch_honor_dungeon`, `metin2_patch_ice`, `metin2_patch_kingdomquest`, `metin2_patch_melhountain`, `metin2_patch_mining`, `metin2_patch_newyear`, `metin2_patch_pet1..2`, `metin2_patch_porcelain`, `metin2_patch_ramadan_costume`, `metin2_patch_season1..2`, `metin2_patch_snow`, `metin2_patch_snow_dungeon`, `metin2_patch_snowyquest`, `metin2_patch_spiderdungeon`, `metin2_patch_thursday_dungeon`, `metin2_patch_tree`, `metin2_patch_vegasnight`, `metin2_patch_winter`, `metin2_patch_woods`, `outdoora1..4`, `outdoorb2`, `outdoorc2`, `outdoorguild1..3`, `outdoormilgyo1`, `outdoort1..4`, `outdoortrent`, `outdoortrent02`, `outdoorwedding`, `property2`, `season1..2`, `terrain2`, `tree2`, `uiloading`, `uiscript`, `zone2` - 40 from Jakub's batch: `effect`, `etc` (uppercase), `item`, `icon`, `locale`, `monster`, `npc`, `outdoor`, `outdoorA1..3`, `outdoorB1/B3`, `outdoorC1/C3`, `outdoorsnow1`, `patch1..2`, `pc`, `pc2`, `property`, `root`, `season3_eu`, `sound`, `sound_m`, `terrain`, `textureset`, `tree`, `zone` Keep decision: **ship as-is**. Strong evidence they're not lossy relative to source. Residual gap: diff doesn't cover runtime behavior. In-game walkthrough still needed before production. ### Category B — REBUILD REQUIRED (1 pack) - **`sound2.m2p`**: diff shows 2 removed + 1 changed paths. - Missing from archive: `sound/monster2/outlaw/fall.wav`, `sound/pc2/assassin/bow/attack1.wav` - Content mismatch: `sound/pc2/assassin/dualhand_sword/combo_07.mss` - Likely cause: Jakub's commit `5e79fc27 Fix missing dualhand combo 7 sound reference` updated the asset source but `sound2.m2p` was built before that fix. Stale artifact. - Action: `m2pack build --input assets/sound2 --output pack/sound2.m2p --key master.key --sign-secret-key signing.key --key-id 1`. Unblocks once done; full `.m2p` content would be current. ### Category C — UNKNOWN / NEEDS HUMAN CALL (0 packs) No packs in this category after diff. Nothing else flagged. ### Category D — DROP (dropped during cleanup, informational) 19 case-collision duplicates were dropped earlier tonight (lowercase-first stems whose uppercase-first sibling already existed in Jakub's batch): `effect`, `etc`, `monster`, `npc`, `outdoor`, `outdoora1..3`, `outdoorb1/b3`, `outdoorc1/c3`, `outdoorsnow1`, `pc`, `property`, `sound`, `terrain`, `tree`, `zone`. Their uppercase-first counterparts (in Category A) are what survives. ## Gates still required before production release These are the remaining steps from `m2pack-secure/docs/migration.md` that I skipped and which this diff does **not** cover: 1. **Runtime scenario gate** — `validate_runtime_gate.py` needs the `assets/` source tree, not the release artifact, so it has to run against the m2dev-client canonical source against a live client boot. Not run tonight. 2. **In-game walkthrough** — login, map transitions, skill use, item interactions per the migration doc's "runtime validation" section. Requires a human + working test account. 3. **`strict-known-issues` gate** against `m2pack-secure/known_issues/runtime_known_issues.json` — tells you if any baseline regressions have landed. Needs the source tree integration. Recommendation for tonight: Keep Category A, rebuild sound2 (Category B), then **do not promote to root** until at least the runtime walkthrough passes. Root stays on `2026.04.15-clean` as safe fallback.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: metin-server/m2dev-client#12