// 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 String Table Types

#define PDB_INVALID_STRING_INDEX max_U32
typedef U32 PDB_StringIndex;
typedef U32 PDB_StringOffset;

enum
{
  PDB_StringTableHeader_MAGIC = 0xEFFEEFFE
};

enum
{
  PDB_StringTableHeader_Version1       = 1,
  PDB_StringTableHeader_CurrentVersion = PDB_StringTableHeader_Version1
};

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

////////////////////////////////

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

////////////////////////////////
//~ PDB Info Types

typedef U32 PDB_InfoVersion;
enum{
  PDB_InfoVersion_VC2      = 19941610,
  PDB_InfoVersion_VC4      = 19950623,
  PDB_InfoVersion_VC41     = 19950814,
  PDB_InfoVersion_VC50     = 19960307,
  PDB_InfoVersion_VC98     = 19970604,
  PDB_InfoVersion_VC70_DEP = 19990604,
  PDB_InfoVersion_VC70     = 20000404,
  PDB_InfoVersion_VC80     = 20030901,
  PDB_InfoVersion_VC110    = 20091201,
  PDB_InfoVersion_VC140    = 20140508
};

// referenced in PDB1::loadPdbStream
enum
{
  PDB_FeatureSig_NULL               = 0,
  PDB_FeatureSig_VC110              = PDB_InfoVersion_VC110,
  PDB_FeatureSig_VC140              = PDB_InfoVersion_VC140,
  PDB_FeatureSig_NO_TYPE_MERGE      = 0x4D544F4E,
  PDB_FeatureSig_MINIMAL_DEBUG_INFO = 0x494E494D,
};
typedef U32 PDB_FeatureSig;

enum
{
  PDB_FeatureFlag_HAS_ID_STREAM    = (1 << 0),
  PDB_FeatureFlag_NO_TYPE_MERGE    = (1 << 1),
  PDB_FeatureFlag_MINIMAL_DBG_INFO = (1 << 2),
};
typedef U32 PDB_FeatureFlags;

#pragma pack(push,1)
typedef struct PDB_InfoHeaderV70
{
  PDB_InfoVersion    version;
  COFF_TimeStamp     time_stamp;
  U32                age;
  Guid               guid;
  // PDB_HashTable   named_stream_hash_table
  // PDB_FeatureFlag features[*]
} PDB_InfoHeaderV70;

#pragma pack(pop)
StaticAssert(sizeof(PDB_InfoHeaderV70) == 28, pdb_info_header_v70_size_check);

#define PDB_SRC_HEADER_BLOCK_STREAM_NAME str8_lit("/src/headerblock")
#define PDB_LINK_INFO_STREAM_NAME        str8_lit("/LinkInfo")
#define PDB_NAMES_STREAM_NAME            str8_lit("/names")

////////////////////////////////
// SRC Header Block

#define PDB_SRC_HEADER_BLOCK_MAGIC_V1 19980827

typedef struct PDB_SrcHeaderBlockHeader
{
  U32 version;
  U32 stream_size;
  U64 file_time;
  U32 age;
  U8  pad[44];
} PDB_SrcHeaderBlockHeader;

enum
{
  PDB_SrcComp_NULL,
  PDB_SrcComp_RUN_LENGTH_RECORD,
  PDB_SrcComp_HUFFMAN,
  PDB_SrcComp_LZ,
  PDB_SrcComp_DOTNET
};
typedef U8 PDB_SrcCompType;

enum
{
  PDB_SrcHeaderBlockEntryFlag_IS_VIRTUAL = (1 << 0)
};
typedef U8 PDB_SrcHeaderFlags;

// (PDB/include/pdb.h: SrcHeaderOut)
typedef struct PDB_SrcHeaderBlockEntry
{
  U32                size;
  U32                version;
  U32                file_crc;
  U32                file_size;
  PDB_StringOffset   file_path;
  PDB_StringOffset   obj;
  PDB_StringOffset   virt_path;
  PDB_SrcCompType    comp;
  PDB_SrcHeaderFlags flags;
  U8                 pad[2];
  U8                 reserved[8];
} PDB_SrcHeaderBlockEntry;

////////////////////////////////
//~ 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_DbiMakeBuildNumber(maj, min) (PDB_DbiBuildNumber)(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;

// "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;
  U16 pad0;
  U32 sec_off;
  U32 size;
  U32 flags;
  CV_ModIndex mod;
  U16 pad1;
} 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;

////////////////////////////////

enum
{
  PDB_DbiOMF_NONE          = 0,
  PDB_DbiOMF_READ          = (1 << 0),
  PDB_DbiOMF_WRITE         = (1 << 1),
  PDB_DbiOMF_EXEC          = (1 << 2),
  PDB_DbiOMF_IS_32BIT_ADDR = (1 << 3), // Descritor is 32-bit address
  PDB_DbiOMF_IS_SELECTOR   = (1 << 8), // Frame is a selector
  PDB_DbiOMF_IS_ABS_ADDR   = (1 << 9), // Frame is absolute address
  PDB_DbiOMF_IS_GROUP      = (1 << 10) // Descriptor is a group
};
typedef U16 PDB_DbiOMF;

typedef struct PDB_DbiSecMapEntry
{
  PDB_DbiOMF flags;
  U16        ovl;
  U16        group;
  U16        frame;
  U16        sec_name;
  U16        class_name;
  U32        offset;
  U32        sec_size;
} PDB_DbiSecMapEntry;

typedef struct PDB_DbiSecMapHeader
{
  U16 section_count;
  U16 segment_count;
} PDB_DbiSecMapHeader;

////////////////////////////////
//~ PDB Format TPI/IPI 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,
};

enum
{
  PDB_TYPE_SERVER_HASH_BUCKET_COUNT_V7   = 0x1000,
  PDB_TYPE_SERVER_HASH_BUCKET_COUNT_V8   = 0x3FFF,
  PDB_TYPE_SERVER_HASH_BUCKET_COUNT_INIT = 0x1000,
  PDB_TYPE_SERVER_HASH_BUCKET_COUNT_MAX  = 0x40000,
  
  PDB_TYPE_SERVER_HASH_BUCKET_COUNT_CURRENT = PDB_TYPE_SERVER_HASH_BUCKET_COUNT_V8,
};

#define PDB_TYPE_OFFSET_MAX  max_U32
typedef U32 PDB_TypeOffset;

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

typedef struct PDB_OffsetSize
{
  U32 off;
  U32 size;
} PDB_OffsetSize;

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;
  PDB_OffsetSize hash_vals;
  PDB_OffsetSize itype_offs;
  PDB_OffsetSize hash_adj;
} PDB_TpiHeader;


////////////////////////////////
//~ 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 hash_record_arr_size;
  U32 bucket_data_size;
} PDB_GsiHeader;

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

typedef struct PDB_GsiHashRecordOffsetCalc
{
  U32 next;
  U32 off;
  U32 cref;
} PDB_GsiHashRecordOffsetCalc;

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;

////////////////////////////////

internal U32 pdb_hash_v1(String8 string);

#endif // PDB_H
