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) <noreply@anthropic.com>
This commit is contained in:
Jan Nedbal
2026-04-15 11:40:57 +02:00
parent 2471e973c2
commit 0d99caf2b0

View File

@@ -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
/// </summary>
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);
}
}
}