public sealed class DosHeaderParser
{
    public DosHeader Parse(BinaryReader reader)
    {
        reader.BaseStream.Position = 0;

        var magic = reader.ReadUInt16();
        var cblp = reader.ReadUInt16();
        var cp = reader.ReadUInt16();
        var crlc = reader.ReadUInt16();
        var cparhdr = reader.ReadUInt16();
        var minAlloc = reader.ReadUInt16();
        var maxAlloc = reader.ReadUInt16();
        var ss = reader.ReadUInt16();
        var sp = reader.ReadUInt16();
        var csum = reader.ReadUInt16();
        var ip = reader.ReadUInt16();
        var cs = reader.ReadUInt16();
        var lfarlc = reader.ReadUInt16();
        var ovno = reader.ReadUInt16();

        var res = new ushort[4];
        for (var i = 0; i < 4; i++) res[i] = reader.ReadUInt16();

        var oemId = reader.ReadUInt16();
        var oemInfo = reader.ReadUInt16();

        var res2 = new ushort[10];
        for (var i = 0; i < 10; i++) res2[i] = reader.ReadUInt16();

        var lfanew = reader.ReadInt32();

        return new DosHeader(
            magic,
            cblp,
            cp,
            crlc,
            cparhdr,
            minAlloc,
            maxAlloc,
            ss,
            sp,
            csum,
            ip,
            cs,
            lfarlc,
            ovno,
            res,
            oemId,
            oemInfo,
            res2,
            lfanew);
    }

    public byte[] ParseDosStub(byte[] rawBytes, int lfanew)
    {
        var dosHeaderSize = 64;
        if (lfanew <= dosHeaderSize || lfanew > rawBytes.Length)
        {
            return Array.Empty<byte>();
        }

        var size = lfanew - dosHeaderSize;
        var stub = new byte[size];
        Buffer.BlockCopy(rawBytes, dosHeaderSize, stub, 0, size);
        return stub;
    }

    public RichHeaderInfo? ParseRichHeader(byte[] rawBytes, int lfanew)
    {
        if (lfanew <= 0 || lfanew > rawBytes.Length)
        {
            return null;
        }

        var richIndex = FindAscii(rawBytes, 0, lfanew, "Rich");
        if (richIndex < 0 || richIndex + 8 > lfanew)
        {
            return null;
        }

        var xorKey = BitConverter.ToUInt32(rawBytes, richIndex + 4);
        var dansIndex = FindXorAscii(rawBytes, 0, richIndex, "DanS", xorKey);
        if (dansIndex < 0)
        {
            return null;
        }

        var entries = new List<RichHeaderEntry>();
        for (var pos = dansIndex + 16; pos + 8 <= richIndex; pos += 8)
        {
            var compId = BitConverter.ToUInt32(rawBytes, pos) ^ xorKey;
            var count = BitConverter.ToUInt32(rawBytes, pos + 4) ^ xorKey;
            entries.Add(new RichHeaderEntry(compId, count));
        }

        return new RichHeaderInfo(xorKey, entries);
    }

    private static int FindAscii(byte[] data, int start, int end, string text)
    {
        var pattern = System.Text.Encoding.ASCII.GetBytes(text);
        for (var i = start; i <= end - pattern.Length; i++)
        {
            var matched = true;
            for (var j = 0; j < pattern.Length; j++)
            {
                if (data[i + j] != pattern[j])
                {
                    matched = false;
                    break;
                }
            }

            if (matched)
            {
                return i;
            }
        }

        return -1;
    }

    private static int FindXorAscii(byte[] data, int start, int end, string text, uint xorKey)
    {
        var pattern = System.Text.Encoding.ASCII.GetBytes(text);
        var xorBytes = BitConverter.GetBytes(xorKey);

        for (var i = start; i <= end - pattern.Length; i++)
        {
            var matched = true;
            for (var j = 0; j < pattern.Length; j++)
            {
                if ((byte)(data[i + j] ^ xorBytes[j % 4]) != pattern[j])
                {
                    matched = false;
                    break;
                }
            }

            if (matched)
            {
                return i;
            }
        }

        return -1;
    }
}