public sealed class ExportDirectoryParser
{
    public IReadOnlyList<ExportSymbolInfo> Parse(byte[] rawBytes, NtHeaders nt, IReadOnlyList<SectionInfo> sections, SafetyLimits limits)
    {
        if (nt.OptionalHeader.DataDirectories.Count == 0)
        {
            return Array.Empty<ExportSymbolInfo>();
        }

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

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

        BoundsChecker.EnsureRange(offset.Value, 40, rawBytes.Length, "export directory");

        using var ms = new MemoryStream(rawBytes, writable: false);
        using var reader = new BinaryReader(ms);
        ms.Position = offset.Value;

        _ = reader.ReadUInt32();
        _ = reader.ReadUInt32();
        _ = reader.ReadUInt16();
        _ = reader.ReadUInt16();
        _ = reader.ReadUInt32();
        var baseOrdinal = reader.ReadUInt32();
        var numberOfFunctions = reader.ReadUInt32();
        var numberOfNames = reader.ReadUInt32();
        var addressOfFunctions = reader.ReadUInt32();
        var addressOfNames = reader.ReadUInt32();
        var addressOfNameOrdinals = reader.ReadUInt32();

        BoundsChecker.EnsureCount((int)numberOfNames, limits.MaxExportsCount, "Export names");

        var functionsOffset = RvaMapper.RvaToFileOffset(addressOfFunctions, sections, nt.OptionalHeader.SizeOfHeaders);
        var namesOffset = RvaMapper.RvaToFileOffset(addressOfNames, sections, nt.OptionalHeader.SizeOfHeaders);
        var ordinalsOffset = RvaMapper.RvaToFileOffset(addressOfNameOrdinals, sections, nt.OptionalHeader.SizeOfHeaders);

        if (functionsOffset is null || namesOffset is null || ordinalsOffset is null)
        {
            return Array.Empty<ExportSymbolInfo>();
        }

        BoundsChecker.EnsureRange(functionsOffset.Value, checked((long)numberOfFunctions * 4), rawBytes.Length, "export address table");
        BoundsChecker.EnsureRange(namesOffset.Value, checked((long)numberOfNames * 4), rawBytes.Length, "export name pointer table");
        BoundsChecker.EnsureRange(ordinalsOffset.Value, checked((long)numberOfNames * 2), rawBytes.Length, "export ordinal table");

        var exports = new List<ExportSymbolInfo>();
        for (var i = 0; i < numberOfNames; i++)
        {
            BoundsChecker.EnsureCount(exports.Count + 1, limits.MaxExportsCount, "Export symbols");

            var namePtr = BitConverter.ToUInt32(rawBytes, namesOffset.Value + i * 4);
            var ordinalIndex = BitConverter.ToUInt16(rawBytes, ordinalsOffset.Value + i * 2);
            if (ordinalIndex >= numberOfFunctions)
            {
                continue;
            }

            var functionRva = BitConverter.ToUInt32(rawBytes, functionsOffset.Value + ordinalIndex * 4);
            var nameOffset = RvaMapper.RvaToFileOffset(namePtr, sections, nt.OptionalHeader.SizeOfHeaders);
            if (nameOffset is null)
            {
                continue;
            }

            var symbolName = BinaryReaderExtensions.ReadAsciiStringAt(rawBytes, nameOffset.Value, 1024);

            var isForwarder = functionRva >= dir.VirtualAddress && functionRva < dir.VirtualAddress + dir.Size;
            string? forwarder = null;
            if (isForwarder)
            {
                var forwarderOffset = RvaMapper.RvaToFileOffset(functionRva, sections, nt.OptionalHeader.SizeOfHeaders);
                if (forwarderOffset is not null)
                {
                    forwarder = BinaryReaderExtensions.ReadAsciiStringAt(rawBytes, forwarderOffset.Value, 1024);
                }
            }

            exports.Add(new ExportSymbolInfo(
                symbolName,
                (ushort)(baseOrdinal + ordinalIndex),
                functionRva,
                isForwarder,
                forwarder));
        }

        return exports;
    }
}