using System.Text; namespace WxAgent.Core; public sealed record WcdbKeyCandidate(string EncKey, string? SaltHint); public sealed class WcdbKeyCandidateScanner { private const int KeyHexLength = 64; private const int SaltHexLength = 32; private readonly StringBuilder _keyPrefix = new(KeyHexLength); private readonly char[] _saltSuffix = new char[SaltHexLength]; private readonly HashSet _seen = new(StringComparer.Ordinal); private long _runLength; private readonly bool _utf16; private bool _hasLowByte; private byte _lowByte; public WcdbKeyCandidateScanner(bool utf16 = false) => _utf16 = utf16; public IReadOnlyList Feed(ReadOnlySpan bytes, bool finalBlock = false) { var found = new List(); foreach (var rawValue in bytes) { var value = rawValue; if (_utf16) { if (!_hasLowByte) { _lowByte = value; _hasLowByte = true; continue; } _hasLowByte = false; if (value != 0) { CompleteRun(found); continue; } value = _lowByte; } if (IsAsciiHex(value)) { AppendHex(value); } else { CompleteRun(found); } } if (finalBlock) { CompleteRun(found); _hasLowByte = false; } return found; } private void AppendHex(byte value) { var character = char.ToLowerInvariant((char)value); if (_runLength < KeyHexLength) { _keyPrefix.Append(character); } _saltSuffix[_runLength % SaltHexLength] = character; _runLength++; } private void CompleteRun(List found) { if (_runLength >= KeyHexLength) { var key = _keyPrefix.ToString(); var salt = _runLength >= KeyHexLength + SaltHexLength ? GetSaltSuffix() : null; if (_seen.Add(key + ":" + salt)) { found.Add(new WcdbKeyCandidate(key, salt)); } } _keyPrefix.Clear(); _runLength = 0; } private string GetSaltSuffix() { var salt = new char[SaltHexLength]; var start = (int)(_runLength % SaltHexLength); for (var index = 0; index < salt.Length; index++) { salt[index] = _saltSuffix[(start + index) % SaltHexLength]; } return new string(salt); } private static bool IsAsciiHex(byte value) => value is >= (byte)'0' and <= (byte)'9' or >= (byte)'a' and <= (byte)'f' or >= (byte)'A' and <= (byte)'F'; }