mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
fix: narrow inline editor selection for pointer values, resolve correct write address
resolvedSpanFor() now applies narrowPtrValueSpan() to trim the "// Module+offset" symbol comment from the editable span, matching hitTestTarget(). Previously the full value column text was selected, making the parser fail on commit (toULongLong rejected the non-hex suffix), so pointer value saves were silently no-ops. With the parse now succeeding, a second bug was exposed: setNodeValue() computed write addresses via computeOffset() which sums tree offsets without dereferencing pointers. For nodes inside expanded pointer targets (e.g. VTable entries), this wrote to struct_base+child_offset instead of *ptr_value+child_offset, causing an access violation crash. The fix passes lm->offsetAddr (the compose-resolved absolute address) through the inlineEditCommitted signal so setNodeValue() uses the correct dereferenced address.
This commit is contained in:
@@ -230,7 +230,8 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
|
|
||||||
// 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,
|
||||||
|
uint64_t resolvedAddr) {
|
||||||
// CommandRow BaseAddress/Source/RootClass edit has nodeIdx=-1
|
// CommandRow BaseAddress/Source/RootClass edit has nodeIdx=-1
|
||||||
if (nodeIdx < 0 && target != EditTarget::BaseAddress && target != EditTarget::Source
|
if (nodeIdx < 0 && target != EditTarget::BaseAddress && target != EditTarget::Source
|
||||||
&& target != EditTarget::RootClassType && target != EditTarget::RootClassName) { refresh(); return; }
|
&& target != EditTarget::RootClassType && target != EditTarget::RootClassName) { refresh(); return; }
|
||||||
@@ -241,7 +242,7 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
const Node& node = m_doc->tree.nodes[nodeIdx];
|
const Node& node = m_doc->tree.nodes[nodeIdx];
|
||||||
// ASCII edit on Hex nodes
|
// ASCII edit on Hex nodes
|
||||||
if (isHexPreview(node.kind)) {
|
if (isHexPreview(node.kind)) {
|
||||||
setNodeValue(nodeIdx, subLine, text, /*isAscii=*/true);
|
setNodeValue(nodeIdx, subLine, text, /*isAscii=*/true, resolvedAddr);
|
||||||
} else {
|
} else {
|
||||||
renameNode(nodeIdx, text);
|
renameNode(nodeIdx, text);
|
||||||
}
|
}
|
||||||
@@ -311,7 +312,7 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EditTarget::Value:
|
case EditTarget::Value:
|
||||||
setNodeValue(nodeIdx, subLine, text);
|
setNodeValue(nodeIdx, subLine, text, /*isAscii=*/false, resolvedAddr);
|
||||||
break;
|
break;
|
||||||
case EditTarget::BaseAddress: {
|
case EditTarget::BaseAddress: {
|
||||||
QString s = text.trimmed();
|
QString s = text.trimmed();
|
||||||
@@ -1023,14 +1024,22 @@ void RcxController::applyCommand(const Command& command, bool isUndo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RcxController::setNodeValue(int nodeIdx, int subLine, const QString& text,
|
void RcxController::setNodeValue(int nodeIdx, int subLine, const QString& text,
|
||||||
bool isAscii) {
|
bool isAscii, uint64_t resolvedAddr) {
|
||||||
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
if (nodeIdx < 0 || nodeIdx >= m_doc->tree.nodes.size()) return;
|
||||||
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];
|
||||||
int64_t signedAddr = m_doc->tree.computeOffset(nodeIdx);
|
|
||||||
if (signedAddr < 0) return; // malformed tree: negative offset
|
// Use the compose-resolved address when available (correct for pointer children).
|
||||||
uint64_t addr = m_doc->tree.baseAddress + static_cast<uint64_t>(signedAddr);
|
// Fall back to tree.baseAddress + computeOffset for callers that don't supply it.
|
||||||
|
uint64_t addr;
|
||||||
|
if (resolvedAddr != 0) {
|
||||||
|
addr = resolvedAddr;
|
||||||
|
} else {
|
||||||
|
int64_t signedAddr = m_doc->tree.computeOffset(nodeIdx);
|
||||||
|
if (signedAddr < 0) return; // malformed tree: negative offset
|
||||||
|
addr = m_doc->tree.baseAddress + 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;
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ public:
|
|||||||
void removeNode(int nodeIdx);
|
void removeNode(int nodeIdx);
|
||||||
void toggleCollapse(int nodeIdx);
|
void toggleCollapse(int nodeIdx);
|
||||||
void materializeRefChildren(int nodeIdx);
|
void materializeRefChildren(int nodeIdx);
|
||||||
void setNodeValue(int nodeIdx, int subLine, const QString& text, bool isAscii = false);
|
void setNodeValue(int nodeIdx, int subLine, const QString& text,
|
||||||
|
bool isAscii = false, uint64_t resolvedAddr = 0);
|
||||||
void duplicateNode(int nodeIdx);
|
void duplicateNode(int nodeIdx);
|
||||||
void convertToTypedPointer(uint64_t nodeId);
|
void convertToTypedPointer(uint64_t nodeId);
|
||||||
void splitHexNode(uint64_t nodeId);
|
void splitHexNode(uint64_t nodeId);
|
||||||
|
|||||||
@@ -488,8 +488,10 @@ RcxEditor::RcxEditor(QWidget* parent) : QWidget(parent) {
|
|||||||
if (id == 1 && (m_editState.target == EditTarget::Type
|
if (id == 1 && (m_editState.target == EditTarget::Type
|
||||||
|| m_editState.target == EditTarget::ArrayElementType
|
|| m_editState.target == EditTarget::ArrayElementType
|
||||||
|| m_editState.target == EditTarget::PointerTarget)) {
|
|| m_editState.target == EditTarget::PointerTarget)) {
|
||||||
|
const LineMeta* lm = metaForLine(m_editState.line);
|
||||||
|
uint64_t addr = lm ? lm->offsetAddr : 0;
|
||||||
auto info = endInlineEdit();
|
auto info = endInlineEdit();
|
||||||
emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text);
|
emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text, addr);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1515,7 +1517,8 @@ bool RcxEditor::resolvedSpanFor(int line, EditTarget t,
|
|||||||
switch (t) {
|
switch (t) {
|
||||||
case EditTarget::Type: s = typeSpan(*lm, typeW); break;
|
case EditTarget::Type: s = typeSpan(*lm, typeW); break;
|
||||||
case EditTarget::Name: s = nameSpan(*lm, typeW, nameW); break;
|
case EditTarget::Name: s = nameSpan(*lm, typeW, nameW); break;
|
||||||
case EditTarget::Value: s = valueSpan(*lm, textLen, typeW, nameW); break;
|
case EditTarget::Value: s = narrowPtrValueSpan(*lm,
|
||||||
|
valueSpan(*lm, textLen, typeW, nameW), lineText); break;
|
||||||
case EditTarget::BaseAddress: break; // No longer on header lines
|
case EditTarget::BaseAddress: break; // No longer on header lines
|
||||||
case EditTarget::ArrayIndex:
|
case EditTarget::ArrayIndex:
|
||||||
case EditTarget::ArrayCount:
|
case EditTarget::ArrayCount:
|
||||||
@@ -2368,8 +2371,12 @@ void RcxEditor::commitInlineEdit() {
|
|||||||
if (m_editState.target == EditTarget::Type && editedText.isEmpty())
|
if (m_editState.target == EditTarget::Type && editedText.isEmpty())
|
||||||
editedText = m_editState.original;
|
editedText = m_editState.original;
|
||||||
|
|
||||||
|
// Grab resolved address from LineMeta before endInlineEdit clears state
|
||||||
|
const LineMeta* lm = metaForLine(m_editState.line);
|
||||||
|
uint64_t addr = lm ? lm->offsetAddr : 0;
|
||||||
|
|
||||||
auto info = endInlineEdit();
|
auto info = endInlineEdit();
|
||||||
emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, editedText);
|
emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, editedText, addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Cancel inline edit ──
|
// ── Cancel inline edit ──
|
||||||
@@ -2469,13 +2476,15 @@ void RcxEditor::showSourcePicker() {
|
|||||||
|
|
||||||
QAction* sel = menu.exec(pos);
|
QAction* sel = menu.exec(pos);
|
||||||
if (sel) {
|
if (sel) {
|
||||||
|
const LineMeta* lm = metaForLine(m_editState.line);
|
||||||
|
uint64_t addr = lm ? lm->offsetAddr : 0;
|
||||||
auto info = endInlineEdit();
|
auto info = endInlineEdit();
|
||||||
QString text = sel->text();
|
QString text = sel->text();
|
||||||
if (sel->data().toString() == QStringLiteral("#clear"))
|
if (sel->data().toString() == QStringLiteral("#clear"))
|
||||||
text = QStringLiteral("#clear");
|
text = QStringLiteral("#clear");
|
||||||
else if (sel->data().isValid())
|
else if (sel->data().isValid())
|
||||||
text = QStringLiteral("#saved:") + QString::number(sel->data().toInt());
|
text = QStringLiteral("#saved:") + QString::number(sel->data().toInt());
|
||||||
emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text);
|
emit inlineEditCommitted(info.nodeIdx, info.subLine, info.target, text, addr);
|
||||||
} else {
|
} else {
|
||||||
cancelInlineEdit();
|
cancelInlineEdit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ signals:
|
|||||||
void keywordConvertRequested(const QString& newKeyword);
|
void keywordConvertRequested(const QString& newKeyword);
|
||||||
void nodeClicked(int line, uint64_t nodeId, Qt::KeyboardModifiers mods);
|
void nodeClicked(int line, uint64_t nodeId, Qt::KeyboardModifiers mods);
|
||||||
void inlineEditCommitted(int nodeIdx, int subLine,
|
void inlineEditCommitted(int nodeIdx, int subLine,
|
||||||
EditTarget target, const QString& text);
|
EditTarget target, const QString& text,
|
||||||
|
uint64_t resolvedAddr = 0);
|
||||||
void inlineEditCancelled();
|
void inlineEditCancelled();
|
||||||
void typeSelectorRequested();
|
void typeSelectorRequested();
|
||||||
void typePickerRequested(EditTarget target, int nodeIdx, QPoint globalPos);
|
void typePickerRequested(EditTarget target, int nodeIdx, QPoint globalPos);
|
||||||
|
|||||||
Reference in New Issue
Block a user