using System.Text.Json;
using System.Text.Json.Serialization;
namespace Metin2Launcher.Runtime;
///
/// In-memory representation of a runtime key bundle delivered alongside an
/// m2pack release. The launcher never decrypts or opens .m2p archives itself;
/// it merely parses the key bundle produced by the release tool and passes it
/// through to the game executable via one of the
/// mechanisms.
///
/// File layout (runtime-key.json, placed next to the manifest by the release
/// tool, never signed independently — its integrity comes from the signed
/// manifest that references it):
///
/// {
/// "key_id": "2026.04.14-1",
/// "master_key_hex": "<64 hex chars>",
/// "sign_pubkey_hex": "<64 hex chars>"
/// }
///
///
public sealed class RuntimeKey
{
[JsonPropertyName("key_id")]
public string KeyId { get; set; } = "";
[JsonPropertyName("master_key_hex")]
public string MasterKeyHex { get; set; } = "";
[JsonPropertyName("sign_pubkey_hex")]
public string SignPubkeyHex { get; set; } = "";
private static readonly JsonSerializerOptions _opts = new()
{
PropertyNameCaseInsensitive = false,
};
/// Parses a runtime-key.json blob. Throws on malformed or incomplete data.
public static RuntimeKey Parse(ReadOnlySpan raw)
{
var rk = JsonSerializer.Deserialize(raw, _opts)
?? throw new InvalidDataException("runtime-key deserialized to null");
Validate(rk);
return rk;
}
public static RuntimeKey Load(string path)
{
var bytes = File.ReadAllBytes(path);
return Parse(bytes);
}
private static void Validate(RuntimeKey rk)
{
if (string.IsNullOrWhiteSpace(rk.KeyId))
throw new InvalidDataException("runtime-key missing 'key_id'");
if (!IsHex(rk.MasterKeyHex, 64))
throw new InvalidDataException("runtime-key 'master_key_hex' must be 64 hex chars");
if (!IsHex(rk.SignPubkeyHex, 64))
throw new InvalidDataException("runtime-key 'sign_pubkey_hex' must be 64 hex chars");
}
private static bool IsHex(string s, int expectedLen)
{
if (s is null || s.Length != expectedLen) return false;
for (int i = 0; i < s.Length; i++)
{
char c = s[i];
bool ok = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
if (!ok) return false;
}
return true;
}
}