Instead of hiding the sentinel tab (which leaked space on macOS),
repurpose it as a visible "+" button that creates a new struct tab
on click. Compact 32px icon-only tab with pixel-perfect cross drawn
via fillRect. Skips context menu and middle-click. Always positioned
as the last tab in the group.
- Add 24px width padding to dock tab size calculation to account for
DockTabButtons close icon (prevents text clipping into button area)
- Enable scroll buttons on dock tab bars so tabs scroll instead of
compressing when they overflow
- Allow scanner dock to be docked on all sides (was missing TopDockWidgetArea)
The previous middle-elide logic had a 2x threshold that caused text
between 1x and 2x overflow to draw un-elided into a clipped rect,
producing ugly truncation like "Projec" instead of "Proje…".
Replace with Qt's built-in elidedText (right-elide) which always
produces clean "…" truncation when text overflows the tab width.
When Project and Modules docks are tabified together, Qt creates a
tab bar with close buttons via setupDockTabBars(). The dock lookup
only searched m_docDocks, so sidebar dock close buttons were installed
but never connected — clicking × did nothing.
Now the lookup also checks m_workspaceDock, m_scannerDock, and
m_symbolsDock. Middle-click close and right-click context menu also
work for sidebar tabs. Sidebar tabs get a minimal context menu
(Close + Float/Dock) while doc tabs keep the full menu.
Implement macOS-specific support for the ProcessMemory plugin and update plugin discovery/build.
- Add macOS build/install support: include plugins/ProcessMemory in top-level CMake and copy the built plugin into Reclass.app/Contents/PlugIns on macOS.
- Implement Apple-specific ProcessMemoryProvider: task_for_pid usage, mach_vm_read_overwrite/mach_vm_write, proc_pidpath/proc_regionfilename based module caching, region enumeration, symbol formatting, module enumeration, and proper cleanup (mach_port_deallocate).
- Extend plugin header to track m_task and adjust readability checks for macOS.
- Add macOS handling in getInitialBaseAddress and process enumeration to find base addresses and processes using proc APIs.
- Improve PluginManager to probe multiple plugin locations (including Contents/PlugIns inside macOS bundles), aggregate/log candidate counts, and continue scanning multiple dirs.
- Add macOS screenshot to README (docs/README_PIC6.png) and reference it in README.
These changes enable the ProcessMemory plugin to operate on macOS and make plugin discovery more robust on macOS app bundles.
"Project" showed as "Pr…ct" and "Modules" as "Mod…les" when two
dock widgets shared a narrow side panel. The middle-elide logic
kicked in as soon as text exceeded the available width. Now it
only elides when the text is more than 2x the available width,
so short names render in full and only genuinely long names
(like struct type names in doc tabs) get truncated.
extractPdbSymbols() was reading S_GDATA32/S_GTHREAD32 records which
contain a typeIndex field linking the symbol to its type definition in
the TPI stream, but this field was discarded — only name + RVA were
kept. This meant loading symbols gave you address resolution but no
way to automatically import the type associated with a global variable.
Changes:
- PdbSymbol now carries typeIndex (0 = no type info / public symbol)
- extractPdbSymbols() captures typeIndex from all global data symbols
- PdbSymbolSet stores nameToTypeIndex mapping alongside nameToRva
- New importTypeForSymbol() follows LF_POINTER/LF_MODIFIER chains to
find the underlying UDT/enum and imports it with full recursive children
- New symbols.importType MCP tool: given "ntdll!g_pShimEngineModule",
resolves its typeIndex, imports the type definition from the PDB, and
merges it into the active project
- loadPdbIntoStore() helper consolidates the extract+store pattern with
type index support
computeOffset() returns int64_t but most callers added the result directly
to baseAddress (uint64_t) without checking for negative values. A malformed
tree with negative cumulative offsets would produce wrapped addresses,
potentially reading/writing arbitrary memory in the bitfield toggle and
edit paths. Added sign checks at all 9 unguarded call sites.
On Linux, QMenuBar inside a custom title bar widget (setMenuWidget) collapses
all items into the extension overflow popup. Replace with QToolButton widgets
on Linux that share the same QMenu objects. Includes hover-to-switch behavior
via event filter on open menus.
Windows and macOS paths are unchanged — guarded by #ifdef __linux__ and
runtime m_useToolButtons flag.
- Guard FramelessWindowHint + WA_TranslucentBackground on QMenu to
Windows only — breaks popup submenus on Linux/Wayland compositors
- Render SVG icons via QSvgRenderer to QPixmap explicitly, avoiding
dependency on the qsvgicon image format plugin which may not be
deployed on Linux
- Double-click symbol in Symbols tab navigates to moduleBase + RVA
- Add symbol-method.svg icon for function symbols, symbol-structure.svg for modules
- Force-populate all modules on search so filter works without expanding first
- Parse module.dll/exe/sys as identifiers in address bar (e.g. client.dll + 0xFF)
- Scale tooltip font to 90% of editor font
- Replace inline edit hint for base address with hover tooltip
- Two-column cheat sheet: syntax examples + explanations
- Dismiss all popups on alt-tab (ActivationChange)
Rewrite RcxTooltip to use WA_TranslucentBackground with a single
contiguous QPainterPath (rounded rect + arrow notch). Pre-set the
DarkTitleBar property to prevent DarkApp from calling
DwmSetWindowAttribute which breaks layered window compositing.
Dismiss all popups (including arrow tooltip) on alt-tab via
MainWindow::changeEvent(ActivationChange).
Runner doesn't have WDK headers installed. Use NuGet to install
Microsoft.Windows.WDK.x64 and pass paths via env vars.
build_driver.bat now accepts WDK_INC_ROOT/WDK_LIB_ROOT overrides.
- KernelMemory plugin: kernel-mode process/physical memory R/W via IOCTL driver
- rcxdrv.sys: MmCopyMemory for reads, MDL mapping with correct cache types
(MmCached for RAM, MmNonCached for MMIO only — fixes cache corruption BSOD)
- Driver reconnect: ensureDriverLoaded tries device handle first, no auto
stop+delete cycle. Manual unload closes handle only, service stays running.
- Unified source menu: ProviderRegistry::populateSourceMenu() shared by both
main window Data Source menu and RcxEditor inline picker (icons + dll names)
- IProviderPlugin::populatePluginMenu() for conditional plugin actions
(e.g. "Unload Kernel Driver" only when loaded)
- Physical memory mode removed from selectTarget (access via context menu only)
- requestOpenProviderTab sets base address from provider after template load
- Address parser: vtop(), cr3(), physRead() callbacks for kernel paging expressions
Right-click context menu adds "Edit Hex Bytes" and "Edit ASCII" for
hex nodes (Hex8/16/32/64). Both are fixed-length overwrite-mode editors
with space-skipping, input validation, and IND_HEX_DIM indicator
preservation.
- Add Rust #[repr(C)] and #define offset code generators with dispatch
- Add format combo + scope combo + gear button as corner widget on Code tab
- Corner controls hidden on Reclass tab, shown only on Code tab
- Chevron-down SVG arrows on combo dropdowns for consistent styling
- Fix enum #define output: emit named members instead of empty 0x0 struct
Sentinel dock refactored to per-tab-group model — each split group gets
its own hidden sentinel so tab bars stay visible without the Hide event
filter hack. Editor inline-edit comment column now anchors correctly
for base-address edits and shows expression hint instead of generic text.
Replace single-client model (m_client/m_readBuffer/m_initialized)
with a ClientState vector. Each client gets its own read buffer and
initialized flag. Responses route to m_currentSender (set during
request processing); notifications broadcast to all initialized
clients.
Re-entrancy guard in onReadyRead: re-resolve ClientState after each
processLine() call since sendJson flush can re-enter the event loop
and trigger onDisconnected, removing the client mid-iteration.
Tests: 378-line test_mcp exercising connect, initialize, tools/list,
disconnect one client, notification broadcast, and serial requests
against a MockMcpServer with the same multi-client architecture.
Scanner engine:
- Add constrainRegions to ScanRequest — callers pass address ranges
that are intersected with provider regions before scanning
- Merge overlapping/adjacent constraints to prevent duplicate results
- Fix final-chunk overlap: skip overlap advance on last chunk to avoid
re-scanning the tail of a region
MCP tools:
- scanner.scan: value scans (int/float types) with optional region
constraints, returns first 15 addresses
- scanner.scan_pattern: pattern/signature scans with wildcards
- source.modules: list loaded modules with base address and size
- mcp.reconnect: graceful client disconnect for IDE reconnection
- parseInteger() helper for hex string args (avoids JSON double
precision loss on 64-bit addresses)
- Fix baseRelative semantics in hex.read/hex.write (was inverted)
- Auto-set tree.baseAddress from provider after process attach
Scanner panel:
- runValueScanAndWait() and runPatternScanAndWait() for blocking
scan execution from MCP/automation code
Tests: 41 new test cases for constrainRegions covering gaps, partial
overlap, adjacent regions, writable filter, degenerate ranges,
overlapping constraints, boundary patterns, alignment, and value
types at region start/end positions.
- MCP bridge: guard against use-after-free when client disconnects
during sendJson flush by re-checking m_client after write
- Scanner engine: fix chunk overlap advancing past region end on
final chunk; fix fallback region flags for providers without
enumerateRegions
- Build scripts: prefer GCC MinGW over LLVM-MinGW in PATH detection
- Status bar: show StructName.field +0xOFFSET with dimmed offset suffix
- Status bar: sync font to global editor font (JetBrains Mono 10pt)
- Dock tab title: include active source name (StructName — source.exe)
- Dock tabs +10% height (28→31), pane tabs (24→26), workspace title (26→29)
- Footer pills (+1024, Trim, +10): add visual hover highlight via IND_HOVER_SPAN
- Fix source switch keeping old base address for plugin providers
- Suppress PE_Frame on QsciScintilla in MenuBarStyle to eliminate the
1px dark (#171717) Fusion outline around the editor area
- Add --screenshot flag for automated pixel regression testing
- Add type inference engine (typeinfer.h) with hex pattern analysis
- Show inferred type hints on hex nodes in compose output
- Style workspace tree corner/header widgets to match theme
- Fix integer overflow in compose.cpp array element addressing
- Fix integer overflow in core.h structSpan calculation
- Add bounds check on activePaneIdx in controller
- Use QPointer for deferred dock lambda safety
- Workspace delegate uses icon Normal/Disabled for viewed state
Stack arrays + pre-lowered QChars in fuzzyScore eliminate all heap
allocations in the hot path. applyFilter uses indices instead of
deep-copying TypeEntry. popup() width estimated from cached max name
length. QListView: uniform sizes, batched layout, cached sizeHint.
Benchmark (5000 structs): warm popup 27ms→7ms, filter 5ms→1.7ms.
- Set WA_DeleteOnClose on doc docks so all close paths trigger cleanup
- Create fresh empty class when last project closes
- Add splitDockWidget/resizeDocks to project_new() so workspace doesn't eat editor space
- Merge applyMarginText, applyMarkers, applyFoldLevels into single-pass applyLineAttributes
- Cache line texts for heatmap/symbol coloring passes (avoid redundant Scintilla IPC)
- Zero-alloc scroll width scan replaces QString::split
- compose.cpp: show static fields for root structs even when collapsed
- test_compose: set collapsed=false on nodes needing expanded rendering
- test_disasm: set collapsed=false on vtable pointer nodes
- test_static_fields: rewrite collapsed test to use non-root child struct