// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)

#ifndef PDB_H
#define PDB_H

// https://github.com/microsoft/microsoft-pdb/tree/master/PDB

////////////////////////////////
//~ PDB Format Types

typedef U32 PDB_Version;
enum{
  PDB_Version_VC2      = 19941610,
  PDB_Version_VC4      = 19950623,
  PDB_Version_VC41     = 19950814,
  PDB_Version_VC50     = 19960307,
  PDB_Version_VC98     = 19970604,
  PDB_Version_VC70_DEP = 19990604,
  PDB_Version_VC70     = 20000404,
  PDB_Version_VC80     = 20030901,
  PDB_Version_VC110    = 20091201,
  PDB_Version_VC140    = 20140508
};

typedef U16 PDB_ModIndex;
typedef U32 PDB_StringIndex;

typedef enum PDB_FixedStream{
  PDB_FixedStream_PdbInfo = 1,
  PDB_FixedStream_Tpi = 2,
  PDB_FixedStream_Dbi = 3,
  PDB_FixedStream_Ipi = 4
} PDB_FixedStream;

typedef enum PDB_NamedStream{
  PDB_NamedStream_HEADER_BLOCK,
  PDB_NamedStream_STRTABLE,
  PDB_NamedStream_LINK_INFO,
  PDB_NamedStream_COUNT
} PDB_NamedStream;

typedef struct PDB_InfoHeader{
  PDB_Version version;
  U32 time;
  U32 age;
} PDB_InfoHeader;

enum{
  PDB_StrtblHeader_MAGIC = 0xEFFEEFFE
};

typedef struct PDB_StrtblHeader{
  U32 magic;
  U32 version;
} PDB_StrtblHeader;

////////////////////////////////
//~ PDB Format DBI Types

typedef U32 PDB_DbiStream;
enum{
  PDB_DbiStream_FPO,
  PDB_DbiStream_EXCEPTION,
  PDB_DbiStream_FIXUP,
  PDB_DbiStream_OMAP_TO_SRC,
  PDB_DbiStream_OMAP_FROM_SRC,
  PDB_DbiStream_SECTION_HEADER,
  PDB_DbiStream_TOKEN_RDI_MAP,
  PDB_DbiStream_XDATA,
  PDB_DbiStream_PDATA,
  PDB_DbiStream_NEW_FPO,
  PDB_DbiStream_SECTION_HEADER_ORIG,
  PDB_DbiStream_COUNT
};

typedef U32 PDB_DbiHeaderSignature;
enum{
  PDB_DbiHeaderSignature_V1 = 0xFFFFFFFF
};

typedef U32 PDB_DbiVersion;
enum{
  PDB_DbiVersion_41  =   930803,
  PDB_DbiVersion_50  = 19960307,
  PDB_DbiVersion_60  = 19970606,
  PDB_DbiVersion_70  = 19990903,
  PDB_DbiVersion_110 = 20091201,
};

typedef U16 PDB_DbiBuildNumber;
#define PDB_DbiBuildNumberNewFormatFlag 0x8000
#define PDB_DbiBuildNumberMinor(bn) ((bn)&0xFF)
#define PDB_DbiBuildNumberMajor(bn) (((bn) >> 8)&0x7F)
#define PDB_DbiBuildNumberNewFormat(bn) (!!((bn)&PDB_DbiBuildNumberNewFormatFlag))
#define PDB_DbiBuildNumber(maj, min) \
(PDB_DbiBuildNumberNewFormatFlag | ((min)&0xFF) | (((maj)&0x7F) << 16))

typedef U16 PDB_DbiHeaderFlags;
enum{
  PDB_DbiHeaderFlag_Incremental = 0x1,
  PDB_DbiHeaderFlag_Stripped    = 0x2,
  PDB_DbiHeaderFlag_CTypes      = 0x4
};

