public sealed class RelocationDirectoryParser
{
    public IReadOnlyList<RelocationBlockInfo> Parse(byte[] rawBytes, NtHeaders nt, IReadOnlyList<SectionInfo> sections)
    {
        if (nt.OptionalHeader.DataDirectories.Count <= 5)
        {
            return Array.Empty<RelocationBlockInfo>();
        }

        var dir = nt.OptionalHeader.DataDirectories[5];
        if (dir.VirtualAddress == 0 || dir.Size == 0)
        {
            return Array.Empty<RelocationBlockInfo>();
        }

        var offset = RvaMapper.RvaToFileOffset(dir.VirtualAddress, sections, nt.OptionalHeader.SizeOfHeaders);
        if (offset is null)
        {
            return Array.Empty<RelocationBlockInfo>();
        }

        var blocks = new List<RelocationBlockInfo>();
        var pos = offset.Value;
        var end = Math.Min(rawBytes.Length, offset.Value + (int)dir.Size);

        while (pos + 8 <= end)
        {
            var pageRva = BitConverter.ToUInt32(rawBytes, pos);
            var blockSize = BitConverter.ToUInt32(rawBytes, pos + 4);
            if (blockSize < 8 || pos + blockSize > end)
            {
                break;
            }

            var count = ((int)blockSize - 8) / 2;
            var entries = new List<RelocationEntryInfo>(count);
            for (var i = 0; i < count; i++)
            {
                var raw = BitConverter.ToUInt16(rawBytes, pos + 8 + i * 2);
                entries.Add(new RelocationEntryInfo((ushort)(raw >> 12), (ushort)(raw & 0x0FFF)));
            }

            blocks.Add(new RelocationBlockInfo(pageRva, blockSize, entries));
            pos += (int)blockSize;
        }

        return blocks;
    }
}