From 0d99caf2b077843c4b9bacdf3bdf2767e153f2b4 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 15 Apr 2026 11:40:57 +0200 Subject: [PATCH] launcher: deliver m2pack runtime key + auto-pick metin wine prefix GameProcess now reads runtime-key.json from the install dir and forwards master_key_hex / sign_public_key_hex / key_id to the spawned Metin2.exe via the M2PACK_MASTER_KEY_HEX, M2PACK_SIGN_PUBKEY_HEX, M2PACK_KEY_ID env vars the new MinGW client loader expects. Without this the m2p loader bails out with "Invalid M2PACK_MASTER_KEY_HEX". Also defaults WINEPREFIX to ~/metin/wine-metin when the parent shell hasn't set one. The default ~/.wine prefix lacks tahoma + d3dx9 + vcrun, which makes the client run with invisible fonts; the metin-specific prefix is prepared by m2dev-client/scripts/setup-wine-prefix.sh. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Metin2Launcher/GameLaunch/GameProcess.cs | 63 +++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/Metin2Launcher/GameLaunch/GameProcess.cs b/src/Metin2Launcher/GameLaunch/GameProcess.cs index ced668f..3de0ac1 100644 --- a/src/Metin2Launcher/GameLaunch/GameProcess.cs +++ b/src/Metin2Launcher/GameLaunch/GameProcess.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Text.Json; using Metin2Launcher.Logging; namespace Metin2Launcher.GameLaunch; @@ -53,23 +54,69 @@ public static class GameProcess /// public static ProcessStartInfo BuildStartInfo(string exePath, string workingDirectory) { + ProcessStartInfo psi; if (OperatingSystem.IsLinux()) { - var psi = new ProcessStartInfo + psi = new ProcessStartInfo { FileName = "wine", WorkingDirectory = workingDirectory, UseShellExecute = false, }; psi.ArgumentList.Add(exePath); - return psi; + // The default ~/.wine prefix lacks tahoma + d3dx9 + vcrun, which makes + // the client run with invisible fonts and missing renderer DLLs. We + // honor WINEPREFIX from the parent env if set; otherwise prefer a + // metin-specific prefix that setup-wine-prefix.sh prepares. + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WINEPREFIX"))) + { + var home = Environment.GetEnvironmentVariable("HOME") ?? "/home/jann"; + var metinPrefix = Path.Combine(home, "metin", "wine-metin"); + if (Directory.Exists(metinPrefix)) + psi.Environment["WINEPREFIX"] = metinPrefix; + } } - - return new ProcessStartInfo + else { - FileName = exePath, - WorkingDirectory = workingDirectory, - UseShellExecute = false, - }; + psi = new ProcessStartInfo + { + FileName = exePath, + WorkingDirectory = workingDirectory, + UseShellExecute = false, + }; + } + ApplyM2PackRuntimeKey(psi, workingDirectory); + return psi; + } + + // m2pack-secure: the new client refuses to load .m2p archives unless a + // runtime master key is delivered out-of-band. The release ships + // runtime-key.json next to the exe; we read it and pass the values via + // env vars (M2PACK_MASTER_KEY_HEX et al) which the loader picks up. + // If the file is missing we just don't set the vars — useful for legacy + // releases that still have the static client. + private static void ApplyM2PackRuntimeKey(ProcessStartInfo psi, string clientRoot) + { + var keyFile = Path.Combine(clientRoot, "runtime-key.json"); + if (!File.Exists(keyFile)) + { + return; + } + try + { + using var doc = JsonDocument.Parse(File.ReadAllText(keyFile)); + var root = doc.RootElement; + if (root.TryGetProperty("master_key_hex", out var mk)) + psi.Environment["M2PACK_MASTER_KEY_HEX"] = mk.GetString() ?? ""; + if (root.TryGetProperty("sign_public_key_hex", out var pk)) + psi.Environment["M2PACK_SIGN_PUBKEY_HEX"] = pk.GetString() ?? ""; + if (root.TryGetProperty("key_id", out var kid)) + psi.Environment["M2PACK_KEY_ID"] = kid.GetInt32().ToString(); + Log.Info("m2pack runtime key loaded from runtime-key.json"); + } + catch (Exception ex) + { + Log.Error("failed to load runtime-key.json", ex); + } } }