diff --git a/README.md b/README.md index 0e55677..b1892c7 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ Built with C++17, Qt 6 (Qt 5 also supported), and QScintilla. The entire editor ## Screenshots +![Base address tooltip with expression cheat sheet](docs/README_PIC5.png) + +![Data source picker with saved sources](docs/README_PIC4.png) + ![Windows — VTable with value history popup](docs/README_PIC1.png) ![macOS — project tree with kernel struct inspection](docs/README_PIC2.png) diff --git a/docs/README_PIC4.png b/docs/README_PIC4.png new file mode 100644 index 0000000..9f98361 Binary files /dev/null and b/docs/README_PIC4.png differ diff --git a/docs/README_PIC5.png b/docs/README_PIC5.png new file mode 100644 index 0000000..ae0a94f Binary files /dev/null and b/docs/README_PIC5.png differ diff --git a/src/main.cpp b/src/main.cpp index b15e346..c2e2cde 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4433,6 +4433,10 @@ void MainWindow::createWorkspaceDock() { ? QStringLiteral("struct") : QStringLiteral("class"); tab.doc->undoStack.push(new rcx::RcxCommand(tab.ctrl, rcx::cmd::ChangeClassKeyword{item.structId, item.keyword, newKw})); + // Sync all dock titles that share this document + for (auto it = m_tabs.begin(); it != m_tabs.end(); ++it) + if (it->doc == tab.doc) + it.key()->setWindowTitle(tabTitle(*it)); rebuildWorkspaceModel(); } else if (chosen && chosen == actPin) { @@ -4839,6 +4843,94 @@ void MainWindow::createSymbolsDock() { styleTree(m_modulesTree); m_modulesTree->setExpandsOnDoubleClick(false); + // Double-click: set base address and trigger PDB import + connect(m_modulesTree, &QTreeView::doubleClicked, this, [this](const QModelIndex& idx) { + auto* item = m_modulesModel->itemFromIndex(idx); + if (!item) return; + + uint64_t base = item->data(Qt::UserRole).toULongLong(); + QString name = item->data(Qt::UserRole + 1).toString(); + QString fullPath = item->data(Qt::UserRole + 2).toString(); + + auto* ctrl = activeController(); + if (!ctrl) return; + + // Set base address + ctrl->document()->tree.baseAddress = base; + ctrl->document()->tree.baseAddressFormula.clear(); + ctrl->resetChangeTracking(); + ctrl->refresh(); + + // Already have symbols for this module? + QString canonical = rcx::SymbolStore::instance().resolveAlias(name); + if (rcx::SymbolStore::instance().moduleData(canonical)) { + setAppStatus(QStringLiteral("Base set to %1 (0x%2) — symbols already loaded") + .arg(name).arg(base, 0, 16)); + return; + } + + // Try to load symbols: local PDB → cache → download + auto prov = ctrl->document()->provider; + if (!prov) return; + + auto info = rcx::extractPdbDebugInfo(*prov, base); + if (!info.valid) { + setAppStatus(QStringLiteral("Base set to %1 (0x%2) — no debug info") + .arg(name).arg(base, 0, 16)); + return; + } + + // Helper to load a PDB file into the symbol store + auto loadPdb = [this, name](const QString& pdbPath) -> bool { + QString symErr; + auto result = rcx::extractPdbSymbols(pdbPath, &symErr); + if (result.symbols.isEmpty()) return false; + QVector> pairs; + pairs.reserve(result.symbols.size()); + for (const auto& s : result.symbols) + pairs.append({s.name, s.rva}); + int count = rcx::SymbolStore::instance().addModule( + result.moduleName, pdbPath, pairs); + setAppStatus(QStringLiteral("Loaded %1 symbols for %2").arg(count).arg(name)); + rebuildSymbolsModel(); + if (auto* c = activeController()) c->refresh(); + return true; + }; + + // Check local + QString localPdb = rcx::SymbolDownloader::findLocal(fullPath, info.pdbName); + if (!localPdb.isEmpty() && loadPdb(localPdb)) return; + + // Check cache + if (!m_symDownloader) + m_symDownloader = new rcx::SymbolDownloader(this); + rcx::SymbolDownloader::DownloadRequest req; + req.moduleName = name; + req.pdbName = info.pdbName; + req.guidString = info.guidString; + req.age = info.age; + + QString cached = m_symDownloader->findCached(req); + if (!cached.isEmpty() && loadPdb(cached)) return; + + // Download + setAppStatus(QStringLiteral("Downloading symbols for %1...").arg(name)); + + // One-shot connection for this download + auto conn = std::make_shared(); + *conn = connect(m_symDownloader, &rcx::SymbolDownloader::finished, + this, [this, conn, loadPdb](const QString& mod, const QString& localPath, + bool success, const QString& error) { + disconnect(*conn); + if (!success) { + setAppStatus(QStringLiteral("Failed to download %1: %2").arg(mod, error)); + return; + } + loadPdb(localPath); + }); + m_symDownloader->download(req); + }); + // Context menu for modules m_modulesTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_modulesTree, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {