feat: PDB import via RawPDB, no msdia140.dll dependency

Replace DIA SDK COM-based PDB importer with RawPDB (MolecularMatters)
which reads PDB files directly via memory-mapped I/O. Adds File menu
"Import PDB..." dialog with type filtering, selection, and progress.

- Vendor raw_pdb into third_party/
- Two-phase API: enumeratePdbTypes() + importPdbSelected()
- Full recursive import of structs/unions/arrays/pointers/bitfields
- PDB import dialog with name filter, select-all, type count
- Benchmark: 1654 types from ntkrnlmp.pdb in 16ms
- Reorganize import/export files into src/imports/
This commit is contained in:
IChooseYou
2026-02-21 17:18:24 -07:00
parent 3a76b03c85
commit 1d7d384b93
100 changed files with 11627 additions and 17 deletions

971
src/imports/import_pdb.cpp Normal file
View File

@@ -0,0 +1,971 @@
#include "import_pdb.h"
#ifdef _WIN32
#include <windows.h>
#include <QFile>
#include <QHash>
#include <QPair>
#include <QSet>
// ── 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<const wchar_t*>(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<size_t>(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<const PDB::CodeView::TPI::Record>(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<uint16_t>(kind);
}
const char* p = data + sizeof(TRK);
switch (kind) {
case TRK::LF_CHAR: return *reinterpret_cast<const uint8_t*>(p);
case TRK::LF_SHORT: return *reinterpret_cast<const int16_t*>(p);
case TRK::LF_USHORT: return *reinterpret_cast<const uint16_t*>(p);
case TRK::LF_LONG: return *reinterpret_cast<const int32_t*>(p);
case TRK::LF_ULONG: return *reinterpret_cast<const uint32_t*>(p);
case TRK::LF_QUADWORD: return *reinterpret_cast<const int64_t*>(p);
case TRK::LF_UQUADWORD: return *reinterpret_cast<const uint64_t*>(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<const TRK*>(data);
}
// ── Import context ──
struct PdbCtx {
NodeTree tree;
const TypeTable* tt = nullptr;
QHash<uint32_t, uint64_t> typeCache; // typeIndex → nodeId
uint64_t importUDT(uint32_t typeIndex);
void importFieldList(uint32_t fieldListIndex, uint64_t parentId);
void importMemberType(uint32_t typeIndex, int offset, const QString& name, uint64_t parentId);
// 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;
}
};
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("<anon>");
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;
}
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<QPair<int,int>> bitfieldSlots;
for (size_t i = 0; i < maximumSize; ) {
auto* field = reinterpret_cast<const PDB::CodeView::TPI::FieldList*>(
reinterpret_cast<const uint8_t*>(&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<const uint16_t*>(field->data.LF_MEMBER.offset);
else
offset = static_cast<uint16_t>(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;
(void)bitLen;
// 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);
Node n;
n.kind = hexForSize(slotSize);
n.name = qname;
n.parentId = parentId;
n.offset = offset;
tree.addNode(n);
}
} else {
importMemberType(memberType, offset, qname, parentId);
}
// Advance past this LF_MEMBER
i += static_cast<size_t>(memberName - reinterpret_cast<const char*>(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<size_t>(leafEnd - reinterpret_cast<const char*>(field));
i = (i + 3) & ~size_t(3);
}
else if (field->kind == TRK::LF_VBCLASS || field->kind == TRK::LF_IVBCLASS) {
TRK vbpKind = *reinterpret_cast<const TRK*>(field->data.LF_IVBCLASS.vbpOffset);
uint8_t vbpSize1 = leafSize(vbpKind);
TRK vbtKind = *reinterpret_cast<const TRK*>(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<size_t>(nestName - reinterpret_cast<const char*>(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<size_t>(smName - reinterpret_cast<const char*>(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<size_t>(mName - reinterpret_cast<const char*>(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<PDB::CodeView::TPI::MethodProperty>(
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<const char*>(field->data.LF_ONEMETHOD.vbaseoff) + sizeof(uint32_t);
else
mName = reinterpret_cast<const char*>(field->data.LF_ONEMETHOD.vbaseoff);
i += static_cast<size_t>(mName - reinterpret_cast<const char*>(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<size_t>(eName - reinterpret_cast<const char*>(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) {
// Need to find the non-fwdref definition by name
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);
if (typeName) {
// Linear scan for the definition (cached after first import)
for (uint32_t ti = tt->firstIndex(); ti < tt->lastIndex(); ti++) {
const auto* candidate = tt->get(ti);
if (!candidate) continue;
if (candidate->header.kind != pointeeRec->header.kind) continue;
bool candidateFwd;
const char* candidateName;
if (candidate->header.kind == TRK::LF_UNION) {
candidateFwd = candidate->data.LF_UNION.property.fwdref;
candidateName = leafName(candidate->data.LF_UNION.data, unionLeafKind(candidate->data.LF_UNION.data));
} else {
candidateFwd = candidate->data.LF_CLASS.property.fwdref;
candidateName = leafName(candidate->data.LF_CLASS.data,
candidate->data.LF_CLASS.lfEasy.kind);
}
if (!candidateFwd && candidateName && strcmp(candidateName, typeName) == 0) {
defIndex = ti;
break;
}
}
}
}
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);
if (typeName) {
for (uint32_t ti = tt->firstIndex(); ti < tt->lastIndex(); ti++) {
const auto* candidate = tt->get(ti);
if (!candidate) continue;
if (candidate->header.kind != rec->header.kind) continue;
bool candidateFwd;
const char* candidateName;
if (candidate->header.kind == TRK::LF_UNION) {
candidateFwd = candidate->data.LF_UNION.property.fwdref;
candidateName = leafName(candidate->data.LF_UNION.data, unionLeafKind(candidate->data.LF_UNION.data));
} else {
candidateFwd = candidate->data.LF_CLASS.property.fwdref;
candidateName = leafName(candidate->data.LF_CLASS.data,
candidate->data.LF_CLASS.lfEasy.kind);
}
if (!candidateFwd && candidateName && strcmp(candidateName, typeName) == 0) {
defIndex = ti;
break;
}
}
}
}
uint64_t refId = importUDT(defIndex);
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);
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<const TRK*>(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<const TRK*>(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<const TRK*>(elemRec->data.LF_ARRAY.data));
}
}
}
int count = (elemSize > 0) ? static_cast<int>(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
uint32_t utype = rec->data.LF_ENUM.utype;
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;
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;
uint64_t slotSize = 4;
if (underlying < tt->firstIndex()) {
NodeKind k = mapPrimitiveType(underlying);
slotSize = sizeForKind(k);
}
Node n;
n.kind = hexForSize(slotSize);
n.name = name;
n.parentId = parentId;
n.offset = offset;
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<PdbTypeInfo> enumeratePdbTypes(const QString& pdbPath, QString* errorMsg) {
PdbFile pdb;
if (!pdb.open(pdbPath, errorMsg)) return {};
const TypeTable& tt = *pdb.typeTable;
QVector<PdbTypeInfo> 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);
if (!isUDT) continue;
const char* name = nullptr;
uint16_t fieldCount = 0;
bool isUnion = false;
uint64_t size = 0;
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<const TRK*>(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;
result.append(info);
}
return result;
}
// ── Public API: importPdbSelected ──
NodeTree importPdbSelected(const QString& pdbPath,
const QVector<uint32_t>& typeIndices,
QString* errorMsg,
ProgressCb progressCb) {
PdbFile pdb;
if (!pdb.open(pdbPath, errorMsg)) return {};
PdbCtx ctx;
ctx.tt = pdb.typeTable;
int total = typeIndices.size();
for (int i = 0; i < total; i++) {
ctx.importUDT(typeIndices[i]);
if (progressCb && !progressCb(i + 1, total)) {
if (errorMsg) *errorMsg = QStringLiteral("Import cancelled");
return ctx.tree; // return partial result
}
}
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<PdbTypeInfo> enumeratePdbTypes(const QString&, QString* errorMsg) {
if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows");
return {};
}
NodeTree importPdbSelected(const QString&, const QVector<uint32_t>&,
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

34
src/imports/import_pdb.h Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include "core.h"
#include <QVector>
#include <functional>
namespace rcx {
struct PdbTypeInfo {
uint32_t typeIndex; // TPI type index
QString name; // struct/class/union name
uint64_t size; // sizeof in bytes
int childCount; // direct member count
bool isUnion; // union vs struct/class
};
// Phase 1: Enumerate all UDT types in the PDB (fast scan, no recursive import).
QVector<PdbTypeInfo> enumeratePdbTypes(const QString& pdbPath,
QString* errorMsg = nullptr);
// Phase 2: Import selected types with full recursive child types.
// progressCb is called with (current, total) for each top-level type;
// return false from the callback to cancel the import.
using ProgressCb = std::function<bool(int current, int total)>;
NodeTree importPdbSelected(const QString& pdbPath,
const QVector<uint32_t>& typeIndices,
QString* errorMsg = nullptr,
ProgressCb progressCb = {});
// Legacy single-call API: import one struct by name (or all if filter empty).
NodeTree importPdb(const QString& pdbPath,
const QString& structFilter = {},
QString* errorMsg = nullptr);
} // namespace rcx

View File

@@ -0,0 +1,184 @@
#include "import_pdb_dialog.h"
#include "import_pdb.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QCheckBox>
#include <QListWidget>
#include <QLabel>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QApplication>
namespace rcx {
PdbImportDialog::PdbImportDialog(QWidget* parent)
: QDialog(parent)
{
setWindowTitle("Import from PDB");
resize(520, 480);
auto* layout = new QVBoxLayout(this);
// PDB path row
auto* pathRow = new QHBoxLayout;
pathRow->addWidget(new QLabel("PDB File:"));
m_pathEdit = new QLineEdit;
m_pathEdit->setPlaceholderText("Select a PDB file...");
pathRow->addWidget(m_pathEdit);
m_browseBtn = new QPushButton("...");
m_browseBtn->setFixedWidth(32);
pathRow->addWidget(m_browseBtn);
layout->addLayout(pathRow);
// Filter row
auto* filterRow = new QHBoxLayout;
filterRow->addWidget(new QLabel("Filter:"));
m_filterEdit = new QLineEdit;
m_filterEdit->setPlaceholderText("Type name filter...");
m_filterEdit->setEnabled(false);
filterRow->addWidget(m_filterEdit);
layout->addLayout(filterRow);
// Select all checkbox
m_selectAll = new QCheckBox("Select All");
m_selectAll->setEnabled(false);
layout->addWidget(m_selectAll);
// Type list
m_typeList = new QListWidget;
m_typeList->setEnabled(false);
layout->addWidget(m_typeList);
// Count label
m_countLabel = new QLabel("No PDB loaded");
layout->addWidget(m_countLabel);
// Buttons
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
m_buttons->button(QDialogButtonBox::Ok)->setText("Import");
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(false);
layout->addWidget(m_buttons);
connect(m_browseBtn, &QPushButton::clicked, this, &PdbImportDialog::browsePdb);
connect(m_pathEdit, &QLineEdit::returnPressed, this, &PdbImportDialog::loadPdb);
connect(m_filterEdit, &QLineEdit::textChanged, this, &PdbImportDialog::filterChanged);
connect(m_selectAll, &QCheckBox::toggled, this, &PdbImportDialog::selectAllToggled);
connect(m_typeList, &QListWidget::itemChanged, this, &PdbImportDialog::updateSelectionCount);
connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
QString PdbImportDialog::pdbPath() const {
return m_pathEdit->text();
}
QVector<uint32_t> PdbImportDialog::selectedTypeIndices() const {
QVector<uint32_t> result;
for (int i = 0; i < m_typeList->count(); i++) {
auto* item = m_typeList->item(i);
if (item->checkState() == Qt::Checked) {
uint32_t typeIndex = item->data(Qt::UserRole).toUInt();
result.append(typeIndex);
}
}
return result;
}
void PdbImportDialog::browsePdb() {
QString path = QFileDialog::getOpenFileName(this,
"Select PDB File", {},
"PDB Files (*.pdb);;All Files (*)");
if (path.isEmpty()) return;
m_pathEdit->setText(path);
loadPdb();
}
void PdbImportDialog::loadPdb() {
QString path = m_pathEdit->text();
if (path.isEmpty()) return;
m_typeList->clear();
m_allTypes.clear();
m_countLabel->setText("Loading...");
m_typeList->setEnabled(false);
m_filterEdit->setEnabled(false);
m_selectAll->setEnabled(false);
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(false);
QApplication::processEvents();
QString error;
QVector<PdbTypeInfo> types = enumeratePdbTypes(path, &error);
if (types.isEmpty()) {
m_countLabel->setText(error.isEmpty() ? "No types found" : error);
return;
}
m_allTypes.reserve(types.size());
for (const auto& t : types) {
TypeItem item;
item.typeIndex = t.typeIndex;
item.name = t.name;
item.childCount = t.childCount;
item.isUnion = t.isUnion;
m_allTypes.append(item);
}
// Sort by name
std::sort(m_allTypes.begin(), m_allTypes.end(),
[](const TypeItem& a, const TypeItem& b) { return a.name < b.name; });
m_filterEdit->setEnabled(true);
m_selectAll->setEnabled(true);
m_typeList->setEnabled(true);
populateList();
}
void PdbImportDialog::populateList() {
m_typeList->blockSignals(true);
m_typeList->clear();
QString filter = m_filterEdit->text();
bool selectAll = m_selectAll->isChecked();
for (const auto& t : m_allTypes) {
if (!filter.isEmpty() && !t.name.contains(filter, Qt::CaseInsensitive))
continue;
QString label = QStringLiteral("%1 (%2 fields)")
.arg(t.name).arg(t.childCount);
auto* item = new QListWidgetItem(label, m_typeList);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(selectAll ? Qt::Checked : Qt::Unchecked);
item->setData(Qt::UserRole, t.typeIndex);
}
m_typeList->blockSignals(false);
updateSelectionCount();
}
void PdbImportDialog::filterChanged(const QString&) {
populateList();
}
void PdbImportDialog::selectAllToggled(bool) {
populateList();
}
void PdbImportDialog::updateSelectionCount() {
int checked = 0;
int total = m_typeList->count();
for (int i = 0; i < total; i++) {
if (m_typeList->item(i)->checkState() == Qt::Checked)
checked++;
}
m_countLabel->setText(QStringLiteral("%1 of %2 types selected")
.arg(checked).arg(m_allTypes.size()));
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(checked > 0);
}
} // namespace rcx

View File

@@ -0,0 +1,53 @@
#pragma once
#include <QDialog>
#include <QVector>
#include <cstdint>
class QLineEdit;
class QCheckBox;
class QListWidget;
class QLabel;
class QDialogButtonBox;
class QPushButton;
namespace rcx {
struct PdbTypeInfo;
class PdbImportDialog : public QDialog {
Q_OBJECT
public:
explicit PdbImportDialog(QWidget* parent = nullptr);
QString pdbPath() const;
QVector<uint32_t> selectedTypeIndices() const;
private slots:
void browsePdb();
void loadPdb();
void filterChanged(const QString& text);
void selectAllToggled(bool checked);
void updateSelectionCount();
private:
QLineEdit* m_pathEdit;
QPushButton* m_browseBtn;
QLineEdit* m_filterEdit;
QCheckBox* m_selectAll;
QListWidget* m_typeList;
QLabel* m_countLabel;
QDialogButtonBox* m_buttons;
struct TypeItem {
uint32_t typeIndex;
QString name;
int childCount;
bool isUnion;
};
QVector<TypeItem> m_allTypes;
void populateList();
};
} // namespace rcx

View File

@@ -1,9 +1,11 @@
#include "mainwindow.h"
#include "providerregistry.h"
#include "generator.h"
#include "import_reclass_xml.h"
#include "import_source.h"
#include "export_reclass_xml.h"
#include "imports/import_reclass_xml.h"
#include "imports/import_source.h"
#include "imports/export_reclass_xml.h"
#include "imports/import_pdb.h"
#include "imports/import_pdb_dialog.h"
#include "mcp/mcp_bridge.h"
#include <QApplication>
#include <QMainWindow>
@@ -41,6 +43,7 @@
#include <QDialogButtonBox>
#include <QVBoxLayout>
#include <QDialog>
#include <QProgressDialog>
#include <Qsci/qsciscintilla.h>
#include <Qsci/qscilexercpp.h>
#include <QProxyStyle>
@@ -431,6 +434,7 @@ void MainWindow::createMenus() {
Qt5Qt6AddAction(file, "Export ReClass &XML...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportReclassXmlAction);
Qt5Qt6AddAction(file, "Import from &Source...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::importFromSource);
Qt5Qt6AddAction(file, "&Import ReClass XML...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::importReclassXml);
Qt5Qt6AddAction(file, "Import &PDB...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::importPdb);
// Examples submenu — scan once at init
{
QDir exDir(QCoreApplication::applicationDirPath() + "/examples");
@@ -1804,6 +1808,56 @@ void MainWindow::importFromSource() {
m_statusLabel->setText(QStringLiteral("Imported %1 classes from source").arg(classCount));
}
// ── Import PDB ──
void MainWindow::importPdb() {
rcx::PdbImportDialog dlg(this);
if (dlg.exec() != QDialog::Accepted) return;
QString pdbPath = dlg.pdbPath();
QVector<uint32_t> indices = dlg.selectedTypeIndices();
if (indices.isEmpty()) return;
QProgressDialog progress("Importing types...", "Cancel", 0, indices.size(), this);
progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(200);
bool cancelled = false;
QString error;
NodeTree tree = rcx::importPdbSelected(pdbPath, indices, &error,
[&](int current, int total) -> bool {
progress.setMaximum(total);
progress.setValue(current);
QApplication::processEvents();
if (progress.wasCanceled()) {
cancelled = true;
return false;
}
return true;
});
progress.close();
if (tree.nodes.isEmpty()) {
if (!cancelled)
QMessageBox::warning(this, "Import Failed", error.isEmpty()
? QStringLiteral("No types imported") : error);
return;
}
int classCount = 0;
for (const auto& n : tree.nodes)
if (n.parentId == 0 && n.kind == rcx::NodeKind::Struct) classCount++;
auto* doc = new rcx::RcxDocument(this);
doc->tree = std::move(tree);
m_mdiArea->closeAllSubWindows();
createTab(doc);
rebuildWorkspaceModel();
m_statusLabel->setText(QStringLiteral("Imported %1 classes from %2")
.arg(classCount).arg(QFileInfo(pdbPath).fileName()));
}
// ── Type Aliases Dialog ──
void MainWindow::showTypeAliasesDialog() {

View File

@@ -53,6 +53,7 @@ private slots:
void exportReclassXmlAction();
void importFromSource();
void importReclassXml();
void importPdb();
void showTypeAliasesDialog();
void editTheme();
void showOptionsDialog();