mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Fix computeDataExtent undercount, setNodeValue signed offset, and dead isStringArray
- computeDataExtent: use structSpan() for Struct/Array nodes instead of byteSize() which returns 0 for Array-of-Struct; use int64_t intermediates to prevent truncation of offsets beyond 2GB - setNodeValue: guard against negative computeOffset results before casting to uint64_t (prevents wrapping to huge addresses on malformed trees) - isStringArray: comment out unused method (was checking UInt8/UInt16 instead of UTF8/UTF16); corrected version preserved in comment
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "typeselectorpopup.h"
|
#include "typeselectorpopup.h"
|
||||||
#include "providers/process_provider.h"
|
|
||||||
#include "providerregistry.h"
|
#include "providerregistry.h"
|
||||||
#include "processpicker.h"
|
|
||||||
#include <Qsci/qsciscintilla.h>
|
#include <Qsci/qsciscintilla.h>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
@@ -18,9 +16,7 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QtConcurrent/QtConcurrentRun>
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
#ifdef _WIN32
|
#include <limits>
|
||||||
#include <psapi.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace rcx {
|
namespace rcx {
|
||||||
|
|
||||||
@@ -204,12 +200,18 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
handleNodeClick(editor, line, nodeId, mods);
|
handleNodeClick(editor, line, nodeId, mods);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Type selector popup
|
// Type selector popup (command row chevron)
|
||||||
connect(editor, &RcxEditor::typeSelectorRequested,
|
connect(editor, &RcxEditor::typeSelectorRequested,
|
||||||
this, [this, editor]() {
|
this, [this, editor]() {
|
||||||
showTypeSelectorPopup(editor);
|
showTypeSelectorPopup(editor);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Type picker popup (array element type / pointer target)
|
||||||
|
connect(editor, &RcxEditor::typePickerRequested,
|
||||||
|
this, [this, editor](EditTarget target, int nodeIdx, QPoint globalPos) {
|
||||||
|
showTypePickerPopup(editor, target, nodeIdx, globalPos);
|
||||||
|
});
|
||||||
|
|
||||||
// Inline editing signals
|
// Inline editing signals
|
||||||
connect(editor, &RcxEditor::inlineEditCommitted,
|
connect(editor, &RcxEditor::inlineEditCommitted,
|
||||||
this, [this](int nodeIdx, int subLine, EditTarget target, const QString& text) {
|
this, [this](int nodeIdx, int subLine, EditTarget target, const QString& text) {
|
||||||
@@ -374,45 +376,6 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (text == QStringLiteral("process")) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
auto* w = qobject_cast<QWidget*>(parent());
|
|
||||||
ProcessPicker picker(w);
|
|
||||||
if (picker.exec() == QDialog::Accepted) {
|
|
||||||
// Save current source's base address before switching
|
|
||||||
if (m_activeSourceIdx >= 0 && m_activeSourceIdx < m_savedSources.size())
|
|
||||||
m_savedSources[m_activeSourceIdx].baseAddress = m_doc->tree.baseAddress;
|
|
||||||
|
|
||||||
uint32_t pid = picker.selectedProcessId();
|
|
||||||
QString procName = picker.selectedProcessName();
|
|
||||||
attachToProcess(pid, procName);
|
|
||||||
|
|
||||||
// Check if this process is already saved
|
|
||||||
int existingIdx = -1;
|
|
||||||
for (int i = 0; i < m_savedSources.size(); i++) {
|
|
||||||
if (m_savedSources[i].kind == QStringLiteral("Process")
|
|
||||||
&& m_savedSources[i].pid == pid) {
|
|
||||||
existingIdx = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (existingIdx >= 0) {
|
|
||||||
m_activeSourceIdx = existingIdx;
|
|
||||||
m_savedSources[existingIdx].baseAddress = m_doc->tree.baseAddress;
|
|
||||||
} else {
|
|
||||||
SavedSourceEntry entry;
|
|
||||||
entry.kind = QStringLiteral("Process");
|
|
||||||
entry.displayName = procName;
|
|
||||||
entry.pid = pid;
|
|
||||||
entry.processName = procName;
|
|
||||||
entry.baseAddress = m_doc->tree.baseAddress;
|
|
||||||
m_savedSources.append(entry);
|
|
||||||
m_activeSourceIdx = m_savedSources.size() - 1;
|
|
||||||
}
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Look up provider in registry
|
// Look up provider in registry
|
||||||
@@ -447,13 +410,41 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
|
|
||||||
// Apply provider or show error
|
// Apply provider or show error
|
||||||
if (provider) {
|
if (provider) {
|
||||||
|
// Save current source's base address before switching
|
||||||
|
if (m_activeSourceIdx >= 0 && m_activeSourceIdx < m_savedSources.size())
|
||||||
|
m_savedSources[m_activeSourceIdx].baseAddress = m_doc->tree.baseAddress;
|
||||||
|
|
||||||
uint64_t newBase = provider->base();
|
uint64_t newBase = provider->base();
|
||||||
|
QString displayName = provider->name();
|
||||||
m_doc->undoStack.clear();
|
m_doc->undoStack.clear();
|
||||||
m_doc->provider = std::move(provider);
|
m_doc->provider = std::move(provider);
|
||||||
m_doc->dataPath.clear();
|
m_doc->dataPath.clear();
|
||||||
m_doc->tree.baseAddress = newBase;
|
m_doc->tree.baseAddress = newBase;
|
||||||
resetSnapshot();
|
resetSnapshot();
|
||||||
emit m_doc->documentChanged();
|
emit m_doc->documentChanged();
|
||||||
|
|
||||||
|
// Save as a source for quick-switch
|
||||||
|
QString identifier = providerInfo->identifier;
|
||||||
|
int existingIdx = -1;
|
||||||
|
for (int i = 0; i < m_savedSources.size(); i++) {
|
||||||
|
if (m_savedSources[i].kind == identifier
|
||||||
|
&& m_savedSources[i].providerTarget == target) {
|
||||||
|
existingIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (existingIdx >= 0) {
|
||||||
|
m_activeSourceIdx = existingIdx;
|
||||||
|
m_savedSources[existingIdx].baseAddress = m_doc->tree.baseAddress;
|
||||||
|
} else {
|
||||||
|
SavedSourceEntry entry;
|
||||||
|
entry.kind = identifier;
|
||||||
|
entry.displayName = displayName;
|
||||||
|
entry.providerTarget = target;
|
||||||
|
entry.baseAddress = m_doc->tree.baseAddress;
|
||||||
|
m_savedSources.append(entry);
|
||||||
|
m_activeSourceIdx = m_savedSources.size() - 1;
|
||||||
|
}
|
||||||
refresh();
|
refresh();
|
||||||
} else if (!errorMsg.isEmpty()) {
|
} else if (!errorMsg.isEmpty()) {
|
||||||
QMessageBox::warning(qobject_cast<QWidget*>(parent()), "Provider Error", errorMsg);
|
QMessageBox::warning(qobject_cast<QWidget*>(parent()), "Provider Error", errorMsg);
|
||||||
@@ -917,7 +908,9 @@ void RcxController::setNodeValue(int nodeIdx, int subLine, const QString& text,
|
|||||||
if (!m_doc->provider->isWritable()) return;
|
if (!m_doc->provider->isWritable()) return;
|
||||||
|
|
||||||
const Node& node = m_doc->tree.nodes[nodeIdx];
|
const Node& node = m_doc->tree.nodes[nodeIdx];
|
||||||
uint64_t addr = m_doc->tree.computeOffset(nodeIdx);
|
int64_t signedAddr = m_doc->tree.computeOffset(nodeIdx);
|
||||||
|
if (signedAddr < 0) return; // malformed tree: negative offset
|
||||||
|
uint64_t addr = static_cast<uint64_t>(signedAddr);
|
||||||
|
|
||||||
// For vector components, redirect to float parsing at sub-offset
|
// For vector components, redirect to float parsing at sub-offset
|
||||||
NodeKind editKind = node.kind;
|
NodeKind editKind = node.kind;
|
||||||
@@ -1543,7 +1536,7 @@ void RcxController::showTypeSelectorPopup(RcxEditor* editor) {
|
|||||||
popup->setTypes(types, m_viewRootId);
|
popup->setTypes(types, m_viewRootId);
|
||||||
|
|
||||||
connect(popup, &TypeSelectorPopup::typeSelected,
|
connect(popup, &TypeSelectorPopup::typeSelected,
|
||||||
this, [this](uint64_t structId) {
|
this, [this](uint64_t structId, const QString&) {
|
||||||
setViewRootId(structId);
|
setViewRootId(structId);
|
||||||
});
|
});
|
||||||
connect(popup, &TypeSelectorPopup::createNewTypeRequested,
|
connect(popup, &TypeSelectorPopup::createNewTypeRequested,
|
||||||
@@ -1564,51 +1557,169 @@ void RcxController::showTypeSelectorPopup(RcxEditor* editor) {
|
|||||||
popup->popup(pos);
|
popup->popup(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RcxController::attachToProcess(uint32_t pid, const QString& processName) {
|
void RcxController::showTypePickerPopup(RcxEditor* editor, EditTarget target,
|
||||||
#ifdef _WIN32
|
int nodeIdx, QPoint globalPos) {
|
||||||
HANDLE hProc = OpenProcess(
|
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
||||||
PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION
|
const Node& node = m_doc->tree.nodes[nodeIdx];
|
||||||
| PROCESS_QUERY_INFORMATION,
|
|
||||||
FALSE, pid);
|
QVector<TypeEntry> entries;
|
||||||
if (!hProc) {
|
uint64_t currentId = 0;
|
||||||
|
|
||||||
|
// Sentinel range for primitive entries: UINT64_MAX - kind
|
||||||
|
constexpr uint64_t kPrimBase = UINT64_MAX - 256;
|
||||||
|
constexpr uint64_t kNoSelection = UINT64_MAX;
|
||||||
|
currentId = kNoSelection;
|
||||||
|
|
||||||
|
if (target == EditTarget::ArrayElementType) {
|
||||||
|
// Primitive types (unique synthetic id per kind)
|
||||||
|
for (const auto& m : kKindMeta) {
|
||||||
|
if (m.kind == NodeKind::Struct || m.kind == NodeKind::Array
|
||||||
|
|| m.kind == NodeKind::Padding) continue;
|
||||||
|
TypeEntry e;
|
||||||
|
e.id = kPrimBase - (uint64_t)m.kind;
|
||||||
|
e.displayName = QString::fromLatin1(m.typeName);
|
||||||
|
entries.append(e);
|
||||||
|
if (m.kind == node.elementKind)
|
||||||
|
currentId = e.id;
|
||||||
|
}
|
||||||
|
// Struct types
|
||||||
|
for (const auto& n : m_doc->tree.nodes) {
|
||||||
|
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||||
|
TypeEntry e;
|
||||||
|
e.id = n.id;
|
||||||
|
e.displayName = n.structTypeName.isEmpty() ? n.name : n.structTypeName;
|
||||||
|
e.classKeyword = n.resolvedClassKeyword();
|
||||||
|
entries.append(e);
|
||||||
|
if (node.elementKind == NodeKind::Struct && n.id == node.refId)
|
||||||
|
currentId = n.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (target == EditTarget::PointerTarget) {
|
||||||
|
// "void" entry
|
||||||
|
{
|
||||||
|
TypeEntry e;
|
||||||
|
e.id = kPrimBase; // unique sentinel for void
|
||||||
|
e.displayName = QStringLiteral("void");
|
||||||
|
entries.append(e);
|
||||||
|
if (node.refId == 0) currentId = e.id;
|
||||||
|
}
|
||||||
|
// Struct types
|
||||||
|
for (const auto& n : m_doc->tree.nodes) {
|
||||||
|
if (n.parentId == 0 && n.kind == NodeKind::Struct) {
|
||||||
|
TypeEntry e;
|
||||||
|
e.id = n.id;
|
||||||
|
e.displayName = n.structTypeName.isEmpty() ? n.name : n.structTypeName;
|
||||||
|
e.classKeyword = n.resolvedClassKeyword();
|
||||||
|
entries.append(e);
|
||||||
|
if (n.id == node.refId) currentId = n.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Font with zoom
|
||||||
|
QSettings settings("ReclassX", "ReclassX");
|
||||||
|
QString fontName = settings.value("font", "JetBrains Mono").toString();
|
||||||
|
QFont font(fontName, 12);
|
||||||
|
font.setFixedPitch(true);
|
||||||
|
auto* sci = editor->scintilla();
|
||||||
|
int zoom = (int)sci->SendScintilla(QsciScintillaBase::SCI_GETZOOM);
|
||||||
|
font.setPointSize(font.pointSize() + zoom);
|
||||||
|
|
||||||
|
auto* popup = new TypeSelectorPopup(editor);
|
||||||
|
popup->setFont(font);
|
||||||
|
popup->setTitle(target == EditTarget::ArrayElementType
|
||||||
|
? QStringLiteral("Choose element type")
|
||||||
|
: QStringLiteral("Choose pointer target"));
|
||||||
|
popup->setTypes(entries, currentId);
|
||||||
|
|
||||||
|
connect(popup, &TypeSelectorPopup::typeSelected,
|
||||||
|
this, [this, target, nodeIdx](uint64_t id, const QString& displayName) {
|
||||||
|
applyTypePickerResult(target, nodeIdx, id, displayName);
|
||||||
|
});
|
||||||
|
connect(popup, &TypeSelectorPopup::createNewTypeRequested,
|
||||||
|
this, [this, target, nodeIdx]() {
|
||||||
|
Node n;
|
||||||
|
n.kind = NodeKind::Struct;
|
||||||
|
n.name = QString();
|
||||||
|
n.parentId = 0;
|
||||||
|
n.offset = 0;
|
||||||
|
n.id = m_doc->tree.reserveId();
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this, cmd::Insert{n}));
|
||||||
|
applyTypePickerResult(target, nodeIdx, n.id, QString());
|
||||||
|
});
|
||||||
|
connect(popup, &TypeSelectorPopup::dismissed,
|
||||||
|
popup, &QObject::deleteLater);
|
||||||
|
|
||||||
|
popup->popup(globalPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RcxController::applyTypePickerResult(EditTarget target, int nodeIdx,
|
||||||
|
uint64_t selectedId, const QString& displayName) {
|
||||||
|
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
||||||
|
const Node& node = m_doc->tree.nodes[nodeIdx];
|
||||||
|
|
||||||
|
constexpr uint64_t kPrimBase = UINT64_MAX - 256;
|
||||||
|
|
||||||
|
if (target == EditTarget::ArrayElementType) {
|
||||||
|
if (selectedId >= kPrimBase) {
|
||||||
|
// Primitive type — resolve from displayName
|
||||||
|
bool ok;
|
||||||
|
NodeKind elemKind = kindFromTypeName(displayName, &ok);
|
||||||
|
if (ok && elemKind != node.elementKind) {
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangeArrayMeta{node.id,
|
||||||
|
node.elementKind, elemKind,
|
||||||
|
node.arrayLen, node.arrayLen}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Struct type — real node id
|
||||||
|
if (node.elementKind != NodeKind::Struct || node.refId != selectedId) {
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangeArrayMeta{node.id,
|
||||||
|
node.elementKind, NodeKind::Struct,
|
||||||
|
node.arrayLen, node.arrayLen}));
|
||||||
|
if (node.refId != selectedId) {
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangePointerRef{node.id, node.refId, selectedId}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (target == EditTarget::PointerTarget) {
|
||||||
|
// Map void sentinel back to refId 0
|
||||||
|
uint64_t realRefId = (selectedId >= kPrimBase) ? 0 : selectedId;
|
||||||
|
if (realRefId != node.refId) {
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangePointerRef{node.id, node.refId, realRefId}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RcxController::attachViaPlugin(const QString& providerIdentifier, const QString& target) {
|
||||||
|
const auto* info = ProviderRegistry::instance().findProvider(providerIdentifier);
|
||||||
|
if (!info || !info->plugin) {
|
||||||
QMessageBox::warning(qobject_cast<QWidget*>(parent()),
|
QMessageBox::warning(qobject_cast<QWidget*>(parent()),
|
||||||
"Attach Failed",
|
"Provider Error",
|
||||||
QString("Could not open process %1 (PID %2).\n"
|
QString("Provider '%1' not found. Is the plugin loaded?").arg(providerIdentifier));
|
||||||
"Try running as administrator.")
|
|
||||||
.arg(processName).arg(pid));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab main module for initial view region
|
QString errorMsg;
|
||||||
HMODULE hMod = nullptr;
|
auto provider = info->plugin->createProvider(target, &errorMsg);
|
||||||
DWORD needed = 0;
|
if (!provider) {
|
||||||
uint64_t base = 0;
|
if (!errorMsg.isEmpty())
|
||||||
int regionSize = 0x10000;
|
QMessageBox::warning(qobject_cast<QWidget*>(parent()), "Provider Error", errorMsg);
|
||||||
|
return;
|
||||||
if (EnumProcessModulesEx(hProc, &hMod, sizeof(hMod), &needed, LIST_MODULES_ALL)
|
|
||||||
&& hMod)
|
|
||||||
{
|
|
||||||
MODULEINFO mi{};
|
|
||||||
if (GetModuleInformation(hProc, hMod, &mi, sizeof(mi))) {
|
|
||||||
base = (uint64_t)mi.lpBaseOfDll;
|
|
||||||
regionSize = (int)mi.SizeOfImage;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "[AttachProcess]" << processName << "PID" << pid
|
uint64_t newBase = provider->base();
|
||||||
<< "base" << Qt::hex << base << "regionSize" << regionSize;
|
|
||||||
|
|
||||||
m_doc->undoStack.clear();
|
m_doc->undoStack.clear();
|
||||||
m_doc->provider = std::make_shared<ProcessProvider>(
|
m_doc->provider = std::move(provider);
|
||||||
hProc, base, regionSize, processName);
|
|
||||||
m_doc->dataPath.clear();
|
m_doc->dataPath.clear();
|
||||||
m_doc->tree.baseAddress = base;
|
m_doc->tree.baseAddress = newBase;
|
||||||
resetSnapshot();
|
resetSnapshot();
|
||||||
emit m_doc->documentChanged();
|
emit m_doc->documentChanged();
|
||||||
refresh();
|
refresh();
|
||||||
#else
|
|
||||||
Q_UNUSED(pid); Q_UNUSED(processName);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RcxController::switchToSavedSource(int idx) {
|
void RcxController::switchToSavedSource(int idx) {
|
||||||
@@ -1626,10 +1737,9 @@ void RcxController::switchToSavedSource(int idx) {
|
|||||||
m_doc->loadData(entry.filePath);
|
m_doc->loadData(entry.filePath);
|
||||||
m_doc->tree.baseAddress = entry.baseAddress;
|
m_doc->tree.baseAddress = entry.baseAddress;
|
||||||
refresh();
|
refresh();
|
||||||
} else if (entry.kind == QStringLiteral("Process")) {
|
} else if (!entry.providerTarget.isEmpty()) {
|
||||||
#ifdef _WIN32
|
// Plugin-based provider (e.g. "processmemory" with target "pid:name")
|
||||||
attachToProcess(entry.pid, entry.processName);
|
attachViaPlugin(entry.kind, entry.providerTarget);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1725,14 +1835,18 @@ void RcxController::onReadComplete() {
|
|||||||
|
|
||||||
int RcxController::computeDataExtent() const {
|
int RcxController::computeDataExtent() const {
|
||||||
// Prefer tree-based extent: exact bytes needed for rendering
|
// Prefer tree-based extent: exact bytes needed for rendering
|
||||||
int treeExtent = 0;
|
int64_t treeExtent = 0;
|
||||||
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
|
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
|
||||||
|
const Node& node = m_doc->tree.nodes[i];
|
||||||
int64_t off = m_doc->tree.computeOffset(i);
|
int64_t off = m_doc->tree.computeOffset(i);
|
||||||
int sz = m_doc->tree.nodes[i].byteSize();
|
// byteSize() returns 0 for Array-of-Struct/Array; use structSpan() for containers
|
||||||
int end = (int)(off + sz);
|
int sz = (node.kind == NodeKind::Struct || node.kind == NodeKind::Array)
|
||||||
|
? m_doc->tree.structSpan(node.id) : node.byteSize();
|
||||||
|
int64_t end = off + sz;
|
||||||
if (end > treeExtent) treeExtent = end;
|
if (end > treeExtent) treeExtent = end;
|
||||||
}
|
}
|
||||||
if (treeExtent > 0) return treeExtent;
|
// Clamp to max int (readBytes takes int length)
|
||||||
|
if (treeExtent > 0) return (int)qMin(treeExtent, (int64_t)std::numeric_limits<int>::max());
|
||||||
|
|
||||||
// Fallback: provider size (empty tree)
|
// Fallback: provider size (empty tree)
|
||||||
int provSize = m_doc->provider->size();
|
int provSize = m_doc->provider->size();
|
||||||
|
|||||||
26
src/core.h
26
src/core.h
@@ -230,11 +230,12 @@ struct Node {
|
|||||||
return classKeyword.isEmpty() ? QStringLiteral("struct") : classKeyword;
|
return classKeyword.isEmpty() ? QStringLiteral("struct") : classKeyword;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: is this a string-like array (char[] or wchar_t[])?
|
// NOTE: isStringArray() was checking UInt8/UInt16 instead of UTF8/UTF16.
|
||||||
bool isStringArray() const {
|
// Currently unused — commented out until a caller needs it.
|
||||||
return kind == NodeKind::Array &&
|
// bool isStringArray() const {
|
||||||
(elementKind == NodeKind::UInt8 || elementKind == NodeKind::UInt16);
|
// return kind == NodeKind::Array &&
|
||||||
}
|
// (elementKind == NodeKind::UTF8 || elementKind == NodeKind::UTF16);
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── NodeTree ──
|
// ── NodeTree ──
|
||||||
@@ -434,6 +435,7 @@ struct LineMeta {
|
|||||||
int effectiveTypeW = 14; // Per-line type column width used for rendering
|
int effectiveTypeW = 14; // Per-line type column width used for rendering
|
||||||
int effectiveNameW = 22; // Per-line name column width used for rendering
|
int effectiveNameW = 22; // Per-line name column width used for rendering
|
||||||
QString pointerTargetName; // Resolved target type name for Pointer32/64 (empty = "void")
|
QString pointerTargetName; // Resolved target type name for Pointer32/64 (empty = "void")
|
||||||
|
bool isArrayElement = false; // true for synthesized primitive array element lines
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool isSyntheticLine(const LineMeta& lm) {
|
inline bool isSyntheticLine(const LineMeta& lm) {
|
||||||
@@ -674,6 +676,16 @@ inline ColumnSpan arrayElemCountSpanFor(const LineMeta& lm, const QString& lineT
|
|||||||
return {openBracket + 1, closeBracket, true};
|
return {openBracket + 1, closeBracket, true};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Click-area version: includes brackets [N] for hit testing
|
||||||
|
inline ColumnSpan arrayElemCountClickSpanFor(const LineMeta& lm, const QString& lineText) {
|
||||||
|
if (lm.lineKind != LineKind::Header || !lm.isArrayHeader) return {};
|
||||||
|
int ind = kFoldCol + lm.depth * 3;
|
||||||
|
int openBracket = lineText.indexOf('[', ind);
|
||||||
|
int closeBracket = lineText.indexOf(']', openBracket);
|
||||||
|
if (openBracket < 0 || closeBracket < 0 || closeBracket <= openBracket + 1) return {};
|
||||||
|
return {openBracket, closeBracket + 1, true};
|
||||||
|
}
|
||||||
|
|
||||||
// ── Pointer kind/target spans (within type column of pointer fields) ──
|
// ── Pointer kind/target spans (within type column of pointer fields) ──
|
||||||
// Line format: " void* name -> 0x..."
|
// Line format: " void* name -> 0x..."
|
||||||
// pointerTargetSpan covers the target name before '*'
|
// pointerTargetSpan covers the target name before '*'
|
||||||
@@ -760,9 +772,9 @@ namespace fmt {
|
|||||||
QString fmtOffsetMargin(uint64_t absoluteOffset, bool isContinuation, int hexDigits = 8);
|
QString fmtOffsetMargin(uint64_t absoluteOffset, bool isContinuation, int hexDigits = 8);
|
||||||
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType = kColType, int colName = kColName);
|
QString fmtStructHeader(const Node& node, int depth, bool collapsed, int colType = kColType, int colName = kColName);
|
||||||
QString fmtStructFooter(const Node& node, int depth, int totalSize = -1);
|
QString fmtStructFooter(const Node& node, int depth, int totalSize = -1);
|
||||||
QString fmtArrayHeader(const Node& node, int depth, int viewIdx, bool collapsed, int colType = kColType, int colName = kColName);
|
QString fmtArrayHeader(const Node& node, int depth, int viewIdx, bool collapsed, int colType = kColType, int colName = kColName, const QString& elemStructName = {});
|
||||||
QString structTypeName(const Node& node); // Full type string for struct headers
|
QString structTypeName(const Node& node); // Full type string for struct headers
|
||||||
QString arrayTypeName(NodeKind elemKind, int count);
|
QString arrayTypeName(NodeKind elemKind, int count, const QString& structName = {});
|
||||||
QString pointerTypeName(NodeKind kind, const QString& targetName);
|
QString pointerTypeName(NodeKind kind, const QString& targetName);
|
||||||
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
|
QString fmtPointerHeader(const Node& node, int depth, bool collapsed,
|
||||||
const Provider& prov, uint64_t addr,
|
const Provider& prov, uint64_t addr,
|
||||||
|
|||||||
Reference in New Issue
Block a user