typedef struct PDB_DbiHeader{
  PDB_DbiHeaderSignature sig;
  PDB_DbiVersion version;
  U32 age;
  MSF_StreamNumber gsi_sn;
  PDB_DbiBuildNumber build_number;
  
  MSF_StreamNumber psi_sn;
  U16 pdb_version;
  
  MSF_StreamNumber sym_sn;
  U16 pdb_version2;
  
  U32 module_info_size;
  U32 sec_con_size;
  U32 sec_map_size;
  U32 file_info_size;
  
  U32 tsm_size;
  U32 mfc_index;
  U32 dbg_header_size;
  U32 ec_info_size;
  
  PDB_DbiHeaderFlags flags;
  COFF_MachineType machine;
  
  U32 reserved;
} PDB_DbiHeader;

//  (this is not "literally" defined by the format - but helpful to have)
typedef enum PDB_DbiRange{
  PDB_DbiRange_ModuleInfo,
  PDB_DbiRange_SecCon,
  PDB_DbiRange_SecMap,
  PDB_DbiRange_FileInfo,
  PDB_DbiRange_TSM,
  PDB_DbiRange_EcInfo,
  PDB_DbiRange_DbgHeader,
  PDB_DbiRange_COUNT
} PDB_DbiRange;

// "ModuleInfo" DBI range

typedef U32 PDB_DbiSectionContribVersion;
#define PDB_DbiSectionContribVersion_1 (0xeffe0000u + 19970605u)
#define PDB_DbiSectionContribVersion_2 (0xeffe0000u + 20140516u)

typedef struct PDB_DbiSectionContrib40{
  CV_SectionIndex sec;
  U32 sec_off;
  U32 size;
  U32 flags;
  PDB_ModIndex mod;
} PDB_DbiSectionContrib40;

typedef struct PDB_DbiSectionContrib{
  PDB_DbiSectionContrib40 base;
  U32 data_crc;
  U32 reloc_crc;
} PDB_DbiSectionContrib;

typedef struct PDB_DbiSectionContrib2{
  PDB_DbiSectionContrib40 base;
  U32 data_crc;
  U32 reloc_crc;
  U32 sec_coff;
} PDB_DbiSectionContrib2;

typedef struct PDB_DbiCompUnitHeader{
  U32 unused;
  PDB_DbiSectionContrib contribution;
  U16 flags; // unknown
  
  MSF_StreamNumber sn;
  U32 symbols_size;
  U32 c11_lines_size;
  U32 c13_lines_size;
  
  U16 num_contrib_files;
  U16 unused2;
  U32 file_names_offset;
  
  PDB_StringIndex src_file;
  PDB_StringIndex pdb_file;
  
  // U8[] module_name (null terminated)
  // U8[] obj_name (null terminated)
} PDB_DbiCompUnitHeader;

//  (this is not "literally" defined by the format - but helpful to have)
typedef enum{
  PDB_DbiCompUnitRange_Symbols,
  PDB_DbiCompUnitRange_C11,
  PDB_DbiCompUnitRange_C13,
  PDB_DbiCompUnitRange_COUNT
} PDB_DbiCompUnitRange;

////////////////////////////////
//~ PDB Format TPI Types

typedef U32 PDB_TpiVersion;
enum{
  PDB_TpiVersion_INTV_VC2 = 920924,
  PDB_TpiVersion_IMPV40 = 19950410,
  PDB_TpiVersion_IMPV41 = 19951122,
  PDB_TpiVersion_IMPV50_INTERIM = 19960307,
  PDB_TpiVersion_IMPV50 = 19961031,
  PDB_TpiVersion_IMPV70 = 19990903,
  PDB_TpiVersion_IMPV80 = 20040203,
};

typedef struct PDB_TpiHeader{
  //   (HDR)
  PDB_TpiVersion version;
  U32 header_size;
  U32 ti_lo;
  U32 ti_hi;
  U32 leaf_data_size;
  
  //   (PdbTpiHash)
  MSF_StreamNumber hash_sn;
  MSF_StreamNumber hash_sn_aux;
  U32 hash_key_size;
  U32 hash_bucket_count;
  U32 hash_vals_off;
  U32 hash_vals_size;
  U32 itype_off;
  U32 itype_size;
  U32 hash_adj_off;
  U32 hash_adj_size;
} PDB_TpiHeader;

typedef struct PDB_TpiOffHint{
  CV_TypeId itype;
  U32 off;
} PDB_TpiOffHint;


////////////////////////////////
//~ PDB Format GSI Types

typedef U32 PDB_GsiSignature;
enum{
  PDB_GsiSignature_Basic = 0xffffffff,
};

typedef U32 PDB_GsiVersion;
enum{
  PDB_GsiVersion_V70 = 0xeffe0000 + 19990810,
};

typedef struct PDB_GsiHeader{
  PDB_GsiSignature signature;
  PDB_GsiVersion version;
  U32 hr_len;
  U32 num_buckets;
} PDB_GsiHeader;

typedef struct PDB_GsiHashRecord{
  U32 symbol_off;
  U32 cref;
} PDB_GsiHashRecord;

typedef struct PDB_PsiHeader{
  U32 sym_hash_size;
  U32 addr_map_size;
  U32 thunk_count;
  U32 thunk_size;
  CV_SectionIndex isec_thunk_table;
  U16 padding;
  U32 sec_thunk_table_off;
  U32 sec_count;
} PDB_PsiHeader;

////////////////////////////////
//~ PDB Parser Types

typedef struct PDB_InfoNode{
  struct PDB_InfoNode *next;
  String8 string;
  MSF_StreamNumber sn;
} PDB_InfoNode;

typedef struct PDB_Info{
  PDB_InfoNode *first;
  PDB_InfoNode *last;
  COFF_Guid auth_guid;
} PDB_Info;

typedef struct PDB_NamedStreamTable{
  MSF_StreamNumber sn[PDB_NamedStream_COUNT];
} PDB_NamedStreamTable;

typedef struct PDB_Strtbl{
  String8 data;
  U32 bucket_count;
  U32 strblock_min;
  U32 strblock_max;
  U32 buckets_min;
  U32 buckets_max;
} PDB_Strtbl;

typedef struct PDB_DbiParsed{
  String8 data;
  COFF_MachineType machine_type;
  MSF_StreamNumber gsi_sn;
  MSF_StreamNumber psi_sn;
  MSF_StreamNumber sym_sn;
  
  U64 range_off[(U64)(PDB_DbiRange_COUNT) + 1];
  MSF_StreamNumber dbg_streams[PDB_DbiStream_COUNT];
} PDB_DbiParsed;

typedef struct PDB_TpiParsed{
  String8 data;
  
  // leaf info
  U64 leaf_first;
  U64 leaf_opl;
  U32 itype_first;
  U32 itype_opl;
  
  // hash info
  MSF_StreamNumber hash_sn;
  MSF_StreamNumber hash_sn_aux;
  U32 hash_key_size;
  U32 hash_bucket_count;
  U32 hash_vals_off;
  U32 hash_vals_size;
  U32 itype_off;
  U32 itype_size;
  U32 hash_adj_off;
  U32 hash_adj_size;
  
} PDB_TpiParsed;

typedef struct PDB_TpiHashBlock{
  struct PDB_TpiHashBlock *next;
  U32 local_count;
  CV_TypeId itypes[13]; // 13 = (64 - 12)/4
} PDB_TpiHashBlock;

typedef struct PDB_TpiHashParsed{
  String8 data;
  String8 aux_data;
  
  PDB_TpiHashBlock **buckets;
  U32 bucket_count;
  U32 bucket_mask;
} PDB_TpiHashParsed;

typedef struct PDB_GsiBucket{
  U32 *offs;
  U64 count;
} PDB_GsiBucket;

typedef struct PDB_GsiParsed{
  PDB_GsiBucket buckets[4096];
} PDB_GsiParsed;

