#include "import_pdb.h" #ifdef _WIN32 #include #include #include #include #include #include // ── RawPDB headers ── #include "PDB.h" #include "PDB_RawFile.h" #include "PDB_TPIStream.h" #include "PDB_TPITypes.h" #include "PDB_DBIStream.h" #include "PDB_InfoStream.h" #include "PDB_CoalescedMSFStream.h" #include "Foundation/PDB_Memory.h" namespace rcx { // ── Memory-mapped file (mirrors ExampleMemoryMappedFile) ── struct MappedFile { HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hMapping = nullptr; const void* base = nullptr; size_t size = 0; bool open(const QString& path) { hFile = CreateFileW(reinterpret_cast(path.utf16()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr); if (hFile == INVALID_HANDLE_VALUE) return false; hMapping = CreateFileMappingW(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr); if (!hMapping) { close(); return false; } base = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); if (!base) { close(); return false; } BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle(hFile, &info)) { close(); return false; } size = (static_cast(info.nFileSizeHigh) << 32) | info.nFileSizeLow; return true; } void close() { if (base) { UnmapViewOfFile(base); base = nullptr; } if (hMapping) { CloseHandle(hMapping); hMapping = nullptr; } if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; } size = 0; } ~MappedFile() { close(); } MappedFile() = default; MappedFile(const MappedFile&) = delete; MappedFile& operator=(const MappedFile&) = delete; }; // ── TypeTable (mirrors ExampleTypeTable) ── // Builds an O(1) lookup table from type index → record pointer. class TypeTable { public: explicit TypeTable(const PDB::TPIStream& tpiStream) { m_firstIndex = tpiStream.GetFirstTypeIndex(); m_lastIndex = tpiStream.GetLastTypeIndex(); m_count = tpiStream.GetTypeRecordCount(); const PDB::DirectMSFStream& ds = tpiStream.GetDirectMSFStream(); m_stream = PDB::CoalescedMSFStream(ds, ds.GetSize(), 0u); m_records = PDB_NEW_ARRAY(const PDB::CodeView::TPI::Record*, m_count); uint32_t idx = 0; tpiStream.ForEachTypeRecordHeaderAndOffset( [this, &idx](const PDB::CodeView::TPI::RecordHeader&, size_t offset) { m_records[idx++] = m_stream.GetDataAtOffset(offset); }); } ~TypeTable() { PDB_DELETE_ARRAY(m_records); } uint32_t firstIndex() const { return m_firstIndex; } uint32_t lastIndex() const { return m_lastIndex; } size_t count() const { return m_count; } const PDB::CodeView::TPI::Record* get(uint32_t typeIndex) const { if (typeIndex < m_firstIndex || typeIndex >= m_lastIndex) return nullptr; return m_records[typeIndex - m_firstIndex]; } private: uint32_t m_firstIndex = 0; uint32_t m_lastIndex = 0; size_t m_count = 0; const PDB::CodeView::TPI::Record** m_records = nullptr; PDB::CoalescedMSFStream m_stream; TypeTable(const TypeTable&) = delete; TypeTable& operator=(const TypeTable&) = delete; }; // ── Leaf numeric helpers (variable-length integer encoding) ── using TRK = PDB::CodeView::TPI::TypeRecordKind; static uint8_t leafSize(TRK kind) { if (kind < TRK::LF_NUMERIC) return sizeof(TRK); // value is the kind itself switch (kind) { case TRK::LF_CHAR: return sizeof(TRK) + sizeof(uint8_t); case TRK::LF_SHORT: case TRK::LF_USHORT: return sizeof(TRK) + sizeof(uint16_t); case TRK::LF_LONG: case TRK::LF_ULONG: return sizeof(TRK) + sizeof(uint32_t); case TRK::LF_QUADWORD: case TRK::LF_UQUADWORD: return sizeof(TRK) + sizeof(uint64_t); default: return sizeof(TRK); } } static const char* leafName(const char* data, TRK kind) { return data + leafSize(kind); } static uint64_t leafValue(const char* data, TRK kind) { if (kind < TRK::LF_NUMERIC) { return static_cast(kind); } const char* p = data + sizeof(TRK); switch (kind) { case TRK::LF_CHAR: return *reinterpret_cast(p); case TRK::LF_SHORT: return *reinterpret_cast(p); case TRK::LF_USHORT: return *reinterpret_cast(p); case TRK::LF_LONG: return *reinterpret_cast(p); case TRK::LF_ULONG: return *reinterpret_cast(p); case TRK::LF_QUADWORD: return *reinterpret_cast(p); case TRK::LF_UQUADWORD: return *reinterpret_cast(p); default: return 0; } } // ── Primitive type index mapping (< 0x1000) ── static NodeKind mapPrimitiveType(uint32_t typeIndex) { uint32_t base = typeIndex & 0xFF; switch (base) { // void case 0x03: return NodeKind::Hex8; // signed char case 0x10: return NodeKind::Int8; // unsigned char case 0x20: return NodeKind::UInt8; // real char case 0x70: return NodeKind::Int8; // wchar case 0x71: return NodeKind::UInt16; // char8 case 0x7c: return NodeKind::UInt8; // char16 case 0x7a: return NodeKind::UInt16; // char32 case 0x7b: return NodeKind::UInt32; // short case 0x11: return NodeKind::Int16; // ushort case 0x21: return NodeKind::UInt16; // long case 0x12: return NodeKind::Int32; // ulong case 0x22: return NodeKind::UInt32; // int8 case 0x68: return NodeKind::Int8; // uint8 case 0x69: return NodeKind::UInt8; // int16 case 0x72: return NodeKind::Int16; // uint16 case 0x73: return NodeKind::UInt16; // int32 case 0x74: return NodeKind::Int32; // uint32 case 0x75: return NodeKind::UInt32; // quad (int64) case 0x13: return NodeKind::Int64; // uquad (uint64) case 0x23: return NodeKind::UInt64; // int64 case 0x76: return NodeKind::Int64; // uint64 case 0x77: return NodeKind::UInt64; // float case 0x40: return NodeKind::Float; // double case 0x41: return NodeKind::Double; // bool case 0x30: return NodeKind::Bool; case 0x31: return NodeKind::UInt16; // bool16 case 0x32: return NodeKind::UInt32; // bool32 case 0x33: return NodeKind::UInt64; // bool64 // HRESULT case 0x08: return NodeKind::UInt32; // bit case 0x60: return NodeKind::UInt8; // int128 / uint128 approximation case 0x78: return NodeKind::Hex64; // int128 → Hex64 (best we can do) case 0x79: return NodeKind::Hex64; // uint128 default: return NodeKind::Hex32; } } static NodeKind hexForSize(uint64_t len) { switch (len) { case 1: return NodeKind::Hex8; case 2: return NodeKind::Hex16; case 4: return NodeKind::Hex32; case 8: return NodeKind::Hex64; default: return NodeKind::Hex32; } } // ── Helper: read the leaf kind from the start of LF_UNION.data ── // (LF_UNION lacks the lfEasy member that LF_CLASS has) static TRK unionLeafKind(const char* data) { return *reinterpret_cast(data); } // ── Import context ── struct PdbCtx { NodeTree tree; const TypeTable* tt = nullptr; QHash typeCache; // typeIndex → nodeId QHash structDefByName; // struct/class definition name → typeIndex QHash unionDefByName; // union definition name → typeIndex bool udtDefIndexBuilt = false; uint64_t importUDT(uint32_t typeIndex); uint64_t importEnum(uint32_t typeIndex); void importFieldList(uint32_t fieldListIndex, uint64_t parentId); void importMemberType(uint32_t typeIndex, int offset, const QString& name, uint64_t parentId); void buildUdtDefinitionIndex(); uint32_t findUdtDefinitionIndex(TRK kind, const char* typeName); // Resolve LF_MODIFIER chain to underlying type index uint32_t unwrapModifier(uint32_t typeIndex) const { if (typeIndex < tt->firstIndex()) return typeIndex; const auto* rec = tt->get(typeIndex); if (!rec) return typeIndex; if (rec->header.kind == TRK::LF_MODIFIER) return rec->data.LF_MODIFIER.type; return typeIndex; } }; void PdbCtx::buildUdtDefinitionIndex() { if (udtDefIndexBuilt || !tt) return; udtDefIndexBuilt = true; for (uint32_t ti = tt->firstIndex(); ti < tt->lastIndex(); ti++) { const auto* rec = tt->get(ti); if (!rec) continue; bool isUnion = false; bool isFwd = false; const char* candidateName = nullptr; if (rec->header.kind == TRK::LF_UNION) { isUnion = true; isFwd = rec->data.LF_UNION.property.fwdref; candidateName = leafName(rec->data.LF_UNION.data, unionLeafKind(rec->data.LF_UNION.data)); } else if (rec->header.kind == TRK::LF_STRUCTURE || rec->header.kind == TRK::LF_CLASS) { isFwd = rec->data.LF_CLASS.property.fwdref; candidateName = leafName(rec->data.LF_CLASS.data, rec->data.LF_CLASS.lfEasy.kind); } else { continue; } if (isFwd || !candidateName || candidateName[0] == '\0') continue; QString qname = QString::fromUtf8(candidateName); QHash& lookup = isUnion ? unionDefByName : structDefByName; if (!lookup.contains(qname)) lookup.insert(qname, ti); } } uint32_t PdbCtx::findUdtDefinitionIndex(TRK kind, const char* typeName) { if (!typeName || typeName[0] == '\0') return 0; buildUdtDefinitionIndex(); const QString qname = QString::fromUtf8(typeName); if (kind == TRK::LF_UNION) { auto it = unionDefByName.constFind(qname); return (it != unionDefByName.cend()) ? it.value() : 0; } if (kind == TRK::LF_STRUCTURE || kind == TRK::LF_CLASS) { auto it = structDefByName.constFind(qname); return (it != structDefByName.cend()) ? it.value() : 0; } return 0; } uint64_t PdbCtx::importUDT(uint32_t typeIndex) { if (typeIndex < tt->firstIndex()) return 0; auto it = typeCache.find(typeIndex); if (it != typeCache.end()) return it.value(); const auto* rec = tt->get(typeIndex); if (!rec) return 0; const char* name = nullptr; uint32_t fieldListIndex = 0; uint16_t fieldCount = 0; bool isUnion = false; const char* sizeData = nullptr; if (rec->header.kind == TRK::LF_STRUCTURE || rec->header.kind == TRK::LF_CLASS) { // Skip forward references — find the definition if (rec->data.LF_CLASS.property.fwdref) return 0; fieldCount = rec->data.LF_CLASS.count; fieldListIndex = rec->data.LF_CLASS.field; sizeData = rec->data.LF_CLASS.data; name = leafName(sizeData, rec->data.LF_CLASS.lfEasy.kind); } else if (rec->header.kind == TRK::LF_UNION) { if (rec->data.LF_UNION.property.fwdref) return 0; isUnion = true; fieldCount = rec->data.LF_UNION.count; fieldListIndex = rec->data.LF_UNION.field; sizeData = rec->data.LF_UNION.data; name = leafName(sizeData, unionLeafKind(sizeData)); } else { return 0; } (void)fieldCount; QString qname = name ? QString::fromUtf8(name) : QStringLiteral(""); Node s; s.kind = NodeKind::Struct; s.name = qname; s.structTypeName = qname; s.classKeyword = isUnion ? QStringLiteral("union") : QStringLiteral("struct"); s.parentId = 0; s.collapsed = true; int idx = tree.addNode(s); uint64_t nodeId = tree.nodes[idx].id; typeCache[typeIndex] = nodeId; importFieldList(fieldListIndex, nodeId); return nodeId; } uint64_t PdbCtx::importEnum(uint32_t typeIndex) { if (typeIndex < tt->firstIndex()) return 0; auto it = typeCache.find(typeIndex); if (it != typeCache.end()) return it.value(); const auto* rec = tt->get(typeIndex); if (!rec || rec->header.kind != TRK::LF_ENUM) return 0; if (rec->data.LF_ENUM.property.fwdref) return 0; QString qname = rec->data.LF_ENUM.name ? QString::fromUtf8(rec->data.LF_ENUM.name) : QStringLiteral(""); Node s; s.kind = NodeKind::Struct; s.name = qname; s.structTypeName = qname; s.classKeyword = QStringLiteral("enum"); s.parentId = 0; s.collapsed = true; // Extract enum members from field list uint32_t fieldListIndex = rec->data.LF_ENUM.field; const auto* flRec = tt->get(fieldListIndex); if (flRec && flRec->header.kind == TRK::LF_FIELDLIST) { auto maxSize = flRec->header.size - sizeof(uint16_t); for (size_t i = 0; i < maxSize; ) { auto* field = reinterpret_cast( reinterpret_cast(&flRec->data.LF_FIELD.list) + i); if (field->kind != TRK::LF_ENUMERATE) break; int64_t val = static_cast(leafValue( field->data.LF_ENUMERATE.value, field->data.LF_ENUMERATE.lfEasy.kind)); const char* eName = leafName( field->data.LF_ENUMERATE.value, field->data.LF_ENUMERATE.lfEasy.kind); if (eName) s.enumMembers.append({QString::fromUtf8(eName), val}); i += static_cast(eName - reinterpret_cast(field)); i += strnlen(eName, maxSize - i - 1) + 1; i = (i + 3) & ~size_t(3); } } int idx = tree.addNode(s); uint64_t nodeId = tree.nodes[idx].id; typeCache[typeIndex] = nodeId; return nodeId; } void PdbCtx::importFieldList(uint32_t fieldListIndex, uint64_t parentId) { const auto* rec = tt->get(fieldListIndex); if (!rec || rec->header.kind != TRK::LF_FIELDLIST) return; auto maximumSize = rec->header.size - sizeof(uint16_t); QSet> bitfieldSlots; QHash, uint64_t> bitfieldNodeIds; for (size_t i = 0; i < maximumSize; ) { auto* field = reinterpret_cast( reinterpret_cast(&rec->data.LF_FIELD.list) + i); if (field->kind == TRK::LF_MEMBER) { // Extract offset from variable-length leaf uint16_t offset = 0; if (field->data.LF_MEMBER.lfEasy.kind < TRK::LF_NUMERIC) offset = *reinterpret_cast(field->data.LF_MEMBER.offset); else offset = static_cast(leafValue(field->data.LF_MEMBER.offset, field->data.LF_MEMBER.lfEasy.kind)); const char* memberName = leafName(field->data.LF_MEMBER.offset, field->data.LF_MEMBER.lfEasy.kind); uint32_t memberType = field->data.LF_MEMBER.index; QString qname = memberName ? QString::fromUtf8(memberName) : QString(); // Check for bitfield type uint32_t resolvedType = unwrapModifier(memberType); const auto* typeRec = tt->get(resolvedType); if (typeRec && typeRec->header.kind == TRK::LF_BITFIELD) { uint32_t underlying = typeRec->data.LF_BITFIELD.type; uint8_t bitLen = typeRec->data.LF_BITFIELD.length; uint8_t bitPos = typeRec->data.LF_BITFIELD.position; // Determine slot size from underlying type uint64_t slotSize = 4; if (underlying < tt->firstIndex()) { NodeKind k = mapPrimitiveType(underlying); slotSize = sizeForKind(k); } auto key = qMakePair((int)offset, (int)slotSize); if (!bitfieldSlots.contains(key)) { bitfieldSlots.insert(key); // Create bitfield container node Node n; n.kind = NodeKind::Struct; n.classKeyword = QStringLiteral("bitfield"); n.elementKind = hexForSize(slotSize); n.parentId = parentId; n.offset = offset; n.collapsed = false; int idx = tree.addNode(n); bitfieldNodeIds[key] = tree.nodes[idx].id; } // Add this member to the bitfield container uint64_t bfNodeId = bitfieldNodeIds[key]; int bfIdx = tree.indexOfId(bfNodeId); if (bfIdx >= 0) { BitfieldMember bm; bm.name = qname; bm.bitOffset = bitPos; bm.bitWidth = bitLen; tree.nodes[bfIdx].bitfieldMembers.append(bm); } } else { importMemberType(memberType, offset, qname, parentId); } // Advance past this LF_MEMBER i += static_cast(memberName - reinterpret_cast(field)); i += strnlen(memberName, maximumSize - i - 1) + 1; i = (i + 3) & ~size_t(3); // align to 4 } else if (field->kind == TRK::LF_BCLASS) { const char* leafEnd = leafName(field->data.LF_BCLASS.offset, field->data.LF_BCLASS.lfEasy.kind); i += static_cast(leafEnd - reinterpret_cast(field)); i = (i + 3) & ~size_t(3); } else if (field->kind == TRK::LF_VBCLASS || field->kind == TRK::LF_IVBCLASS) { TRK vbpKind = *reinterpret_cast(field->data.LF_IVBCLASS.vbpOffset); uint8_t vbpSize1 = leafSize(vbpKind); TRK vbtKind = *reinterpret_cast(field->data.LF_IVBCLASS.vbpOffset + vbpSize1); uint8_t vbpSize2 = leafSize(vbtKind); i += sizeof(PDB::CodeView::TPI::FieldList::Data::LF_VBCLASS) + vbpSize1 + vbpSize2; i = (i + 3) & ~size_t(3); } else if (field->kind == TRK::LF_INDEX) { // Continuation of field list in another record importFieldList(field->data.LF_INDEX.type, parentId); i += sizeof(PDB::CodeView::TPI::FieldList::Data::LF_INDEX); i = (i + 3) & ~size_t(3); } else if (field->kind == TRK::LF_VFUNCTAB) { i += sizeof(PDB::CodeView::TPI::FieldList::Data::LF_VFUNCTAB); i = (i + 3) & ~size_t(3); } else if (field->kind == TRK::LF_NESTTYPE) { const char* nestName = field->data.LF_NESTTYPE.name; i += static_cast(nestName - reinterpret_cast(field)); i += strnlen(nestName, maximumSize - i - 1) + 1; i = (i + 3) & ~size_t(3); } else if (field->kind == TRK::LF_STMEMBER) { const char* smName = field->data.LF_STMEMBER.name; i += static_cast(smName - reinterpret_cast(field)); i += strnlen(smName, maximumSize - i - 1) + 1; i = (i + 3) & ~size_t(3); } else if (field->kind == TRK::LF_METHOD) { const char* mName = field->data.LF_METHOD.name; i += static_cast(mName - reinterpret_cast(field)); i += strnlen(mName, maximumSize - i - 1) + 1; i = (i + 3) & ~size_t(3); } else if (field->kind == TRK::LF_ONEMETHOD) { // Determine if it has a vbaseoff field auto prop = static_cast( field->data.LF_ONEMETHOD.attributes.mprop); const char* mName; if (prop == PDB::CodeView::TPI::MethodProperty::Intro || prop == PDB::CodeView::TPI::MethodProperty::PureIntro) mName = reinterpret_cast(field->data.LF_ONEMETHOD.vbaseoff) + sizeof(uint32_t); else mName = reinterpret_cast(field->data.LF_ONEMETHOD.vbaseoff); i += static_cast(mName - reinterpret_cast(field)); i += strnlen(mName, maximumSize - i - 1) + 1; i = (i + 3) & ~size_t(3); } else if (field->kind == TRK::LF_ENUMERATE) { const char* eName = leafName(field->data.LF_ENUMERATE.value, field->data.LF_ENUMERATE.lfEasy.kind); i += static_cast(eName - reinterpret_cast(field)); i += strnlen(eName, maximumSize - i - 1) + 1; i = (i + 3) & ~size_t(3); } else { break; // unknown field kind, stop } } } void PdbCtx::importMemberType(uint32_t typeIndex, int offset, const QString& name, uint64_t parentId) { // Handle primitive type indices (< 0x1000) if (typeIndex < tt->firstIndex()) { uint32_t ptrMode = (typeIndex >> 8) & 0xF; if (ptrMode == 0x04 || ptrMode == 0x05) { // 32-bit pointer to a base type Node n; n.kind = NodeKind::Pointer32; n.name = name; n.parentId = parentId; n.offset = offset; n.collapsed = true; tree.addNode(n); return; } if (ptrMode == 0x06) { // 64-bit pointer to a base type Node n; n.kind = NodeKind::Pointer64; n.name = name; n.parentId = parentId; n.offset = offset; n.collapsed = true; tree.addNode(n); return; } if (ptrMode != 0x00) { // Some other pointer mode (near, far, huge) — treat as 32-bit Node n; n.kind = NodeKind::Pointer32; n.name = name; n.parentId = parentId; n.offset = offset; n.collapsed = true; tree.addNode(n); return; } // Direct base type Node n; n.kind = mapPrimitiveType(typeIndex); n.name = name; n.parentId = parentId; n.offset = offset; tree.addNode(n); return; } const auto* rec = tt->get(typeIndex); if (!rec) { Node n; n.kind = NodeKind::Hex32; n.name = name; n.parentId = parentId; n.offset = offset; tree.addNode(n); return; } switch (rec->header.kind) { case TRK::LF_MODIFIER: importMemberType(rec->data.LF_MODIFIER.type, offset, name, parentId); break; case TRK::LF_POINTER: { uint32_t ptrSize = rec->data.LF_POINTER.attr.size; uint32_t pointee = rec->data.LF_POINTER.utype; // Unwrap modifier on pointee uint32_t realPointee = unwrapModifier(pointee); Node n; n.kind = (ptrSize <= 4) ? NodeKind::Pointer32 : NodeKind::Pointer64; n.name = name; n.parentId = parentId; n.offset = offset; n.collapsed = true; // Check if pointee is a UDT if (realPointee >= tt->firstIndex()) { const auto* pointeeRec = tt->get(realPointee); if (pointeeRec) { if (pointeeRec->header.kind == TRK::LF_STRUCTURE || pointeeRec->header.kind == TRK::LF_CLASS || pointeeRec->header.kind == TRK::LF_UNION) { // If this is a forward ref, search for the definition uint32_t defIndex = realPointee; bool isFwd = false; if (pointeeRec->header.kind == TRK::LF_UNION) isFwd = pointeeRec->data.LF_UNION.property.fwdref; else isFwd = pointeeRec->data.LF_CLASS.property.fwdref; if (isFwd) { const char* typeName = nullptr; if (pointeeRec->header.kind == TRK::LF_UNION) typeName = leafName(pointeeRec->data.LF_UNION.data, unionLeafKind(pointeeRec->data.LF_UNION.data)); else typeName = leafName(pointeeRec->data.LF_CLASS.data, pointeeRec->data.LF_CLASS.lfEasy.kind); uint32_t resolved = findUdtDefinitionIndex(pointeeRec->header.kind, typeName); if (resolved != 0) defIndex = resolved; } // Skip anonymous pointer targets — they'd create root orphans const char* ptName = nullptr; const auto* defRec2 = tt->get(defIndex); if (defRec2) { if (defRec2->header.kind == TRK::LF_UNION) ptName = leafName(defRec2->data.LF_UNION.data, unionLeafKind(defRec2->data.LF_UNION.data)); else if (defRec2->header.kind == TRK::LF_STRUCTURE || defRec2->header.kind == TRK::LF_CLASS) ptName = leafName(defRec2->data.LF_CLASS.data, defRec2->data.LF_CLASS.lfEasy.kind); } bool isAnonTarget = !ptName || ptName[0] == '<' || ptName[0] == '\0'; if (!isAnonTarget) n.refId = importUDT(defIndex); } else if (pointeeRec->header.kind == TRK::LF_PROCEDURE || pointeeRec->header.kind == TRK::LF_MFUNCTION) { n.kind = (ptrSize <= 4) ? NodeKind::FuncPtr32 : NodeKind::FuncPtr64; } } } tree.addNode(n); break; } case TRK::LF_STRUCTURE: case TRK::LF_CLASS: case TRK::LF_UNION: { // Embedded struct/union uint32_t defIndex = typeIndex; // Handle forward reference bool isFwd = false; if (rec->header.kind == TRK::LF_UNION) isFwd = rec->data.LF_UNION.property.fwdref; else isFwd = rec->data.LF_CLASS.property.fwdref; if (isFwd) { const char* typeName = nullptr; if (rec->header.kind == TRK::LF_UNION) typeName = leafName(rec->data.LF_UNION.data, unionLeafKind(rec->data.LF_UNION.data)); else typeName = leafName(rec->data.LF_CLASS.data, rec->data.LF_CLASS.lfEasy.kind); uint32_t resolved = findUdtDefinitionIndex(rec->header.kind, typeName); if (resolved != 0) defIndex = resolved; } const char* typeName = nullptr; bool isUnion = (rec->header.kind == TRK::LF_UNION); if (isUnion) typeName = leafName(rec->data.LF_UNION.data, unionLeafKind(rec->data.LF_UNION.data)); else typeName = leafName(rec->data.LF_CLASS.data, rec->data.LF_CLASS.lfEasy.kind); // Anonymous types: inline fields directly instead of creating root orphan bool isAnonymous = !typeName || typeName[0] == '<' || typeName[0] == '\0'; if (isAnonymous) { // Resolve to definition if needed const auto* defRec = tt->get(defIndex); uint32_t fieldListIdx = 0; if (defRec) { if (defRec->header.kind == TRK::LF_UNION) fieldListIdx = defRec->data.LF_UNION.field; else if (defRec->header.kind == TRK::LF_STRUCTURE || defRec->header.kind == TRK::LF_CLASS) fieldListIdx = defRec->data.LF_CLASS.field; } if (fieldListIdx != 0) { // Create inline container (no refId, no root orphan) Node n; n.kind = NodeKind::Struct; n.name = name; n.classKeyword = isUnion ? QStringLiteral("union") : QStringLiteral("struct"); n.parentId = parentId; n.offset = offset; n.collapsed = true; int idx = tree.addNode(n); uint64_t inlineId = tree.nodes[idx].id; importFieldList(fieldListIdx, inlineId); break; } // Fallthrough if no field list } uint64_t refId = importUDT(defIndex); Node n; n.kind = NodeKind::Struct; n.name = name; n.structTypeName = typeName ? QString::fromUtf8(typeName) : QString(); n.classKeyword = isUnion ? QStringLiteral("union") : QStringLiteral("struct"); n.parentId = parentId; n.offset = offset; n.refId = refId; n.collapsed = true; tree.addNode(n); break; } case TRK::LF_ARRAY: { uint32_t elemType = rec->data.LF_ARRAY.elemtype; uint64_t totalSize = leafValue(rec->data.LF_ARRAY.data, *reinterpret_cast(rec->data.LF_ARRAY.data)); // Get element size uint64_t elemSize = 0; uint32_t realElemType = unwrapModifier(elemType); if (realElemType < tt->firstIndex()) { NodeKind ek = mapPrimitiveType(realElemType); elemSize = sizeForKind(ek); } else { const auto* elemRec = tt->get(realElemType); if (elemRec) { if (elemRec->header.kind == TRK::LF_STRUCTURE || elemRec->header.kind == TRK::LF_CLASS) { const char* sizeData = elemRec->data.LF_CLASS.data; elemSize = leafValue(sizeData, elemRec->data.LF_CLASS.lfEasy.kind); } else if (elemRec->header.kind == TRK::LF_UNION) { const char* sizeData = elemRec->data.LF_UNION.data; elemSize = leafValue(sizeData, *reinterpret_cast(sizeData)); } else if (elemRec->header.kind == TRK::LF_POINTER) { elemSize = elemRec->data.LF_POINTER.attr.size; } else if (elemRec->header.kind == TRK::LF_ENUM) { // Size of enum's underlying type uint32_t ut = elemRec->data.LF_ENUM.utype; if (ut < tt->firstIndex()) { NodeKind ek = mapPrimitiveType(ut); elemSize = sizeForKind(ek); } else { elemSize = 4; } } else if (elemRec->header.kind == TRK::LF_ARRAY) { // Nested array — get total size elemSize = leafValue(elemRec->data.LF_ARRAY.data, *reinterpret_cast(elemRec->data.LF_ARRAY.data)); } } } int count = (elemSize > 0) ? static_cast(totalSize / elemSize) : 1; Node n; n.kind = NodeKind::Array; n.name = name; n.parentId = parentId; n.offset = offset; n.arrayLen = count; // Determine element kind if (realElemType < tt->firstIndex()) { n.elementKind = mapPrimitiveType(realElemType); } else { const auto* elemRec = tt->get(realElemType); if (elemRec) { if (elemRec->header.kind == TRK::LF_STRUCTURE || elemRec->header.kind == TRK::LF_CLASS || elemRec->header.kind == TRK::LF_UNION) { n.elementKind = NodeKind::Struct; n.refId = importUDT(realElemType); const char* tn = nullptr; if (elemRec->header.kind == TRK::LF_UNION) tn = leafName(elemRec->data.LF_UNION.data, unionLeafKind(elemRec->data.LF_UNION.data)); else tn = leafName(elemRec->data.LF_CLASS.data, elemRec->data.LF_CLASS.lfEasy.kind); if (tn) n.structTypeName = QString::fromUtf8(tn); } else if (elemRec->header.kind == TRK::LF_POINTER) { uint32_t sz = elemRec->data.LF_POINTER.attr.size; n.elementKind = (sz <= 4) ? NodeKind::Pointer32 : NodeKind::Pointer64; } else { n.elementKind = hexForSize(elemSize); } } } tree.addNode(n); break; } case TRK::LF_ENUM: { // Map enum to its underlying integer type, link to enum definition uint32_t utype = rec->data.LF_ENUM.utype; uint64_t enumNodeId = importEnum(typeIndex); Node n; if (utype < tt->firstIndex()) { n.kind = mapPrimitiveType(utype); } else { n.kind = NodeKind::UInt32; // fallback } n.name = name; n.parentId = parentId; n.offset = offset; n.refId = enumNodeId; tree.addNode(n); break; } case TRK::LF_PROCEDURE: case TRK::LF_MFUNCTION: { Node n; n.kind = NodeKind::Hex64; n.name = name; n.parentId = parentId; n.offset = offset; tree.addNode(n); break; } case TRK::LF_BITFIELD: { uint32_t underlying = rec->data.LF_BITFIELD.type; uint8_t bitLen = rec->data.LF_BITFIELD.length; uint8_t bitPos = rec->data.LF_BITFIELD.position; uint64_t slotSize = 4; if (underlying < tt->firstIndex()) { NodeKind k = mapPrimitiveType(underlying); slotSize = sizeForKind(k); } Node n; n.kind = NodeKind::Struct; n.classKeyword = QStringLiteral("bitfield"); n.elementKind = hexForSize(slotSize); n.name = name; n.parentId = parentId; n.offset = offset; n.bitfieldMembers.append({name, bitPos, bitLen}); tree.addNode(n); break; } default: { // Unknown complex type — emit as Hex32 Node n; n.kind = NodeKind::Hex32; n.name = name; n.parentId = parentId; n.offset = offset; tree.addNode(n); break; } } } // ── Helper: open PDB and build type table ── struct PdbFile { MappedFile mapped; PDB::RawFile* rawFile = nullptr; PDB::TPIStream* tpiStream = nullptr; TypeTable* typeTable = nullptr; ~PdbFile() { delete typeTable; delete tpiStream; delete rawFile; } bool open(const QString& pdbPath, QString* errorMsg) { auto setErr = [&](const QString& msg) { if (errorMsg) *errorMsg = msg; }; if (!QFile::exists(pdbPath)) { setErr(QStringLiteral("PDB file not found: ") + pdbPath); return false; } if (!mapped.open(pdbPath)) { setErr(QStringLiteral("Failed to memory-map PDB file: ") + pdbPath); return false; } if (PDB::ValidateFile(mapped.base, mapped.size) != PDB::ErrorCode::Success) { setErr(QStringLiteral("Invalid PDB file: ") + pdbPath); return false; } rawFile = new PDB::RawFile(PDB::CreateRawFile(mapped.base)); if (PDB::HasValidTPIStream(*rawFile) != PDB::ErrorCode::Success) { setErr(QStringLiteral("PDB has no valid TPI stream: ") + pdbPath); return false; } tpiStream = new PDB::TPIStream(PDB::CreateTPIStream(*rawFile)); typeTable = new TypeTable(*tpiStream); return true; } }; // ── Public API: enumeratePdbTypes ── QVector enumeratePdbTypes(const QString& pdbPath, QString* errorMsg) { PdbFile pdb; if (!pdb.open(pdbPath, errorMsg)) return {}; const TypeTable& tt = *pdb.typeTable; QVector result; for (uint32_t ti = tt.firstIndex(); ti < tt.lastIndex(); ti++) { const auto* rec = tt.get(ti); if (!rec) continue; bool isUDT = (rec->header.kind == TRK::LF_STRUCTURE || rec->header.kind == TRK::LF_CLASS || rec->header.kind == TRK::LF_UNION); bool isEnum = (rec->header.kind == TRK::LF_ENUM); if (!isUDT && !isEnum) continue; const char* name = nullptr; uint16_t fieldCount = 0; bool isUnion = false; uint64_t size = 0; if (isEnum) { if (rec->data.LF_ENUM.property.fwdref) continue; fieldCount = rec->data.LF_ENUM.count; name = rec->data.LF_ENUM.name; // Size from underlying type uint32_t ut = rec->data.LF_ENUM.utype; if (ut < tt.firstIndex()) { NodeKind ek = mapPrimitiveType(ut); size = sizeForKind(ek); } else { size = 4; } } else if (rec->header.kind == TRK::LF_UNION) { if (rec->data.LF_UNION.property.fwdref) continue; isUnion = true; fieldCount = rec->data.LF_UNION.count; const char* sizeData = rec->data.LF_UNION.data; TRK sizeKind = *reinterpret_cast(sizeData); size = leafValue(sizeData, sizeKind); name = leafName(sizeData, sizeKind); } else { if (rec->data.LF_CLASS.property.fwdref) continue; fieldCount = rec->data.LF_CLASS.count; const char* sizeData = rec->data.LF_CLASS.data; size = leafValue(sizeData, rec->data.LF_CLASS.lfEasy.kind); name = leafName(sizeData, rec->data.LF_CLASS.lfEasy.kind); } if (!name || name[0] == '\0') continue; // Skip anonymous types with compiler-generated names if (name[0] == '<') continue; PdbTypeInfo info; info.typeIndex = ti; info.name = QString::fromUtf8(name); info.size = size; info.childCount = fieldCount; info.isUnion = isUnion; info.isEnum = isEnum; result.append(info); } int enumCount = 0; for (const auto& r : result) if (r.isEnum) enumCount++; qDebug() << "[PDB] enumeratePdbTypes:" << result.size() << "types," << enumCount << "enums"; return result; } // ── Public API: importPdbSelected ── NodeTree importPdbSelected(const QString& pdbPath, const QVector& typeIndices, QString* errorMsg, ProgressCb progressCb) { PdbFile pdb; if (!pdb.open(pdbPath, errorMsg)) return {}; PdbCtx ctx; ctx.tt = pdb.typeTable; int total = typeIndices.size(); int enumDispatched = 0, enumCreated = 0; for (int i = 0; i < total; i++) { uint32_t ti = typeIndices[i]; const auto* rec = pdb.typeTable->get(ti); if (rec && rec->header.kind == TRK::LF_ENUM) { enumDispatched++; uint64_t id = ctx.importEnum(ti); if (id != 0) enumCreated++; else qDebug() << "[PDB] importEnum FAILED for typeIndex" << ti; } else { ctx.importUDT(ti); } if (progressCb && !progressCb(i + 1, total)) { if (errorMsg) *errorMsg = QStringLiteral("Import cancelled"); return ctx.tree; // return partial result } } // Count enum nodes in tree int enumNodes = 0; for (const auto& n : ctx.tree.nodes) if (n.classKeyword == QLatin1String("enum")) enumNodes++; qDebug() << "[PDB] importPdbSelected:" << total << "types," << enumDispatched << "enum dispatches," << enumCreated << "enum created," << enumNodes << "enum nodes in tree," << ctx.tree.nodes.size() << "total nodes"; if (ctx.tree.nodes.isEmpty()) { if (errorMsg) *errorMsg = QStringLiteral("No types imported"); } return ctx.tree; } // ── Public API: importPdb (legacy) ── NodeTree importPdb(const QString& pdbPath, const QString& structFilter, QString* errorMsg) { PdbFile pdb; if (!pdb.open(pdbPath, errorMsg)) return {}; const TypeTable& tt = *pdb.typeTable; PdbCtx ctx; ctx.tt = &tt; for (uint32_t ti = tt.firstIndex(); ti < tt.lastIndex(); ti++) { const auto* rec = tt.get(ti); if (!rec) continue; bool isUDT = (rec->header.kind == TRK::LF_STRUCTURE || rec->header.kind == TRK::LF_CLASS || rec->header.kind == TRK::LF_UNION); if (!isUDT) continue; bool fwdref = false; const char* name = nullptr; if (rec->header.kind == TRK::LF_UNION) { fwdref = rec->data.LF_UNION.property.fwdref; name = leafName(rec->data.LF_UNION.data, unionLeafKind(rec->data.LF_UNION.data)); } else { fwdref = rec->data.LF_CLASS.property.fwdref; name = leafName(rec->data.LF_CLASS.data, rec->data.LF_CLASS.lfEasy.kind); } if (fwdref) continue; if (!name) continue; if (!structFilter.isEmpty()) { if (QString::fromUtf8(name) != structFilter) continue; } ctx.importUDT(ti); // If filtering to a single struct, stop after finding it if (!structFilter.isEmpty()) break; } if (ctx.tree.nodes.isEmpty()) { if (!structFilter.isEmpty()) { if (errorMsg) *errorMsg = QStringLiteral("Type '") + structFilter + QStringLiteral("' not found in PDB"); } else { if (errorMsg) *errorMsg = QStringLiteral("No types found in PDB"); } } return ctx.tree; } } // namespace rcx #else // !_WIN32 namespace rcx { QVector enumeratePdbTypes(const QString&, QString* errorMsg) { if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows"); return {}; } NodeTree importPdbSelected(const QString&, const QVector&, QString* errorMsg, ProgressCb) { if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows"); return {}; } NodeTree importPdb(const QString&, const QString&, QString* errorMsg) { if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows"); return {}; } } // namespace rcx #endif