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 <noreply@anthropic.com>
This commit is contained in:
sysadmin
2026-02-06 08:02:08 -07:00
parent be0c818075
commit 8daed45414
9 changed files with 566 additions and 368 deletions

22
README.md Normal file
View File

@@ -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
```

View File

@@ -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;
}

View File

@@ -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<QString> 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);

View File

@@ -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},

View File

@@ -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))

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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)));
}

View File

@@ -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<QVariant> 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);