scripts: make-manifest.py drops launcher exe from files list #9

Closed
opened 2026-04-15 11:48:28 +02:00 by jann · 1 comment
Member

make-manifest.py currently stores the launcher file only under the top-level launcher field of the manifest and excludes it from the files array.

The launcher-side orchestrator (jann/metin-launcher UpdateOrchestrator) only iterates manifest.files when downloading blobs — it never consults the launcher field for a download action. Result: the launcher exe (e.g. Metin2.exe) is never fetched, the install dir is left without the client binary, and GameProcess.Launch logs game executable not found.

Hit this during today's m2pack-full release: had to post-process every signed manifest by reinjecting the launcher entry into files before publish.

Reproduction

  1. python3 scripts/make-manifest.py --source /path/to/client --launcher Metin2.exe --out manifest.json
  2. jq '.files | map(select(.path == "Metin2.exe")) | length' manifest.json0
  3. Orchestrator downloads everything except Metin2.exe.

Fix options

  • Append the launcher FileEntry to files in walk_client before returning (and keep the launcher field for spawn-time metadata).
  • Or have the launcher-side orchestrator additionally download manifest.launcher on a hash mismatch.

The first option is simpler and keeps the orchestrator contract ("files lists every blob") honest. Opening as a blocker for the next clean-pipeline release.

Workaround currently in use: the release wrapper patches the JSON with a second python pass that sorts the launcher entry into files and re-signs with sign-manifest.py.

`make-manifest.py` currently stores the launcher file only under the top-level `launcher` field of the manifest and excludes it from the `files` array. The launcher-side orchestrator (jann/metin-launcher `UpdateOrchestrator`) only iterates `manifest.files` when downloading blobs — it never consults the `launcher` field for a download action. Result: the launcher exe (e.g. `Metin2.exe`) is never fetched, the install dir is left without the client binary, and `GameProcess.Launch` logs `game executable not found`. Hit this during today's m2pack-full release: had to post-process every signed manifest by reinjecting the launcher entry into files before publish. ## Reproduction 1. `python3 scripts/make-manifest.py --source /path/to/client --launcher Metin2.exe --out manifest.json` 2. `jq '.files | map(select(.path == "Metin2.exe")) | length' manifest.json` → `0` 3. Orchestrator downloads everything except Metin2.exe. ## Fix options - Append the launcher FileEntry to `files` in `walk_client` before returning (and keep the `launcher` field for spawn-time metadata). - Or have the launcher-side orchestrator additionally download `manifest.launcher` on a hash mismatch. The first option is simpler and keeps the orchestrator contract ("files lists every blob") honest. Opening as a blocker for the next clean-pipeline release. Workaround currently in use: the release wrapper patches the JSON with a second python pass that sorts the launcher entry into files and re-signs with `sign-manifest.py`.
Author
Member

Closing as misframed after Jakub's review.

The upstream make-manifest.py does the right thing per spec (m2dev-client/docs/update-manifest.md):

  • the top-level launcher entry is privileged, always required, never in the files array
  • the launcher file is Metin2Launcher.exe (the self-updating updater), not Metin2.exe (the game client)
  • the game client is a regular file and belongs in files like any other content

The "bug" I hit yesterday was actually my own release pipeline passing --launcher Metin2.exe to make-manifest.py, which is a spec misuse — it tells the script "this PE binary is the updater", and the script correctly excludes it from files. My post-process wrapper then re-injected it back, which worked but muddles the contract.

The correct fix is in the release tree assembly (reframed as [new issue]), not in make-manifest.py.

Closing as **misframed** after Jakub's review. The upstream `make-manifest.py` does the right thing per spec (`m2dev-client/docs/update-manifest.md`): - the top-level `launcher` entry is privileged, always required, **never in the `files` array** - the launcher file is `Metin2Launcher.exe` (the self-updating updater), not `Metin2.exe` (the game client) - the game client is a regular file and belongs in `files` like any other content The "bug" I hit yesterday was actually my own release pipeline passing `--launcher Metin2.exe` to `make-manifest.py`, which is a spec misuse — it tells the script "this PE binary is the updater", and the script correctly excludes it from files. My post-process wrapper then re-injected it back, which worked but muddles the contract. The correct fix is in the release tree assembly (reframed as [new issue]), not in `make-manifest.py`.
jann closed this issue 2026-04-15 12:24:12 +02:00
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#9