From 8daed4541480d2f1572ec315a91894a272f818ed Mon Sep 17 00:00:00 2001 From: sysadmin Date: Fri, 6 Feb 2026 08:02:08 -0700 Subject: [PATCH] Remove ambient validation markers, show ptr64 as void*, add struct types to picker, add README - Remove M_ERR and M_PTR0 ambient markers from compose (validation only during inline edit) - Change ptr64 display name to void* in kKindMeta - Type picker now includes custom struct type names from the tree - Controller handles struct type selection from picker - Add project README Co-Authored-By: Claude Opus 4.6 --- README.md | 22 ++ src/compose.cpp | 17 +- src/controller.cpp | 35 ++- src/core.h | 2 +- src/editor.cpp | 9 +- src/editor.h | 6 + src/main.cpp | 472 ++++++++++++++++++----------------------- tests/test_compose.cpp | 9 +- tests/test_editor.cpp | 362 +++++++++++++++++++++++-------- 9 files changed, 566 insertions(+), 368 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..41c40ce --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# ReclassX + +A hex editor for reading live process memory. It shows structs, pointers, arrays, and padding so you can see how data is laid out and how it connects. + +Work in progress. + +![screenshot](build/screenshot.png) + +## Build + +Requires Qt 6, QScintilla, and MinGW on Windows. + +``` +cmake -B build -G Ninja +cmake --build build +``` + +QScintilla must be built first as a static lib. There is a helper script: + +``` +powershell -File scripts/build_qscintilla.ps1 +``` diff --git a/src/compose.cpp b/src/compose.cpp index f53cfbf..1719926 100644 --- a/src/compose.cpp +++ b/src/compose.cpp @@ -56,23 +56,12 @@ int computeFoldLevel(int depth, bool isHead) { return level; } -uint32_t computeMarkers(const Node& node, const Provider& prov, - uint64_t addr, bool isCont, int depth) { +uint32_t computeMarkers(const Node& node, const Provider& /*prov*/, + uint64_t /*addr*/, bool isCont, int /*depth*/) { uint32_t mask = 0; if (isCont) mask |= (1u << M_CONT); if (node.kind == NodeKind::Padding) mask |= (1u << M_PAD); - - if (prov.isValid()) { - int sz = node.byteSize(); - if (sz > 0 && !prov.isReadable(addr, sz)) { - mask |= (1u << M_ERR); - } else if (sz > 0) { - if (node.kind == NodeKind::Pointer32 && prov.readU32(addr) == 0) - mask |= (1u << M_PTR0); - if (node.kind == NodeKind::Pointer64 && prov.readU64(addr) == 0) - mask |= (1u << M_PTR0); - } - } + // No ambient validation markers — errors only shown during inline editing. return mask; } diff --git a/src/controller.cpp b/src/controller.cpp index a3de008..195148f 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -208,7 +208,27 @@ void RcxController::connectEditor(RcxEditor* editor) { // Regular type change bool ok; NodeKind k = kindFromTypeName(text, &ok); - if (ok) changeNodeKind(nodeIdx, k); + if (ok) { + changeNodeKind(nodeIdx, k); + } else if (nodeIdx < m_doc->tree.nodes.size()) { + // Check if it's a defined struct type name + bool isStructType = false; + for (const auto& n : m_doc->tree.nodes) { + if (n.kind == NodeKind::Struct && n.structTypeName == text) { + isStructType = true; + break; + } + } + if (isStructType) { + auto& node = m_doc->tree.nodes[nodeIdx]; + if (node.kind != NodeKind::Struct) + changeNodeKind(nodeIdx, NodeKind::Struct); + // Set the struct type name via rename of structTypeName + int idx = m_doc->tree.indexOfId(node.id); + if (idx >= 0) + m_doc->tree.nodes[idx].structTypeName = text; + } + } } break; } @@ -309,7 +329,20 @@ void RcxController::refresh() { } m_selIds = valid; + // Collect unique struct type names for the type picker + QStringList customTypes; + QSet seen; + for (const auto& node : m_doc->tree.nodes) { + if (node.kind == NodeKind::Struct && !node.structTypeName.isEmpty()) { + if (!seen.contains(node.structTypeName)) { + seen.insert(node.structTypeName); + customTypes << node.structTypeName; + } + } + } + for (auto* editor : m_editors) { + editor->setCustomTypeNames(customTypes); ViewState vs = editor->saveViewState(); editor->applyDocument(m_lastResult); editor->restoreViewState(vs); diff --git a/src/core.h b/src/core.h index 880a34c..7189b26 100644 --- a/src/core.h +++ b/src/core.h @@ -71,7 +71,7 @@ inline constexpr KindMeta kKindMeta[] = { {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::Pointer64, "Pointer64", "void*", 8, 1, 8, KF_None}, {NodeKind::Vec2, "Vec2", "Vec2", 8, 2, 4, KF_Vector}, {NodeKind::Vec3, "Vec3", "Vec3", 12, 3, 4, KF_Vector}, {NodeKind::Vec4, "Vec4", "Vec4", 16, 4, 4, KF_Vector}, diff --git a/src/editor.cpp b/src/editor.cpp index ac6e791..0e71d1f 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1341,8 +1341,15 @@ void RcxEditor::showTypeListFiltered(const QString& filter) { if (!m_editState.active || m_editState.target != EditTarget::Type) return; - // Filter type names by prefix + // Combine native types with custom (struct) type names QStringList all = allTypeNamesForUI(); + for (const QString& ct : m_customTypeNames) { + if (!all.contains(ct)) + all << ct; + } + all.sort(Qt::CaseInsensitive); + + // Filter by prefix QStringList filtered; for (const QString& t : all) { if (filter.isEmpty() || t.startsWith(filter, Qt::CaseInsensitive)) diff --git a/src/editor.h b/src/editor.h index 54d5dc9..196e4b0 100644 --- a/src/editor.h +++ b/src/editor.h @@ -41,6 +41,9 @@ public: void setEditorFont(const QString& fontName); static void setGlobalFontName(const QString& fontName); + // Custom type names (struct types from the tree) shown in type picker + void setCustomTypeNames(const QStringList& names) { m_customTypeNames = names; } + signals: void marginClicked(int margin, int line, Qt::KeyboardModifiers mods); void contextMenuRequested(int line, int nodeIdx, int subLine, QPoint globalPos); @@ -102,6 +105,9 @@ private: // ── Tab cycling state ── EditTarget m_lastTabTarget = EditTarget::Value; + // ── Custom type names for type picker ── + QStringList m_customTypeNames; + // ── Reentrancy guards ── bool m_clampingSelection = false; bool m_updatingComment = false; diff --git a/src/main.cpp b/src/main.cpp index 5ca812d..888a294 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -271,291 +271,243 @@ void MainWindow::newFile() { auto* doc = new RcxDocument(this); // ══════════════════════════════════════════════════════════════════════════ - // PE Header Demo - Realistic PE32+ (64-bit) executable structure - // ══════════════════════════════════════════════════════════════════════════ - // Layout: - // 0x000: DOS Header (64 bytes) - // 0x040: DOS Stub (64 bytes padding) - // 0x080: PE Signature (4 bytes) - // 0x084: File Header (20 bytes) - // 0x098: Optional Header PE32+ (240 bytes) - // - Standard fields (24 bytes) - // - Windows fields (88 bytes) - // - Data Directories (16 * 8 = 128 bytes) - // 0x188: Section Headers (4 * 40 = 160 bytes) - // 0x228: End of headers (total 552 bytes) + // _PEB64 Demo — Process Environment Block (0x7D0 bytes) // ══════════════════════════════════════════════════════════════════════════ - QByteArray peData(0x300, '\0'); // 768 bytes - char* d = peData.data(); + QByteArray pebData(0x7D0, '\0'); + char* d = pebData.data(); - // ── DOS Header (IMAGE_DOS_HEADER) ── - d[0x00] = 'M'; d[0x01] = 'Z'; // e_magic - *(uint16_t*)(d + 0x02) = 0x0090; // e_cblp (bytes on last page) - *(uint16_t*)(d + 0x04) = 0x0003; // e_cp (pages in file) - *(uint16_t*)(d + 0x06) = 0x0000; // e_crlc (relocations) - *(uint16_t*)(d + 0x08) = 0x0004; // e_cparhdr (header size in paragraphs) - *(uint16_t*)(d + 0x0A) = 0x0000; // e_minalloc - *(uint16_t*)(d + 0x0C) = 0xFFFF; // e_maxalloc - *(uint16_t*)(d + 0x0E) = 0x0000; // e_ss - *(uint16_t*)(d + 0x10) = 0x00B8; // e_sp - *(uint16_t*)(d + 0x12) = 0x0000; // e_csum - *(uint16_t*)(d + 0x14) = 0x0000; // e_ip - *(uint16_t*)(d + 0x16) = 0x0000; // e_cs - *(uint16_t*)(d + 0x18) = 0x0040; // e_lfarlc - *(uint16_t*)(d + 0x1A) = 0x0000; // e_ovno - // e_res[4] at 0x1C-0x23 (zeroed) - *(uint16_t*)(d + 0x24) = 0x0000; // e_oemid - *(uint16_t*)(d + 0x26) = 0x0000; // e_oeminfo - // e_res2[10] at 0x28-0x3B (zeroed) - *(uint32_t*)(d + 0x3C) = 0x00000080; // e_lfanew → PE header at 0x80 + auto w8 = [&](int off, uint8_t v) { d[off] = (char)v; }; + auto w16 = [&](int off, uint16_t v) { memcpy(d+off, &v, 2); }; + auto w32 = [&](int off, uint32_t v) { memcpy(d+off, &v, 4); }; + auto w64 = [&](int off, uint64_t v) { memcpy(d+off, &v, 8); }; - // ── PE Signature ── - const int peOff = 0x80; - d[peOff+0] = 'P'; d[peOff+1] = 'E'; d[peOff+2] = 0; d[peOff+3] = 0; + w8 (0x002, 1); // BeingDebugged + w8 (0x003, 0x04); // BitField + w64(0x008, 0xFFFFFFFFFFFFFFFFULL); // Mutant (-1) + w64(0x010, 0x00007FF6DE120000ULL); // ImageBaseAddress + w64(0x018, 0x00007FFE3B8B53C0ULL); // Ldr + w64(0x020, 0x000001A4C3E20F90ULL); // ProcessParameters + w64(0x030, 0x000001A4C3D40000ULL); // ProcessHeap + w64(0x038, 0x00007FFE3B8D4260ULL); // FastPebLock + w32(0x050, 0x01); // CrossProcessFlags + w64(0x058, 0x00007FFE3B720000ULL); // KernelCallbackTable + w64(0x068, 0x00007FFE3E570000ULL); // ApiSetMap + w64(0x078, 0x00007FFE3B8D3F50ULL); // TlsBitmap + w32(0x080, 0x00000003); // TlsBitmapBits[0] + w64(0x088, 0x00007FFE38800000ULL); // ReadOnlySharedMemoryBase + w64(0x090, 0x00007FFE38820000ULL); // SharedData + w64(0x0A0, 0x00007FFE3B8D1000ULL); // AnsiCodePageData + w64(0x0A8, 0x00007FFE3B8D2040ULL); // OemCodePageData + w64(0x0B0, 0x00007FFE3B8CE020ULL); // UnicodeCaseTableData + w32(0x0B8, 8); // NumberOfProcessors + w32(0x0BC, 0x70); // NtGlobalFlag + w64(0x0C0, 0xFFFFFFFF7C91E000ULL); // CriticalSectionTimeout + w64(0x0C8, 0x0000000000100000ULL); // HeapSegmentReserve + w64(0x0D0, 0x0000000000002000ULL); // HeapSegmentCommit + w32(0x0E8, 4); // NumberOfHeaps + w32(0x0EC, 16); // MaximumNumberOfHeaps + w64(0x0F0, 0x000001A4C3D40688ULL); // ProcessHeaps + w64(0x0F8, 0x00007FFE388B0000ULL); // GdiSharedHandleTable + w64(0x110, 0x00007FFE3B8D42E8ULL); // LoaderLock + w32(0x118, 10); // OSMajorVersion + w16(0x120, 19045); // OSBuildNumber + w32(0x124, 2); // OSPlatformId + w32(0x128, 3); // ImageSubsystem (CUI) + w32(0x12C, 10); // ImageSubsystemMajorVersion + w64(0x138, 0x00000000000000FFULL); // ActiveProcessAffinityMask + w64(0x238, 0x00007FFE3B8D3F70ULL); // TlsExpansionBitmap + w32(0x2C0, 1); // SessionId + w64(0x2F8, 0x000001A4C3E21000ULL); // ActivationContextData + w64(0x308, 0x00007FFE38840000ULL); // SystemDefaultActivationContextData + w64(0x318, 0x0000000000002000ULL); // MinimumStackCommit + w16(0x34C, 1252); // ActiveCodePage + w16(0x34E, 437); // OemCodePage + w64(0x358, 0x000001A4C3E30000ULL); // WerRegistrationData + w64(0x380, 0x00007FFE38890000ULL); // CsrServerReadOnlySharedMemoryBase + w64(0x390, 0x000000D87B5E5390ULL); // TppWorkerpList.Flink (self) + w64(0x398, 0x000000D87B5E5390ULL); // TppWorkerpList.Blink (self) + w64(0x7B8, 0x00007FFE38860000ULL); // LeapSecondData - // ── File Header (IMAGE_FILE_HEADER) ── - const int fhOff = peOff + 4; // 0x84 - *(uint16_t*)(d + fhOff + 0) = 0x8664; // Machine (AMD64) - *(uint16_t*)(d + fhOff + 2) = 0x0004; // NumberOfSections - *(uint32_t*)(d + fhOff + 4) = 0x65A3B2C1; // TimeDateStamp - *(uint32_t*)(d + fhOff + 8) = 0x00000000; // PointerToSymbolTable - *(uint32_t*)(d + fhOff + 12) = 0x00000000; // NumberOfSymbols - *(uint16_t*)(d + fhOff + 16) = 0x00F0; // SizeOfOptionalHeader (240) - *(uint16_t*)(d + fhOff + 18) = 0x0022; // Characteristics (EXECUTABLE|LARGE_ADDRESS_AWARE) - - // ── Optional Header PE32+ (IMAGE_OPTIONAL_HEADER64) ── - const int ohOff = fhOff + 20; // 0x98 - *(uint16_t*)(d + ohOff + 0) = 0x020B; // Magic (PE32+) - *(uint8_t*)(d + ohOff + 2) = 0x0E; // MajorLinkerVersion - *(uint8_t*)(d + ohOff + 3) = 0x00; // MinorLinkerVersion - *(uint32_t*)(d + ohOff + 4) = 0x00012000; // SizeOfCode - *(uint32_t*)(d + ohOff + 8) = 0x00008000; // SizeOfInitializedData - *(uint32_t*)(d + ohOff + 12) = 0x00000000; // SizeOfUninitializedData - *(uint32_t*)(d + ohOff + 16) = 0x00001000; // AddressOfEntryPoint - *(uint32_t*)(d + ohOff + 20) = 0x00001000; // BaseOfCode - - // Windows-specific fields (PE32+) - *(uint64_t*)(d + ohOff + 24) = 0x0000000140000000ULL; // ImageBase - *(uint32_t*)(d + ohOff + 32) = 0x00001000; // SectionAlignment - *(uint32_t*)(d + ohOff + 36) = 0x00000200; // FileAlignment - *(uint16_t*)(d + ohOff + 40) = 0x0006; // MajorOperatingSystemVersion - *(uint16_t*)(d + ohOff + 42) = 0x0000; // MinorOperatingSystemVersion - *(uint16_t*)(d + ohOff + 44) = 0x0000; // MajorImageVersion - *(uint16_t*)(d + ohOff + 46) = 0x0000; // MinorImageVersion - *(uint16_t*)(d + ohOff + 48) = 0x0006; // MajorSubsystemVersion - *(uint16_t*)(d + ohOff + 50) = 0x0000; // MinorSubsystemVersion - *(uint32_t*)(d + ohOff + 52) = 0x00000000; // Win32VersionValue - *(uint32_t*)(d + ohOff + 56) = 0x00025000; // SizeOfImage - *(uint32_t*)(d + ohOff + 60) = 0x00000200; // SizeOfHeaders - *(uint32_t*)(d + ohOff + 64) = 0x00000000; // CheckSum - *(uint16_t*)(d + ohOff + 68) = 0x0003; // Subsystem (CONSOLE) - *(uint16_t*)(d + ohOff + 70) = 0x8160; // DllCharacteristics (DYNAMIC_BASE|NX_COMPAT|TERMINAL_SERVER_AWARE) - *(uint64_t*)(d + ohOff + 72) = 0x0000000000100000ULL; // SizeOfStackReserve - *(uint64_t*)(d + ohOff + 80) = 0x0000000000001000ULL; // SizeOfStackCommit - *(uint64_t*)(d + ohOff + 88) = 0x0000000000100000ULL; // SizeOfHeapReserve - *(uint64_t*)(d + ohOff + 96) = 0x0000000000001000ULL; // SizeOfHeapCommit - *(uint32_t*)(d + ohOff + 104) = 0x00000000; // LoaderFlags - *(uint32_t*)(d + ohOff + 108) = 0x00000010; // NumberOfRvaAndSizes (16) - - // ── Data Directories (16 entries × 8 bytes) ── - const int ddOff = ohOff + 112; // 0x108 - // Each entry: VirtualAddress (4) + Size (4) - struct { uint32_t rva; uint32_t size; } dataDirs[16] = { - {0x00000000, 0x00000000}, // 0: Export - {0x00014000, 0x000000A0}, // 1: Import - {0x00000000, 0x00000000}, // 2: Resource - {0x00000000, 0x00000000}, // 3: Exception - {0x00000000, 0x00000000}, // 4: Security - {0x00000000, 0x00000000}, // 5: BaseReloc - {0x00013000, 0x00000038}, // 6: Debug - {0x00000000, 0x00000000}, // 7: Architecture - {0x00000000, 0x00000000}, // 8: GlobalPtr - {0x00000000, 0x00000000}, // 9: TLS - {0x00000000, 0x00000000}, // 10: LoadConfig - {0x00000000, 0x00000000}, // 11: BoundImport - {0x00014050, 0x00000048}, // 12: IAT - {0x00000000, 0x00000000}, // 13: DelayImport - {0x00000000, 0x00000000}, // 14: CLR - {0x00000000, 0x00000000}, // 15: Reserved - }; - for (int i = 0; i < 16; i++) { - *(uint32_t*)(d + ddOff + i*8 + 0) = dataDirs[i].rva; - *(uint32_t*)(d + ddOff + i*8 + 4) = dataDirs[i].size; - } - - // ── Section Headers (4 sections × 40 bytes) ── - const int shOff = ddOff + 128; // 0x188 - struct SectionDef { const char* name; uint32_t vsize; uint32_t vaddr; uint32_t rawsz; uint32_t rawptr; uint32_t chars; }; - SectionDef sections[4] = { - {".text", 0x00011234, 0x00001000, 0x00011400, 0x00000200, 0x60000020}, // CODE|EXECUTE|READ - {".rdata", 0x00002ABC, 0x00013000, 0x00002C00, 0x00011600, 0x40000040}, // INITIALIZED|READ - {".data", 0x00001000, 0x00016000, 0x00000400, 0x00014200, 0xC0000040}, // INITIALIZED|READ|WRITE - {".pdata", 0x00000800, 0x00017000, 0x00000800, 0x00014600, 0x40000040}, // INITIALIZED|READ - }; - for (int i = 0; i < 4; i++) { - int off = shOff + i * 40; - memcpy(d + off, sections[i].name, 8); // Name[8] - *(uint32_t*)(d + off + 8) = sections[i].vsize; // VirtualSize - *(uint32_t*)(d + off + 12) = sections[i].vaddr; // VirtualAddress - *(uint32_t*)(d + off + 16) = sections[i].rawsz; // SizeOfRawData - *(uint32_t*)(d + off + 20) = sections[i].rawptr; // PointerToRawData - *(uint32_t*)(d + off + 24) = 0x00000000; // PointerToRelocations - *(uint32_t*)(d + off + 28) = 0x00000000; // PointerToLinenumbers - *(uint16_t*)(d + off + 32) = 0x0000; // NumberOfRelocations - *(uint16_t*)(d + off + 34) = 0x0000; // NumberOfLinenumbers - *(uint32_t*)(d + off + 36) = sections[i].chars; // Characteristics - } - - doc->loadData(peData); - doc->tree.baseAddress = 0x140000000; // Typical 64-bit image base + doc->loadData(pebData); + doc->tree.baseAddress = 0x000000D87B5E5000ULL; // ══════════════════════════════════════════════════════════════════════════ - // Build Node Tree + // Build _PEB64 Node Tree (0x7D0 bytes, unions mapped to first member) // ══════════════════════════════════════════════════════════════════════════ auto addField = [&](uint64_t parent, int offset, NodeKind kind, const QString& name) -> uint64_t { - Node n; - n.kind = kind; - n.name = name; - n.parentId = parent; - n.offset = offset; + Node n; n.kind = kind; n.name = name; + n.parentId = parent; n.offset = offset; int idx = doc->tree.addNode(n); return doc->tree.nodes[idx].id; }; - - auto addStruct = [&](uint64_t parent, int offset, const QString& typeName, const QString& name) -> uint64_t { - Node n; - n.kind = NodeKind::Struct; - n.structTypeName = typeName; - n.name = name; - n.parentId = parent; - n.offset = offset; - n.collapsed = true; // Auto-collapse structs + auto addPad = [&](uint64_t parent, int offset, int len, const QString& name) { + Node n; n.kind = NodeKind::Padding; n.name = name; + n.parentId = parent; n.offset = offset; n.arrayLen = len; + doc->tree.addNode(n); + }; + auto addStruct = [&](uint64_t parent, int offset, const QString& typeName, const QString& name, bool collapse = true) -> uint64_t { + Node n; n.kind = NodeKind::Struct; + n.structTypeName = typeName; n.name = name; + n.parentId = parent; n.offset = offset; n.collapsed = collapse; int idx = doc->tree.addNode(n); return doc->tree.nodes[idx].id; }; - - auto addArray = [&](uint64_t parent, int offset, const QString& name, int count, NodeKind elemKind) -> uint64_t { - Node n; - n.kind = NodeKind::Array; - n.name = name; - n.parentId = parent; - n.offset = offset; - n.arrayLen = count; - n.elementKind = elemKind; - n.collapsed = true; // Auto-collapse arrays - int idx = doc->tree.addNode(n); - return doc->tree.nodes[idx].id; + auto addArray = [&](uint64_t parent, int offset, const QString& name, int count, NodeKind elemKind) { + Node n; n.kind = NodeKind::Array; n.name = name; + n.parentId = parent; n.offset = offset; + n.arrayLen = count; n.elementKind = elemKind; + n.collapsed = true; + doc->tree.addNode(n); }; - // ── Root: IMAGE_DOS_HEADER ── - uint64_t dosId = addStruct(0, 0x00, "IMAGE_DOS_HEADER", "DosHeader"); - addField(dosId, 0x00, NodeKind::UInt16, "e_magic"); - addField(dosId, 0x02, NodeKind::UInt16, "e_cblp"); - addField(dosId, 0x04, NodeKind::UInt16, "e_cp"); - addField(dosId, 0x06, NodeKind::UInt16, "e_crlc"); - addField(dosId, 0x08, NodeKind::UInt16, "e_cparhdr"); - addField(dosId, 0x0A, NodeKind::UInt16, "e_minalloc"); - addField(dosId, 0x0C, NodeKind::UInt16, "e_maxalloc"); - addField(dosId, 0x0E, NodeKind::UInt16, "e_ss"); - addField(dosId, 0x10, NodeKind::UInt16, "e_sp"); - addField(dosId, 0x12, NodeKind::UInt16, "e_csum"); - addField(dosId, 0x14, NodeKind::UInt16, "e_ip"); - addField(dosId, 0x16, NodeKind::UInt16, "e_cs"); - addField(dosId, 0x18, NodeKind::UInt16, "e_lfarlc"); - addField(dosId, 0x1A, NodeKind::UInt16, "e_ovno"); - addField(dosId, 0x3C, NodeKind::UInt32, "e_lfanew"); + // Root struct (not collapsed so fields are visible on open) + uint64_t peb = addStruct(0, 0, "_PEB64", "Peb", false); - // ── PE Signature ── - addField(0, peOff, NodeKind::UInt32, "Signature"); + // 0x000 – 0x007 + addField(peb, 0x000, NodeKind::UInt8, "InheritedAddressSpace"); + addField(peb, 0x001, NodeKind::UInt8, "ReadImageFileExecOptions"); + addField(peb, 0x002, NodeKind::UInt8, "BeingDebugged"); + addField(peb, 0x003, NodeKind::UInt8, "BitField"); + addPad (peb, 0x004, 4, "Padding0"); - // ── IMAGE_FILE_HEADER ── - uint64_t fhId = addStruct(0, fhOff, "IMAGE_FILE_HEADER", "FileHeader"); - addField(fhId, 0, NodeKind::UInt16, "Machine"); - addField(fhId, 2, NodeKind::UInt16, "NumberOfSections"); - addField(fhId, 4, NodeKind::UInt32, "TimeDateStamp"); - addField(fhId, 8, NodeKind::UInt32, "PointerToSymbolTable"); - addField(fhId, 12, NodeKind::UInt32, "NumberOfSymbols"); - addField(fhId, 16, NodeKind::UInt16, "SizeOfOptionalHeader"); - addField(fhId, 18, NodeKind::UInt16, "Characteristics"); + // 0x008 – 0x04F + addField(peb, 0x008, NodeKind::Pointer64, "Mutant"); + addField(peb, 0x010, NodeKind::Pointer64, "ImageBaseAddress"); + addField(peb, 0x018, NodeKind::Pointer64, "Ldr"); + addField(peb, 0x020, NodeKind::Pointer64, "ProcessParameters"); + addField(peb, 0x028, NodeKind::Pointer64, "SubSystemData"); + addField(peb, 0x030, NodeKind::Pointer64, "ProcessHeap"); + addField(peb, 0x038, NodeKind::Pointer64, "FastPebLock"); + addField(peb, 0x040, NodeKind::Pointer64, "AtlThunkSListPtr"); + addField(peb, 0x048, NodeKind::Pointer64, "IFEOKey"); - // ── IMAGE_OPTIONAL_HEADER64 ── - uint64_t ohId = addStruct(0, ohOff, "IMAGE_OPTIONAL_HEADER64", "OptionalHeader"); - addField(ohId, 0, NodeKind::UInt16, "Magic"); - addField(ohId, 2, NodeKind::UInt8, "MajorLinkerVersion"); - addField(ohId, 3, NodeKind::UInt8, "MinorLinkerVersion"); - addField(ohId, 4, NodeKind::UInt32, "SizeOfCode"); - addField(ohId, 8, NodeKind::UInt32, "SizeOfInitializedData"); - addField(ohId, 12, NodeKind::UInt32, "SizeOfUninitializedData"); - addField(ohId, 16, NodeKind::UInt32, "AddressOfEntryPoint"); - addField(ohId, 20, NodeKind::UInt32, "BaseOfCode"); - addField(ohId, 24, NodeKind::UInt64, "ImageBase"); - addField(ohId, 32, NodeKind::UInt32, "SectionAlignment"); - addField(ohId, 36, NodeKind::UInt32, "FileAlignment"); - addField(ohId, 40, NodeKind::UInt16, "MajorOperatingSystemVersion"); - addField(ohId, 42, NodeKind::UInt16, "MinorOperatingSystemVersion"); - addField(ohId, 44, NodeKind::UInt16, "MajorImageVersion"); - addField(ohId, 46, NodeKind::UInt16, "MinorImageVersion"); - addField(ohId, 48, NodeKind::UInt16, "MajorSubsystemVersion"); - addField(ohId, 50, NodeKind::UInt16, "MinorSubsystemVersion"); - addField(ohId, 52, NodeKind::UInt32, "Win32VersionValue"); - addField(ohId, 56, NodeKind::UInt32, "SizeOfImage"); - addField(ohId, 60, NodeKind::UInt32, "SizeOfHeaders"); - addField(ohId, 64, NodeKind::UInt32, "CheckSum"); - addField(ohId, 68, NodeKind::UInt16, "Subsystem"); - addField(ohId, 70, NodeKind::UInt16, "DllCharacteristics"); - addField(ohId, 72, NodeKind::UInt64, "SizeOfStackReserve"); - addField(ohId, 80, NodeKind::UInt64, "SizeOfStackCommit"); - addField(ohId, 88, NodeKind::UInt64, "SizeOfHeapReserve"); - addField(ohId, 96, NodeKind::UInt64, "SizeOfHeapCommit"); - addField(ohId, 104, NodeKind::UInt32, "LoaderFlags"); - addField(ohId, 108, NodeKind::UInt32, "NumberOfRvaAndSizes"); + // 0x050 – 0x07F + addField(peb, 0x050, NodeKind::UInt32, "CrossProcessFlags"); + addPad (peb, 0x054, 4, "Padding1"); + addField(peb, 0x058, NodeKind::Pointer64, "KernelCallbackTable"); + addField(peb, 0x060, NodeKind::UInt32, "SystemReserved"); + addField(peb, 0x064, NodeKind::UInt32, "AtlThunkSListPtr32"); + addField(peb, 0x068, NodeKind::Pointer64, "ApiSetMap"); + addField(peb, 0x070, NodeKind::UInt32, "TlsExpansionCounter"); + addPad (peb, 0x074, 4, "Padding2"); + addField(peb, 0x078, NodeKind::Pointer64, "TlsBitmap"); + addArray(peb, 0x080, "TlsBitmapBits", 2, NodeKind::UInt32); - // ── Data Directories Array (16 entries) ── - uint64_t ddArrId = addArray(ohId, 112, "DataDirectory", 16, NodeKind::Struct); - const char* ddNames[16] = { - "Export", "Import", "Resource", "Exception", - "Security", "BaseReloc", "Debug", "Architecture", - "GlobalPtr", "TLS", "LoadConfig", "BoundImport", - "IAT", "DelayImport", "CLR", "Reserved" - }; - for (int i = 0; i < 16; i++) { - uint64_t entryId = addStruct(ddArrId, i * 8, "IMAGE_DATA_DIRECTORY", QString("%1").arg(ddNames[i])); - addField(entryId, 0, NodeKind::UInt32, "VirtualAddress"); - addField(entryId, 4, NodeKind::UInt32, "Size"); + // 0x088 – 0x0BF + addField(peb, 0x088, NodeKind::Pointer64, "ReadOnlySharedMemoryBase"); + addField(peb, 0x090, NodeKind::Pointer64, "SharedData"); + addField(peb, 0x098, NodeKind::Pointer64, "ReadOnlyStaticServerData"); + addField(peb, 0x0A0, NodeKind::Pointer64, "AnsiCodePageData"); + addField(peb, 0x0A8, NodeKind::Pointer64, "OemCodePageData"); + addField(peb, 0x0B0, NodeKind::Pointer64, "UnicodeCaseTableData"); + addField(peb, 0x0B8, NodeKind::UInt32, "NumberOfProcessors"); + addField(peb, 0x0BC, NodeKind::Hex32, "NtGlobalFlag"); + + // 0x0C0 – 0x0EF + addField(peb, 0x0C0, NodeKind::UInt64, "CriticalSectionTimeout"); + addField(peb, 0x0C8, NodeKind::UInt64, "HeapSegmentReserve"); + addField(peb, 0x0D0, NodeKind::UInt64, "HeapSegmentCommit"); + addField(peb, 0x0D8, NodeKind::UInt64, "HeapDeCommitTotalFreeThreshold"); + addField(peb, 0x0E0, NodeKind::UInt64, "HeapDeCommitFreeBlockThreshold"); + addField(peb, 0x0E8, NodeKind::UInt32, "NumberOfHeaps"); + addField(peb, 0x0EC, NodeKind::UInt32, "MaximumNumberOfHeaps"); + + // 0x0F0 – 0x13F + addField(peb, 0x0F0, NodeKind::Pointer64, "ProcessHeaps"); + addField(peb, 0x0F8, NodeKind::Pointer64, "GdiSharedHandleTable"); + addField(peb, 0x100, NodeKind::Pointer64, "ProcessStarterHelper"); + addField(peb, 0x108, NodeKind::UInt32, "GdiDCAttributeList"); + addPad (peb, 0x10C, 4, "Padding3"); + addField(peb, 0x110, NodeKind::Pointer64, "LoaderLock"); + addField(peb, 0x118, NodeKind::UInt32, "OSMajorVersion"); + addField(peb, 0x11C, NodeKind::UInt32, "OSMinorVersion"); + addField(peb, 0x120, NodeKind::UInt16, "OSBuildNumber"); + addField(peb, 0x122, NodeKind::UInt16, "OSCSDVersion"); + addField(peb, 0x124, NodeKind::UInt32, "OSPlatformId"); + addField(peb, 0x128, NodeKind::UInt32, "ImageSubsystem"); + addField(peb, 0x12C, NodeKind::UInt32, "ImageSubsystemMajorVersion"); + addField(peb, 0x130, NodeKind::UInt32, "ImageSubsystemMinorVersion"); + addPad (peb, 0x134, 4, "Padding4"); + addField(peb, 0x138, NodeKind::UInt64, "ActiveProcessAffinityMask"); + + // 0x140 – 0x22F + addArray(peb, 0x140, "GdiHandleBuffer", 60, NodeKind::UInt32); + + // 0x230 – 0x2BF + addField(peb, 0x230, NodeKind::Pointer64, "PostProcessInitRoutine"); + addField(peb, 0x238, NodeKind::Pointer64, "TlsExpansionBitmap"); + addArray(peb, 0x240, "TlsExpansionBitmapBits", 32, NodeKind::UInt32); + + // 0x2C0 – 0x2E7 + addField(peb, 0x2C0, NodeKind::UInt32, "SessionId"); + addPad (peb, 0x2C4, 4, "Padding5"); + addField(peb, 0x2C8, NodeKind::UInt64, "AppCompatFlags"); + addField(peb, 0x2D0, NodeKind::UInt64, "AppCompatFlagsUser"); + addField(peb, 0x2D8, NodeKind::Pointer64, "pShimData"); + addField(peb, 0x2E0, NodeKind::Pointer64, "AppCompatInfo"); + + // 0x2E8 – 0x2F7: _STRING64 CSDVersion + { + uint64_t sid = addStruct(peb, 0x2E8, "_STRING64", "CSDVersion"); + addField(sid, 0, NodeKind::UInt16, "Length"); + addField(sid, 2, NodeKind::UInt16, "MaximumLength"); + addPad (sid, 4, 4, "Pad"); + addField(sid, 8, NodeKind::Pointer64, "Buffer"); } - // ── Section Headers Array (4 sections) ── - uint64_t shArrId = addArray(0, shOff, "SectionHeaders", 4, NodeKind::Struct); - const char* secNames[4] = {".text", ".rdata", ".data", ".pdata"}; - for (int i = 0; i < 4; i++) { - uint64_t secId = addStruct(shArrId, i * 40, "IMAGE_SECTION_HEADER", QString("%1").arg(secNames[i])); - // Name is 8 bytes - show as UTF8 string - Node nameNode; - nameNode.kind = NodeKind::UTF8; - nameNode.name = "Name"; - nameNode.parentId = secId; - nameNode.offset = 0; - nameNode.strLen = 8; - doc->tree.addNode(nameNode); - addField(secId, 8, NodeKind::UInt32, "VirtualSize"); - addField(secId, 12, NodeKind::UInt32, "VirtualAddress"); - addField(secId, 16, NodeKind::UInt32, "SizeOfRawData"); - addField(secId, 20, NodeKind::UInt32, "PointerToRawData"); - addField(secId, 24, NodeKind::UInt32, "PointerToRelocations"); - addField(secId, 28, NodeKind::UInt32, "PointerToLinenumbers"); - addField(secId, 32, NodeKind::UInt16, "NumberOfRelocations"); - addField(secId, 34, NodeKind::UInt16, "NumberOfLinenumbers"); - addField(secId, 36, NodeKind::UInt32, "Characteristics"); + // 0x2F8 – 0x31F + addField(peb, 0x2F8, NodeKind::Pointer64, "ActivationContextData"); + addField(peb, 0x300, NodeKind::Pointer64, "ProcessAssemblyStorageMap"); + addField(peb, 0x308, NodeKind::Pointer64, "SystemDefaultActivationContextData"); + addField(peb, 0x310, NodeKind::Pointer64, "SystemAssemblyStorageMap"); + addField(peb, 0x318, NodeKind::UInt64, "MinimumStackCommit"); + + // 0x320 – 0x34B + addArray(peb, 0x320, "SparePointers", 2, NodeKind::UInt64); + addField(peb, 0x330, NodeKind::Pointer64, "PatchLoaderData"); + addField(peb, 0x338, NodeKind::Pointer64, "ChpeV2ProcessInfo"); + addField(peb, 0x340, NodeKind::UInt32, "AppModelFeatureState"); + addArray(peb, 0x344, "SpareUlongs", 2, NodeKind::UInt32); + addField(peb, 0x34C, NodeKind::UInt16, "ActiveCodePage"); + addField(peb, 0x34E, NodeKind::UInt16, "OemCodePage"); + addField(peb, 0x350, NodeKind::UInt16, "UseCaseMapping"); + addField(peb, 0x352, NodeKind::UInt16, "UnusedNlsField"); + + // 0x354 – 0x37F + addPad (peb, 0x354, 4, "Pad354"); + addField(peb, 0x358, NodeKind::Pointer64, "WerRegistrationData"); + addField(peb, 0x360, NodeKind::Pointer64, "WerShipAssertPtr"); + addField(peb, 0x368, NodeKind::Pointer64, "EcCodeBitMap"); + addField(peb, 0x370, NodeKind::Pointer64, "pImageHeaderHash"); + addField(peb, 0x378, NodeKind::UInt32, "TracingFlags"); + addPad (peb, 0x37C, 4, "Padding6"); + + // 0x380 – 0x39F + addField(peb, 0x380, NodeKind::Pointer64, "CsrServerReadOnlySharedMemoryBase"); + addField(peb, 0x388, NodeKind::UInt64, "TppWorkerpListLock"); + + // LIST_ENTRY64 TppWorkerpList + { + uint64_t sid = addStruct(peb, 0x390, "LIST_ENTRY64", "TppWorkerpList"); + addField(sid, 0, NodeKind::Pointer64, "Flink"); + addField(sid, 8, NodeKind::Pointer64, "Blink"); } - // ── Hex64 fields after headers ── - const int tailOff = shOff + 4 * 40; // 0x228 - addField(0, tailOff + 0, NodeKind::Hex64, "RawData0"); - addField(0, tailOff + 8, NodeKind::Hex64, "RawData1"); - addField(0, tailOff + 16, NodeKind::Hex64, "RawData2"); - addField(0, tailOff + 24, NodeKind::Hex64, "RawData3"); + // 0x3A0 – 0x79F + addArray(peb, 0x3A0, "WaitOnAddressHashTable", 128, NodeKind::UInt64); + + // 0x7A0 – 0x7CF + addField(peb, 0x7A0, NodeKind::Pointer64, "TelemetryCoverageHeader"); + addField(peb, 0x7A8, NodeKind::UInt32, "CloudFileFlags"); + addField(peb, 0x7AC, NodeKind::UInt32, "CloudFileDiagFlags"); + addField(peb, 0x7B0, NodeKind::Int8, "PlaceholderCompatibilityMode"); + addArray(peb, 0x7B1, "PlaceholderCompatibilityModeReserved", 7, NodeKind::Int8); + addField(peb, 0x7B8, NodeKind::Pointer64, "LeapSecondData"); + addField(peb, 0x7C0, NodeKind::UInt32, "LeapSecondFlags"); + addField(peb, 0x7C4, NodeKind::UInt32, "NtGlobalFlag2"); + addField(peb, 0x7C8, NodeKind::UInt64, "ExtendedFeatureDisableMask"); createTab(doc); } @@ -777,7 +729,7 @@ int main(int argc, char* argv[]) { window.setWindowOpacity(0.0); window.show(); - // Always auto-open PE header demo on startup + // Always auto-open PEB64 demo on startup QMetaObject::invokeMethod(&window, "newFile"); if (screenshotMode) { diff --git a/tests/test_compose.cpp b/tests/test_compose.cpp index b29e452..daaff34 100644 --- a/tests/test_compose.cpp +++ b/tests/test_compose.cpp @@ -150,7 +150,8 @@ private slots: ComposeResult result = compose(tree, prov); QCOMPARE(result.meta.size(), 4); - QVERIFY(result.meta[2].markerMask & (1u << M_PTR0)); + // No ambient validation markers — M_PTR0 is no longer set + QVERIFY(!(result.meta[2].markerMask & (1u << M_PTR0))); } void testCollapsedStruct() { @@ -182,7 +183,7 @@ private slots: } void testUnreadablePointerNoRead() { - // A pointer at an unreadable address should get M_ERR, not M_PTR0 + // No ambient validation — neither M_ERR nor M_PTR0 set NodeTree tree; tree.baseAddress = 0; @@ -206,8 +207,8 @@ private slots: ComposeResult result = compose(tree, prov); QCOMPARE(result.meta.size(), 4); - // Should have M_ERR, should NOT have M_PTR0 - QVERIFY(result.meta[2].markerMask & (1u << M_ERR)); + // No ambient validation markers + QVERIFY(!(result.meta[2].markerMask & (1u << M_ERR))); QVERIFY(!(result.meta[2].markerMask & (1u << M_PTR0))); } diff --git a/tests/test_editor.cpp b/tests/test_editor.cpp index 8304588..a056bb0 100644 --- a/tests/test_editor.cpp +++ b/tests/test_editor.cpp @@ -10,106 +10,296 @@ using namespace rcx; -// Load first 0x6000 bytes of the test exe for realistic data +// 0x7D0 bytes of PEB-like data with recognizable values at key offsets static BufferProvider makeTestProvider() { - QFile exe(QCoreApplication::applicationFilePath()); - if (exe.open(QIODevice::ReadOnly)) { - QByteArray data = exe.read(0x6000); - exe.close(); - if (data.size() >= 0x6000) - return BufferProvider(data); - } - // Fallback: minimal PE header stub - QByteArray data(0x6000, '\0'); - data[0] = 'M'; data[1] = 'Z'; // DOS signature - return BufferProvider(data); + QByteArray data(0x7D0, '\0'); + + auto w8 = [&](int off, uint8_t v) { data[off] = (char)v; }; + auto w16 = [&](int off, uint16_t v) { memcpy(data.data()+off, &v, 2); }; + auto w32 = [&](int off, uint32_t v) { memcpy(data.data()+off, &v, 4); }; + auto w64 = [&](int off, uint64_t v) { memcpy(data.data()+off, &v, 8); }; + + w8 (0x002, 1); // BeingDebugged + w8 (0x003, 0x04); // BitField + w64(0x008, 0xFFFFFFFFFFFFFFFFULL); // Mutant (-1) + w64(0x010, 0x00007FF6DE120000ULL); // ImageBaseAddress + w64(0x018, 0x00007FFE3B8B53C0ULL); // Ldr + w64(0x020, 0x000001A4C3E20F90ULL); // ProcessParameters + w64(0x028, 0x0000000000000000ULL); // SubSystemData + w64(0x030, 0x000001A4C3D40000ULL); // ProcessHeap + w64(0x038, 0x00007FFE3B8D4260ULL); // FastPebLock + w64(0x040, 0x0000000000000000ULL); // AtlThunkSListPtr + w64(0x048, 0x0000000000000000ULL); // IFEOKey + w32(0x050, 0x01); // CrossProcessFlags + w64(0x058, 0x00007FFE3B720000ULL); // KernelCallbackTable + w32(0x060, 0); // SystemReserved + w32(0x064, 0); // AtlThunkSListPtr32 + w64(0x068, 0x00007FFE3E570000ULL); // ApiSetMap + w32(0x070, 0); // TlsExpansionCounter + w64(0x078, 0x00007FFE3B8D3F50ULL); // TlsBitmap + w32(0x080, 0x00000003); // TlsBitmapBits[0] + w32(0x084, 0x00000000); // TlsBitmapBits[1] + w64(0x088, 0x00007FFE38800000ULL); // ReadOnlySharedMemoryBase + w64(0x090, 0x00007FFE38820000ULL); // SharedData + w64(0x098, 0x00007FFE388A0000ULL); // ReadOnlyStaticServerData + w64(0x0A0, 0x00007FFE3B8D1000ULL); // AnsiCodePageData + w64(0x0A8, 0x00007FFE3B8D2040ULL); // OemCodePageData + w64(0x0B0, 0x00007FFE3B8CE020ULL); // UnicodeCaseTableData + w32(0x0B8, 8); // NumberOfProcessors + w32(0x0BC, 0x70); // NtGlobalFlag + w64(0x0C0, 0xFFFFFFFF7C91E000ULL); // CriticalSectionTimeout + w64(0x0C8, 0x0000000000100000ULL); // HeapSegmentReserve + w64(0x0D0, 0x0000000000002000ULL); // HeapSegmentCommit + w64(0x0D8, 0x0000000000040000ULL); // HeapDeCommitTotalFreeThreshold + w64(0x0E0, 0x0000000000001000ULL); // HeapDeCommitFreeBlockThreshold + w32(0x0E8, 4); // NumberOfHeaps + w32(0x0EC, 16); // MaximumNumberOfHeaps + w64(0x0F0, 0x000001A4C3D40688ULL); // ProcessHeaps + w64(0x0F8, 0x00007FFE388B0000ULL); // GdiSharedHandleTable + w64(0x100, 0x0000000000000000ULL); // ProcessStarterHelper + w32(0x108, 0); // GdiDCAttributeList + w64(0x110, 0x00007FFE3B8D42E8ULL); // LoaderLock + w32(0x118, 10); // OSMajorVersion + w32(0x11C, 0); // OSMinorVersion + w16(0x120, 19045); // OSBuildNumber + w16(0x122, 0); // OSCSDVersion + w32(0x124, 2); // OSPlatformId + w32(0x128, 3); // ImageSubsystem (CUI) + w32(0x12C, 10); // ImageSubsystemMajorVersion + w32(0x130, 0); // ImageSubsystemMinorVersion + w64(0x138, 0x00000000000000FFULL); // ActiveProcessAffinityMask + w64(0x230, 0x0000000000000000ULL); // PostProcessInitRoutine + w64(0x238, 0x00007FFE3B8D3F70ULL); // TlsExpansionBitmap + w32(0x2C0, 1); // SessionId + w64(0x2C8, 0x0000000000000000ULL); // AppCompatFlags + w64(0x2D0, 0x0000000000000000ULL); // AppCompatFlagsUser + w64(0x2D8, 0x0000000000000000ULL); // pShimData + w64(0x2E0, 0x0000000000000000ULL); // AppCompatInfo + w16(0x2E8, 0); // CSDVersion.Length + w16(0x2EA, 0); // CSDVersion.MaximumLength + w64(0x2F0, 0x0000000000000000ULL); // CSDVersion.Buffer + w64(0x2F8, 0x000001A4C3E21000ULL); // ActivationContextData + w64(0x300, 0x000001A4C3E22000ULL); // ProcessAssemblyStorageMap + w64(0x308, 0x00007FFE38840000ULL); // SystemDefaultActivationContextData + w64(0x310, 0x00007FFE38850000ULL); // SystemAssemblyStorageMap + w64(0x318, 0x0000000000002000ULL); // MinimumStackCommit + w64(0x330, 0x0000000000000000ULL); // PatchLoaderData + w64(0x338, 0x0000000000000000ULL); // ChpeV2ProcessInfo + w32(0x340, 0); // AppModelFeatureState + w16(0x34C, 1252); // ActiveCodePage + w16(0x34E, 437); // OemCodePage + w16(0x350, 0); // UseCaseMapping + w16(0x352, 0); // UnusedNlsField + w64(0x358, 0x000001A4C3E30000ULL); // WerRegistrationData + w64(0x360, 0x0000000000000000ULL); // WerShipAssertPtr + w64(0x368, 0x0000000000000000ULL); // EcCodeBitMap + w64(0x370, 0x0000000000000000ULL); // pImageHeaderHash + w32(0x378, 0); // TracingFlags + w64(0x380, 0x00007FFE38890000ULL); // CsrServerReadOnlySharedMemoryBase + w64(0x388, 0x0000000000000000ULL); // TppWorkerpListLock + w64(0x390, 0x000000D87B5E5390ULL); // TppWorkerpList.Flink (self) + w64(0x398, 0x000000D87B5E5390ULL); // TppWorkerpList.Blink (self) + w64(0x7A0, 0x0000000000000000ULL); // TelemetryCoverageHeader + w32(0x7A8, 0); // CloudFileFlags + w32(0x7AC, 0); // CloudFileDiagFlags + w8 (0x7B0, 0); // PlaceholderCompatibilityMode + w64(0x7B8, 0x00007FFE38860000ULL); // LeapSecondData + w32(0x7C0, 0); // LeapSecondFlags + w32(0x7C4, 0); // NtGlobalFlag2 + w64(0x7C8, 0x0000000000000000ULL); // ExtendedFeatureDisableMask + + return BufferProvider(data, "peb_snapshot.bin"); } -// Build a PE-like test tree with IMAGE_FILE_HEADER fields +// Build the full _PEB64 tree (0x7D0 bytes), unions mapped to first member static NodeTree makeTestTree() { NodeTree tree; - tree.baseAddress = 0x140000000; + tree.baseAddress = 0x000000D87B5E5000ULL; - // Root struct: IMAGE_FILE_HEADER + // Root struct Node root; root.kind = NodeKind::Struct; - root.structTypeName = "IMAGE_FILE_HEADER"; - root.name = "FileHeader"; + root.structTypeName = "_PEB64"; + root.name = "Peb"; root.parentId = 0; root.offset = 0; int ri = tree.addNode(root); uint64_t rootId = tree.nodes[ri].id; - int offset = 0; + // Helpers + auto field = [&](int off, NodeKind k, const char* name) { + Node n; n.kind = k; n.name = name; + n.parentId = rootId; n.offset = off; + tree.addNode(n); + }; + auto pad = [&](int off, int len, const char* name) { + Node n; n.kind = NodeKind::Padding; n.name = name; + n.parentId = rootId; n.offset = off; n.arrayLen = len; + tree.addNode(n); + }; + auto arr = [&](int off, NodeKind ek, int len, const char* name) { + Node n; n.kind = NodeKind::Array; n.name = name; + n.parentId = rootId; n.offset = off; + n.arrayLen = len; n.elementKind = ek; + tree.addNode(n); + }; + auto sub = [&](int off, const char* ty, const char* name) -> uint64_t { + Node n; n.kind = NodeKind::Struct; n.structTypeName = ty; n.name = name; + n.parentId = rootId; n.offset = off; + int idx = tree.addNode(n); return tree.nodes[idx].id; + }; - // IMAGE_FILE_HEADER fields (matches Windows PE format) - Node machine; - machine.kind = NodeKind::UInt16; - machine.name = "Machine"; - machine.parentId = rootId; - machine.offset = offset; - tree.addNode(machine); - offset += 2; + // ── 0x000 – 0x007 ── + field(0x000, NodeKind::UInt8, "InheritedAddressSpace"); + field(0x001, NodeKind::UInt8, "ReadImageFileExecOptions"); + field(0x002, NodeKind::UInt8, "BeingDebugged"); + field(0x003, NodeKind::UInt8, "BitField"); // union → first member + pad (0x004, 4, "Padding0"); - Node numSections; - numSections.kind = NodeKind::UInt16; - numSections.name = "NumberOfSections"; - numSections.parentId = rootId; - numSections.offset = offset; - tree.addNode(numSections); - offset += 2; + // ── 0x008 – 0x04F ── + field(0x008, NodeKind::Pointer64, "Mutant"); + field(0x010, NodeKind::Pointer64, "ImageBaseAddress"); + field(0x018, NodeKind::Pointer64, "Ldr"); + field(0x020, NodeKind::Pointer64, "ProcessParameters"); + field(0x028, NodeKind::Pointer64, "SubSystemData"); + field(0x030, NodeKind::Pointer64, "ProcessHeap"); + field(0x038, NodeKind::Pointer64, "FastPebLock"); + field(0x040, NodeKind::Pointer64, "AtlThunkSListPtr"); + field(0x048, NodeKind::Pointer64, "IFEOKey"); - Node timestamp; - timestamp.kind = NodeKind::Hex32; - timestamp.name = "TimeDateStamp"; - timestamp.parentId = rootId; - timestamp.offset = offset; - tree.addNode(timestamp); - offset += 4; + // ── 0x050 – 0x07F ── + field(0x050, NodeKind::UInt32, "CrossProcessFlags"); // union → first member + pad (0x054, 4, "Padding1"); + field(0x058, NodeKind::Pointer64, "KernelCallbackTable"); // union → first member + field(0x060, NodeKind::UInt32, "SystemReserved"); + field(0x064, NodeKind::UInt32, "AtlThunkSListPtr32"); + field(0x068, NodeKind::Pointer64, "ApiSetMap"); + field(0x070, NodeKind::UInt32, "TlsExpansionCounter"); + pad (0x074, 4, "Padding2"); + field(0x078, NodeKind::Pointer64, "TlsBitmap"); + arr (0x080, NodeKind::UInt32, 2, "TlsBitmapBits"); - Node ptrSymbols; - ptrSymbols.kind = NodeKind::Hex32; - ptrSymbols.name = "PointerToSymbolTable"; - ptrSymbols.parentId = rootId; - ptrSymbols.offset = offset; - tree.addNode(ptrSymbols); - offset += 4; + // ── 0x088 – 0x0BF ── + field(0x088, NodeKind::Pointer64, "ReadOnlySharedMemoryBase"); + field(0x090, NodeKind::Pointer64, "SharedData"); + field(0x098, NodeKind::Pointer64, "ReadOnlyStaticServerData"); + field(0x0A0, NodeKind::Pointer64, "AnsiCodePageData"); + field(0x0A8, NodeKind::Pointer64, "OemCodePageData"); + field(0x0B0, NodeKind::Pointer64, "UnicodeCaseTableData"); + field(0x0B8, NodeKind::UInt32, "NumberOfProcessors"); + field(0x0BC, NodeKind::Hex32, "NtGlobalFlag"); - Node numSymbols; - numSymbols.kind = NodeKind::UInt32; - numSymbols.name = "NumberOfSymbols"; - numSymbols.parentId = rootId; - numSymbols.offset = offset; - tree.addNode(numSymbols); - offset += 4; + // ── 0x0C0 – 0x0EF ── + field(0x0C0, NodeKind::UInt64, "CriticalSectionTimeout"); // _LARGE_INTEGER union + field(0x0C8, NodeKind::UInt64, "HeapSegmentReserve"); + field(0x0D0, NodeKind::UInt64, "HeapSegmentCommit"); + field(0x0D8, NodeKind::UInt64, "HeapDeCommitTotalFreeThreshold"); + field(0x0E0, NodeKind::UInt64, "HeapDeCommitFreeBlockThreshold"); + field(0x0E8, NodeKind::UInt32, "NumberOfHeaps"); + field(0x0EC, NodeKind::UInt32, "MaximumNumberOfHeaps"); - Node optHeaderSize; - optHeaderSize.kind = NodeKind::UInt16; - optHeaderSize.name = "SizeOfOptionalHeader"; - optHeaderSize.parentId = rootId; - optHeaderSize.offset = offset; - tree.addNode(optHeaderSize); - offset += 2; + // ── 0x0F0 – 0x13F ── + field(0x0F0, NodeKind::Pointer64, "ProcessHeaps"); + field(0x0F8, NodeKind::Pointer64, "GdiSharedHandleTable"); + field(0x100, NodeKind::Pointer64, "ProcessStarterHelper"); + field(0x108, NodeKind::UInt32, "GdiDCAttributeList"); + pad (0x10C, 4, "Padding3"); + field(0x110, NodeKind::Pointer64, "LoaderLock"); + field(0x118, NodeKind::UInt32, "OSMajorVersion"); + field(0x11C, NodeKind::UInt32, "OSMinorVersion"); + field(0x120, NodeKind::UInt16, "OSBuildNumber"); + field(0x122, NodeKind::UInt16, "OSCSDVersion"); + field(0x124, NodeKind::UInt32, "OSPlatformId"); + field(0x128, NodeKind::UInt32, "ImageSubsystem"); + field(0x12C, NodeKind::UInt32, "ImageSubsystemMajorVersion"); + field(0x130, NodeKind::UInt32, "ImageSubsystemMinorVersion"); + pad (0x134, 4, "Padding4"); + field(0x138, NodeKind::UInt64, "ActiveProcessAffinityMask"); - Node characteristics; - characteristics.kind = NodeKind::Hex16; - characteristics.name = "Characteristics"; - characteristics.parentId = rootId; - characteristics.offset = offset; - tree.addNode(characteristics); - offset += 2; + // ── 0x140 – 0x22F ── + arr (0x140, NodeKind::UInt32, 60, "GdiHandleBuffer"); - // 8 Hex64 fields for additional test coverage - for (int i = 0; i < 8; i++) { - Node hex; - hex.kind = NodeKind::Hex64; - hex.name = QString("Reserved%1").arg(i); - hex.parentId = rootId; - hex.offset = offset; - tree.addNode(hex); - offset += 8; + // ── 0x230 – 0x2BF ── + field(0x230, NodeKind::Pointer64, "PostProcessInitRoutine"); + field(0x238, NodeKind::Pointer64, "TlsExpansionBitmap"); + arr (0x240, NodeKind::UInt32, 32, "TlsExpansionBitmapBits"); + + // ── 0x2C0 – 0x2E7 ── + field(0x2C0, NodeKind::UInt32, "SessionId"); + pad (0x2C4, 4, "Padding5"); + field(0x2C8, NodeKind::UInt64, "AppCompatFlags"); // _ULARGE_INTEGER union + field(0x2D0, NodeKind::UInt64, "AppCompatFlagsUser"); // _ULARGE_INTEGER union + field(0x2D8, NodeKind::Pointer64, "pShimData"); + field(0x2E0, NodeKind::Pointer64, "AppCompatInfo"); + + // ── 0x2E8 – 0x2F7: _STRING64 CSDVersion (nested struct) ── + { + uint64_t sid = sub(0x2E8, "_STRING64", "CSDVersion"); + Node n; + n.parentId = sid; + + n.kind = NodeKind::UInt16; n.name = "Length"; n.offset = 0; tree.addNode(n); + n.kind = NodeKind::UInt16; n.name = "MaximumLength"; n.offset = 2; tree.addNode(n); + n.kind = NodeKind::Padding; n.name = "Pad"; + n.offset = 4; n.arrayLen = 4; tree.addNode(n); + n.kind = NodeKind::Pointer64; n.name = "Buffer"; n.offset = 8; n.arrayLen = 1; + tree.addNode(n); } + // ── 0x2F8 – 0x31F ── + field(0x2F8, NodeKind::Pointer64, "ActivationContextData"); + field(0x300, NodeKind::Pointer64, "ProcessAssemblyStorageMap"); + field(0x308, NodeKind::Pointer64, "SystemDefaultActivationContextData"); + field(0x310, NodeKind::Pointer64, "SystemAssemblyStorageMap"); + field(0x318, NodeKind::UInt64, "MinimumStackCommit"); + + // ── 0x320 – 0x34B ── + arr (0x320, NodeKind::UInt64, 2, "SparePointers"); + field(0x330, NodeKind::Pointer64, "PatchLoaderData"); + field(0x338, NodeKind::Pointer64, "ChpeV2ProcessInfo"); + field(0x340, NodeKind::UInt32, "AppModelFeatureState"); + arr (0x344, NodeKind::UInt32, 2, "SpareUlongs"); + field(0x34C, NodeKind::UInt16, "ActiveCodePage"); + field(0x34E, NodeKind::UInt16, "OemCodePage"); + field(0x350, NodeKind::UInt16, "UseCaseMapping"); + field(0x352, NodeKind::UInt16, "UnusedNlsField"); + + // ── 0x354 – 0x37F (implicit padding + fields) ── + pad (0x354, 4, "Pad354"); + field(0x358, NodeKind::Pointer64, "WerRegistrationData"); + field(0x360, NodeKind::Pointer64, "WerShipAssertPtr"); + field(0x368, NodeKind::Pointer64, "EcCodeBitMap"); + field(0x370, NodeKind::Pointer64, "pImageHeaderHash"); + field(0x378, NodeKind::UInt32, "TracingFlags"); // union → first member + pad (0x37C, 4, "Padding6"); + + // ── 0x380 – 0x39F ── + field(0x380, NodeKind::Pointer64, "CsrServerReadOnlySharedMemoryBase"); + field(0x388, NodeKind::UInt64, "TppWorkerpListLock"); + + // ── 0x390 – 0x39F: LIST_ENTRY64 TppWorkerpList (nested struct) ── + { + uint64_t sid = sub(0x390, "LIST_ENTRY64", "TppWorkerpList"); + Node n; + n.parentId = sid; + n.kind = NodeKind::Pointer64; n.name = "Flink"; n.offset = 0; tree.addNode(n); + n.kind = NodeKind::Pointer64; n.name = "Blink"; n.offset = 8; tree.addNode(n); + } + + // ── 0x3A0 – 0x79F ── + arr (0x3A0, NodeKind::UInt64, 128, "WaitOnAddressHashTable"); + + // ── 0x7A0 – 0x7CF ── + field(0x7A0, NodeKind::Pointer64, "TelemetryCoverageHeader"); + field(0x7A8, NodeKind::UInt32, "CloudFileFlags"); + field(0x7AC, NodeKind::UInt32, "CloudFileDiagFlags"); + field(0x7B0, NodeKind::Int8, "PlaceholderCompatibilityMode"); + arr (0x7B1, NodeKind::Int8, 7, "PlaceholderCompatibilityModeReserved"); + field(0x7B8, NodeKind::Pointer64, "LeapSecondData"); + field(0x7C0, NodeKind::UInt32, "LeapSecondFlags"); // union → first member + field(0x7C4, NodeKind::UInt32, "NtGlobalFlag2"); + field(0x7C8, NodeKind::UInt64, "ExtendedFeatureDisableMask"); + return tree; } @@ -155,7 +345,7 @@ private slots: // Set CommandRow text with an ADDR value (simulates controller.updateCommandRow) m_editor->setCommandRowText( - QStringLiteral(" File Address: 0x140000000")); + QStringLiteral(" File Address: 0xD87B5E5000")); // BaseAddress should be ALLOWED on CommandRow (ADDR field) bool ok = m_editor->beginInlineEdit(EditTarget::BaseAddress, 0); @@ -252,8 +442,6 @@ private slots: QCOMPARE(cancelSpy.count(), 0); } - - // ── Test: type edit begins and can be cancelled ── void testTypeEditCancel() { m_editor->applyDocument(m_result); @@ -287,7 +475,7 @@ private slots: QVERIFY(lm); QCOMPARE(lm->lineKind, LineKind::Header); - // Type edit on header should succeed (has typename IMAGE_FILE_HEADER) + // Type edit on header should succeed (has typename _PEB64) bool ok = m_editor->beginInlineEdit(EditTarget::Type, 1); QVERIFY(ok); QVERIFY(m_editor->isEditing()); @@ -320,7 +508,6 @@ private slots: QVERIFY(!m_editor->isEditing()); } - // ── Test: parseValue accepts space-separated hex bytes ── void testParseValueHexWithSpaces() { bool ok; @@ -417,10 +604,11 @@ private slots: QCOMPARE(commitSpy.count(), 1); // The committed text should be the original typeName (no change) + // First field at line 2 is InheritedAddressSpace (UInt8 → "uint8_t") QList args = commitSpy.first(); QString committedText = args.at(3).toString(); - QVERIFY2(committedText == "uint16_t", - qPrintable("Expected 'uint16_t', got: " + committedText)); + QVERIFY2(committedText == "uint8_t", + qPrintable("Expected 'uint8_t', got: " + committedText)); m_editor->applyDocument(m_result); } @@ -429,7 +617,7 @@ private slots: void testColumnSpanHitTest() { m_editor->applyDocument(m_result); - // Line 2 is a field line (UInt16), verify spans are valid (line 0=CommandRow, 1=header) + // Line 2 is a field line (UInt8), verify spans are valid (line 0=CommandRow, 1=header) const LineMeta* lm = m_editor->metaForLine(2); QVERIFY(lm); QCOMPARE(lm->lineKind, LineKind::Field); @@ -527,7 +715,7 @@ private slots: // Set CommandRow text with ADDR value (simulates controller) m_editor->setCommandRowText( - QStringLiteral(" File Address: 0x140000000")); + QStringLiteral(" File Address: 0xD87B5E5000")); // Line 0 is CommandRow const LineMeta* lm = m_editor->metaForLine(0); @@ -566,7 +754,7 @@ private slots: // Set CommandRow text with ADDR value (simulates controller) m_editor->setCommandRowText( - QStringLiteral(" File Address: 0x140000000")); + QStringLiteral(" File Address: 0xD87B5E5000")); // Begin base address edit on line 0 (CommandRow ADDR field) bool ok = m_editor->beginInlineEdit(EditTarget::BaseAddress, 0);