using System.Security.Cryptography; namespace WxAgent.Core; /// Finds a fixed byte pattern across contiguous read-only memory chunks. public sealed class MemoryPatternScanner : IDisposable { private readonly byte[] _pattern; private readonly byte[] _tail; private int _tailLength; private ulong? _end; public MemoryPatternScanner(byte[] pattern) { if (pattern.Length is < 1 or > 1024) throw new ArgumentOutOfRangeException(nameof(pattern)); _pattern = pattern.ToArray(); _tail = new byte[pattern.Length - 1]; } public IReadOnlyList Feed(ReadOnlySpan bytes, ulong address) { if (_end != address) _tailLength = 0; var matches = new List(); Span boundary = stackalloc byte[_tail.Length * 2]; var headLength = Math.Min(bytes.Length, _tail.Length); _tail.AsSpan(0, _tailLength).CopyTo(boundary); bytes[..headLength].CopyTo(boundary[_tailLength..]); var joined = boundary[..(_tailLength + headLength)]; for (var i = 0; i < _tailLength; i++) if (i + _pattern.Length > _tailLength && i + _pattern.Length <= joined.Length && joined.Slice(i, _pattern.Length).SequenceEqual(_pattern)) matches.Add(address - (ulong)_tailLength + (ulong)i); var offset = 0; while (offset <= bytes.Length - _pattern.Length) { var found = bytes[offset..].IndexOf(_pattern); if (found < 0) break; offset += found; matches.Add(address + (ulong)offset); offset++; } if (bytes.Length >= _tail.Length) { bytes[^_tail.Length..].CopyTo(_tail); _tailLength = _tail.Length; } else { _tailLength = Math.Min(joined.Length, _tail.Length); joined[^_tailLength..].CopyTo(_tail); } CryptographicOperations.ZeroMemory(boundary); _end = checked(address + (ulong)bytes.Length); return matches; } public void Dispose() => CryptographicOperations.ZeroMemory(_tail); }