typedef struct PDB_CompUnit{
  MSF_StreamNumber sn;
  U32 range_off[(U32)(PDB_DbiCompUnitRange_COUNT) + 1];
  
  String8 obj_name;
  String8 group_name;
} PDB_CompUnit;

typedef struct PDB_CoffSectionArray{
  COFF_SectionHeader *sections;
  U64 count;
} PDB_CoffSectionArray;

typedef struct PDB_CompUnitNode{
  struct PDB_CompUnitNode *next;
  PDB_CompUnit unit;
} PDB_CompUnitNode;

typedef struct PDB_CompUnitArray{
  PDB_CompUnit **units;
  U64 count;
} PDB_CompUnitArray;

typedef struct PDB_CompUnitContribution{
  U32 mod;
  U64 voff_first;
  U64 voff_opl;
} PDB_CompUnitContribution;

typedef struct PDB_CompUnitContributionArray{
  PDB_CompUnitContribution *contributions;
  U64 count;
} PDB_CompUnitContributionArray;

////////////////////////////////
//~ PDB Parser Functions

internal PDB_Info*            pdb_info_from_data(Arena *arena, String8 pdb_info_data);
internal PDB_NamedStreamTable*pdb_named_stream_table_from_info(Arena *arena, PDB_Info *info);
internal PDB_Strtbl*          pdb_strtbl_from_data(Arena *arena, String8 strtbl_data);

internal PDB_DbiParsed*       pdb_dbi_from_data(Arena *arena, String8 dbi_data);
internal PDB_TpiParsed*       pdb_tpi_from_data(Arena *arena, String8 tpi_data);
internal PDB_TpiHashParsed*   pdb_tpi_hash_from_data(Arena *arena,
                                                     PDB_Strtbl *strtbl,
                                                     PDB_TpiParsed *tpi,
                                                     String8 tpi_hash_data,
                                                     String8 tpi_hash_aux_data);
internal PDB_GsiParsed*       pdb_gsi_from_data(Arena *arena, String8 gsi_data);

internal PDB_CoffSectionArray*pdb_coff_section_array_from_data(Arena *arena,
                                                               String8 section_data);

internal PDB_CompUnitArray*   pdb_comp_unit_array_from_data(Arena *arena,
                                                            String8 module_info_data);

internal PDB_CompUnitContributionArray*
pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 seccontrib_data,
                                           PDB_CoffSectionArray *sections);

////////////////////////////////
//~ PDB Definition Functions

internal U32                  pdb_string_hash1(String8 string);

////////////////////////////////
//~ PDB Dbi Functions

internal String8              pdb_data_from_dbi_range(PDB_DbiParsed *dbi, PDB_DbiRange range);
internal String8              pdb_data_from_unit_range(MSF_Parsed *msf, PDB_CompUnit *unit,
                                                       PDB_DbiCompUnitRange range);

////////////////////////////////
//~ PDB Tpi Functions

internal String8              pdb_leaf_data_from_tpi(PDB_TpiParsed *tpi);

internal CV_TypeIdArray       pdb_tpi_itypes_from_name(Arena *arena,
                                                       PDB_TpiHashParsed *tpi_hash,
                                                       CV_LeafParsed *tpi_leaf,
                                                       String8 name,
                                                       B32 compare_unique_name,
                                                       U32 output_cap);

internal CV_TypeId            pdb_tpi_first_itype_from_name(PDB_TpiHashParsed *tpi_hash,
                                                            CV_LeafParsed *tpi_leaf,
                                                            String8 name,
                                                            B32 compare_unique_name);

////////////////////////////////
//~ PDB Strtbl Functions

internal String8              pdb_strtbl_string_from_off(PDB_Strtbl *strtbl, U32 off);
internal String8              pdb_strtbl_string_from_index(PDB_Strtbl *strtbl,
                                                           PDB_StringIndex idx);

#endif // PDB_H
