adds RuntimeKey DTO parsed from runtime-key.json, an IRuntimeKeyDelivery strategy interface, and the env-var delivery used by the m2pack MVP (M2PACK_MASTER_KEY_HEX / M2PACK_SIGN_PUBKEY_HEX / M2PACK_KEY_ID). SharedMemoryKeyDelivery is a documented stub — will be wired once the client-side receiver lands. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
77 lines
2.5 KiB
C#
77 lines
2.5 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace Metin2Launcher.Runtime;
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="IRuntimeKeyDelivery"/>
|
|
/// 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):
|
|
/// <code>
|
|
/// {
|
|
/// "key_id": "2026.04.14-1",
|
|
/// "master_key_hex": "<64 hex chars>",
|
|
/// "sign_pubkey_hex": "<64 hex chars>"
|
|
/// }
|
|
/// </code>
|
|
/// </summary>
|
|
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,
|
|
};
|
|
|
|
/// <summary>Parses a runtime-key.json blob. Throws on malformed or incomplete data.</summary>
|
|
public static RuntimeKey Parse(ReadOnlySpan<byte> raw)
|
|
{
|
|
var rk = JsonSerializer.Deserialize<RuntimeKey>(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;
|
|
}
|
|
}
|