#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include "providers/provider.h" #include "providers/buffer_provider.h" #include "providers/null_provider.h" namespace rcx { // ── Node kind enum ── enum class NodeKind : uint8_t { Hex8, Hex16, Hex32, Hex64, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float, Double, Bool, Pointer32, Pointer64, FuncPtr32, FuncPtr64, Vec2, Vec3, Vec4, Mat4x4, UTF8, UTF16, Struct, Array }; } // namespace rcx (temporarily close for qHash) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) inline uint qHash(rcx::NodeKind key, uint seed = 0) { return qHash(static_cast(key), seed); } #endif namespace rcx { // reopen // ── Kind flags (replaces repeated Hex switches) ── enum KindFlags : uint32_t { KF_None = 0, KF_HexPreview = 1 << 0, // Hex8..Hex64 (ASCII+hex layout) KF_Container = 1 << 1, // Struct/Array KF_String = 1 << 2, // UTF8/UTF16 KF_Vector = 1 << 3, // Vec2/3/4 }; // ── Unified kind metadata table (single source of truth) ── struct KindMeta { NodeKind kind; const char* name; // UI/JSON name: "Hex64", "UInt16" const char* typeName; // display name: "Hex64", "uint16_t" int size; // byte size (0 = dynamic: Struct/Array) int lines; // display line count int align; // natural alignment uint32_t flags; // KindFlags bitmask }; inline constexpr KindMeta kKindMeta[] = { // kind name typeName sz ln al flags {NodeKind::Hex8, "Hex8", "hex8", 1, 1, 1, KF_HexPreview}, {NodeKind::Hex16, "Hex16", "hex16", 2, 1, 2, KF_HexPreview}, {NodeKind::Hex32, "Hex32", "hex32", 4, 1, 4, KF_HexPreview}, {NodeKind::Hex64, "Hex64", "hex64", 8, 1, 8, KF_HexPreview}, {NodeKind::Int8, "Int8", "int8_t", 1, 1, 1, KF_None}, {NodeKind::Int16, "Int16", "int16_t", 2, 1, 2, KF_None}, {NodeKind::Int32, "Int32", "int32_t", 4, 1, 4, KF_None}, {NodeKind::Int64, "Int64", "int64_t", 8, 1, 8, KF_None}, {NodeKind::UInt8, "UInt8", "uint8_t", 1, 1, 1, KF_None}, {NodeKind::UInt16, "UInt16", "uint16_t", 2, 1, 2, KF_None}, {NodeKind::UInt32, "UInt32", "uint32_t", 4, 1, 4, KF_None}, {NodeKind::UInt64, "UInt64", "uint64_t", 8, 1, 8, KF_None}, {NodeKind::Float, "Float", "float", 4, 1, 4, KF_None}, {NodeKind::Double, "Double", "double", 8, 1, 8, KF_None}, {NodeKind::Bool, "Bool", "bool", 1, 1, 1, KF_None}, {NodeKind::Pointer32, "Pointer32", "ptr32", 4, 1, 4, KF_None}, {NodeKind::Pointer64, "Pointer64", "ptr64", 8, 1, 8, KF_None}, {NodeKind::FuncPtr32, "FuncPtr32", "fnptr32", 4, 1, 4, KF_None}, {NodeKind::FuncPtr64, "FuncPtr64", "fnptr64", 8, 1, 8, KF_None}, {NodeKind::Vec2, "Vec2", "vec2", 8, 1, 4, KF_Vector}, {NodeKind::Vec3, "Vec3", "vec3", 12, 1, 4, KF_Vector}, {NodeKind::Vec4, "Vec4", "vec4", 16, 1, 4, KF_Vector}, {NodeKind::Mat4x4, "Mat4x4", "mat4x4", 64, 4, 4, KF_None}, {NodeKind::UTF8, "UTF8", "str", 1, 1, 1, KF_String}, {NodeKind::UTF16, "UTF16", "wstr", 2, 1, 2, KF_String}, {NodeKind::Struct, "Struct", "struct", 0, 1, 1, KF_Container}, {NodeKind::Array, "Array", "array", 0, 1, 1, KF_Container}, }; inline constexpr const KindMeta* kindMeta(NodeKind k) { for (const auto& m : kKindMeta) if (m.kind == k) return &m; return nullptr; } inline constexpr int sizeForKind(NodeKind k) { auto* m = kindMeta(k); return m ? m->size : 0; } inline constexpr int linesForKind(NodeKind k) { auto* m = kindMeta(k); return m ? m->lines : 1; } inline constexpr int alignmentFor(NodeKind k) { auto* m = kindMeta(k); return m ? m->align : 1; } inline const char* kindToString(NodeKind k) { auto* m = kindMeta(k); return m ? m->name : "Unknown"; } inline NodeKind kindFromString(const QString& s) { for (const auto& m : kKindMeta) if (s == m.name) return m.kind; return NodeKind::Hex8; } inline NodeKind kindFromTypeName(const QString& s, bool* ok = nullptr) { for (const auto& m : kKindMeta) { if (s == m.typeName) { if (ok) *ok = true; return m.kind; } } if (ok) *ok = false; return NodeKind::Hex8; } inline constexpr uint32_t flagsFor(NodeKind k) { const auto* m = kindMeta(k); return m ? m->flags : 0; } inline constexpr bool isHexNode(NodeKind k) { return k >= NodeKind::Hex8 && k <= NodeKind::Hex64; } inline constexpr bool isHexPreview(NodeKind k) { return isHexNode(k); } inline constexpr bool isVectorKind(NodeKind k) { return k == NodeKind::Vec2 || k == NodeKind::Vec3 || k == NodeKind::Vec4; } inline constexpr bool isMatrixKind(NodeKind k) { return k == NodeKind::Mat4x4; } inline constexpr bool isFuncPtr(NodeKind k) { return k == NodeKind::FuncPtr32 || k == NodeKind::FuncPtr64; } // Hex types, pointer types, function pointers, and containers are not meaningful // primitive-pointer targets — dereferencing them produces the same output as void*. inline constexpr bool isValidPrimitivePtrTarget(NodeKind k) { if (isHexNode(k)) return false; if (k == NodeKind::Pointer32 || k == NodeKind::Pointer64) return false; if (isFuncPtr(k)) return false; if (k == NodeKind::Struct || k == NodeKind::Array) return false; return true; } inline QStringList allTypeNamesForUI(bool /*stripBrackets*/ = false) { QStringList out; out.reserve(std::size(kKindMeta)); for (const auto& m : kKindMeta) out << QString::fromLatin1(m.typeName); return out; } // ── Marker vocabulary ── enum Marker : int { M_CONT = 0, M_PTR0 = 2, M_CYCLE = 3, M_ERR = 4, M_STRUCT_BG = 5, M_HOVER = 6, M_SELECTED = 7, M_CMD_ROW = 8, M_ACCENT = 9, M_FOCUS = 10, // Presentation mode: AI focus glow }; // ── Bitfield member (name + bit position + width within a container) ── struct BitfieldMember { QString name; uint8_t bitOffset = 0; // position from LSB within the container uint8_t bitWidth = 1; // number of bits (1..64) }; // ── Node ── struct Node { uint64_t id = 0; NodeKind kind = NodeKind::Hex8; QString name; QString structTypeName; // Struct/Array: optional type name (e.g., "IMAGE_DOS_HEADER") QString classKeyword; // "struct", "class", or "enum" (empty = "struct") uint64_t parentId = 0; // 0 = root (no parent) int offset = 0; bool isStatic = false; // static field — excluded from struct layout QString offsetExpr; // C/C++ expression → absolute address (static fields only) bool isRelative = false; // Pointer: target = base + value (RVA) instead of absolute int arrayLen = 1; // Array: element count int strLen = 64; bool collapsed = true; uint64_t refId = 0; // Pointer32/64: id of Struct to expand at *ptr NodeKind elementKind = NodeKind::UInt8; // Array: element type; Pointer with ptrDepth>0: target type int ptrDepth = 0; // Pointer: 0=struct/void ptr, 1=primitive*, 2=primitive** int viewIndex = 0; // Array: current view offset (transient) QVector> enumMembers; // Enum: name→value pairs QVector bitfieldMembers; // Bitfield: per-bit member definitions // Note: Returns 0 for Array-of-Struct/Array. Use tree.structSpan() for accurate size. int byteSize() const { switch (kind) { case NodeKind::UTF8: return strLen; case NodeKind::UTF16: return qMin(strLen, INT_MAX / 2) * 2; case NodeKind::Array: { int elemSz = sizeForKind(elementKind); if (elemSz <= 0) return 0; return qMin(arrayLen, INT_MAX / elemSz) * elemSz; } case NodeKind::Struct: if (classKeyword == QStringLiteral("bitfield")) { int sz = sizeForKind(elementKind); return sz > 0 ? sz : 4; } return 0; default: return sizeForKind(kind); } } QJsonObject toJson() const { QJsonObject o; o["id"] = QString::number(id); o["kind"] = kindToString(kind); o["name"] = name; if (!structTypeName.isEmpty()) o["structTypeName"] = structTypeName; if (!classKeyword.isEmpty() && classKeyword != QStringLiteral("struct")) o["classKeyword"] = classKeyword; o["parentId"] = QString::number(parentId); o["offset"] = offset; if (isStatic) o["isStatic"] = true; if (!offsetExpr.isEmpty()) o["offsetExpr"] = offsetExpr; if (isRelative) o["isRelative"] = true; o["arrayLen"] = arrayLen; o["strLen"] = strLen; o["collapsed"] = collapsed; o["refId"] = QString::number(refId); o["elementKind"] = kindToString(elementKind); if (ptrDepth > 0) o["ptrDepth"] = ptrDepth; if (!enumMembers.isEmpty()) { QJsonArray arr; for (const auto& m : enumMembers) { QJsonObject em; em["name"] = m.first; em["value"] = QString::number(m.second); arr.append(em); } o["enumMembers"] = arr; } if (!bitfieldMembers.isEmpty()) { QJsonArray arr; for (const auto& m : bitfieldMembers) { QJsonObject bm; bm["name"] = m.name; bm["bitOffset"] = m.bitOffset; bm["bitWidth"] = m.bitWidth; arr.append(bm); } o["bitfieldMembers"] = arr; } return o; } static Node fromJson(const QJsonObject& o) { Node n; n.id = o["id"].toString("0").toULongLong(); n.kind = kindFromString(o["kind"].toString()); n.name = o["name"].toString(); n.structTypeName = o["structTypeName"].toString(); n.classKeyword = o["classKeyword"].toString(); n.parentId = o["parentId"].toString("0").toULongLong(); n.offset = o["offset"].toInt(0); n.isStatic = o["isStatic"].toBool(o["isHelper"].toBool(false)); n.offsetExpr = o["offsetExpr"].toString(); n.isRelative = o["isRelative"].toBool(false); n.arrayLen = qBound(1, o["arrayLen"].toInt(1), 1000000); n.strLen = qBound(1, o["strLen"].toInt(64), 1000000); n.collapsed = true; // Always load collapsed; user expands as needed n.refId = o["refId"].toString("0").toULongLong(); n.elementKind = kindFromString(o["elementKind"].toString("UInt8")); n.ptrDepth = qBound(0, o["ptrDepth"].toInt(0), 2); if (o.contains("enumMembers")) { QJsonArray arr = o["enumMembers"].toArray(); for (const auto& v : arr) { QJsonObject em = v.toObject(); n.enumMembers.emplaceBack(em["name"].toString(), em["value"].toString("0").toLongLong()); } } if (o.contains("bitfieldMembers")) { QJsonArray arr = o["bitfieldMembers"].toArray(); for (const auto& v : arr) { QJsonObject bm = v.toObject(); BitfieldMember m; m.name = bm["name"].toString(); m.bitOffset = (uint8_t)qBound(0, bm["bitOffset"].toInt(0), 255); m.bitWidth = (uint8_t)qBound(1, bm["bitWidth"].toInt(1), 64); n.bitfieldMembers.append(m); } } return n; } // Resolved class keyword (never empty) QString resolvedClassKeyword() const { return classKeyword.isEmpty() ? QStringLiteral("struct") : classKeyword; } // NOTE: isStringArray() was checking UInt8/UInt16 instead of UTF8/UTF16. // Currently unused — commented out until a caller needs it. // bool isStringArray() const { // return kind == NodeKind::Array && // (elementKind == NodeKind::UTF8 || elementKind == NodeKind::UTF16); // } }; // ── NodeTree ── struct NodeTree { QVector nodes; uint64_t baseAddress = 0x00400000; QString baseAddressFormula; // e.g. " + 0x100" int pointerSize = 8; // 4 for 32-bit targets, 8 for 64-bit uint64_t m_nextId = 1; mutable QHash m_idCache; mutable QHash> m_childCache; int addNode(const Node& n) { Node copy = n; if (copy.id == 0) copy.id = m_nextId++; else if (copy.id >= m_nextId) m_nextId = copy.id + 1; int idx = nodes.size(); nodes.append(copy); if (!m_idCache.isEmpty()) m_idCache[copy.id] = idx; if (!m_childCache.isEmpty()) m_childCache[copy.parentId].append(idx); return idx; } // Reserve a unique ID atomically (for use before pushing undo commands) uint64_t reserveId() { return m_nextId++; } void invalidateIdCache() const { m_idCache.clear(); m_childCache.clear(); } int indexOfId(uint64_t id) const { if (m_idCache.isEmpty() && !nodes.isEmpty()) { for (int i = 0; i < nodes.size(); i++) m_idCache[nodes[i].id] = i; } return m_idCache.value(id, -1); } QVector childrenOf(uint64_t parentId) const { if (m_childCache.isEmpty() && !nodes.isEmpty()) { for (int i = 0; i < nodes.size(); i++) m_childCache[nodes[i].parentId].append(i); } return m_childCache.value(parentId); } // Collect node + all descendants (iterative, cycle-safe) QVector subtreeIndices(uint64_t nodeId) const { int idx = indexOfId(nodeId); if (idx < 0) return {}; // Build parent→children map QHash> childMap; for (int i = 0; i < nodes.size(); i++) childMap[nodes[i].parentId].append(i); // BFS with visited guard QVector result; QSet visited; QVector stack; stack.append(nodeId); result.append(idx); visited.insert(nodeId); while (!stack.isEmpty()) { uint64_t pid = stack.takeLast(); for (int ci : childMap.value(pid)) { uint64_t cid = nodes[ci].id; if (!visited.contains(cid)) { visited.insert(cid); result.append(ci); stack.append(cid); } } } return result; } int depthOf(int idx) const { int d = 0; QSet visited; int cur = idx; while (cur >= 0 && cur < nodes.size() && nodes[cur].parentId != 0) { uint64_t nid = nodes[cur].id; if (visited.contains(nid)) break; visited.insert(nid); cur = indexOfId(nodes[cur].parentId); if (cur < 0) break; d++; } return d; } int64_t computeOffset(int idx) const { int64_t total = 0; QSet visited; int cur = idx; while (cur >= 0 && cur < nodes.size()) { uint64_t nid = nodes[cur].id; if (visited.contains(nid)) break; visited.insert(nid); total += nodes[cur].offset; if (nodes[cur].parentId == 0) break; cur = indexOfId(nodes[cur].parentId); } return total; } int structSpan(uint64_t structId, const QHash>* childMap = nullptr, QSet* visited = nullptr) const { QSet localVisited; if (!visited) visited = &localVisited; if (visited->contains(structId)) return 0; // Cycle detected visited->insert(structId); int idx = indexOfId(structId); if (idx < 0) return 0; const Node& node = nodes[idx]; int declaredSize = node.byteSize(); int maxEnd = 0; QVector kids = childMap ? childMap->value(structId) : childrenOf(structId); for (int ci : kids) { const Node& c = nodes[ci]; if (c.isStatic) continue; // static fields don't affect struct size int sz = (c.kind == NodeKind::Struct || c.kind == NodeKind::Array) ? structSpan(c.id, childMap, visited) : c.byteSize(); int64_t end = (int64_t)c.offset + sz; if (end > maxEnd) maxEnd = (int)qMin(end, (int64_t)INT_MAX); } // Embedded struct reference: no own children but refId points to a struct definition if (kids.isEmpty() && node.kind == NodeKind::Struct && node.refId != 0) maxEnd = qMax(maxEnd, structSpan(node.refId, childMap, visited)); return qMax(declaredSize, maxEnd); } // Batch selection normalizers QSet normalizePreferAncestors(const QSet& ids) const; QSet normalizePreferDescendants(const QSet& ids) const; QJsonObject toJson() const { QJsonObject o; o["baseAddress"] = QString::number(baseAddress, 16); if (!baseAddressFormula.isEmpty()) o["baseAddressFormula"] = baseAddressFormula; if (pointerSize != 8) o["pointerSize"] = pointerSize; o["nextId"] = QString::number(m_nextId); QJsonArray arr; for (const auto& n : nodes) arr.append(n.toJson()); o["nodes"] = arr; return o; } static NodeTree fromJson(const QJsonObject& o) { NodeTree t; t.baseAddress = o["baseAddress"].toString("400000").toULongLong(nullptr, 16); t.baseAddressFormula = o["baseAddressFormula"].toString(); t.pointerSize = o["pointerSize"].toInt(8); t.m_nextId = o["nextId"].toString("1").toULongLong(); QJsonArray arr = o["nodes"].toArray(); t.nodes.reserve(arr.size()); for (const auto& v : arr) { Node n = Node::fromJson(v.toObject()); t.nodes.append(n); if (n.id >= t.m_nextId) t.m_nextId = n.id + 1; } return t; } }; // ── Value History (ring buffer for heatmap) ── struct ValueHistory { static constexpr int kCapacity = 10; std::array values; std::array timestamps{}; // msec since epoch int count = 0; // total unique values recorded int head = 0; // next write position in ring void record(const QString& v) { if (count > 0) { int last = (head + kCapacity - 1) % kCapacity; if (values[last] == v) return; // no change } values[head] = v; timestamps[head] = QDateTime::currentMSecsSinceEpoch(); head = (head + 1) % kCapacity; if (count < INT_MAX) count++; } void clear() { count = 0; head = 0; } int uniqueCount() const { return qMin(count, kCapacity); } // 0=static, 1=cold(2 unique), 2=warm(3-4), 3=hot(5+) int heatLevel() const { if (count <= 1) return 0; if (count == 2) return 1; if (count <= 4) return 2; return 3; } QString last() const { if (count == 0) return {}; return values[(head + kCapacity - 1) % kCapacity]; } // Iterate from oldest to newest (up to uniqueCount entries) template void forEach(Fn&& fn) const { int n = uniqueCount(); int start = (head + kCapacity - n) % kCapacity; for (int i = 0; i < n; i++) fn(values[(start + i) % kCapacity]); } // Iterate with timestamps from newest to oldest template void forEachWithTime(Fn&& fn) const { int n = uniqueCount(); for (int i = 0; i < n; i++) { int idx = (head + kCapacity - 1 - i) % kCapacity; fn(values[idx], timestamps[idx]); } } }; // ── LineMeta ── enum class LineKind : uint8_t { CommandRow, // line 0: source + address + root class type + name Blank, // (unused — kept for enum stability) Header, Field, Continuation, Footer, ArrayElementSeparator }; static constexpr uint64_t kCommandRowId = UINT64_MAX; static constexpr int kCommandRowLine = 0; static constexpr int kFirstDataLine = 1; static constexpr uint64_t kFooterIdBit = 0x8000000000000000ULL; static constexpr uint64_t kArrayElemBit = 0x4000000000000000ULL; // marks array element selection static constexpr uint64_t kArrayElemShift = 42; // bits 42-61 hold element index static constexpr uint64_t kArrayElemMask = 0x3FFFFC0000000000ULL; // 20 bits → max 1048575 elements // Encode an array element selection ID: nodeId | kArrayElemBit | (elemIdx << 42) inline uint64_t makeArrayElemSelId(uint64_t nodeId, int elemIdx) { Q_ASSERT(elemIdx >= 0); return nodeId | kArrayElemBit | ((uint64_t)(elemIdx & 0xFFFFF) << kArrayElemShift); } inline int arrayElemIdxFromSelId(uint64_t selId) { return (int)((selId & kArrayElemMask) >> kArrayElemShift); } // Member selection encoding (enum/bitfield members) — mirrors array element pattern static constexpr uint64_t kMemberBit = 0x2000000000000000ULL; static constexpr uint64_t kMemberSubShift = 42; static constexpr uint64_t kMemberSubMask = 0x3FFFFC0000000000ULL; inline uint64_t makeMemberSelId(uint64_t nodeId, int subLine) { return nodeId | kMemberBit | ((uint64_t)(subLine & 0xFFFFF) << kMemberSubShift); } inline int memberSubFromSelId(uint64_t selId) { return (int)((selId & kMemberSubMask) >> kMemberSubShift); } struct LineMeta { int nodeIdx = -1; uint64_t nodeId = 0; int subLine = 0; int depth = 0; int foldLevel = 0; bool foldHead = false; bool foldCollapsed = false; bool isContinuation = false; bool isRootHeader = false; // true for top-level struct headers (base address editable) bool isArrayHeader = false; // true for array headers (has nav) LineKind lineKind = LineKind::Field; NodeKind nodeKind = NodeKind::Int32; NodeKind elementKind = NodeKind::UInt8; // Array element type int arrayViewIdx = 0; // Array: current view index int arrayCount = 0; // Array: total element count int arrayElementIdx = -1; // Index of this element within parent array (-1 if not array element) QString offsetText; uint64_t offsetAddr = 0; // Raw absolute address (for margin toggle) uint64_t ptrBase = 0; // Pointer expansion base (non-zero = use for RVA) uint32_t markerMask = 0; bool dataChanged = false; // true if any byte in this node changed since last refresh int heatLevel = 0; // 0=static, 1=cold, 2=warm, 3=hot (from ValueHistory) QVector changedByteIndices; // Hex preview: which byte indices (0-based) changed on this line int lineByteCount = 0; // Hex preview: actual data byte count on this line int effectiveTypeW = 14; // Per-line type 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") bool isArrayElement = false; // true for synthesized primitive array element lines bool isMemberLine = false; // true for enum member / bitfield member lines bool isStaticLine = false; // true for static field node lines QString typeHint; // Type inference hint text (e.g. "Float×2") — only set for hex nodes when hints enabled int typeHintStart = -1; // Character offset where hint text starts in line text (-1 = none) QVector typeHintKinds; // Suggested kinds from inference (empty = no hint) }; inline bool isSyntheticLine(const LineMeta& lm) { return lm.lineKind == LineKind::CommandRow; } // ── Layout Info ── struct LayoutInfo { int typeW = 14; // Effective type column width (default = kColType) int nameW = 22; // Effective name column width (default = kColName) int offsetHexDigits = 8; // Hex digits for offset margin (4/8/12/16) uint64_t baseAddress = 0; // Base address for relative offset computation bool treeLines = false; // Whether tree line connectors are embedded in the text }; // ── ComposeResult ── struct ComposeResult { QString text; QVector meta; LayoutInfo layout; }; // ── Command ── namespace cmd { struct OffsetAdj { uint64_t nodeId; int oldOffset, newOffset; }; struct ChangeKind { uint64_t nodeId; NodeKind oldKind, newKind; QVector offAdjs; }; struct Rename { uint64_t nodeId; QString oldName, newName; }; struct Collapse { uint64_t nodeId; bool oldState, newState; }; struct Insert { Node node; QVector offAdjs; }; struct Remove { uint64_t nodeId; QVector subtree; QVector offAdjs; }; struct ChangeBase { uint64_t oldBase, newBase; QString oldFormula, newFormula; }; struct WriteBytes { uint64_t addr; QByteArray oldBytes, newBytes; }; struct ChangeArrayMeta { uint64_t nodeId; NodeKind oldElementKind, newElementKind; int oldArrayLen, newArrayLen; }; struct ChangePointerRef { uint64_t nodeId; uint64_t oldRefId, newRefId; }; struct ChangeStructTypeName { uint64_t nodeId; QString oldName, newName; }; struct ChangeClassKeyword { uint64_t nodeId; QString oldKeyword, newKeyword; }; struct ChangeOffset { uint64_t nodeId; int oldOffset, newOffset; }; struct ChangeEnumMembers { uint64_t nodeId; QVector> oldMembers, newMembers; }; struct ChangeOffsetExpr { uint64_t nodeId; QString oldExpr, newExpr; }; struct ToggleStatic { uint64_t nodeId; bool oldVal, newVal; }; struct ToggleRelative { uint64_t nodeId; bool oldVal, newVal; }; } using Command = std::variant< cmd::ChangeKind, cmd::Rename, cmd::Collapse, cmd::Insert, cmd::Remove, cmd::ChangeBase, cmd::WriteBytes, cmd::ChangeArrayMeta, cmd::ChangePointerRef, cmd::ChangeStructTypeName, cmd::ChangeClassKeyword, cmd::ChangeOffset, cmd::ChangeEnumMembers, cmd::ChangeOffsetExpr, cmd::ToggleStatic, cmd::ToggleRelative >; // ── Column spans (for inline editing) ── struct ColumnSpan { int start = 0; // inclusive column index int end = 0; // exclusive column index bool valid = false; }; enum class EditTarget { Name, Type, Value, BaseAddress, Source, ArrayIndex, ArrayCount, ArrayElementType, ArrayElementCount, PointerTarget, RootClassType, RootClassName, TypeSelector, StaticExpr }; // Column layout constants (shared with format.cpp span computation) inline constexpr int kFoldCol = 3; // 3-char fold indicator prefix per line inline constexpr int kColType = 14; // Max type column width (fits "uint64_t[999]") inline constexpr int kColName = 22; inline constexpr int kColValue = 96; inline constexpr int kColComment = 28; // "// Enter=Save Esc=Cancel" fits inline constexpr int kColBaseAddr = 12; // "0x" + up to 10 hex digits (40-bit address) inline constexpr int kSepWidth = 1; inline constexpr int kMinTypeW = 7; // Minimum type column width (fits "uint8_t") inline constexpr int kMaxTypeW = 128; // Maximum type column width inline constexpr int kMinNameW = 8; // Minimum name column width (matches ASCII preview) inline constexpr int kMaxNameW = 128; // Maximum name column width inline constexpr int kCompactTypeW = 20; // Type column cap for compact column mode inline ColumnSpan typeSpanFor(const LineMeta& lm, int typeW = kColType) { if (lm.lineKind != LineKind::Field || lm.isContinuation || lm.isMemberLine) return {}; int ind = kFoldCol + lm.depth * 3; return {ind, ind + typeW, true}; } inline ColumnSpan nameSpanFor(const LineMeta& lm, int typeW = kColType, int nameW = kColName) { if (lm.isContinuation || lm.lineKind != LineKind::Field || lm.isMemberLine) return {}; int ind = kFoldCol + lm.depth * 3; int start = ind + typeW + kSepWidth; // Hex: ASCII preview occupies the name column (padded to nameW) if (isHexPreview(lm.nodeKind)) return {start, start + nameW, true}; return {start, start + nameW, true}; } inline ColumnSpan valueSpanFor(const LineMeta& lm, int /*lineLength*/, int typeW = kColType, int nameW = kColName) { if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer || lm.lineKind == LineKind::ArrayElementSeparator) return {}; if (lm.isMemberLine) return {}; int ind = kFoldCol + lm.depth * 3; // Hex uses nameW for ASCII column (same as regular name column) bool isHex = isHexPreview(lm.nodeKind); int valWidth = isHex ? 23 : kColValue; int prefixW = typeW + nameW + 2 * kSepWidth; if (lm.isContinuation) { int start = ind + prefixW; return {start, start + valWidth, true}; } if (lm.lineKind != LineKind::Field) return {}; int start = ind + prefixW; return {start, start + valWidth, true}; } // Member line spans (enum "name = value", bitfield "name : N = value") inline ColumnSpan memberNameSpanFor(const LineMeta& lm, const QString& lineText) { if (!lm.isMemberLine) return {}; int ind = kFoldCol + lm.depth * 3; int eq = lineText.indexOf(QLatin1String(" = "), ind); if (eq < 0) return {}; int nameEnd = eq; while (nameEnd > ind && lineText[nameEnd - 1] == ' ') nameEnd--; return {ind, nameEnd, true}; } inline ColumnSpan memberValueSpanFor(const LineMeta& lm, const QString& lineText) { if (!lm.isMemberLine) return {}; int eq = lineText.indexOf(QLatin1String(" = ")); if (eq < 0) return {}; int valStart = eq + 3; int valEnd = lineText.size(); while (valEnd > valStart && lineText[valEnd - 1] == ' ') valEnd--; return {valStart, valEnd, true}; } // Static field expression span: locates text between "return " and "→" / "(error)" / end inline ColumnSpan staticExprSpanFor(const LineMeta& /*lm*/, const QString& lineText) { int ret = lineText.indexOf(QLatin1String("return ")); if (ret < 0) return {}; int exprStart = ret + 7; // End: before arrow, before "(error)", or line end int exprEnd = lineText.size(); int arrow = lineText.indexOf(QChar(0x2192), exprStart); if (arrow > exprStart) exprEnd = arrow; int err = lineText.indexOf(QLatin1String("(error)"), exprStart); if (err > exprStart && err < exprEnd) exprEnd = err; // Also stop at " }" for collapsed format int brace = lineText.indexOf(QLatin1String(" }"), exprStart); if (brace > exprStart && brace < exprEnd) exprEnd = brace; while (exprEnd > exprStart && lineText[exprEnd - 1] == ' ') exprEnd--; return {exprStart, exprEnd, true}; } inline ColumnSpan commentSpanFor(const LineMeta& lm, int lineLength, int typeW = kColType, int nameW = kColName) { if (lm.lineKind == LineKind::Header || lm.lineKind == LineKind::Footer) return {}; int ind = kFoldCol + lm.depth * 3; bool isHex = isHexPreview(lm.nodeKind); int valWidth = isHex ? 23 : kColValue; int prefixW = typeW + nameW + 2 * kSepWidth; int start; if (lm.isContinuation) { start = ind + prefixW + valWidth; } else { start = ind + prefixW + valWidth; } return {start, lineLength, start < lineLength}; } // ── CommandRow spans ── // Line format: "source▾ · 0x140000000" inline ColumnSpan commandRowSrcSpan(const QString& lineText) { // Source label ends at the ▾ dropdown arrow int arrow = lineText.indexOf(QChar(0x25BE)); if (arrow < 0) return {}; int start = 0; while (start < arrow && !lineText[start].isLetterOrNumber() && lineText[start] != '<' && lineText[start] != '\'') start++; if (start >= arrow) return {}; return {start, arrow, true}; } // ── CommandRow root-class spans ── // Combined CommandRow format ends with: " struct ClassName {" inline int commandRowRootStart(const QString& lineText) { int best = -1; int i; // Match "struct " / "class " / "enum " as whole words before the class name i = lineText.lastIndexOf(QStringLiteral("struct ")); if (i > best) best = i; i = lineText.lastIndexOf(QStringLiteral("class ")); if (i > best) best = i; i = lineText.lastIndexOf(QStringLiteral("enum ")); if (i > best) best = i; return best; } inline ColumnSpan commandRowAddrSpan(const QString& lineText) { // Address starts at "0x" after the source dropdown arrow int arrow = lineText.indexOf(QChar(0x25BE)); if (arrow < 0) return {}; int start = lineText.indexOf(QStringLiteral("0x"), arrow); if (start < 0) { // Formula mode: address is between arrow and root keyword start = arrow + 1; while (start < lineText.size() && lineText[start].isSpace()) start++; } // End at root keyword (struct/class/enum) or end of line int rootStart = commandRowRootStart(lineText); int end = (rootStart > start) ? rootStart : lineText.size(); // Trim trailing whitespace while (end > start && lineText[end - 1].isSpace()) end--; if (end <= start) return {}; return {start, end, true}; } inline ColumnSpan commandRowRootTypeSpan(const QString& lineText) { int start = commandRowRootStart(lineText); if (start < 0) return {}; int end = start; while (end < lineText.size() && lineText[end] != QChar(' ')) end++; if (end <= start) return {}; return {start, end, true}; } inline ColumnSpan commandRowRootNameSpan(const QString& lineText) { int base = commandRowRootStart(lineText); if (base < 0) return {}; int space = lineText.indexOf(' ', base); if (space < 0) return {}; int nameStart = space + 1; while (nameStart < lineText.size() && lineText[nameStart].isSpace()) nameStart++; if (nameStart >= lineText.size()) return {}; int nameEnd = lineText.indexOf(QStringLiteral(" {"), nameStart); if (nameEnd < 0) nameEnd = lineText.size(); while (nameEnd > nameStart && lineText[nameEnd - 1].isSpace()) nameEnd--; if (nameEnd <= nameStart) return {}; return {nameStart, nameEnd, true}; } // ── CommandRow type-selector chevron span ── // Detects "[▸]" at the start of the command row text inline ColumnSpan commandRowChevronSpan(const QString& lineText) { if (lineText.size() < 3) return {}; if (lineText[0] == '[' && lineText[1] == QChar(0x25B8) && lineText[2] == ']') return {0, qMin(4, (int)lineText.size()), true}; // include trailing space for easier clicking return {}; } // ── Array element type/count spans (within type column of array headers) ── // Line format: " int32_t[10] name {" // arrayElemTypeSpan covers "int32_t", arrayElemCountSpan covers "10" inline ColumnSpan arrayElemTypeSpanFor(const LineMeta& lm, const QString& lineText) { if (lm.lineKind != LineKind::Header || !lm.isArrayHeader) return {}; int ind = kFoldCol + lm.depth * 3; // Find '[' in the type portion int bracket = lineText.indexOf('[', ind); if (bracket <= ind) return {}; return {ind, bracket, true}; } inline ColumnSpan arrayElemCountSpanFor(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 + 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) ── // Line format: " void* name -> 0x..." // pointerTargetSpan covers the target name before '*' inline ColumnSpan pointerKindSpanFor(const LineMeta& /*lm*/, const QString& /*lineText*/) { return {}; // No separate kind span in "Type*" format } inline ColumnSpan pointerTargetSpanFor(const LineMeta& lm, const QString& lineText) { if ((lm.lineKind != LineKind::Field && lm.lineKind != LineKind::Header) || lm.isContinuation) return {}; if (lm.nodeKind != NodeKind::Pointer32 && lm.nodeKind != NodeKind::Pointer64) return {}; int ind = kFoldCol + lm.depth * 3; int star = lineText.indexOf('*', ind); if (star <= ind) return {}; return {ind, star, true}; } // ── Array navigation spans ── // Line format: "uint32_t[16] name { <0/16>" inline ColumnSpan arrayPrevSpanFor(const LineMeta& lm, const QString& lineText) { if (!lm.isArrayHeader) return {}; int lt = lineText.lastIndexOf('<'); if (lt < 0) return {}; return {lt, lt + 1, true}; } inline ColumnSpan arrayIndexSpanFor(const LineMeta& lm, const QString& lineText) { if (!lm.isArrayHeader) return {}; int lt = lineText.lastIndexOf('<'); int slash = lineText.indexOf('/', lt); if (lt < 0 || slash < 0) return {}; return {lt + 1, slash, true}; } inline ColumnSpan arrayCountSpanFor(const LineMeta& lm, const QString& lineText) { if (!lm.isArrayHeader) return {}; int slash = lineText.lastIndexOf('/'); int gt = lineText.indexOf('>', slash); if (slash < 0 || gt < 0) return {}; return {slash + 1, gt, true}; } inline ColumnSpan arrayNextSpanFor(const LineMeta& lm, const QString& lineText) { if (!lm.isArrayHeader) return {}; int gt = lineText.lastIndexOf('>'); if (gt < 0) return {}; return {gt, gt + 1, true}; } // ── ViewState ── struct ViewState { int scrollLine = 0; int cursorLine = 0; int cursorCol = 0; int xOffset = 0; // horizontal scroll in pixels }; // ── Format function forward declarations ── namespace fmt { using TypeNameFn = QString (*)(NodeKind); void setTypeNameProvider(TypeNameFn fn); QString typeName(NodeKind kind, int colType = kColType); QString typeNameRaw(NodeKind kind); // Unpadded type name for width calculation QString fmtInt8(int8_t v); QString fmtInt16(int16_t v); QString fmtInt32(int32_t v); QString fmtInt64(int64_t v); QString fmtUInt8(uint8_t v); QString fmtUInt16(uint16_t v); QString fmtUInt32(uint32_t v); QString fmtUInt64(uint64_t v); QString fmtFloat(float v); QString fmtDouble(double v); QString fmtBool(uint8_t v); QString fmtPointer32(uint32_t v); QString fmtPointer64(uint64_t v); QString fmtNodeLine(const Node& node, const Provider& prov, uint64_t addr, int depth, int subLine = 0, const QString& comment = {}, int colType = kColType, int colName = kColName, const QString& typeOverride = {}, bool compact = false); 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, bool compact = false); 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, const QString& elemStructName = {}, bool compact = false); QString structTypeName(const Node& node); // Full type string for struct headers QString arrayTypeName(NodeKind elemKind, int count, const QString& structName = {}); QString pointerTypeName(NodeKind kind, const QString& targetName); QString fmtPointerHeader(const Node& node, int depth, bool collapsed, const Provider& prov, uint64_t addr, const QString& ptrTypeName, int colType = kColType, int colName = kColName, bool compact = false); QString validateBaseAddress(const QString& text); QString indent(int depth); QString readValue(const Node& node, const Provider& prov, uint64_t addr, int subLine); QString editableValue(const Node& node, const Provider& prov, uint64_t addr, int subLine); QByteArray parseValue(NodeKind kind, const QString& text, bool* ok); QByteArray parseAsciiValue(const QString& text, int expectedSize, bool* ok); QString validateValue(NodeKind kind, const QString& text); QString fmtEnumMember(const QString& name, int64_t value, int depth, int nameW); QString fmtBitfieldMember(const QString& name, uint8_t bitWidth, uint64_t value, int depth, int nameW); uint64_t extractBits(const Provider& prov, uint64_t addr, NodeKind containerKind, uint8_t bitOffset, uint8_t bitWidth); } // namespace fmt // ── Compose function forward declaration ── // Optional callback: given an absolute address, return a symbol name (e.g. "nt!PsActiveProcessHead") // or empty string if no symbol matches. Used for PDB symbol annotations on rows. using SymbolLookupFn = std::function; ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId = 0, bool compactColumns = false, bool treeLines = false, bool braceWrap = false, bool typeHints = false, SymbolLookupFn symbolLookup = {}); } // namespace rcx