using System.Security.Cryptography; using System.Text; namespace WxAgent.Core; public enum KeyBindingConfidence { PageHmacVerified } public sealed record DatabaseKeyEvidence( string RelativePath, string Salt, string EncKey, int SourceProcessId, DateTimeOffset VerifiedAt, KeyBindingConfidence Confidence); public sealed record AccountKeySet( string AccountRootFingerprint, string AccountRootPath, string? WechatVersion, DateTimeOffset LastSeenAt, IReadOnlyList Databases); public static class AccountKeySetMerge { public static IReadOnlyList Merge( IReadOnlyList existing, IReadOnlyList incoming) { var accounts = existing.ToDictionary(account => account.AccountRootFingerprint, StringComparer.OrdinalIgnoreCase); foreach (var current in incoming) { if (!accounts.TryGetValue(current.AccountRootFingerprint, out var previous)) { accounts[current.AccountRootFingerprint] = current; continue; } var databases = previous.Databases.ToDictionary(database => database.RelativePath, StringComparer.OrdinalIgnoreCase); foreach (var database in current.Databases) { databases[database.RelativePath] = database; } accounts[current.AccountRootFingerprint] = current with { WechatVersion = current.WechatVersion ?? previous.WechatVersion, Databases = databases.Values.OrderBy(database => database.RelativePath, StringComparer.OrdinalIgnoreCase).ToArray() }; } return accounts.Values.OrderBy(account => account.AccountRootFingerprint, StringComparer.OrdinalIgnoreCase).ToArray(); } } public static class AccountRootFingerprint { public static string Create(string accountRootPath) { ArgumentException.ThrowIfNullOrWhiteSpace(accountRootPath); var normalized = accountRootPath.Replace('/', '\\').TrimEnd('\\').ToUpperInvariant(); return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(normalized))).ToLowerInvariant(); } }