const std = @import("std.zig");
const io = std.io;
const math = std.math;
const mem = std.mem;
const os = std.os;
const coff = std.coff;
const fs = std.fs;
const File = std.fs.File;
const debug = std.debug;
const ArrayList = std.ArrayList;
pub const DbiStreamHeader = extern struct {
    VersionSignature: i32,
    VersionHeader: u32,
    Age: u32,
    GlobalStreamIndex: u16,
    BuildNumber: u16,
    PublicStreamIndex: u16,
    PdbDllVersion: u16,
    SymRecordStream: u16,
    PdbDllRbld: u16,
    ModInfoSize: u32,
    SectionContributionSize: u32,
    SectionMapSize: u32,
    SourceInfoSize: i32,
    TypeServerSize: i32,
    MFCTypeServerIndex: u32,
    OptionalDbgHeaderSize: i32,
    ECSubstreamSize: i32,
    Flags: u16,
    Machine: u16,
    Padding: u32,
};
pub const SectionContribEntry = extern struct {
    
    Section: u16,
    Padding1: [2]u8,
    Offset: u32,
    Size: u32,
    Characteristics: u32,
    ModuleIndex: u16,
    Padding2: [2]u8,
    DataCrc: u32,
    RelocCrc: u32,
};
pub const ModInfo = extern struct {
    Unused1: u32,
    SectionContr: SectionContribEntry,
    Flags: u16,
    ModuleSymStream: u16,
    SymByteSize: u32,
    C11ByteSize: u32,
    C13ByteSize: u32,
    SourceFileCount: u16,
    Padding: [2]u8,
    Unused2: u32,
    SourceFileNameIndex: u32,
    PdbFilePathNameIndex: u32,
    
    
    
};
pub const SectionMapHeader = extern struct {
    
    Count: u16,
    
    LogCount: u16,
};
pub const SectionMapEntry = extern struct {
    
    Flags: u16,
    
    Ovl: u16,
    
    Group: u16,
    Frame: u16,
    
    SectionName: u16,
    
    ClassName: u16,
    
    Offset: u32,
    
    SectionLength: u32,
};
pub const StreamType = enum(u16) {
    Pdb = 1,
    Tpi = 2,
    Dbi = 3,
    Ipi = 4,
};
pub const SymbolKind = enum(u16) {
    S_COMPILE = 1,
    S_REGISTER_16t = 2,
    S_CONSTANT_16t = 3,
    S_UDT_16t = 4,
    S_SSEARCH = 5,
    S_SKIP = 7,
    S_CVRESERVE = 8,
    S_OBJNAME_ST = 9,
    S_ENDARG = 10,
    S_COBOLUDT_16t = 11,
    S_MANYREG_16t = 12,
    S_RETURN = 13,
    S_ENTRYTHIS = 14,
    S_BPREL16 = 256,
    S_LDATA16 = 257,
    S_GDATA16 = 258,
    S_PUB16 = 259,
    S_LPROC16 = 260,
    S_GPROC16 = 261,
    S_THUNK16 = 262,
    S_BLOCK16 = 263,
    S_WITH16 = 264,
    S_LABEL16 = 265,
    S_CEXMODEL16 = 266,
    S_VFTABLE16 = 267,
    S_REGREL16 = 268,
    S_BPREL32_16t = 512,
    S_LDATA32_16t = 513,
    S_GDATA32_16t = 514,
    S_PUB32_16t = 515,
    S_LPROC32_16t = 516,
    S_GPROC32_16t = 517,
    S_THUNK32_ST = 518,
    S_BLOCK32_ST = 519,
    S_WITH32_ST = 520,
    S_LABEL32_ST = 521,
    S_CEXMODEL32 = 522,
    S_VFTABLE32_16t = 523,
    S_REGREL32_16t = 524,
    S_LTHREAD32_16t = 525,
    S_GTHREAD32_16t = 526,
    S_SLINK32 = 527,
    S_LPROCMIPS_16t = 768,
    S_GPROCMIPS_16t = 769,
    S_PROCREF_ST = 1024,
    S_DATAREF_ST = 1025,
    S_ALIGN = 1026,
    S_LPROCREF_ST = 1027,
    S_OEM = 1028,
    S_TI16_MAX = 4096,
    S_REGISTER_ST = 4097,
    S_CONSTANT_ST = 4098,
    S_UDT_ST = 4099,
    S_COBOLUDT_ST = 4100,
    S_MANYREG_ST = 4101,
    S_BPREL32_ST = 4102,
    S_LDATA32_ST = 4103,
    S_GDATA32_ST = 4104,
    S_PUB32_ST = 4105,
    S_LPROC32_ST = 4106,
    S_GPROC32_ST = 4107,
    S_VFTABLE32 = 4108,
    S_REGREL32_ST = 4109,
    S_LTHREAD32_ST = 4110,
    S_GTHREAD32_ST = 4111,
    S_LPROCMIPS_ST = 4112,
    S_GPROCMIPS_ST = 4113,
    S_COMPILE2_ST = 4115,
    S_MANYREG2_ST = 4116,
    S_LPROCIA64_ST = 4117,
    S_GPROCIA64_ST = 4118,
    S_LOCALSLOT_ST = 4119,
    S_PARAMSLOT_ST = 4120,
    S_ANNOTATION = 4121,
    S_GMANPROC_ST = 4122,
    S_LMANPROC_ST = 4123,
    S_RESERVED1 = 4124,
    S_RESERVED2 = 4125,
    S_RESERVED3 = 4126,
    S_RESERVED4 = 4127,
    S_LMANDATA_ST = 4128,
    S_GMANDATA_ST = 4129,
    S_MANFRAMEREL_ST = 4130,
    S_MANREGISTER_ST = 4131,
    S_MANSLOT_ST = 4132,
    S_MANMANYREG_ST = 4133,
    S_MANREGREL_ST = 4134,
    S_MANMANYREG2_ST = 4135,
    S_MANTYPREF = 4136,
    S_UNAMESPACE_ST = 4137,
    S_ST_MAX = 4352,
    S_WITH32 = 4356,
    S_MANYREG = 4362,
    S_LPROCMIPS = 4372,
    S_GPROCMIPS = 4373,
    S_MANYREG2 = 4375,
    S_LPROCIA64 = 4376,
    S_GPROCIA64 = 4377,
    S_LOCALSLOT = 4378,
    S_PARAMSLOT = 4379,
    S_MANFRAMEREL = 4382,
    S_MANREGISTER = 4383,
    S_MANSLOT = 4384,
    S_MANMANYREG = 4385,
    S_MANREGREL = 4386,
    S_MANMANYREG2 = 4387,
    S_UNAMESPACE = 4388,
    S_DATAREF = 4390,
    S_ANNOTATIONREF = 4392,
    S_TOKENREF = 4393,
    S_GMANPROC = 4394,
    S_LMANPROC = 4395,
    S_ATTR_FRAMEREL = 4398,
    S_ATTR_REGISTER = 4399,
    S_ATTR_REGREL = 4400,
    S_ATTR_MANYREG = 4401,
    S_SEPCODE = 4402,
    S_LOCAL_2005 = 4403,
    S_DEFRANGE_2005 = 4404,
    S_DEFRANGE2_2005 = 4405,
    S_DISCARDED = 4411,
    S_LPROCMIPS_ID = 4424,
    S_GPROCMIPS_ID = 4425,
    S_LPROCIA64_ID = 4426,
    S_GPROCIA64_ID = 4427,
    S_DEFRANGE_HLSL = 4432,
    S_GDATA_HLSL = 4433,
    S_LDATA_HLSL = 4434,
    S_LOCAL_DPC_GROUPSHARED = 4436,
    S_DEFRANGE_DPC_PTR_TAG = 4439,
    S_DPC_SYM_TAG_MAP = 4440,
    S_ARMSWITCHTABLE = 4441,
    S_POGODATA = 4444,
    S_INLINESITE2 = 4445,
    S_MOD_TYPEREF = 4447,
    S_REF_MINIPDB = 4448,
    S_PDBMAP = 4449,
    S_GDATA_HLSL32 = 4450,
    S_LDATA_HLSL32 = 4451,
    S_GDATA_HLSL32_EX = 4452,
    S_LDATA_HLSL32_EX = 4453,
    S_FASTLINK = 4455,
    S_INLINEES = 4456,
    S_END = 6,
    S_INLINESITE_END = 4430,
    S_PROC_ID_END = 4431,
    S_THUNK32 = 4354,
    S_TRAMPOLINE = 4396,
    S_SECTION = 4406,
    S_COFFGROUP = 4407,
    S_EXPORT = 4408,
    S_LPROC32 = 4367,
    S_GPROC32 = 4368,
    S_LPROC32_ID = 4422,
    S_GPROC32_ID = 4423,
    S_LPROC32_DPC = 4437,
    S_LPROC32_DPC_ID = 4438,
    S_REGISTER = 4358,
    S_PUB32 = 4366,
    S_PROCREF = 4389,
    S_LPROCREF = 4391,
    S_ENVBLOCK = 4413,
    S_INLINESITE = 4429,
    S_LOCAL = 4414,
    S_DEFRANGE = 4415,
    S_DEFRANGE_SUBFIELD = 4416,
    S_DEFRANGE_REGISTER = 4417,
    S_DEFRANGE_FRAMEPOINTER_REL = 4418,
    S_DEFRANGE_SUBFIELD_REGISTER = 4419,
    S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE = 4420,
    S_DEFRANGE_REGISTER_REL = 4421,
    S_BLOCK32 = 4355,
    S_LABEL32 = 4357,
    S_OBJNAME = 4353,
    S_COMPILE2 = 4374,
    S_COMPILE3 = 4412,
    S_FRAMEPROC = 4114,
    S_CALLSITEINFO = 4409,
    S_FILESTATIC = 4435,
    S_HEAPALLOCSITE = 4446,
    S_FRAMECOOKIE = 4410,
    S_CALLEES = 4442,
    S_CALLERS = 4443,
    S_UDT = 4360,
    S_COBOLUDT = 4361,
    S_BUILDINFO = 4428,
    S_BPREL32 = 4363,
    S_REGREL32 = 4369,
    S_CONSTANT = 4359,
    S_MANCONSTANT = 4397,
    S_LDATA32 = 4364,
    S_GDATA32 = 4365,
    S_LMANDATA = 4380,
    S_GMANDATA = 4381,
    S_LTHREAD32 = 4370,
    S_GTHREAD32 = 4371,
};
pub const TypeIndex = u32;
pub const ProcSym = extern struct {
    Parent: u32,
    End: u32,
    Next: u32,
    CodeSize: u32,
    DbgStart: u32,
    DbgEnd: u32,
    FunctionType: TypeIndex,
    CodeOffset: u32,
    Segment: u16,
    Flags: ProcSymFlags,
    Name: [1]u8, 
};
pub const ProcSymFlags = packed struct {
    HasFP: bool,
    HasIRET: bool,
    HasFRET: bool,
    IsNoReturn: bool,
    IsUnreachable: bool,
    HasCustomCallingConv: bool,
    IsNoInline: bool,
    HasOptimizedDebugInfo: bool,
};
pub const SectionContrSubstreamVersion = enum(u32) {
    Ver60 = 0xeffe0000 + 19970605,
    V2 = 0xeffe0000 + 20140516,
    _,
};
pub const RecordPrefix = extern struct {
    
    RecordLen: u16,
    
    RecordKind: SymbolKind,
};
pub const LineFragmentHeader = extern struct {
    
    RelocOffset: u32,
    
    RelocSegment: u16,
    Flags: LineFlags,
    
    CodeSize: u32,
};
pub const LineFlags = packed struct {
    
    LF_HaveColumns: bool,
    unused: u15,
};
pub const LineBlockFragmentHeader = extern struct {
    
    
    
    
    NameIndex: u32,
    NumLines: u32,
    
    BlockSize: u32,
};
pub const LineNumberEntry = extern struct {
    
    Offset: u32,
    Flags: u32,
    
    pub const Flags = packed struct {
        
        Start: u24,
        
        
        End: u7,
        IsStatement: bool,
    };
};
pub const ColumnNumberEntry = extern struct {
    StartColumn: u16,
    EndColumn: u16,
};
pub const FileChecksumEntryHeader = extern struct {
    
    FileNameOffset: u32,
    
    ChecksumSize: u8,
    
    ChecksumKind: u8,
};
pub const DebugSubsectionKind = enum(u32) {
    None = 0,
    Symbols = 0xf1,
    Lines = 0xf2,
    StringTable = 0xf3,
    FileChecksums = 0xf4,
    FrameData = 0xf5,
    InlineeLines = 0xf6,
    CrossScopeImports = 0xf7,
    CrossScopeExports = 0xf8,
    
    ILLines = 0xf9,
    FuncMDTokenMap = 0xfa,
    TypeMDTokenMap = 0xfb,
    MergedAssemblyInput = 0xfc,
    CoffSymbolRVA = 0xfd,
};
pub const DebugSubsectionHeader = extern struct {
    
    Kind: DebugSubsectionKind,
    
    Length: u32,
};
pub const PDBStringTableHeader = extern struct {
    
    Signature: u32,
    
    HashVersion: u32,
    
    ByteSize: u32,
};
fn readSparseBitVector(stream: anytype, allocator: mem.Allocator) ![]u32 {
    const num_words = try stream.readIntLittle(u32);
    var list = ArrayList(u32).init(allocator);
    errdefer list.deinit();
    var word_i: u32 = 0;
    while (word_i != num_words) : (word_i += 1) {
        const word = try stream.readIntLittle(u32);
        var bit_i: u5 = 0;
        while (true) : (bit_i += 1) {
            if (word & (@as(u32, 1) << bit_i) != 0) {
                try list.append(word_i * 32 + bit_i);
            }
            if (bit_i == std.math.maxInt(u5)) break;
        }
    }
    return list.toOwnedSlice();
}
pub const Pdb = struct {
    in_file: File,
    msf: Msf,
    allocator: mem.Allocator,
    string_table: ?*MsfStream,
    dbi: ?*MsfStream,
    modules: []Module,
    sect_contribs: []SectionContribEntry,
    guid: [16]u8,
    age: u32,
    pub const Module = struct {
        mod_info: ModInfo,
        module_name: []u8,
        obj_file_name: []u8,
        
        populated: bool,
        symbols: []u8,
        subsect_info: []u8,
        checksum_offset: ?usize,
        pub fn deinit(self: *Module, allocator: mem.Allocator) void {
            allocator.free(self.module_name);
            allocator.free(self.obj_file_name);
            if (self.populated) {
                allocator.free(self.symbols);
                allocator.free(self.subsect_info);
            }
        }
    };
    pub fn init(allocator: mem.Allocator, path: []const u8) !Pdb {
        const file = try fs.cwd().openFile(path, .{ .intended_io_mode = .blocking });
        errdefer file.close();
        return Pdb{
            .in_file = file,
            .allocator = allocator,
            .string_table = null,
            .dbi = null,
            .msf = try Msf.init(allocator, file),
            .modules = &[_]Module{},
            .sect_contribs = &[_]SectionContribEntry{},
            .guid = undefined,
            .age = undefined,
        };
    }
    pub fn deinit(self: *Pdb) void {
        self.in_file.close();
        self.msf.deinit(self.allocator);
        for (self.modules) |*module| {
            module.deinit(self.allocator);
        }
        self.allocator.free(self.modules);
        self.allocator.free(self.sect_contribs);
    }
    pub fn parseDbiStream(self: *Pdb) !void {
        var stream = self.getStream(StreamType.Dbi) orelse
            return error.InvalidDebugInfo;
        const reader = stream.reader();
        const header = try reader.readStruct(DbiStreamHeader);
        if (header.VersionHeader != 19990903) 
            return error.UnknownPDBVersion;
        
        
        const mod_info_size = header.ModInfoSize;
        const section_contrib_size = header.SectionContributionSize;
        var modules = ArrayList(Module).init(self.allocator);
        errdefer modules.deinit();
        
        var mod_info_offset: usize = 0;
        while (mod_info_offset != mod_info_size) {
            const mod_info = try reader.readStruct(ModInfo);
            var this_record_len: usize = @sizeOf(ModInfo);
            const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
            errdefer self.allocator.free(module_name);
            this_record_len += module_name.len + 1;
            const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
            errdefer self.allocator.free(obj_file_name);
            this_record_len += obj_file_name.len + 1;
            if (this_record_len % 4 != 0) {
                const round_to_next_4 = (this_record_len | 0x3) + 1;
                const march_forward_bytes = round_to_next_4 - this_record_len;
                try stream.seekBy(@intCast(isize, march_forward_bytes));
                this_record_len += march_forward_bytes;
            }
            try modules.append(Module{
                .mod_info = mod_info,
                .module_name = module_name,
                .obj_file_name = obj_file_name,
                .populated = false,
                .symbols = undefined,
                .subsect_info = undefined,
                .checksum_offset = null,
            });
            mod_info_offset += this_record_len;
            if (mod_info_offset > mod_info_size)
                return error.InvalidDebugInfo;
        }
        
        var sect_contribs = ArrayList(SectionContribEntry).init(self.allocator);
        errdefer sect_contribs.deinit();
        var sect_cont_offset: usize = 0;
        if (section_contrib_size != 0) {
            const version = reader.readEnum(SectionContrSubstreamVersion, .Little) catch |err| switch (err) {
                error.InvalidValue => return error.InvalidDebugInfo,
                else => |e| return e,
            };
            _ = version;
            sect_cont_offset += @sizeOf(u32);
        }
        while (sect_cont_offset != section_contrib_size) {
            const entry = try sect_contribs.addOne();
            entry.* = try reader.readStruct(SectionContribEntry);
            sect_cont_offset += @sizeOf(SectionContribEntry);
            if (sect_cont_offset > section_contrib_size)
                return error.InvalidDebugInfo;
        }
        self.modules = modules.toOwnedSlice();
        self.sect_contribs = sect_contribs.toOwnedSlice();
    }
    pub fn parseInfoStream(self: *Pdb) !void {
        var stream = self.getStream(StreamType.Pdb) orelse
            return error.InvalidDebugInfo;
        const reader = stream.reader();
        
        const version = try reader.readIntLittle(u32);
        const signature = try reader.readIntLittle(u32);
        _ = signature;
        const age = try reader.readIntLittle(u32);
        const guid = try reader.readBytesNoEof(16);
        if (version != 20000404) 
            return error.UnknownPDBVersion;
        self.guid = guid;
        self.age = age;
        
        const string_table_index = str_tab_index: {
            const name_bytes_len = try reader.readIntLittle(u32);
            const name_bytes = try self.allocator.alloc(u8, name_bytes_len);
            defer self.allocator.free(name_bytes);
            try reader.readNoEof(name_bytes);
            const HashTableHeader = extern struct {
                Size: u32,
                Capacity: u32,
                fn maxLoad(cap: u32) u32 {
                    return cap * 2 / 3 + 1;
                }
            };
            const hash_tbl_hdr = try reader.readStruct(HashTableHeader);
            if (hash_tbl_hdr.Capacity == 0)
                return error.InvalidDebugInfo;
            if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity))
                return error.InvalidDebugInfo;
            const present = try readSparseBitVector(&reader, self.allocator);
            defer self.allocator.free(present);
            if (present.len != hash_tbl_hdr.Size)
                return error.InvalidDebugInfo;
            const deleted = try readSparseBitVector(&reader, self.allocator);
            defer self.allocator.free(deleted);
            for (present) |_| {
                const name_offset = try reader.readIntLittle(u32);
                const name_index = try reader.readIntLittle(u32);
                if (name_offset > name_bytes.len)
                    return error.InvalidDebugInfo;
                const name = mem.sliceTo(std.meta.assumeSentinel(name_bytes.ptr + name_offset, 0), 0);
                if (mem.eql(u8, name, "/names")) {
                    break :str_tab_index name_index;
                }
            }
            return error.MissingDebugInfo;
        };
        self.string_table = self.getStreamById(string_table_index) orelse
            return error.MissingDebugInfo;
    }
    pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 {
        _ = self;
        std.debug.assert(module.populated);
        var symbol_i: usize = 0;
        while (symbol_i != module.symbols.len) {
            const prefix = @ptrCast(*align(1) RecordPrefix, &module.symbols[symbol_i]);
            if (prefix.RecordLen < 2)
                return null;
            switch (prefix.RecordKind) {
                .S_LPROC32, .S_GPROC32 => {
                    const proc_sym = @ptrCast(*align(1) ProcSym, &module.symbols[symbol_i + @sizeOf(RecordPrefix)]);
                    if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) {
                        return mem.sliceTo(@ptrCast([*:0]u8, &proc_sym.Name[0]), 0);
                    }
                },
                else => {},
            }
            symbol_i += prefix.RecordLen + @sizeOf(u16);
        }
        return null;
    }
    pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.LineInfo {
        std.debug.assert(module.populated);
        const subsect_info = module.subsect_info;
        var sect_offset: usize = 0;
        var skip_len: usize = undefined;
        const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo;
        while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
            const subsect_hdr = @ptrCast(*align(1) DebugSubsectionHeader, &subsect_info[sect_offset]);
            skip_len = subsect_hdr.Length;
            sect_offset += @sizeOf(DebugSubsectionHeader);
            switch (subsect_hdr.Kind) {
                .Lines => {
                    var line_index = sect_offset;
                    const line_hdr = @ptrCast(*align(1) LineFragmentHeader, &subsect_info[line_index]);
                    if (line_hdr.RelocSegment == 0)
                        return error.MissingDebugInfo;
                    line_index += @sizeOf(LineFragmentHeader);
                    const frag_vaddr_start = line_hdr.RelocOffset;
                    const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize;
                    if (address >= frag_vaddr_start and address < frag_vaddr_end) {
                        
                        
                        
                        const subsection_end_index = sect_offset + subsect_hdr.Length;
                        while (line_index < subsection_end_index) {
                            const block_hdr = @ptrCast(*align(1) LineBlockFragmentHeader, &subsect_info[line_index]);
                            line_index += @sizeOf(LineBlockFragmentHeader);
                            const start_line_index = line_index;
                            const has_column = line_hdr.Flags.LF_HaveColumns;
                            
                            
                            
                            
                            var line_i: u32 = 0;
                            while (line_i < block_hdr.NumLines) : (line_i += 1) {
                                const line_num_entry = @ptrCast(*align(1) LineNumberEntry, &subsect_info[line_index]);
                                line_index += @sizeOf(LineNumberEntry);
                                const vaddr_start = frag_vaddr_start + line_num_entry.Offset;
                                if (address < vaddr_start) {
                                    break;
                                }
                            }
                            
                            if (line_i > 0) {
                                const subsect_index = checksum_offset + block_hdr.NameIndex;
                                const chksum_hdr = @ptrCast(*align(1) FileChecksumEntryHeader, &module.subsect_info[subsect_index]);
                                const strtab_offset = @sizeOf(PDBStringTableHeader) + chksum_hdr.FileNameOffset;
                                try self.string_table.?.seekTo(strtab_offset);
                                const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024);
                                const line_entry_idx = line_i - 1;
                                const column = if (has_column) blk: {
                                    const start_col_index = start_line_index + @sizeOf(LineNumberEntry) * block_hdr.NumLines;
                                    const col_index = start_col_index + @sizeOf(ColumnNumberEntry) * line_entry_idx;
                                    const col_num_entry = @ptrCast(*align(1) ColumnNumberEntry, &subsect_info[col_index]);
                                    break :blk col_num_entry.StartColumn;
                                } else 0;
                                const found_line_index = start_line_index + line_entry_idx * @sizeOf(LineNumberEntry);
                                const line_num_entry = @ptrCast(*align(1) LineNumberEntry, &subsect_info[found_line_index]);
                                const flags = @ptrCast(*LineNumberEntry.Flags, &line_num_entry.Flags);
                                return debug.LineInfo{
                                    .file_name = source_file_name,
                                    .line = flags.Start,
                                    .column = column,
                                };
                            }
                        }
                        
                        if (line_index != subsection_end_index) {
                            return error.InvalidDebugInfo;
                        }
                    }
                },
                else => {},
            }
            if (sect_offset > subsect_info.len)
                return error.InvalidDebugInfo;
        }
        return error.MissingDebugInfo;
    }
    pub fn getModule(self: *Pdb, index: usize) !?*Module {
        if (index >= self.modules.len)
            return null;
        const mod = &self.modules[index];
        if (mod.populated)
            return mod;
        
        if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0)
            return error.InvalidDebugInfo;
        if (mod.mod_info.C13ByteSize == 0)
            return error.InvalidDebugInfo;
        const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse
            return error.MissingDebugInfo;
        const reader = stream.reader();
        const signature = try reader.readIntLittle(u32);
        if (signature != 4)
            return error.InvalidDebugInfo;
        mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4);
        errdefer self.allocator.free(mod.symbols);
        try reader.readNoEof(mod.symbols);
        mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize);
        errdefer self.allocator.free(mod.subsect_info);
        try reader.readNoEof(mod.subsect_info);
        var sect_offset: usize = 0;
        var skip_len: usize = undefined;
        while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) {
            const subsect_hdr = @ptrCast(*align(1) DebugSubsectionHeader, &mod.subsect_info[sect_offset]);
            skip_len = subsect_hdr.Length;
            sect_offset += @sizeOf(DebugSubsectionHeader);
            switch (subsect_hdr.Kind) {
                .FileChecksums => {
                    mod.checksum_offset = sect_offset;
                    break;
                },
                else => {},
            }
            if (sect_offset > mod.subsect_info.len)
                return error.InvalidDebugInfo;
        }
        mod.populated = true;
        return mod;
    }
    pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream {
        if (id >= self.msf.streams.len)
            return null;
        return &self.msf.streams[id];
    }
    pub fn getStream(self: *Pdb, stream: StreamType) ?*MsfStream {
        const id = @enumToInt(stream);
        return self.getStreamById(id);
    }
};
const Msf = struct {
    directory: MsfStream,
    streams: []MsfStream,
    fn init(allocator: mem.Allocator, file: File) !Msf {
        const in = file.reader();
        const superblock = try in.readStruct(SuperBlock);
        
        if (!mem.eql(u8, &superblock.FileMagic, SuperBlock.file_magic))
            return error.InvalidDebugInfo;
        if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2)
            return error.InvalidDebugInfo;
        const file_len = try file.getEndPos();
        if (superblock.NumBlocks * superblock.BlockSize != file_len)
            return error.InvalidDebugInfo;
        switch (superblock.BlockSize) {
            
            512, 1024, 2048, 4096 => {},
            else => return error.InvalidDebugInfo,
        }
        const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize);
        if (dir_block_count > superblock.BlockSize / @sizeOf(u32))
            return error.UnhandledBigDirectoryStream; 
        try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr);
        var dir_blocks = try allocator.alloc(u32, dir_block_count);
        for (dir_blocks) |*b| {
            b.* = try in.readIntLittle(u32);
        }
        var directory = MsfStream.init(
            superblock.BlockSize,
            file,
            dir_blocks,
        );
        const begin = directory.pos;
        const stream_count = try directory.reader().readIntLittle(u32);
        const stream_sizes = try allocator.alloc(u32, stream_count);
        defer allocator.free(stream_sizes);
        
        
        
        const Nil = 0xFFFFFFFF;
        for (stream_sizes) |*s| {
            const size = try directory.reader().readIntLittle(u32);
            s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize);
        }
        const streams = try allocator.alloc(MsfStream, stream_count);
        for (streams) |*stream, i| {
            const size = stream_sizes[i];
            if (size == 0) {
                stream.* = MsfStream{
                    .blocks = &[_]u32{},
                };
            } else {
                var blocks = try allocator.alloc(u32, size);
                var j: u32 = 0;
                while (j < size) : (j += 1) {
                    const block_id = try directory.reader().readIntLittle(u32);
                    const n = (block_id % superblock.BlockSize);
                    
                    if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > file_len)
                        return error.InvalidBlockIndex;
                    blocks[j] = block_id;
                }
                stream.* = MsfStream.init(
                    superblock.BlockSize,
                    file,
                    blocks,
                );
            }
        }
        const end = directory.pos;
        if (end - begin != superblock.NumDirectoryBytes)
            return error.InvalidStreamDirectory;
        return Msf{
            .directory = directory,
            .streams = streams,
        };
    }
    fn deinit(self: *Msf, allocator: mem.Allocator) void {
        allocator.free(self.directory.blocks);
        for (self.streams) |*stream| {
            allocator.free(stream.blocks);
        }
        allocator.free(self.streams);
    }
};
fn blockCountFromSize(size: u32, block_size: u32) u32 {
    return (size + block_size - 1) / block_size;
}
pub const SuperBlock = extern struct {
    
    pub const file_magic = "Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53\x00\x00\x00";
    FileMagic: [file_magic.len]u8,
    
    
    
    
    BlockSize: u32,
    
    
    
    
    FreeBlockMapBlock: u32,
    
    
    NumBlocks: u32,
    
    
    
    NumDirectoryBytes: u32,
    Unknown: u32,
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    BlockMapAddr: u32,
};
const MsfStream = struct {
    in_file: File = undefined,
    pos: u64 = undefined,
    blocks: []u32 = undefined,
    block_size: u32 = undefined,
    pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).Fn.return_type.?).ErrorUnion.error_set;
    fn init(block_size: u32, file: File, blocks: []u32) MsfStream {
        const stream = MsfStream{
            .in_file = file,
            .pos = 0,
            .blocks = blocks,
            .block_size = block_size,
        };
        return stream;
    }
    fn read(self: *MsfStream, buffer: []u8) !usize {
        var block_id = @intCast(usize, self.pos / self.block_size);
        if (block_id >= self.blocks.len) return 0; 
        var block = self.blocks[block_id];
        var offset = self.pos % self.block_size;
        try self.in_file.seekTo(block * self.block_size + offset);
        const in = self.in_file.reader();
        var size: usize = 0;
        var rem_buffer = buffer;
        while (size < buffer.len) {
            const size_to_read = math.min(self.block_size - offset, rem_buffer.len);
            size += try in.read(rem_buffer[0..size_to_read]);
            rem_buffer = buffer[size..];
            offset += size_to_read;
            
            if (offset == self.block_size) {
                offset = 0;
                block_id += 1;
                if (block_id >= self.blocks.len) break; 
                block = self.blocks[block_id];
                try self.in_file.seekTo(block * self.block_size);
            }
        }
        self.pos += buffer.len;
        return buffer.len;
    }
    pub fn seekBy(self: *MsfStream, len: i64) !void {
        self.pos = @intCast(u64, @intCast(i64, self.pos) + len);
        if (self.pos >= self.blocks.len * self.block_size)
            return error.EOF;
    }
    pub fn seekTo(self: *MsfStream, len: u64) !void {
        self.pos = len;
        if (self.pos >= self.blocks.len * self.block_size)
            return error.EOF;
    }
    fn getSize(self: *const MsfStream) u64 {
        return self.blocks.len * self.block_size;
    }
    fn getFilePos(self: MsfStream) u64 {
        const block_id = self.pos / self.block_size;
        const block = self.blocks[block_id];
        const offset = self.pos % self.block_size;
        return block * self.block_size + offset;
    }
    pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) {
        return .{ .context = self };
    }
};